В практике разработчика 1С часто возникает задача сопоставить данные из нескольких источников. Типичный пример: проверка соответствия прихода и расхода товаров, зафиксированных разными документами. Начинающие программисты часто сталкиваются с ситуацией, когда запрос не возвращает ожидаемые строки или работает некорректно. В этой статье мы подробно разберем, как правильно организовать сравнение числовых значений в запросе, почему стандартное условие ГДЕ может не работать и как корректно обрабатывать пустые значения.
Рассмотрим ситуацию: у нас есть два документа — Приходный ордер на товары и Расходный ордер на товары. Нам нужно найти такие позиции номенклатуры, по которым общее количество прихода не совпадает с общим количеством расхода. Посмотрим на структуру запроса, которая часто вызывает затруднения:
ВЫБРАТЬ
ТаблицыИзДокументов.Номенклатура КАК Номенклатура,
СУММА(ТаблицыИзДокументов.КоличествоРасхода) КАК КоличествоРасхода,
СУММА(ТаблицыИзДокументов.КоличествоПрихода) КАК КоличествоПрихода
ИЗ
(ВЫБРАТЬ
ПриходныйОрдер.Номенклатура КАК Номенклатура,
NULL КАК КоличествоРасхода,
ПриходныйОрдер.Количество КАК КоличествоПрихода
ИЗ
Документ.ПриходныйОрдерНаТовары.Товары КАК ПриходныйОрдер
ГДЕ
ПриходныйОрдер.Ссылка = &ПриходныйОрдер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
РасходныйОрдер.Номенклатура,
РасходныйОрдер.Количество,
NULL
ИЗ
Документ.РасходныйОрдерНаТовары.Товары КАК РасходныйОрдер
ГДЕ
РасходныйОрдер.Ссылка = &РасходныйОрдер) КАК ТаблицыИзДокументов
ГДЕ
ТаблицыИзДокументов.КоличествоРасхода <> ТаблицыИзДокументов.КоличествоПрихода
СГРУППИРОВАТЬ ПО
ТаблицыИзДокументов.Номенклатура
В данном коде кроются две фундаментальные проблемы, которые не позволяют получить верный результат: неправильное использование секции ГДЕ для агрегированных данных и некорректное сравнение с NULL.
Проанализируем порядок выполнения запроса СУБД. Секция ГДЕ (WHERE) выполняется до того, как данные будут сгруппированы. Это означает, что на момент проверки условия система видит отдельные строки из документов, а не итоговые суммы по номенклатуре.
Если в строке прихода КоличествоРасхода равно NULL, а в строке расхода КоличествоПрихода равно NULL, прямое сравнение в секции ГДЕ не даст нужного нам результата по итоговым суммам. Чтобы отфильтровать результат уже после суммирования, необходимо использовать секцию ИМЕЮЩИЕ (HAVING).
Правильный подход: заменим условие фильтрации на работу с агрегатными функциями:
ИМЕЮЩИЕ
СУММА(ТаблицыИзДокументов.КоличествоРасхода) <> СУММА(ТаблицыИзДокументов.КоличествоПрихода)
В языке запросов 1С (как и в SQL) NULL — это не ноль, а «отсутствие значения». Любая математическая операция с NULL (сложение, вычитание, сравнение) всегда возвращает NULL.
Проанализируем выражение: СУММА(КоличествоПрихода) <> СУММА(КоличествоРасхода). Если по какой-то номенклатуре был только приход, то СУММА(КоличествоРасхода) вернет NULL. В итоге сравнение 10 <> NULL даст результат NULL (неопределенность), что в условии фильтрации трактуется как Ложь. Строка просто пропадет из результата, хотя количества явно не равны.
Выясним, как это исправить. Существует два способа:
NULL во вложенных запросах (в конструкции ОБЪЕДИНИТЬ ВСЕ), сразу указывайте 0.Разберем пример с использованием ЕСТЬNULL, так как это наиболее надежный метод:
ВЫБРАТЬ
ТаблицыИзДокументов.Номенклатура КАК Номенклатура,
СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоРасхода, 0)) КАК КоличествоРасхода,
СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоПрихода, 0)) КАК КоличествоПрихода
...
ИМЕЮЩИЕ
СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоРасхода, 0)) <> СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоПрихода, 0))
Часто возникает вопрос: что эффективнее — ОБЪЕДИНИТЬ ВСЕ с последующей группировкой или ПОЛНОЕ СОЕДИНЕНИЕ? Проанализируем ситуацию с точки зрения производительности и полноты данных.
Преимущества объединения:
ЛЕВОГО СОЕДИНЕНИЯ вы можете потерять данные, если товар есть во второй таблице, но отсутствует в первой. ОБЪЕДИНИТЬ ВСЕ гарантирует, что в выборку попадут все уникальные позиции номенклатуры из обоих документов.Соберем все рекомендации воедино и посмотрим на финальный, оптимизированный вариант запроса, который решает поставленную задачу:
ВЫБРАТЬ
ВТ_Данные.Номенклатура КАК Номенклатура,
СУММА(ВТ_Данные.КоличествоПрихода) КАК ВсегоПриход,
СУММА(ВТ_Данные.КоличествоРасхода) КАК ВсегоРасход
ИЗ
(ВЫБРАТЬ
ТЧ.Номенклатура КАК Номенклатура,
ТЧ.Количество КАК КоличествоПрихода,
0 КАК КоличествоРасхода
ИЗ
Документ.ПриходныйОрдерНаТовары.Товары КАК ТЧ
ГДЕ
ТЧ.Ссылка = &ПриходныйОрдер
ОБЪЕДИНИТЬ ВСЕ
ВЫБРАТЬ
ТЧ.Номенклатура,
0,
ТЧ.Количество
ИЗ
Документ.РасходныйОрдерНаТовары.Товары КАК ТЧ
ГДЕ
ТЧ.Ссылка = &РасходныйОрдер) КАК ВТ_Данные
СГРУППИРОВАТЬ ПО
ВТ_Данные.Номенклатура
ИМЕЮЩИЕ
СУММА(ВТ_Данные.КоличествоПрихода) <> СУММА(ВТ_Данные.КоличествоРасхода)
УПОРЯДОЧИТЬ ПО
Номенклатура
Рассмотрим более профессиональный подход. Если ваши документы Приходный ордер на товары и Расходный ордер на товары уже проведены, они, скорее всего, делают записи в регистр накопления (например, Товары на складах).
Вместо того чтобы обращаться напрямую к табличным частям документов, гораздо эффективнее обратиться к виртуальной таблице Обороты этого регистра. Это позволит:
Посмотрим на пример логики через регистр:
ВЫБРАТЬ
Обороты.Номенклатура КАК Номенклатура,
Обороты.В количествеПриход КАК Приход,
Обороты.КоличествоРасход КАК Расход
ИЗ
РегистрНакопления.ТоварыНаСкладах.Обороты(,, Авто, Регистратор В (&СписокДокументов)) КАК Обороты
ГДЕ
...
Для корректного сравнения данных из разных источников в запросе 1С важно придерживаться трех правил: всегда заменяйте NULL на 0 при помощи ЕСТЬNULL или литералов, используйте ИМЕЮЩИЕ для фильтрации результатов группировки и отдавайте предпочтение ОБЪЕДИНИТЬ ВСЕ перед сложными соединениями, если необходимо гарантированно собрать данные из разных таблиц.