Данная публикация – результат попытки разобраться в механизмах записи регистра накопления на стороне СУБД. Какие запросы генерирует платформа в зависимости от вида и настроек регистра, режима записи, агрегатов?

В статье рассмотрены запросы, выполняемые на стороне СУБД при записи регистров накопления остатков и оборотов с разделением итогов и без.

Все примеры выполнены на платформе 1С 8.3.23 в связке с MSSQL.

Структура всех рассматриваемых регистр одинаковая:

Измерения – ссылочного типа, ресурс – число, реквизит – строка.

Регистр остатков

Запись регистра остатков рассмотрим на примере регистра с выключенным разделением итогов, с указанием отличий для регистра с включенным разделением итогов.

Структура таблиц SQL рассматриваемого регистра:

Запись регистра выполняется кодом:

НаборЗаписей = РегистрыНакопления.ОстаткиБезРазделенияИтогов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Регистратор);  
НаборЗаписей.ОбменДанными.Загрузка = РежимЗаписи;

Запись = НаборЗаписей.Добавить();
Запись.Период = Период;
Запись.ВидДвижения = ВидДвижения;
Запись.Измерение1 = Измерение1;
Запись.Измерение2 = Измерение2;
Запись.Ресурс1 = 1;
Запись.Реквизит1 = "тест";
	
НаборЗаписей.Записать(Замещать);

Остатки, без замещения, итоги включены

Режим записи:

НаборЗаписей.ОбменДанными.Загрузка = Ложь;
НаборЗаписей.Записать(Ложь);

Генерируемые запросы:

1.1 Открываем транзакцию.

BEGIN TRANSACTION

1.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД, повторно выполняется после изменения настроек итогов регистра.

SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt93 T1
WHERE T1._RegID = @P1

1.3 Получаем запись из основной таблицы с отбором по регистратору.

SELECT TOP 1
T1._LineNo,
T1._Active
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo DESC

1.4 Добавляем строку в основную таблицу, если в наборе несколько строк, запрос выполняется для каждой строки.

INSERT INTO dbo._AccumRg72
(_Period,_RecorderRRef,_LineNo,_Active,_RecordKind,_Fld73RRef,_Fld74RRef,_Fld75,_Fld76) 
VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8,@P9)

1.5 Обновляем запись в таблице итогов. При включенных итогах запрос повторяется для каждого периода изменённых итогов(конец месяца) и для текущих итогов.

UPDATE T1 SET _Fld75 = T1._Fld75 + @P1
FROM dbo._AccumRgT77 T1
WHERE T1._Period = @P2 AND T1._Fld73RRef = @P3 AND T1._Fld74RRef = @P4
Для регистра с разделением итогов

При включенном разделении итогов в блок отбора добавляется условие по разделителю "AND T1._Splitter = @P5". Благодаря нему у нас появляется возможность параллельной записи совпадающих наборов.

UPDATE T1 SET _Fld55 = T1._Fld55 + @P1
FROM dbo._AccumRgT57 T1
WHERE T1._Period = @P2 AND T1._Fld53RRef = @P3 AND T1._Fld54RRef = @P4 AND T1._Splitter = @P5

Для записей с видом движения расход ресурс берется с противоположным знаком(T1._Fld75 + -@P1).

1.6 Добавляем запись в таблицу итогов. Данный запрос выполняется если в таблице итогов нет записей по добавляемым измерениям и периоду итогов.

INSERT INTO dbo._AccumRgT77 (_Period,_Fld73RRef,_Fld74RRef,_Fld75) 
VALUES(@P1,@P2,@P3,@P4)
Для регистра с разделением итогов

При включенном разделении итогов добавляется поле "_Splitter".

INSERT INTO dbo._AccumRgT57 (_Period,_Fld53RRef,_Fld54RRef,_Fld55,_Splitter) 
VALUES(@P1,@P2,@P3,@P4,@P5)

1.7 Фиксируем транзакцию.

COMMIT TRANSACTION
Для регистратора без движений

Для регистратора без движений по записываемому регистру, при записи с замещением, изменится только запрос 1.3. Остальные запросы остаются без изменений.

SELECT
T1._Period,
T1._RecorderRRef,
T1._LineNo,
T1._Active,
T1._RecordKind,
T1._Fld73RRef,
T1._Fld74RRef,
T1._Fld75,
T1._Fld76
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo

Остатки, с замещением, итоги включены

Режим записи:

