Работа с платформой 1С:Предприятие 7.7, особенно в части «черных запросов» (встроенного языка запросов), таит в себе немало особенностей, которые могут поставить в тупик даже опытных разработчиков. Одной из самых коварных проблем является внезапное исчезновение или «зануление» начальных (НачОст) и конечных (КонОст) остатков при изменении границ периода отчета. В этой статье мы подробно разберем, почему это происходит, как механизм итогов 1С 7.7 взаимодействует с базой данных и как правильно составить запрос, чтобы данные всегда оставались корректными.
Рассмотрим типичную ситуацию. Программист создает запрос по регистру остатков. В запросе используются стандартные функции для получения итогов. Проблема проявляется странным образом: если период запроса совпадает с границами месяца (например, с 1 по 30 апреля), остатки отображаются верно. Однако, стоит сдвинуть дату начала всего на один день (например, со 2 апреля), как конечный остаток по определенным позициям превращается в ноль. Если же сдвинуть дату конца — пропадает начальный остаток. Проанализируем типичный текст такого «проблемного» запроса:
|Период с ДатаНачала по ДатаКонца;
|Продукт = Регистр.Сыр.Продукт;
|ДатаВарки = Регистр.Сыр.Документ.ДатаДок; // Обращение через точку!
|ЗПодразделение = Регистр.Сыр.Подразделение;
|Группировка ЗПодразделение;
|Группировка Продукт;
|Группировка ДатаВарки; // Группировка по виртуальному полю
|Функция СНД_Г = НачОст(Головы);
|Функция СКД_Г = КонОст(Головы);
Важный момент: в данном примере ДатаВарки получается не напрямую из измерения регистра, а через обращение к реквизиту документа, который является измерением. Именно в этой «точке» кроется причина всех бед.
Чтобы понять, почему данные исчезают, нам необходимо заглянуть «под капот» системы. Выясним причины, опираясь на архитектуру хранения данных в SQL-версии 1С 7.7:
Остаток на 02.04 = Итоги на 01.04 + Движения за 01.04. Для этого система обращается к таблице движений (RA).Регистр.Сыр.Документ.ДатаДок, встроенный генератор запросов 1С добавляет соединение (JOIN) с таблицей заголовков документов. При расчете виртуальных остатков (когда данные «докручиваются» движениями), механизм сопоставления итогов из таблицы RG и движений из таблицы RA часто дает сбой, если группировка идет по реквизиту через точку.Документ в регистре не заполнен или документ помечен на удаление (и его дата в БД стала неопределенной), соединение через точку возвращает NULL. В этом случае агрегатные функции НачОст и КонОст во встроенных запросах 1С 7.7 часто некорректно отрабатывают группировку, обнуляя результат для всей строки.Проанализируем самый эффективный способ исправления ситуации. Основное правило «черного запроса» 1С 7.7 гласит: никогда не используйте обращение через точку в секции группировок, если вам нужны корректные остатки.
Вместо того чтобы группировать по дате документа, мы должны сгруппировать данные по самому документу (измерению). Рассмотрим исправленный код запроса:
// Вместо обращения к реквизиту через точку:
// |ДатаВарки = Регистр.Сыр.Документ.ДатаДок;
// Используем само измерение напрямую:
|ДокВарки = Регистр.Сыр.Документ;
|Группировка ЗПодразделение;
|Группировка Продукт;
|Группировка ДокВарки; // Группируем по объекту-ссылке
При таком подходе 1С строит запрос к таблицам итогов и движений напрямую по физическим полям (идентификаторам объектов), что гарантирует математическую точность расчета остатков. Выясним, как же теперь получить дату? Мы сделаем это позже, при обходе результата.
После того как мы изменили группировку на объектную, нам все равно нужно вывести дату в отчет. Рассмотрим, как правильно организовать цикл обработки данных:
Пока Запрос.Группировка(1) = 1 Цикл // Подразделение
Пока Запрос.Группировка(2) = 1 Цикл // Продукт
Пока Запрос.Группировка(3) = 1 Цикл // ДокВарки
// Получаем дату варки напрямую из объекта группировки
ТекДатаВарки = Запрос.ДокВарки.ДатаДок;
ПечатьСтр(ТекДатаВарки, Запрос.СНД_Г, Запрос.СКД_Г);
КонецЦикла;
КонецЦикла;
КонецЦикла;
Хотя этот метод может работать чуть медленнее на огромных выборках (из-за обращения к базе за реквизитом каждого объекта в цикле), он является единственным надежным способом сохранения итогов при использовании штатного механизма запросов.
Если вам критически важно видеть остатки именно в разрезе дат (например, когда в один день много документов и вы хотите их схлопнуть), можно воспользоваться встроенной группировкой День. Посмотрим на пример:
|Период с ДатаНачала по ДатаКонца;
|Продукт = Регистр.Сыр.Продукт;
|Группировка Продукт;
|Группировка День; // Автоматическая группировка по датам движений
|Функция Нач = НачОст(Вес);
|Функция Кон = КонОст(Вес);
Важно: этот метод подходит для хронологических отчетов, но будьте осторожны — он может «раздуть» количество строк в выборке, если вы неправильно настроите параметры вывода пустых значений.
Для тех, кто работает на SQL-версиях 1С 7.7 (как в случае автора вопроса с SQL 2008 R2), существует еще более радикальное и эффективное решение — использование внешних компонент, таких как 1С++.
Прямые запросы позволяют написать текст на чистом T-SQL, обращаясь напрямую к таблицам RG* и RA*. Это полностью снимает проблему «глюков» встроенного языка запросов, позволяет использовать любые типы соединений и работает в десятки (а иногда и в сотни) раз быстрее штатных средств. Если ваш отчет по остаткам сыра становится слишком сложным, рассмотрите возможность перехода на прямой запрос к базе данных.
Проанализировав ситуацию, мы можем составить краткий чек-лист для предотвращения потери остатков в 1С 7.7:
Группировка используйте только те переменные, которые ссылаются непосредственно на измерения.Пока Запрос.Группировка() = 1.Соблюдение этих простых, но строгих правил позволит вам строить надежные отчеты, которые будут выдавать корректные остатки независимо от выбранного пользователем периода.