Как в запросе 1С правильно сравнить числовые данные из разных документов и найти расхождения

Программист 1С v8.3 (Управляемые формы) Управленческий учет
← На главную

В практике разработчика 1С часто возникает задача сопоставить данные из нескольких источников. Типичный пример: проверка соответствия прихода и расхода товаров, зафиксированных разными документами. Начинающие программисты часто сталкиваются с ситуацией, когда запрос не возвращает ожидаемые строки или работает некорректно. В этой статье мы подробно разберем, как правильно организовать сравнение числовых значений в запросе, почему стандартное условие ГДЕ может не работать и как корректно обрабатывать пустые значения.

Разбор типичной ошибки в логике запроса

Рассмотрим ситуацию: у нас есть два документа — Приходный ордер на товары и Расходный ордер на товары. Нам нужно найти такие позиции номенклатуры, по которым общее количество прихода не совпадает с общим количеством расхода. Посмотрим на структуру запроса, которая часто вызывает затруднения:


ВЫБРАТЬ
    ТаблицыИзДокументов.Номенклатура КАК Номенклатура,
    СУММА(ТаблицыИзДокументов.КоличествоРасхода) КАК КоличествоРасхода,
    СУММА(ТаблицыИзДокументов.КоличествоПрихода) КАК КоличествоПрихода
ИЗ
    (ВЫБРАТЬ
        ПриходныйОрдер.Номенклатура КАК Номенклатура,
        NULL КАК КоличествоРасхода,
        ПриходныйОрдер.Количество КАК КоличествоПрихода
    ИЗ
        Документ.ПриходныйОрдерНаТовары.Товары КАК ПриходныйОрдер
    ГДЕ
        ПриходныйОрдер.Ссылка = &ПриходныйОрдер
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        РасходныйОрдер.Номенклатура,
        РасходныйОрдер.Количество,
        NULL
    ИЗ
        Документ.РасходныйОрдерНаТовары.Товары КАК РасходныйОрдер
    ГДЕ
        РасходныйОрдер.Ссылка = &РасходныйОрдер) КАК ТаблицыИзДокументов
ГДЕ
    ТаблицыИзДокументов.КоличествоРасхода <> ТаблицыИзДокументов.КоличествоПрихода
СГРУППИРОВАТЬ ПО
    ТаблицыИзДокументов.Номенклатура

В данном коде кроются две фундаментальные проблемы, которые не позволяют получить верный результат: неправильное использование секции ГДЕ для агрегированных данных и некорректное сравнение с NULL.

Проблема 1: Использование ГДЕ вместо ИМЕЮЩИЕ

Проанализируем порядок выполнения запроса СУБД. Секция ГДЕ (WHERE) выполняется до того, как данные будут сгруппированы. Это означает, что на момент проверки условия система видит отдельные строки из документов, а не итоговые суммы по номенклатуре.

Если в строке прихода КоличествоРасхода равно NULL, а в строке расхода КоличествоПрихода равно NULL, прямое сравнение в секции ГДЕ не даст нужного нам результата по итоговым суммам. Чтобы отфильтровать результат уже после суммирования, необходимо использовать секцию ИМЕЮЩИЕ (HAVING).

Правильный подход: заменим условие фильтрации на работу с агрегатными функциями:


ИМЕЮЩИЕ
    СУММА(ТаблицыИзДокументов.КоличествоРасхода) <> СУММА(ТаблицыИзДокументов.КоличествоПрихода)

Проблема 2: Коварство значения NULL

В языке запросов 1С (как и в SQL) NULL — это не ноль, а «отсутствие значения». Любая математическая операция с NULL (сложение, вычитание, сравнение) всегда возвращает NULL.

Проанализируем выражение: СУММА(КоличествоПрихода) <> СУММА(КоличествоРасхода). Если по какой-то номенклатуре был только приход, то СУММА(КоличествоРасхода) вернет NULL. В итоге сравнение 10 <> NULL даст результат NULL (неопределенность), что в условии фильтрации трактуется как Ложь. Строка просто пропадет из результата, хотя количества явно не равны.

