При разработке систем лояльности, расчете скидок или определении тарифов программисты 1С часто сталкиваются с необходимостью определить, в какой числовой или временной интервал попадает конкретное значение. Стандартный подход с использованием оператора МЕЖДУ (или BETWEEN в SQL) кажется очевидным, но он таит в себе подводный камень, который может привести к неверным результатам и дублированию строк. Рассмотрим подробнее, почему это происходит и как реализовать поиск по интервалам правильно.
Разберем ситуацию, описанную в теме. В регистре сведений хранятся пороги скидок: от 20 000 до 50 000 (5%) и от 50 000 до 80 000 (6%). Проблема заключается в том, что в языке запросов 1С оператор МЕЖДУ всегда включает обе границы интервала.
Если мы напишем условие:
ГДЕ &СуммаНакопления МЕЖДУ ЗначенияСкидок.От И ЗначенияСкидок.До
то при значении параметра СуммаНакопления равном ровно 50 000, система вернет обе записи. Это происходит потому, что 50 000 одновременно «больше или равно 20 000 и меньше или равно 50 000» (первая запись) и «больше или равно 50 000 и меньше или равно 80 000» (вторая запись). Для корректной работы алгоритма нам нужно, чтобы значение попадало только в один из интервалов.
Самый простой и эффективный способ исправить логику — отказаться от оператора МЕЖДУ в пользу комбинации условий со строгим и нестрогим сравнением. Это позволит сделать одну из границ интервала «открытой». Проанализируем, как изменится текст запроса:
ВЫБРАТЬ
ЗначенияСкидок.ПроцентСкидки КАК ПроцентСкидки
ИЗ
РегистрСведений.ЗначенияСкидок КАК ЗначенияСкидок
ГДЕ
&СуммаНакопления >= ЗначенияСкидок.От
И &СуммаНакопления < ЗначенияСкидок.До
В данном примере мы включили нижнюю границу (>=) и исключили верхнюю (<). Теперь значение 50 000 не попадет в первый интервал, так как условие «50 000 меньше 50 000» ложно, но успешно попадет во второй. Этот метод считается классическим при работе с непрерывными шкалами.
Существует еще более надежный архитектурный подход, который часто применяется в типовых конфигурациях 1С (например, в 1С:ERP или УТ 11). Мы можем хранить в регистре только нижнюю границу диапазона (поле От). Это избавляет нас от необходимости синхронизировать верхнюю границу предыдущей записи с нижней границей последующей.
Давайте выясним, как построить запрос в этом случае. Нам нужно найти ближайший «порог», который меньше или равен нашей сумме, и взять самую первую такую запись при сортировке по убыванию:
ВЫБРАТЬ ПЕРВЫЕ 1
ЗначенияСкидок.ПроцентСкидки КАК ПроцентСкидки
ИЗ
РегистрСведений.ЗначенияСкидок КАК ЗначенияСкидок
ГДЕ
ЗначенияСкидок.От <= &СуммаНакопления
УПОРЯДОЧИТЬ ПО
ЗначенияСкидок.От УБЫВ
Рассмотрим пример: у нас есть пороги 0 (скидка 3%), 10 000 (5%) и 50 000 (10%).
От <= 5000 (это только 0), и вернет 3%.От равным 0 и 10 000. Благодаря сортировке УБЫВ и ПЕРВЫЕ 1, система выберет 10 000 и вернет 5%.Этот метод наиболее устойчив к ошибкам заполнения данных, так как исключает саму возможность появления «дырок» или перекрытий в интервалах.
Часто в шкалах последний интервал не имеет верхней границы (например, «от 100 000 и выше»). Если вы используете структуру с двумя колонками (От и До), в поле До может оказаться 0 или пустое значение. В этом случае МЕЖДУ или обычное сравнение перестанут работать.
Проанализируем ситуацию: если мы ищем значение 150 000, а в базе последняя запись — «От 100 000, До 0», условие 150 000 <= 0 вернет ложь. Чтобы этого избежать, используйте функцию ВЫБОР или ЕСТЬNULL для подстановки бесконечно большого числа:
ГДЕ
&СуммаНакопления >= ЗначенияСкидок.От
И (&СуммаНакопления < ЗначенияСкидок.До ИЛИ ЗначенияСкидок.До = 0)
Или через подстановку константы в ВЫБОР:
ГДЕ
&СуммаНакопления >= ЗначенияСкидок.От
И &СуммаНакопления < ВЫБОР
КОГДА ЗначенияСкидок.До = 0 ТОГДА 9999999999
ИНАЧЕ ЗначенияСкидок.До
КОНЕЦ
При работе с интервалами крайне важно учитывать специфику типов данных в 1С:
МЕЖДУ для дат включает время. Если интервал заканчивается датой 31.12.2023, это означает 31.12.2023 00:00:00. Любой документ от этой же даты, но со временем 12:00:00, формально окажется вне интервала, если сравнивать его с концом дня через МЕЖДУ. Поэтому для дат всегда лучше использовать КОНЕЦПЕРИОДА или строгое сравнение < ДобавитьКДате(&ДатаКонца, "Секунда", 1).Посмотрим на ситуацию со стороны пользователя. Если вы позволяете вручную заполнять регистр с интервалами, обязательно добавьте программную проверку при записи (в модуле набора записей или в модуле менеджера регистра).
Система должна проверять:
Использование метода Top 1 (Решение №2) автоматически снимает большинство этих проблем, поэтому мы рекомендуем его как наиболее надежный стандарт разработки в среде 1С:Предприятие.