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

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

При работе с высоконагруженными базами на платформе 1С 7.7 стандартных средств языка зачастую становится недостаточно. В таких случаях программисты прибегают к использованию прямых SQL-запросов через внешние компоненты (например, 1C++ или Rainbow). Однако специфика работы препроцессора 1С++ и особенности структуры таблиц SQL Server могут приводить к труднодиагностируемым ошибкам. В данной статье мы разберем ситуацию, когда необходимо получить данные о последней продаже по списку контрагентов из регистра «Продажи», соединив их с данными справочника.

Разбор ошибки «Недопустимое имя столбца» в подзапросах

Рассмотрим первую проблему, с которой сталкиваются разработчики. В исходном запросе автора возникла ошибка Недопустимое имя столбца "Контрагент". Проанализируем причину. Когда мы пишем конструкцию вида:


SELECT
  ДПослПрод.IDDoc as [Док $Документ],
  ДПослПрод.Контрагент AS [Контрагент $Справочник.Контрагенты] 
FROM ( 
  SELECT TOP 1
    Рег.IDDoc,
    $Рег.Покупатель AS [Контрагент $Справочник.Контрагенты]
  ...
) ДПослПрод

Важно понимать механизм типизации метаданных в 1С++. Символы $ и конструкции в квадратных скобках (например, [Контрагент $Справочник.Контрагенты]) обрабатываются драйвером 1С++ до того, как запрос будет отправлен на SQL Server. Однако во внутреннем подзапросе алиас (псевдоним) столбца не должен содержать метасимволы 1С++, если вы планируете обращаться к этому столбцу во внешнем запросе или в условии соединения JOIN.

Решение: Внутри подзапроса всегда используйте простые имена для колонок (например, BuyerID или Покупатель). Типизацию через $ применяйте только на самом верхнем уровне выборки SELECT. Выясним, как это выглядит на практике:


SELECT
  ДПослПрод.Покупатель AS [Контрагент $Справочник.Контрагенты]
FROM (
  SELECT
    $Рег.Покупатель AS Покупатель -- Используем простое имя
  FROM
    $Регистр.Продажи as Рег
) ДПослПрод
LEFT JOIN $Справочник.Контрагенты AS Спр ON Спр.ID = ДПослПрод.Покупатель

Проблема использования TOP 1 для групповых данных

Второй важный момент — логика получения «самой последней» записи. Использование SELECT TOP 1 внутри подзапроса без корреляции с внешними данными вернет ровно одну запись из всей таблицы регистра. Если задача стоит получить последнюю продажу по каждому контрагенту из списка, TOP 1 не сработает — результат будет содержать данные только по одному клиенту или будет пустым при несовпадении условий.

Для решения этой задачи в 1С 7.7 классическим подходом является использование агрегатной функции MAX() по полю Date_Time_IDDOC в сочетании с GROUP BY.

Реализация через GROUP BY и MAX (Рабочий вариант)

Рассмотрим пошагово, как построить эффективный запрос. Нам нужно объединить временную таблицу #TempTable (со списком нужных нам контрагентов) с результатами группировки по регистру продаж.


SELECT
  Спр.Code AS Код, 
  Спр.ID AS [Контрагент $Справочник.Контрагенты], 
  subsel.ДатаПослПродажи 
FROM #TempTable AS temp 
LEFT JOIN (
  SELECT
    $Рег.Покупатель AS Покупатель,
    -- Получаем максимальное значение даты-времени-идентификатора
    CAST(LEFT(MAX(Рег.Date_Time_IDDOC), 8) AS datetime) AS ДатаПослПродажи
  FROM
    $Регистр.Продажи AS Рег
  WHERE
    $Рег.Фирма = :ВбрФирма 
  GROUP BY 
    $Рег.Покупатель
) AS subsel ON temp.val = subsel.Покупатель
LEFT JOIN $Справочник.Контрагенты AS Спр ON Спр.ID = temp.val

В данном примере мы используем поле Date_Time_IDDOC. В 1С 7.7 это строковое поле, которое содержит дату, время и ID документа в компактном виде. Поскольку строки сортируются лексикографически, MAX(Date_Time_IDDOC) гарантированно вернет самую позднюю запись.

Оптимизация производительности: фильтрация внутри подзапроса

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


SELECT
  temp.val AS [Контрагент $Справочник.Контрагенты], 
  subsel.ДатаПослПродажи 
FROM #TempTable temp 
LEFT JOIN (
  SELECT
    $Рег.Покупатель AS Покупатель,
    CAST(LEFT(MAX(Рег.Date_Time_IDDOC), 8) AS datetime) AS ДатаПослПродажи
  FROM
    $Регистр.Продажи AS Рег
  WHERE
    $Рег.Фирма = :ВбрФирма 
    -- Критически важная оптимизация:
    AND $Рег.Покупатель IN (SELECT val FROM #TempTable)
  GROUP BY 
    $Рег.Покупатель
) subsel ON temp.val = subsel.Покупатель

Таким образом, SQL Server сначала отфильтрует записи только по нужным контрагентам, а затем выполнит группировку. Это значительно сокращает объем обрабатываемых данных.

Дополнительные рекомендации при работе с прямыми запросами

  1. Обработка типизации идентификаторов: Поле ID в справочниках 1С 7.7 всегда имеет фиксированную длину 13 символов (Base36). Если ваша временная таблица #TempTable заполняется строками другой длины, соединение JOIN не сработает. Убедитесь, что при заполнении временной таблицы идентификаторы не обрезаются и не дополняются лишними символами.
  2. Пометки на удаление: Прямой запрос к таблице справочника $Справочник.Контрагенты вернет в том числе и помеченные на удаление элементы. Если их нужно исключить, добавьте условие WHERE Спр.IsMark = 0.
  3. Использование индексов: Поле Date_Time_IDDOC проиндексировано. Однако использование функций типа LEFT() в условии WHERE может привести к сканированию таблицы (Table Scan). В нашем случае мы используем MAX(), что позволяет SQL Server эффективно использовать индекс.
  4. Соединения (JOIN): Помните, что LEFT JOIN предпочтительнее, если вам нужно вывести всех контрагентов из временной таблицы, даже тех, по которым не было продаж в указанном периоде. Если нужен только список купивших — используйте INNER JOIN.

Подводя итог, можно сказать, что ключом к успеху в написании сложных прямых запросов в 1С 7.7 является четкое разделение метаданных 1С (типизация через $) и чистого синтаксиса SQL в алиасах подзапросов, а также грамотное использование фильтрации внутри вложенных выборок.

← На главную