Почему задваиваются строки в запросе 1С при использовании ВЫБОР КОГДА и виртуальной таблицы ОстаткиИОбороты

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

Работа с виртуальными таблицами в системе 1С:Предприятие требует глубокого понимания того, как платформа формирует итоговые данные «под капотом». Одной из самых распространенных проблем, с которой сталкиваются даже опытные разработчики, является задвоение строк при попытке вывести детальные записи (до регистратора) совместно с начальными и конечными остатками. Ситуация осложняется, когда в запрос добавляются вычисляемые поля через конструкцию ВЫБОР КОГДА. Рассмотрим детально, почему это происходит и как правильно спроектировать запрос, чтобы получить корректный результат.

Разбор механики виртуальной таблицы ОстаткиИОбороты

Для начала проанализируем, как работает виртуальная таблица ОстаткиИОбороты. Когда мы обращаемся к ней, платформа 1С выполняет сложный запрос к физическим таблицам итогов и движений регистра. Если в параметрах таблицы указана периодичность Период, Запись или используется поле Регистратор, система обязана предоставить данные не только о самих движениях, но и о состоянии остатков на границы выбранного периода.

Проблема заключается в том, что остатки (начальный и конечный) — это не свойства конкретного документа-регистратора. Это значения, существующие «между» документами. Чтобы вывести их в одной таблице с движениями, платформа генерирует специальные технические строки:

  1. Строки движений: в них заполнен Регистратор, и отражены обороты (приход/расход).
  2. Технические строки остатков: в них поле Регистратор обычно содержит NULL (или пустую ссылку), но заполнены поля КоличествоНачальныйОстаток или КоличествоКонечныйОстаток.

Когда вы используете простой запрос без условий в ВЫБОР КОГДА, система компоновки данных (СКД) или сам механизм запросов часто «схлопывает» эти строки. Но как только появляется пользовательская логика, ситуация меняется.

Почему ВЫБОР КОГДА «ломает» группировку

Давайте посмотрим на пример кода, который часто вызывает проблему:


ВЫБОР
    КОГДА ШихтаМастеровВозвратыОстаткиИОбороты.Регистратор ССЫЛКА Документ.ВозвратВозвратаМастерам
        ТОГДА ШихтаМастеровВозвратыОстаткиИОбороты.КоличествоРасход
    ИНАЧЕ 0
КОНЕЦ КАК Мастерам

Проанализируем, что происходит в этот момент в памяти сервера 1С. Система обрабатывает две записи для одной и той же номенклатуры:

  1. Запись 1 (Движение): Регистратор заполнен. Условие ССЫЛКА срабатывает, в колонке «Мастерам» появляется число (например, 10). Остатки в этой строке могут быть нулевыми или неполными.
  2. Запись 2 (Техническая): Регистратор равен NULL. Условие ССЫЛКА не срабатывает. В колонке «Мастерам» получается 0. Зато здесь заполнены поля начального и конечного остатка.

В итоге мы видим две строки вместо одной. Система не может объединить их автоматически, так как значения в колонке «Мастерам» различаются (в одной 10, в другой 0). С точки зрения SQL — это разные записи.

Решение №1: Использование вложенных запросов и группировки

Самый надежный способ «схлопнуть» задвоившиеся строки — это обернуть ваш запрос во вложенную выборку и применить агрегатные функции. Нам нужно сгруппировать данные по всем измерениям и регистратору, а числовые показатели просуммировать. Выясним, как это выглядит на практике.

Рассмотрим структуру правильного запроса:


ВЫБРАТЬ
    ВложенныйЗапрос.Регистратор,
    ВложенныйЗапрос.Шихта,
    СУММА(ВложенныйЗапрос.КоличествоНачальныйОстаток) КАК НачОстаток,
    СУММА(ВложенныйЗапрос.КоличествоПриход) КАК Приход,
    СУММА(ВложенныйЗапрос.КоличествоРасход) КАК Расход,
    СУММА(ВложенныйЗапрос.КоличествоКонечныйОстаток) КАК КонОстаток,
    СУММА(ВложенныйЗапрос.Мастерам) КАК Мастерам,
    СУММА(ВложенныйЗапрос.Контрагенту) КАК Контрагенту
