При работе с высоконагруженными базами на платформе 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 = ДПослПрод.Покупатель
Второй важный момент — логика получения «самой последней» записи. Использование SELECT TOP 1 внутри подзапроса без корреляции с внешними данными вернет ровно одну запись из всей таблицы регистра. Если задача стоит получить последнюю продажу по каждому контрагенту из списка, TOP 1 не сработает — результат будет содержать данные только по одному клиенту или будет пустым при несовпадении условий.
Для решения этой задачи в 1С 7.7 классическим подходом является использование агрегатной функции MAX() по полю Date_Time_IDDOC в сочетании с GROUP BY.
Рассмотрим пошагово, как построить эффективный запрос. Нам нужно объединить временную таблицу #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 сначала отфильтрует записи только по нужным контрагентам, а затем выполнит группировку. Это значительно сокращает объем обрабатываемых данных.
ID в справочниках 1С 7.7 всегда имеет фиксированную длину 13 символов (Base36). Если ваша временная таблица #TempTable заполняется строками другой длины, соединение JOIN не сработает. Убедитесь, что при заполнении временной таблицы идентификаторы не обрезаются и не дополняются лишними символами.$Справочник.Контрагенты вернет в том числе и помеченные на удаление элементы. Если их нужно исключить, добавьте условие WHERE Спр.IsMark = 0.Date_Time_IDDOC проиндексировано. Однако использование функций типа LEFT() в условии WHERE может привести к сканированию таблицы (Table Scan). В нашем случае мы используем MAX(), что позволяет SQL Server эффективно использовать индекс.LEFT JOIN предпочтительнее, если вам нужно вывести всех контрагентов из временной таблицы, даже тех, по которым не было продаж в указанном периоде. Если нужен только список купивших — используйте INNER JOIN.Подводя итог, можно сказать, что ключом к успеху в написании сложных прямых запросов в 1С 7.7 является четкое разделение метаданных 1С (типизация через $) и чистого синтаксиса SQL в алиасах подзапросов, а также грамотное использование фильтрации внутри вложенных выборок.