При переходе с версии 1С 7.7 на платформу 1С:Предприятие 8 у многих разработчиков возникает вопрос: как устроено хранение актуальных итогов регистров и срезов последних, и почему стандарты разработки настоятельно требуют передавать все условия фильтрации исключительно в параметры виртуальных таблиц, а не в секцию ГДЕ или через соединения. Разберем детально физическую структуру хранения данных в СУБД, логику работы платформы и влияние условий на производительность запросов.
Проанализируем, как платформа 1С организует таблицы на уровне реляционной СУБД:
AccumRgT<n>. Записи с максимально возможной датой (31.12.3999 или 01.01.4000 в зависимости от СУБД) представляют собой общее итоговое сальдо со всеми когда-либо проведенными движениями. Когда мы обращаемся к виртуальной таблице остатков без указания даты, платформа читает именно эти строки на 3999 год без обращения к таблице движений AccumRg<n>.СрезПоследних и СрезПервых отдельных физических таблиц в базе данных вообще не существует. Данные среза всегда рассчитываются динамически «на лету» на основе основной физической таблицы регистра сведений InfoRg<n> через подзапрос с поиском максимальной даты для каждого набора измерений.Выясним, почему обращение к виртуальной таблице даже за актуальными итогами требует передачи отборов внутрь её параметров. Дело в том, как платформа 1С транслирует виртуальные таблицы в SQL-код для СУБД:
SUM и секцией GROUP BY. Это необходимо, так как включенный механизм разделения итогов создает несколько физических строк с системным полем _Splitter для одной и той же комбинации измерений.Index Seek) и отбирает только нужные записи до выполнения группировки и суммирования.ГДЕ или задается через соединение JOIN, оптимизаторы СУБД (MS SQL Server, PostgreSQL) часто не могут протолкнуть предикат внутрь агрегирующего подзапроса. В этом случае СУБД сначала сканирует и группирует все строки таблицы итогов базы данных в памяти или во временных таблицах (tempdb), и лишь затем отсекает ненужное, вызывая колоссальную нагрузку на дисковую подсистему и процессор.СрезПоследних не передать фильтр по измерениям, подзапрос СУБД будет вынужден сначала найти максимальные даты для всех существующих в регистре объектов, выполнив полное сканирование индекса (Index Scan), а затем отфильтровать одну нужную строку.Рассмотрим практические примеры и сравним подходы к написанию запросов.
Неэффективный вариант: условие наложено в секции ГДЕ или через левое соединение с другой таблицей без параметров в виртуальной таблице:
// НЕ РЕКОМЕНДУЕТСЯ: СУБД может сначала агрегировать все остатки на 3999 год
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.КоличествоОстаток КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки() КАК Остатки
ГДЕ
Остатки.Номенклатура = &Номенклатура
Рекомендуемый вариант: отбор передан непосредственно в параметры виртуальной таблицы:
// ПРАВИЛЬНО: фильтрация происходит на этапе индексного чтения из таблицы итогов
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.КоличествоОстаток КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(, Номенклатура = &Номенклатура) КАК Остатки
Разберем ситуацию, когда требуется получить остатки или срез сведений не по одиночному значению, а по списку строк из другой таблицы (например, из табличной части документа или временной таблицы). Часто разработчики совершают ошибку, связывая временную таблицу с виртуальной через ВНУТРЕННЕЕ СОЕДИНЕНИЕ без параметров. Корректный способ заключается в передаче подзапроса в параметры виртуальной таблицы через оператор В:
// 1. Формируем временную таблицу с требуемыми товарами
ВЫБРАТЬ РАЗЛИЧНЫЕ
ДокТовары.Номенклатура КАК Номенклатура
ПОМЕСТИТЬ ВТ_Товары
ИЗ
Документ.РеализацияТоваровУслуг.Товары КАК ДокТовары
ГДЕ
ДокТовары.Ссылка = &Ссылка;
ИНДЕКСИРОВАТЬ ПО
Номенклатура;
;
// 2. Получаем остатки, передавая отбор во внутренний параметр
ВЫБРАТЬ
Остатки.Номенклатура КАК Номенклатура,
Остатки.Склад КАК Склад,
Остатки.КоличествоОстаток КАК Количество
ИЗ
РегистрНакопления.ТоварыНаСкладах.Остатки(
,
Номенклатура В (ВЫБРАТЬ ВТ.Номенклатура ИЗ ВТ_Товары КАК ВТ)
) КАК Остатки