НаборЗаписей.ОбменДанными.Загрузка - "не влияет на запросы";
НаборЗаписей.Записать(Истина);

Генерируемые запросы:

2.1 Открываем транзакцию.

BEGIN TRANSACTION

2.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД, повторно выполняется после изменения настроек итогов регистра.

Запрос такой же как 1.2
SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt93 T1
WHERE T1._RegID = @P1

2.3 Получаем записи из основной таблицы с отбором по регистратору.

SELECT
T1._Period,
T1._RecorderRRef,
T1._LineNo,
T1._Active,
T1._RecordKind,
T1._Fld73RRef,
T1._Fld74RRef,
T1._Fld75,
T1._Fld76
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo

2.4 Удаляем существующие записи, которые отсутствуют в записываемом наборе. Если в записываемом наборе не изменяются существующие записи, а только добавляются новые, запрос отсутствует.

DELETE FROM T1
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1 AND ((T1._LineNo >= @P2 AND T1._LineNo < @P3))

Когда удаляется одна запись, применяется условие отбора:

WHERE T1._RecorderRRef = @P1 AND ((T1._LineNo = @P2))

Или применяется подобное условия, когда номера удаляемых строк следуют не по порядку

WHERE T1._RecorderRRef = @P1 AND ((T1._LineNo >= @P2 AND T1._LineNo < @P3) OR (T1._LineNo >= @P4 AND T1._LineNo < @P5)) 

2.5 Добавляем строку в основную таблицу, если в наборе несколько строк, запрос выполняется для каждой строки. Запрос выполняется только для новых и изменённых строк.

INSERT INTO dbo._AccumRg72
(_Period,_RecorderRRef,_LineNo,_Active,_RecordKind,_Fld73RRef,_Fld74RRef,_Fld75,_Fld76) 
VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8,@P9)

2.6 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt1') IS NOT NULL

2.7 Создаем временную таблицу.

CREATE TABLE #tt1 (_Period DATETIME2(0), _RecordKind NUMERIC(1, 0), _Fld73RRef BINARY(16), _Fld74RRef BINARY(16), _Fld75 NUMERIC(16, 0))

2.8 Добавляем измененные записи во временную таблицу.

INSERT INTO #tt1 (_Period,_RecordKind,_Fld73RRef,_Fld74RRef,_Fld75) 
VALUES(@P1,@P2,@P3,@P4,@P5)

Запрос выполняется для изменённых существующих записей и для новых записей в наборе данных. Запрос повторяется для каждого уникального состава полей измерения+период из существующих и новых записей, ресурс суммируется. При добавлении записей во временную таблицу знак ресурса зависит от вида движения и это новая или существующая запись. В новых записях для ресурса с видом движения приход знак остается без изменений, ресурс расхода берется с противоположным знаком. В существующих записей наоборот: приход берется с противоположным знаком, расход без изменений.

Вид движения/Запись

Новая

Существующая

Приход

Без изменений

Знак инвертируется

Расход

Знак инвертируется

Без изменений

Таким образом итогами временной таблицы будет дельта движений регистратора.

2.9 Получаем минимальный период из временной таблицы.

SELECT
MIN(T1._Period)
FROM #tt1 T1 WITH(NOLOCK)

2.10 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt2') IS NOT NULL

2.11 Создаем временную таблицу.

CREATE TABLE #tt2 (_Period DATETIME2(0))

2.12 Создаем индекс.

CREATE UNIQUE CLUSTERED INDEX idx4 ON #tt2 (_Period)

2.13 Добавляем во временную таблицу периоды, для которых необходимо обновить итоги.

INSERT INTO #tt2 (_Period) VALUES(@P1)

2.14 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt3') IS NOT NULL

2.15 Создаем временную таблицу.

CREATE TABLE #tt3 (_Period DATETIME2(0), _Fld73RRef BINARY(16), _Fld74RRef BINARY(16), _Fld75 NUMERIC(22, 0))

2.16 Сохраняем итоги во временную таблицу.

INSERT INTO #tt3 WITH(TABLOCK) (_Period, _Fld73RRef, _Fld74RRef, _Fld75) SELECT
T2._Period,
T1._Fld73RRef,
T1._Fld74RRef,
CAST(SUM(CASE WHEN T1._RecordKind = 0.0 THEN T1._Fld75 ELSE -T1._Fld75 END) AS NUMERIC(22, 0))
FROM #tt1 T1 WITH(NOLOCK)
INNER JOIN #tt2 T2 WITH(NOLOCK)
ON T1._Period < T2._Period
GROUP BY T2._Period,
T1._Fld73RRef,
T1._Fld74RRef
Когда дата новых записей больше даты актуальности итогов

