#sql-server #tsql #triggers #azure-sql-database #historical-db
Вопрос:
Для контекста у меня есть простая база данных SQL Azure, и у меня есть теоретическая размерная модель с примерно 12 измерениями и некоторыми таблицами фактов, которые в конечном итоге должны функционировать как DWH.
Я собираюсь применить историзацию к этой размерной модели. Чтобы начать с простого и применить концепцию историзации, я хочу включить ее в одном измерении. В моем случае это DimEmployee, который выглядит так со строкой выборочных данных (в нем больше столбцов, но для простоты я беру только их).
ЭмпКей | Имя | ПусТота | DWHDatStart | Двдатенд | Является активным |
---|---|---|---|---|---|
52 | Джон | Лондон | 02/02/2020 | 31/12/9999 | Y |
Теперь, если, допустим, Джон переедет в другой город, я хочу, чтобы это отслеживалось, чтобы желаемая ситуация, когда значение EmpCity обновляется, была
ЭмпКей | Имя | ПусТота | DWHDatStart | Двдатенд | Является активным |
---|---|---|---|---|---|
52 | Джон | Амстердам | 08/06/2021 | 31/12/9999 | Y |
52 | Джон | Лондон | 02/02/2020 | 08/06/2021 | N |
Как бы я применил это с помощью TSQL? Я предполагаю, что это должно быть своего рода триггером, но мне не хватает конкретных знаний, чтобы применить это. Я читал о временных таблицах и о том, что обычно при включении историзации данных вы сохраняете исторические данные в отдельной таблице истории. Я думаю, что из-за этого дизайна моделей всегда можно расширить эту концепцию, добавив размеры DimEmployeeHist или что-то еще, но для первой концепции это не нужно. Я не знаю, какую ценность это принесло бы, делая это в отдельных таблицах, кроме тех случаев, когда у вас много записей истории, поэтому вы хотите, чтобы все они аккуратно хранились в одном месте, чтобы не загромождать ваши основные таблицы.
Нужно ли это делать в скрипте создания таблицы или это можно сделать после создания таблицы?
Комментарии:
1. Это SCD2. Обычно это работает так, что у вас есть пакетные задания (которые могут быть в основном T-SQL или хранимыми процедурами), которые используют множество обновлений и вставок для получения данных в правильном формате. Итак, короткий ответ заключается в том, что вы используете хранимую процедуру для использования входных (измененных) данных и таблицы измерений для выполнения правильных операций. То есть:
insert
одна новая пластинка иupdate
старая.2. временные таблицы также могут работать для этого сценария для вас (без необходимости поддерживать собственные триггеры).
3. Несмотря на многие утверждения, утверждающие обратное, временные таблицы не будут выполнять функцию Кимбалла SCD2. Они отслеживают исторические изменения, но не являются «размерными». Триггеры определенно не нужны для этого (и большинства других) случаев использования
4. @Ник. McDermaid У вас есть пример такой хранимой процедуры, которая добавляет функциональность, когда одна запись вставляется/ обновляется, а другая обновляется?
5. Мой пример приведен ниже. Это полезно?
Ответ №1:
Во-первых: забудьте о триггерах. Триггеры плохие
Это подсказка о том, как я обычно это делаю. На этот счет есть много перестановок, но это должно дать вам представление.
Это строка в целевом измерении. Конечно, есть много других строк
Суррогатный ребенок | Источник | Исходная система | Имя | ПусТота | DWHDatStart | Двдатенд | Является активным |
---|---|---|---|---|---|---|---|
3678 | 52 | HRSystem1 | Джон | Лондон | 2020-02-20 | 9999-12-31 | Y |
3642 | 73 | HRSystem1 | Джим | Брисбен | 2021-03-18 | 9999-12-31 | Y |
SurrogateKey
является ключом, который присоединяется к вашему факту и уникален в таблице измерений (и должен быть применен с ограничением или индексом)SourceKey
является ключом в исходной системеSourceSystem
это код для любой системы, предоставившей эту запись.SourceKey
SourceSystem
является уникальным (и должен быть применен с ограничением или индексом)
Мы загружаем данные в наш центр обработки данных. Обычно первым шагом является загрузка входных данных в промежуточную таблицу. Итак, скажем, у нас есть это в промежуточной таблице:
ЭмпКей | Имя | ПусТота | АктивнЫйкордСуррогатеКей | Игнорировать |
---|---|---|---|---|
52 | Джон | Амстердам | нулевой | нулевой |
73 | Джим | Брисбен | нулевой | нулевой |
7 | Джек | Техас | нулевой | нулевой |
ActiveRecord
и Ignore
являются рабочими колонками, они не исходят из источника. Все остальные столбцы действительно исходят из источника, но ничего не знают об измерении
Очевидно, что в таблице измерений и промежуточной таблице будет много записей.
Сначала исключите все записи, которые являются актуальными в измерении.
UPDATE StagingTable
SET Ignore = 'Y'
FROM StagingTable TGT
INNER JOIN DimensionTable SRC
ON TGT.EmpKey = SRC.SourceKey
AND TGT.SourceKey = 'HRSystem1'
AND TGT.EmpName=SRC.EmpName
AND TGT.EmpCity=SRC.EmpCity
AND SRC.IsActive = 'Y'
Мы определили, что Джим не изменился и его можно игнорировать
ЭмпКей | Имя | ПусТота | АктивнЫйкордСуррогатеКей | Игнорировать |
---|---|---|---|---|
52 | Джон | Амстердам | нулевой | нулевой |
73 | Джим | Брисбен | нулевой | Y |
7 | Джек | Техас | нулевой | нулевой |
определите все записи в промежуточной таблице, которые уже имеют активную запись в измерении, но имеют разные атрибуты
UPDATE StagingTable
SET ActiveRecordSurrogateKey = SRC.SurrogateKey
FROM StagingTable TGT
INNER JOIN DimensionTable SRC
ON TGT.EmpKey = SRC.SourceKey
AND TGT.SourceKey = 'HRSystem1'
AND TGT.IsActive='Y'
AND (TGT.EmpName<>SRC.EmpName OR TGT.EmpCity<>SRC.EmpCity)
(Эти два отдельных обновления можно объединить в одно, если хотите.)
Теперь наша промежуточная таблица выглядит так.
ЭмпКей | Имя | ПусТота | АктивнЫйкордСуррогатеКей | Игнорировать |
---|---|---|---|---|
52 | Джон | Амстердам | 3678 | нулевой |
73 | Джим | Брисбен | нулевой | Y |
7 | Джек | Техас | нулевой | нулевой |
Теперь у нас достаточно информации, чтобы обновить измерение. Мы можем написать еще немного SQL для применения к измерению на основе вспомогательных столбцов.
Но сначала давайте укажем фиксированную дату. Это останавливает странные вещи, происходящие, если это происходит до и после полуночи. Или вы можете определить это каким-либо другим способом, например, с помощью входного параметра или данных в источнике
DECLARE @Date DATE = GETDATE();
Теперь мы можем вставить все новые записи (совершенно новые или измененные)
-- This line inserts new dimension records:
INSERT INTO DimensionTable (SourceKey,SourceSystem,EmpName,EmpCity, StartDate,EndDate,IsActive)
SELECT EmpKey,'HRSystem1',EmpName,EmpCity, @Date,'2999-01-01','Y'
FROM StagingTable
WHERE Ignore IS NULL
Теперь наше измерение выглядит так
Суррогатный ребенок | Источник | Исходная система | Имя | ПусТота | DWHDatStart | Двдатенд | Является активным |
---|---|---|---|---|---|---|---|
3678 | 52 | HRSystem1 | Джон | Лондон | 2020-02-20 | 9999-12-31 | Y |
3642 | 73 | HRSystem1 | Джим | Брисбен | 2021-03-18 | 9999-12-31 | Y |
3693 | 7 | HRSystem1 | Джек | Техас | 2021-06-09 | 9999-12-31 | Y |
3694 | 52 | HRSystem1 | Джон | Амстердам | 2021-06-09 | 9999-12-31 | Y |
Теперь мы заканчиваем датировать существующие записи:
-- This line end-dates existing records:
UPDATE DimensionTable
SET DWEndDate = @Date, Active = 'N'
FROM DimensionTable TGT
INNER JOIN StagingTable SRC
ON TGT.SurrogateKey = ActiveRecordSurrogateKey
Суррогатный ребенок | Источник | Исходная система | Имя | ПусТота | DWHDatStart | Двдатенд | Является активным |
---|---|---|---|---|---|---|---|
3678 | 52 | HRSystem1 | Джон | Лондон | 2020-02-20 | 2021-06-09 | N |
3642 | 73 | HRSystem1 | Джим | Брисбен | 2021-03-18 | 9999-12-31 | Y |
3693 | 52 | HRSystem1 | Джек | Техас | 2021-06-09 | 9999-12-31 | Y |
3694 | 52 | HRSystem1 | Джон | Амстердам | 2021-06-09 | 9999-12-31 | Y |
Таким образом, вы в основном объединяете все эти инструкции T-SQL в одну хранимую процедуру, добавляете некоторые транзакции, ведение журнала и обработку ошибок.
CREATE PROC pUpdateDimPerson
AS
BEGIN
-- All the code above
END
Хранимая процедура сравнивает исходный (промежуточный) и целевой (измерение) и делает все правильно.
Есть куча других вещей, которые следует учитывать, но это дает вам представление.
Комментарии:
1. Помогает ли этот ответ?