ИЗ (
    ВЫБРАТЬ
        Т.Регистратор КАК Регистратор,
        Т.Шихта КАК Шихта,
        Т.КоличествоНачальныйОстаток КАК КоличествоНачальныйОстаток,
        Т.КоличествоПриход КАК КоличествоПриход,
        Т.КоличествоРасход КАК КоличествоРасход,
        Т.КоличествоКонечныйОстаток КАК КоличествоКонечныйОстаток,
        ВЫБОР
            КОГДА Т.Регистратор ССЫЛКА Документ.ВозвратВозвратаМастерам
                ТОГДА Т.КоличествоРасход
            ИНАЧЕ 0
        КОНЕЦ КАК Мастерам,
        ВЫБОР
            КОГДА Т.Регистратор ССЫЛКА Документ.ВывозВозвратаМастеров
                ТОГДА Т.КоличествоРасход
            ИНАЧЕ 0
        КОНЕЦ КАК Контрагенту
    ИЗ
        РегистрНакопления.ШихтаМастеровВозвраты.ОстаткиИОбороты(&Начало, &Конец, Авто, , ) КАК Т
) КАК ВложенныйЗапрос

СГРУППИРОВАТЬ ПО
    ВложенныйЗапрос.Регистратор,
    ВложенныйЗапрос.Шихта

Важный момент: при использовании такого подхода нужно быть осторожным с остатками. Если в один период (секунду) попадает несколько документов, простое суммирование остатков по регистратору может привести к их неверному отображению в итогах. Однако для детальных записей этот метод работает корректно, так как он собирает данные из «технической» строки и строки движения в одну общую строку.

Решение №2: Разделение на обороты и остатки (JOIN/UNION)

Если вы хотите добиться максимальной производительности и чистоты данных, проанализируем ситуацию с разделением запросов. Вместо того чтобы мучить одну таблицу ОстаткиИОбороты, мы можем взять данные из двух разных источников.

  1. Таблица Обороты: из нее мы получаем Регистратор и все наши вычисляемые поля через ВЫБОР КОГДА. Здесь не будет лишних строк с остатками, так как таблица оборотов их просто не содержит.
  2. Таблица Остатки: из нее мы получаем только итоговые цифры на начало и конец периода.

Затем эти данные объединяются через ОБЪЕДИНИТЬ ВСЕ и группируются. Посмотрим на пример логики:


// Часть 1: Получаем только обороты с регистратором
ВЫБРАТЬ
    Т.Регистратор,
    Т.Шихта,
    0 КАК НачОстаток,
    Т.КоличествоОборот КАК Оборот,
    0 КАК КонОстаток,
    ВЫБОР КОГДА Т.Регистратор ССЫЛКА Документ.Тип1 ТОГДА Т.КоличествоОборот ИНАЧЕ 0 КОНЕЦ КАК Поле1
ИЗ
    РегистрНакопления.ИмяРегистра.Обороты(&Начало, &Конец, Регистратор, ) КАК Т

ОБЪЕДИНИТЬ ВСЕ

// Часть 2: Получаем остатки без привязки к документам
ВЫБРАТЬ
    НЕОПРЕДЕЛЕНО,
    Т.Шихта,
    Т.КоличествоНачальныйОстаток,
    0,
    Т.КоличествоКонечныйОстаток,
    0
ИЗ
    РегистрНакопления.ИмяРегистра.ОстаткиИОбороты(&Начало, &Конец, , , ) КАК Т

Такой подход гарантирует, что каждая операция будет учтена ровно один раз, а остатки будут выведены отдельной строкой или приклеены к общим итогам без дублирования.

Решение №3: Тонкая настройка Системы компоновки данных (СКД)

Если ваш отчет строится на СКД, проблему можно решить без переписывания запроса в «слоеный пирог». Система компоновки умеет самостоятельно обрабатывать технические строки виртуальных таблиц, если правильно расставить Роли полей.

Проанализируем шаги настройки в схеме компоновки:

Важно: в настройках компоновки (вкладка «Прочее») проверьте флаг «Игнорировать значения NULL». Иногда его установка помогает СКД корректно схлопнуть строки, где регистратор не заполнен.

Заключение и рекомендации

Мы выяснили, что причина задвоения — это не ошибка в коде ВЫБОР КОГДА, а следствие работы виртуальной таблицы 1С, которая разделяет информацию о движениях и информацию о границах периода.

Для исправления ситуации рекомендуем придерживаться следующих правил:

  1. Если вам нужны детальные записи и остатки в одном списке, всегда используйте вложенный запрос с итоговой группировкой. Это «защитит» ваши данные от дублей, возникших из-за технических строк.
  2. Старайтесь не использовать Регистратор в параметрах периодичности виртуальной таблицы ОстаткиИОбороты без крайней необходимости. Если нужны только обороты — используйте таблицу Обороты.
  3. В СКД всегда проверяйте заполнение ролей для полей остатков и периодов. Это автоматизирует процесс объединения строк и избавляет от необходимости писать сложные SQL-конструкции вручную.

Помните, что правильная структура запроса не только убирает визуальные дубли, но и существенно ускоряет работу отчета, так как сервер баз данных более эффективно строит план выполнения при четко заданных группировках.

← На главную