Если в записываемом наборе добавляются новые записи, с датой больше даты актуальности итогов, то запросы 2.10-2.13 отсутствуют. Запрос 2.16 заменяется на запрос:

INSERT INTO #tt3 WITH(TABLOCK) (_Period, _Fld53RRef, _Fld54RRef, _Fld55) SELECT
@P1,
T1._Fld53RRef,
T1._Fld54RRef,
CAST(SUM(CASE WHEN T1._RecordKind = 0.0 THEN T1._Fld55 ELSE -T1._Fld55 END) AS NUMERIC(22, 0))
FROM #tt1 T1 WITH(NOLOCK)
GROUP BY T1._Fld53RRef,
T1._Fld54RRef 

и выполняется только для текущих итогов.

2.17 Обновляем итоги в таблице СУБД.

UPDATE T2 SET _Fld75 = T2._Fld75 + T1._Fld75
FROM #tt3 T1 WITH(NOLOCK)
INNER JOIN dbo._AccumRgT77 T2
ON T1._Period = T2._Period AND T1._Fld73RRef = T2._Fld73RRef AND T1._Fld74RRef = T2._Fld74RRef
Для регистра с разделением итогов

При включенном разделении итогов добавляется условие "AND T2._Splitter = @P1".

UPDATE T2 SET _Fld55 = T2._Fld55 + T1._Fld55
FROM #tt3 T1 WITH(NOLOCK)
INNER JOIN dbo._AccumRgT57 T2
ON T1._Period = T2._Period AND T1._Fld53RRef = T2._Fld53RRef AND T1._Fld54RRef = T2._Fld54RRef AND T2._Splitter = @P1 

2.18 Добавляем недостающие итоги.

INSERT INTO dbo._AccumRgT77 (_Period, _Fld73RRef, _Fld74RRef, _Fld75) SELECT
T1._Period,
T1._Fld73RRef,
T1._Fld74RRef,
CAST(T1._Fld75 AS NUMERIC(16, 0))
FROM #tt3 T1 WITH(NOLOCK)
LEFT OUTER JOIN dbo._AccumRgT77 T2
ON T2._Period = T1._Period AND T1._Fld73RRef = T2._Fld73RRef AND T1._Fld74RRef = T2._Fld74RRef
WHERE T2._Period IS NULL
Для регистра с разделением итогов

При включенном разделении итогов добавляется условие "AND T2._Splitter = @P1".

INSERT INTO dbo._AccumRgT57 (_Period, _Fld53RRef, _Fld54RRef, _Fld55, _Splitter) SELECT
T1._Period,
T1._Fld53RRef,
T1._Fld54RRef,
CAST(T1._Fld55 AS NUMERIC(16, 0)),
CAST(@P1 AS NUMERIC(10, 0))
FROM #tt2 T1 WITH(NOLOCK)
LEFT OUTER JOIN dbo._AccumRgT57 T2
ON T2._Period = T1._Period AND T1._Fld53RRef = T2._Fld53RRef AND T1._Fld54RRef = T2._Fld54RRef AND T2._Splitter = @P2
WHERE T2._Period IS NULL 

2.19 Чистим временные таблицы.

TRUNCATE TABLE #tt3
TRUNCATE TABLE #tt2
TRUNCATE TABLE #tt1

2.20 Фиксируем транзакцию.

COMMIT TRANSACTION

Все запросы проверки наличия временных таблиц и их создания, для однотипной структуры, в рамках одного соединения выполняются один раз. При повторных записях используются существующие таблицы.

При повторной записи движений, если изменилось только значение реквизита, или номер строки(поменяли записи местами), выполняются запросы изменения записей основной таблицы (2.1-2.5) и фиксация транзакции (2.20). Если изменился период, на любое значение, выполняются все запросы, пересчитываются и обновляются итоги.

Если повторно записывается набор без изменение выполняются запросы получения данных 2.1-2.3 и фиксация транзакции (2.20).

Остатки, итоги отключены

Генерируемые запросы:

Первые запросы такие же как для регистра с включёнными итогами.

Открываем транзакцию, получаем настройки и существующие записи

3.1 Открываем транзакцию

BEGIN TRANSACTION

3.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД, повторно выполняется после изменения настроек итогов регистра

SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt93 T1
WHERE T1._RegID = @P1

