При работе с платформой 1С:Предприятие 7.7 программисты часто прибегают к использованию прямых запросов (через компоненты 1C++ или Rainbow) для повышения производительности. Одной из типовых задач является получение данных из шапки документа (журнала) одновременно с информацией о количестве строк в его табличной части. Стандартными средствами метаданных это делается легко, но в SQL нам нужно вручную связывать таблицы журнала и таблицы строк документов.
В этой статье мы разберем несколько эффективных способов решения этой задачи, проанализируем их производительность и выясним, какой метод лучше использовать в зависимости от версии вашего SQL-сервера.
Прежде чем переходить к коду, вспомним, как хранятся данные. Информация о документах распределена следующим образом:
_1SJOURN — общий журнал документов, где хранятся общие реквизиты (дата, номер, ID документа, вид документа).$ДокументСтроки.ИмяДокумента (в SQL это таблицы вида DT*) — здесь хранятся строки конкретного вида документа. Связь осуществляется по полю IDDOC.Этот метод стал доступен начиная с SQL Server 2005 и является одним из самых элегантных и современных способов решения задачи. Разберем пример, предложенный в сообщении форума.
Конструкция OUTER APPLY работает как коррелированный подзапрос, но позволяет SQL-серверу более гибко строить план выполнения. Посмотрим на код:
SELECT
жур.IDDOC as [Документ_ID],
жур.DOCNO as [НомерДокумента],
СтрокиДокументов.Количество as [КоличествоСтрок]
FROM
_1SJOURN as жур
OUTER APPLY (
SELECT
COUNT(*) as [Количество]
FROM
$ДокументСтроки.Реализация as док
WHERE
док.IDDOC = жур.IDDOC
) as СтрокиДокументов
WHERE
жур.IDDOCDEF = $ВидДокумента.Реализация
Проанализируем преимущества этого подхода:
APPLY на несколько ядер (особенно в версиях 2014+), что значительно ускоряет обработку больших массивов данных.APPLY "видит" текущую строку из таблицы _1SJOURN.Если вы используете старые версии SQL или предпочитаете классический синтаксис, можно использовать соединение с предварительно сгруппированной таблицей строк. Этот метод считается «золотым стандартом» совместимости.
Разберем, как это реализовать:
SELECT
jur.IDDOC as [Док],
ISNULL(T_Count.Cnt, 0) as [КоличествоСтрок]
FROM
_1SJOURN as jur
LEFT JOIN (
SELECT
IDDOC,
COUNT(*) as Cnt
FROM
$ДокументСтроки.Реализация
GROUP BY
IDDOC
) as T_Count ON T_Count.IDDOC = jur.IDDOC
WHERE
jur.IDDOCDEF = $ВидДокумента.Реализация
Обратите внимание на использование функции ISNULL. При использовании LEFT JOIN, если у документа по какой-то причине нет строк, запрос вернет NULL. Оборачивание в ISNULL(..., 0) гарантирует получение корректного числового значения.
Когда выбирать этот метод? На больших выборках (например, при получении итогов за несколько лет) SQL Server часто строит план типа Hash Match, который может отработать быстрее, чем последовательный перебор строк в APPLY.
Если вам нужно получить количество строк только для одного небольшого отчета и вы не хотите усложнять структуру JOIN, можно вставить подзапрос прямо в список выбираемых полей.
SELECT
jur.IDDOC,
(SELECT COUNT(*) FROM $ДокументСтроки.Реализация as dt WHERE dt.IDDOC = jur.IDDOC) as [К-во строк]
FROM
_1SJOURN as jur
WHERE
jur.IDDOCDEF = $ВидДокумента.Реализация
Этот способ максимально нагляден, но будьте осторожны: на очень больших объемах данных (сотни тысяч строк) он может проигрывать в скорости первым двум вариантам.
При реализации подсчета строк в 1С 7.7 следует учитывать специфику платформы:
1. Поле LINENO_ или COUNT(*)?
В таблицах DT* всегда есть поле LINENO_ (номер строки). Иногда возникает соблазн использовать MAX(LINENO_) вместо COUNT(*). Мы не рекомендуем так делать. Если в базе проводились прямые манипуляции с данными или были сбои, нумерация строк может иметь пропуски. COUNT(*) всегда вернет фактическое количество записей.
2. Подсчет для разных видов документов
Если ваш запрос выбирает данные по нескольким видам документов сразу, задача усложняется, так как строки каждого вида лежат в своей таблице. В этом случае придется использовать либо несколько LEFT JOIN с CASE, либо предварительно собирать данные через UNION ALL во временную таблицу:
-- Пример с UNION для разных ТЧ
SELECT
IDDOC, COUNT(*) as Cnt
INTO #TempLines
FROM (
SELECT IDDOC FROM $ДокументСтроки.Реализация
UNION ALL
SELECT IDDOC FROM $ДокументСтроки.Поступление
) as T
GROUP BY IDDOC
3. Влияние индексов
Для быстрой работы любого из приведенных выше запросов SQL-сервер использует кластерный индекс таблицы табличной части. В 1С 7.7 этот индекс всегда строится по полю IDDOC. Поэтому, пока вы фильтруете или связываете данные только по IDDOC, запросы будут летать.
Мы рассмотрели три основных способа получения количества строк. Для современных систем на базе SQL Server 2008 и выше наиболее рекомендуемым является использование OUTER APPLY благодаря его читаемости и потенциалу оптимизации. Для универсальных решений, работающих на старых версиях, используйте LEFT JOIN с предварительной группировкой. Не забывайте про обработку NULL значений и всегда проверяйте планы выполнения на реальных объемах данных.