Выясним, как это исправить. Существует два способа:

  1. Замена NULL на 0 в источнике данных: Вместо выбора NULL во вложенных запросах (в конструкции ОБЪЕДИНИТЬ ВСЕ), сразу указывайте 0.
  2. Использование функции ЕСТЬNULL: Оборачивайте поля в функцию, которая подставит ноль, если значение не определено.

Разберем пример с использованием ЕСТЬNULL, так как это наиболее надежный метод:


ВЫБРАТЬ
    ТаблицыИзДокументов.Номенклатура КАК Номенклатура,
    СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоРасхода, 0)) КАК КоличествоРасхода,
    СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоПрихода, 0)) КАК КоличествоПрихода
...
ИМЕЮЩИЕ
    СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоРасхода, 0)) <> СУММА(ЕСТЬNULL(ТаблицыИзДокументов.КоличествоПрихода, 0))

Оптимизация: Почему ОБЪЕДИНИТЬ ВСЕ лучше СОЕДИНЕНИЯ?

Часто возникает вопрос: что эффективнее — ОБЪЕДИНИТЬ ВСЕ с последующей группировкой или ПОЛНОЕ СОЕДИНЕНИЕ? Проанализируем ситуацию с точки зрения производительности и полноты данных.

Преимущества объединения:

Эталонный пример запроса

Соберем все рекомендации воедино и посмотрим на финальный, оптимизированный вариант запроса, который решает поставленную задачу:


ВЫБРАТЬ
    ВТ_Данные.Номенклатура КАК Номенклатура,
    СУММА(ВТ_Данные.КоличествоПрихода) КАК ВсегоПриход,
    СУММА(ВТ_Данные.КоличествоРасхода) КАК ВсегоРасход
ИЗ
    (ВЫБРАТЬ
        ТЧ.Номенклатура КАК Номенклатура,
        ТЧ.Количество КАК КоличествоПрихода,
        0 КАК КоличествоРасхода
    ИЗ
        Документ.ПриходныйОрдерНаТовары.Товары КАК ТЧ
    ГДЕ
        ТЧ.Ссылка = &ПриходныйОрдер
    
    ОБЪЕДИНИТЬ ВСЕ
    
    ВЫБРАТЬ
        ТЧ.Номенклатура,
        0,
        ТЧ.Количество
    ИЗ
        Документ.РасходныйОрдерНаТовары.Товары КАК ТЧ
    ГДЕ
        ТЧ.Ссылка = &РасходныйОрдер) КАК ВТ_Данные

СГРУППИРОВАТЬ ПО
    ВТ_Данные.Номенклатура

ИМЕЮЩИЕ
    СУММА(ВТ_Данные.КоличествоПрихода) <> СУММА(ВТ_Данные.КоличествоРасхода)

УПОРЯДОЧИТЬ ПО
    Номенклатура

Важное замечание: Работа с регистрами

Рассмотрим более профессиональный подход. Если ваши документы Приходный ордер на товары и Расходный ордер на товары уже проведены, они, скорее всего, делают записи в регистр накопления (например, Товары на складах).

Вместо того чтобы обращаться напрямую к табличным частям документов, гораздо эффективнее обратиться к виртуальной таблице Обороты этого регистра. Это позволит:

Посмотрим на пример логики через регистр:


ВЫБРАТЬ
    Обороты.Номенклатура КАК Номенклатура,
    Обороты.В количествеПриход КАК Приход,
    Обороты.КоличествоРасход КАК Расход
ИЗ
    РегистрНакопления.ТоварыНаСкладах.Обороты(,, Авто, Регистратор В (&СписокДокументов)) КАК Обороты
ГДЕ
    ...

Подведем итоги

Для корректного сравнения данных из разных источников в запросе 1С важно придерживаться трех правил: всегда заменяйте NULL на 0 при помощи ЕСТЬNULL или литералов, используйте ИМЕЮЩИЕ для фильтрации результатов группировки и отдавайте предпочтение ОБЪЕДИНИТЬ ВСЕ перед сложными соединениями, если необходимо гарантированно собрать данные из разных таблиц.

← На главную