3.3 Получаем записи из основной таблицы с отбором по регистратору

3.3.1 С замещением

SELECT
T1._Period,
T1._RecorderRRef,
T1._LineNo,
T1._Active,
T1._RecordKind,
T1._Fld73RRef,
T1._Fld74RRef,
T1._Fld75,
T1._Fld76
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo

3.3.2 Без замещения

SELECT TOP 1
T1._LineNo,
T1._Active
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo DESC 

3.4 Получаем минимальный период из основной таблицы с отбором по регистратору.

SELECT
MIN(T1._Period)
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1

3.5 Обновляем период в таблице настроек.

UPDATE T1 SET _MinPeriod = @P1
FROM dbo._AccumRgOpt93 T1
WHERE T1._RegID = @P2 AND T1._MinPeriod > @P3

3.6 Удаляем существующие записи регистратора.

DELETE FROM T1
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1 AND ((T1._LineNo >= @P2 AND T1._LineNo < @P3))

При записи без замещения запросы 3.4-3.6 не выполняются.

3.7 Добавляем записи.

INSERT INTO dbo._AccumRg72 (_Period,_RecorderRRef,_LineNo,_Active,_RecordKind,
_Fld73RRef,_Fld74RRef,_Fld75,_Fld76) 
VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8,@P9)

3.6 Получаем минимальный период из основной таблицы с отбором по регистратору.

SELECT
MIN(T1._Period)
FROM dbo._AccumRg72 T1
WHERE T1._RecorderRRef = @P1

3.7 Обновляем период в таблице настроек.

UPDATE T1 SET _MinPeriod = @P1
FROM dbo._AccumRgOpt93 T1
WHERE T1._RegID = @P2 AND T1._MinPeriod > @P3

3.8 Фиксируем транзакцию.

COMMIT TRANSACTION

Как видим, запись регистра с отключенными итогами отличается отсутствием запросов расчета итогов, добавляются запросы, которые обновляют данные в таблице настроек.

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

При отключённых итогах перечень выполняемых запросов одинаковый для регистра с разделением и без разделения итогов.

Регистр оборотов

Запись регистра остатков рассмотрим на примере регистра с выключенным разделением итогов, с указанием отличий для регистра с включенным разделением итогов.

Структура таблиц SQL рассматриваемого регистра:

Запись регистра выполняется кодом:

НаборЗаписей = РегистрыНакопления.ОборотыБезРазделенияИтогов.СоздатьНаборЗаписей();
НаборЗаписей.Отбор.Регистратор.Установить(Регистратор);  
НаборЗаписей.ОбменДанными.Загрузка = РежимЗаписи;
Запись = НаборЗаписей.Добавить();
Запись.Период = Период;
Запись.Измерение1 = Измерение1;
Запись.Измерение2 = Измерение2;
Запись.Ресурс1 = 1;
Запись.Реквизит1 = "тест";
НаборЗаписей.Записать(Замещать);

Обороты, без замещения, итоги включены

Запись без замещения для регистра оборотов с включенными итогами отличается от регистра остатков одним запросом: чтением настроек агрегатов (4.4).

Режим записи:

НаборЗаписей.ОбменДанными.Загрузка = Ложь;
НаборЗаписей.Записать(Ложь);
Генерируемые запросы

4.1 Открываем транзакцию

BEGIN TRANSACTION

4.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД, повторно выполняется после изменения настроек итогов регистра

SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt94 T1
WHERE T1._RegID = @P1

4.3 Получаем запись из основной таблицы с отбором по регистратору

SELECT TOP 1
T1._LineNo,
T1._Active
FROM dbo._AccumRg78 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo DESC 

4.4 Получаем настройки режима агрегатов

SELECT
T1._AggMode,
T1._EnableUse,
T1._FillStartDate,
T1._FillEndDate,
T1._DeltaPeriodic,
T1._BufferPeriodic,
T1._State
FROM dbo._AccumRgAggOptK85 T1
WHERE T1._RegID = @P1 

4.5 Добавляем строку в основную таблицу, если в наборе несколько строк, запрос выполняется для каждой строки

INSERT INTO dbo._AccumRg78 (_Period,_RecorderRRef,_LineNo,_Active,_Fld79RRef,_Fld80RRef,
_Fld81,_Fld82) VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8) 

4.6 Обновляем запись в таблице итогов, запрос выполняется для каждой строки набора

