Почему в запросе 1С 7.7 на SQL перестает работать разность в функции Сумма и как это исправить?

Программист 1С v7.7 Управленческий учет IT и автоматизация бизнеса
← На главную

При переходе конфигураций с файловой базы данных (DBF) на клиент-серверный вариант (MS SQL Server) разработчики 1С 7.7 часто сталкиваются с ошибками в штатных запросах. Типичная ситуация возникает при попытке рассчитать сложную агрегатную функцию по реквизитам нескольких видов документов, например: Функция КоличествоСумма = Сумма(Количество1 - Количество2);. В DBF-версии такой запрос возвращал корректные итоги, а в SQL-версии начинает рассчитываться сумма только первого слагаемого.

Разберем причины такого поведения платформы 1С 7.7 и рассмотрим несколько надежных способов решения этой задачи.

Выясним причину: почему запрос работает в DBF, но ломается в SQL

Проанализируем механизм выполнения запроса платформой 1С:Предприятие 7.7 в двух различных режимах:

  1. В режиме DBF: штатный движок платформы последовательно перебирает записи таблиц документов встроенным интерпретатором. Для каждой строки документа вычисляется переданное выражение на встроенном языке и накапливается сумма, поэтому арифметические операции внутри агрегата отрабатывают корректно.
  2. В режиме SQL: платформа транслирует текст запроса 1С в команду на языке T-SQL. При обращении в одном запросе к разным видам документов (например, Документ.Заявка и Документ.ВозвратОтПокупателя) транслятор формирует объединение выборок через UNION ALL. В ветке выборки одного документа поля другого документа отсутствуют (принимают значение NULL или 0). Синтаксический анализатор платформы 1С 7.7 не умеет корректно транслировать межтабличные арифметические выражения внутри функции Сумма(), отсекает второй операнд и оставляет в SQL-запросе только первую переменную.

Способ 1. Расчет разности при обходе результатов запроса

Самый простой и быстрый способ исправить отчет без изменения архитектуры данных — разделить агрегацию на две независимые функции и выполнять вычитание программно при выводе или обходе группировок.

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


ТекстЗапроса = 
"//{{ЗАПРОС(Сформировать)
|Период с ВыбНачПериода по ВыбКонПериода;
|Док = Документ.Заявка.ТекущийДокумент, Документ.ВозвратОтПокупателя.ТекущийДокумент;
|Количество1 = Документ.Заявка.Количество;
|Количество2 = Документ.ВозвратОтПокупателя.Количество;
|Товар = Документ.Заявка.Товар, Документ.ВозвратОтПокупателя.Товар;
|Функция Количество1Сумма = Сумма(Количество1);
|Функция Количество2Сумма = Сумма(Количество2);
|Группировка Товар без групп;
|Условие(Товар в ВыбТовар);
|";

Разберем по шагам, как получить итоговую разницу в цикле обхода результатов:


Запрос = СоздатьОбъект("Запрос");
Если Запрос.Выполнить(ТекстЗапроса) = 1 Тогда
    Пока Запрос.Группировка("Товар") = 1 Цикл
        ТекТовар = Запрос.Товар;
        Кол1 = Запрос.Количество1Сумма;
        Кол2 = Запрос.Количество2Сумма;
        
        // Вычисляем итоговую разницу программно
        ИтоговоеКоличество = Кол1 - Кол2;
        
        Таб.ВывестиСекцию("Строка");
    КонецЦикла;
КонецЕсли;

Способ 2. Обработка через выгрузку в Таблицу Значений

Если результат запроса передается в другие процедуры или требует сортировки по результирующему количеству, удобно выгрузить данные в объект ТаблицаЗначений и рассчитать колонку разницы:


ТЗ = СоздатьОбъект("ТаблицаЗначений");
Запрос.Выгрузить(ТЗ);

// Добавляем расчетную числовую колонку
ТЗ.НоваяКолонка("КоличествоСумма", "Число", 15, 3);

ТЗ.ВыбратьСтроки();
Пока ТЗ.ПолучитьСтроку() = 1 Цикл
    ТЗ.КоличествоСумма = ТЗ.Количество1Сумма - ТЗ.Количество2Сумма;
КонецЦикла;

Способ 3. Переход на запросы к регистрам учета

Построение отчетов по документам движения напрямую — неоптимальная практика для оперативного учета. В типовых конфигурациях (например, «Торговля и Склад 7.7») операции отгрузки по заявкам и возвратов фиксируются в регистрах (например, регистр Продажи или ОстаткиТМЦ).

Запросы к регистрам в SQL-версии 1С 7.7 работают в десятки раз быстрее, так как платформа обращается к готовым агрегированным таблицам итогов:


ТекстЗапроса = 
"//{{ЗАПРОС(Сформировать)
|Период с ВыбНачПериода по ВыбКонПериода;
|Товар = Регистр.Продажи.Номенклатура;
|Кол = Регистр.Продажи.Количество;
|Функция РасходКоличество = Расход(Кол);
|Функция ПриходКоличество = Приход(Кол);
|Группировка Товар без групп;
|";

Способ 4. Использование прямых SQL-запросов (ODBC)

Для высоконагруженных баз данных и сложных отчетов оптимальным решением является использование прямых запросов к СУБД через компоненты работы с ODBC (например, 1CSSQL или ODBCRecordSet из состава 1C++). В этом случае разработчик полностью контролирует текст формируемого SQL-запроса, используя конструкцию UNION ALL с инверсией знака для возвратов:


// Пример логики прямого запроса:
// SELECT ID_Товара, SUM(Количество) AS ИтогоКоличество
// FROM (
//     SELECT IDDOC, Товар AS ID_Товара, Количество FROM _1SDOCR_Заявка
//     UNION ALL
//     SELECT IDDOC, Товар AS ID_Товара, -Количество FROM _1SDOCR_Возврат
// ) AS Движения
// GROUP BY ID_Товара

Важное предостережение: использование метода Запрос.ВключитьSQL(0) отключает генерацию T-SQL и переводит выполнение в режим эмуляции DBF. Хотя это восстанавливает работу исходного кода без переписывания, скорость формирования отчета на сервере SQL критически падает (в 10–20 раз и более), поэтому использовать данный флаг в рабочих базах крайне не рекомендуется.

← На главную