UPDATE T1 SET _Fld81 = T1._Fld81 + @P1
FROM dbo._AccumRgTn83 T1
WHERE T1._Period = @P2 AND T1._Fld79RRef = @P3 AND T1._Fld80RRef = @P4

4.7 Добавляем запись в таблицу итогов. Данный запрос выполняется если в таблице итогов нет записей по добавляемым измерениям и периоду итогов

INSERT INTO dbo._AccumRgTn83 (_Period,_Fld79RRef,_Fld80RRef,_Fld81) 
VALUES(@P1,@P2,@P3,@P4)

Для регистра с включенным разделением итогов в запросах 4.6 и 4.7 появляется поле разделитель, подобно запросам 1.5 и 1.6 соответственно.

4.8 Фиксируем транзакцию

COMMIT TRANSACTION

Обороты, с замещением, итоги включены

Запросы записи оборотов с замещением подобны запросам для регистра остатков(убраны под спойлер), основное отличие в запросах формирования итогов.

Режим записи:

НаборЗаписей.ОбменДанными.Загрузка - "не влияет на запросы";
НаборЗаписей.Записать(Истина);

Генерируемые запросы:

Открываем транзакцию, получаем настройки, удаляем записи, добавляем новые

5.1 Открываем транзакцию.

BEGIN TRANSACTION

5.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД.

SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt94 T1
WHERE T1._RegID = @P1

5.3 Получаем записи из основной таблицы с отбором по регистратору.

SELECT
T1._Period,
T1._RecorderRRef,
T1._LineNo,
T1._Active,
T1._Fld79RRef,
T1._Fld80RRef,
T1._Fld81,
T1._Fld82
FROM dbo._AccumRg78 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo

5.4 Получаем настройки режима агрегатов.

SELECT
T1._AggMode,
T1._EnableUse,
T1._FillStartDate,
T1._FillEndDate,
T1._DeltaPeriodic,
T1._BufferPeriodic,
T1._State
FROM dbo._AccumRgAggOptK85 T1
WHERE T1._RegID = @P1

5.5 Удаляем существующие записи, которые отсутствуют в записываемом наборе.

DELETE FROM T1
FROM dbo._AccumRg78 T1
WHERE T1._RecorderRRef = @P1 AND ((T1._LineNo >= @P2 AND T1._LineNo < @P3))

5.6 Добавляем строку в основную таблицу, если в наборе несколько строк, запрос выполняется для каждой строки.

INSERT INTO dbo._AccumRg78 (_Period,_RecorderRRef,_LineNo,_Active,_Fld79RRef,_Fld80RRef,_Fld81,_Fld82) 
VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8)

Создаем временные таблицы

SELECT 1 WHERE OBJECT_ID('tempdb..#tt1') IS NOT NULL

CREATE TABLE #tt1 (_Period DATETIME2(0), _Fld79RRef BINARY(16), _Fld80RRef BINARY(16), _Fld81 NUMERIC(16, 0))

INSERT INTO #tt1 (_Period,_Fld79RRef,_Fld80RRef,_Fld81) 
VALUES(@P1,@P2,@P3,@P4)

SELECT 1 WHERE OBJECT_ID('tempdb..#tt2') IS NOT NULL

CREATE TABLE #tt2 (_Period DATETIME2(0), _Fld79RRef BINARY(16), _Fld80RRef BINARY(16), _Fld81 NUMERIC(22, 0))

Основное отличие от расчета итогов остатков, это отсутствие необходимости обновлять итоги периодов, следующих за периодом записи. В следствии этого, отсутствует временная таблица с периодами и итоги рассчитываются только для периода записи.

5.12 Сохраняем итоги во временную таблицу.

INSERT INTO #tt2 WITH(TABLOCK) (_Period, _Fld79RRef, _Fld80RRef, _Fld81) SELECT
DATETIME2FROMPARTS(DATEPART(YEAR,T1._Period),DATEPART(MONTH,T1._Period),1,0,0,0,0,0),
T1._Fld79RRef,
T1._Fld80RRef,
CAST(SUM(T1._Fld81) AS NUMERIC(22, 0))
FROM #tt1 T1 WITH(NOLOCK)
GROUP BY DATETIME2FROMPARTS(DATEPART(YEAR,T1._Period),DATEPART(MONTH,T1._Period),1,0,0,0,0,0),
T1._Fld79RRef,
T1._Fld80RRef
Обновляем итоги, добавляем недостающие итоги, чистим временные таблицы

5.13 Обновляем итоги в таблице СУБД.

UPDATE T2 SET _Fld81 = T2._Fld81 + T1._Fld81
FROM #tt2 T1 WITH(NOLOCK)
INNER JOIN dbo._AccumRgTn83 T2
ON T1._Period = T2._Period AND T1._Fld79RRef = T2._Fld79RRef AND T1._Fld80RRef = T2._Fld80RRef

При включенном разделении итогов добавляется условие "AND T2._Splitter = @P1"

5.14 Добавляем недостающие итоги.

INSERT INTO dbo._AccumRgTn83 (_Period, _Fld79RRef, _Fld80RRef, _Fld81) SELECT
T1._Period,
T1._Fld79RRef,
T1._Fld80RRef,
CAST(T1._Fld81 AS NUMERIC(16, 0))
FROM #tt2 T1 WITH(NOLOCK)
LEFT OUTER JOIN dbo._AccumRgTn83 T2
ON T2._Period = T1._Period AND T1._Fld79RRef = T2._Fld79RRef AND T1._Fld80RRef = T2._Fld80RRef
WHERE T2._Period IS NULL

При включенном разделении итогов добавляется условие "AND T2._Splitter = @P1"

5.15 Чистим временные таблицы.

TRUNCATE TABLE #tt2
TRUNCATE TABLE #tt1

5.16 Фиксируем транзакцию.

COMMIT TRANSACTION

Временные таблицы также создается один раз. Настройки агрегатов (5.4) читаются при каждой записи.

При повторной записи набора без изменение выполняются только запросы получения данных (5.1-5.4) и фиксация транзакции (5.16).

При повторной записи движений, если изменилось только значение реквизита, или номер строки(поменяли записи местами), выполняются запросы изменения записей основной таблицы (5.1-5.6) и фиксация транзакции (5.16). Если изменился период, на любое значение, выполняются все запросы, пересчитываются и обновляются итоги.

Обороты, без замещения, режим агрегатов

Режим записи:

НаборЗаписей.ОбменДанными.Загрузка = Ложь;
НаборЗаписей.Записать(Ложь);

Генерируемые запросы:

Первые запросы при записи с включенным режимов агрегатов такие же как при записи в режиме итогов: открываем транзакцию, читаем существующие записи, получаем настройки.

Открываем транзакцию, получаем настройки

6.1 Открываем транзакцию

BEGIN TRANSACTION

6.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД

SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt94 T1
WHERE T1._RegID = @P1

6.3 Получаем записи из основной таблицы с отбором по регистратору

SELECT TOP 1
T1._LineNo,
T1._Active
FROM dbo._AccumRg78 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo DESC 

6.4 Получаем настройки режима агрегатов

SELECT
T1._AggMode,
T1._EnableUse,
T1._FillStartDate,
T1._FillEndDate,
T1._DeltaPeriodic,
T1._BufferPeriodic,
T1._State
FROM dbo._AccumRgAggOptK85 T1
WHERE T1._RegID = @P1

Состав и последовательность остальных запросов отличается от ранее рассмотренных примеров.

6.5 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt1') IS NOT NULL 

6.6 Создаем временную таблицу. Состав полей такой же как у основной таблицы регистра.

CREATE TABLE #tt1 (_Period DATETIME2(0), _RecorderRRef BINARY(16), _LineNo NUMERIC(9, 0), _Active BINARY(1), _Fld79RRef BINARY(16), _Fld80RRef BINARY(16), _Fld81 NUMERIC(10, 0), _Fld82 NVARCHAR(10) COLLATE DATABASE_DEFAULT) 

6.7 Добавляем записи во временную таблицу. Запрос выполняется для каждой записи из записываемого набора.

INSERT INTO #tt1 (_Period,_RecorderRRef,_LineNo,_Active,_Fld79RRef,_Fld80RRef,_Fld81,_Fld82) 
VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8) 

6.8 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt2') IS NOT NULL 

6.9 Создаем временную таблицу.

CREATE TABLE #tt2 (_Period DATETIME2(0), _Fld79RRef BINARY(16), _Fld80RRef BINARY(16), _Fld81 NUMERIC(16, 0)) 

6.10 Создаем индекс.

CREATE CLUSTERED INDEX idx2 ON #tt2 (_Period, _Fld79RRef, _Fld80RRef) 

6.11 Добавляем во временную таблицу записи, сгруппированные по периоду и измерениям.

INSERT INTO #tt2 WITH(TABLOCK) (_Period, _Fld79RRef, _Fld80RRef, _Fld81) SELECT
DATETIME2FROMPARTS(DATEPART(YEAR,T1._Period),DATEPART(MONTH,T1._Period),DATEPART(DAY,T1._Period),0,0,0,0,0),
T1._Fld79RRef,
T1._Fld80RRef,
CAST(SUM(T1._Fld81) AS NUMERIC(16, 0))
FROM #tt1 T1 WITH(NOLOCK)
GROUP BY DATETIME2FROMPARTS(DATEPART(YEAR,T1._Period),DATEPART(MONTH,T1._Period),DATEPART(DAY,T1._Period),0,0,0,0,0),
T1._Fld79RRef,
T1._Fld80RRef 

6.12 Добавляем сгруппированные записи в таблицу новых оборотов.

INSERT INTO dbo._AccumRgDlK88 (_Period, _Fld79RRef, _Fld80RRef, _Fld81) SELECT
T1._Period,
T1._Fld79RRef,
T1._Fld80RRef,
T1._Fld81
FROM #tt2 T1 WITH(NOLOCK) 

Для регистра с разделением итогов добавляется поле "_Splitter"

INSERT INTO dbo._AccumRgDlK69 (_Period, _Fld59RRef, _Fld60RRef, _Fld61, _Splitter) SELECT
T1._Period,
T1._Fld59RRef,
T1._Fld60RRef,
T1._Fld61,
CAST(@P1 AS NUMERIC(10, 0))
FROM #tt2 T1 WITH(NOLOCK)

6.13 Добавляем записи в основную таблицу.

INSERT INTO dbo._AccumRg78 (_Period, _RecorderRRef, _LineNo, _Active, _Fld79RRef, _Fld80RRef, _Fld81, _Fld82) SELECT
T1._Period,
T1._RecorderRRef,
T1._LineNo,
T1._Active,
T1._Fld79RRef,
T1._Fld80RRef,
T1._Fld81,
T1._Fld82
FROM #tt1 T1 WITH(NOLOCK) 

6.14 Удаляем записи временной таблицы и исходными данными.

TRUNCATE TABLE #tt1

6.15 Фиксируем транзакцию.

COMMIT TRANSACTION

6.16 Удаляем записи из временной таблицы с итоговыми данными

TRUNCATE TABLE #tt2 

Логика записи регистра оборотов без замещения в режиме агрегатов отличается от ранее рассмотренных примеров. Во всех предыдущих режимах сначала вносятся изменения в основную таблицу и после этого рассчитываются итоги, вносятся изменения в таблицу итогов. Для агрегатов без замещения сначала формируется временная таблица с исходными данными, и в самом конце, на основании временной таблицы, вносятся изменения в основную таблицу.

Обороты, с замещением, режим агрегатов

Логика записи с замещением в режиме агрегатов такая же как при записи в режиме итогов: вносим изменения в основную таблицу, формируем временные таблицы с дельтой и итоговыми данными, добавляем данные в таблицу новых оборотов.

Режим записи:

НаборЗаписей.ОбменДанными.Загрузка - "не влияет на запросы";
НаборЗаписей.Записать(Истина);

Генерируемые запросы:

Открываем транзакцию, получаем настройки, удаляем записи, добавляем новые

7.1 Открываем транзакцию.

BEGIN TRANSACTION

7.2 Получаем настройки регистра. Запрос выполняется один раз в рамках одного соединения с СУБД.

SELECT
T1._Period,
T1._UseTotals,
T1._ActualPeriod,
T1._UseSplitter,
T1._MinPeriod,
T1._MinCalculatedPeriod
FROM dbo._AccumRgOpt94 T1
WHERE T1._RegID = @P1

7.3 Получаем записи из основной таблицы с отбором по регистратору.

SELECT
T1._Period,
T1._RecorderRRef,
T1._LineNo,
T1._Active,
T1._Fld79RRef,
T1._Fld80RRef,
T1._Fld81,
T1._Fld82
FROM dbo._AccumRg78 T1
WHERE T1._RecorderRRef = @P1
ORDER BY T1._LineNo

7.4 Получаем настройки режима агрегатов.

SELECT
T1._AggMode,
T1._EnableUse,
T1._FillStartDate,
T1._FillEndDate,
T1._DeltaPeriodic,
T1._BufferPeriodic,
T1._State
FROM dbo._AccumRgAggOptK85 T1
WHERE T1._RegID = @P1

7.5 Удаляем существующие записи, которые отсутствуют в записываемом наборе.

DELETE FROM T1
FROM dbo._AccumRg78 T1
WHERE T1._RecorderRRef = @P1 AND ((T1._LineNo >= @P2 AND T1._LineNo < @P3))

7.6 Добавляем строку в основную таблицу, если в наборе несколько строк, запрос выполняется для каждой строки.

INSERT INTO dbo._AccumRg78 (_Period,_RecorderRRef,_LineNo,_Active,_Fld79RRef,_Fld80RRef,_Fld81,_Fld82) 
VALUES(@P1,@P2,@P3,@P4,@P5,@P6,@P7,@P8)

Создаем временные таблицы

7.7 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt1') IS NOT NULL

7.8 Создаем временную таблицу.

CREATE TABLE #tt1 (_Period DATETIME2(0), _Fld79RRef BINARY(16), _Fld80RRef BINARY(16), _Fld81 NUMERIC(16, 0)) 

7.9 Добавляем измененные записи во временную таблицу.

INSERT INTO #tt1 (_Period,_Fld79RRef,_Fld80RRef,_Fld81) VALUES(@P1,@P2,@P3,@P4)

Запрос, также как и для остатков, выполнятся для новых и существующих записей, для которых есть изменения. Для существующих записей значение ресурса добавляется с противоположным знаком, для новых записей знак остается без изменений.

7.10 Проверяем наличие временной таблицы.

SELECT 1 WHERE OBJECT_ID('tempdb..#tt2') IS NOT NULL

7.11 Создаем временную таблицу.

CREATE TABLE #tt2 (_Period DATETIME2(0), _Fld79RRef BINARY(16), _Fld80RRef BINARY(16), _Fld81 NUMERIC(22, 0))

7.12 Создаем индекс.

CREATE CLUSTERED INDEX idx2 ON #tt2 (_Period, _Fld79RRef, _Fld80RRef) 

7.13 Сохраняем итоги во временную таблицу.

INSERT INTO #tt2 WITH(TABLOCK) (_Period, _Fld79RRef, _Fld80RRef, _Fld81) SELECT
DATETIME2FROMPARTS(DATEPART(YEAR,T1._Period),DATEPART(MONTH,T1._Period),DATEPART(DAY,T1._Period),0,0,0,0,0),
T1._Fld79RRef,
T1._Fld80RRef,
CAST(SUM(T1._Fld81) AS NUMERIC(22, 0))
FROM #tt1 T1 WITH(NOLOCK)
GROUP BY DATETIME2FROMPARTS(DATEPART(YEAR,T1._Period),DATEPART(MONTH,T1._Period),DATEPART(DAY,T1._Period),0,0,0,0,0),
T1._Fld79RRef,
T1._Fld80RRef 

Записываем новые обороты, чистим временные таблицы

7.14 Добавляем сгруппированные записи в таблицу новых оборотов.

INSERT INTO dbo._AccumRgDlK88 (_Period, _Fld79RRef, _Fld80RRef, _Fld81) SELECT
T1._Period,
T1._Fld79RRef,
T1._Fld80RRef,
CAST(T1._Fld81 AS NUMERIC(16, 0))
FROM #tt2 T1 WITH(NOLOCK) 

Для регистра с разделением итогов.

INSERT INTO dbo._AccumRgDlK69 (_Period, _Fld59RRef, _Fld60RRef, _Fld61, _Splitter) SELECT
T1._Period,
T1._Fld59RRef,
T1._Fld60RRef,
T1._Fld61,
CAST(@P1 AS NUMERIC(10, 0))
FROM #tt2 T1 WITH(NOLOCK)

7.15 Чистим временную таблицу.

TRUNCATE TABLE #tt1

7.16 Фиксируем транзакцию.

COMMIT TRANSACTION

7.17 Чистим временную таблицу.

TRUNCATE TABLE #tt2

Временные таблицы также создается один раз.

При повторной записи логика подобна записи остатков: для набора без изменений только получаем текущие данные, если изменился реквизит - обновляем данные основной таблицы, если изменился период, измерение, ресурс - выполняем весь пакет запросов.

Обороты, режим итогов, итоги отключены

Логика записи с отключенными итогами такая же как для регистра остатков с отключенными итогами. Вместо запросов расчета и обновления итогов выполняется запрос обновления периода в регистре настроек.

На этом все, буду рад дополнениям и замечаниям в комментариях!

Комментарии (1)


  1. HADGEHOGs
    05.09.2023 20:56
    +5

    Статья, бессмысленная и беспощадная.