Вчера я опубликовал вопрос о моем динамическом триггере, и выяснилось, что моя последняя проблема была связана с включением одного слишком большого количества полей в мой unpivot
. Это, как говорится, мое творение работает! (злой смех)
Многие из вас выразили обеспокоенность тем, что это может негативно сказаться на общем состоянии моей базы данных, и мне любопытно, почему это будет ... Пожалуйста, уточните после ознакомления с тем, что я придумала.
Это триггер, который можно поместить на ЛЮБОЙ СТОЛ на ЛЮБОМ СЕРВЕРЕ (если у вас есть необходимые вещи на месте). Вам нужно либо следовать приведенной ниже модели, либо настроить ее по своему вкусу. (т. е. ваша собственная таблица журналов, ваш собственный способ отслеживания внесенных изменений и т. д.)
Все, что вам нужно: Триггер, таблица журналов (см. Мою ниже) и поле в ваших таблицах, которое отслеживает пользователя, который внес изменение (в нашем случае все соединения проходят через один централизованный SQL профиль, так что это единственный способ, которым мы можем отследить, какой пользователь внес изменение (передав его в качестве параметра во время обновления)).
tblChangeLog:
- ID - int - NOT NULL (Identitity (1, 1))
- Сообщение - varchar (max) - НЕ NULL
- TableName - varchar (50) - НЕ NULL
- PrimaryKey - varchar (50) - НЕ NULL
- Активность - varchar (50) - НЕ NULL
- CreatedByUser - varchar (30) - НЕ NULL - ПО УМОЛЧАНИЮ ('System')
- CreatedDate - datetime - NOT NULL - DEFAULT (getdate ())
Триггер: (еще не добавил удалить / вставить, может делать это только для обновлений) ...
CREATE TRIGGER (your name here) ON (your table here)
AFTER UPDATE
AS
BEGIN
SET NOCOUNT ON;
DECLARE @tableName sysname
DECLARE @tableId INT
DECLARE @activity VARCHAR(50)
DECLARE @sql nvarchar(MAX)
-- DETECT AN UPDATE (Records present in both inserted and deleted)
IF EXISTS(SELECT * FROM inserted) AND EXISTS(SELECT * FROM deleted)
BEGIN
SET @activity = 'UPDATE'
-- Gets TableName and TableId
SELECT @tableName = OBJECT_NAME(parent_object_id)
, @tableId = parent_object_id
FROM sys.objects
WHERE sys.objects.name = OBJECT_NAME(@@PROCID)
-- Get the user who made the change
DECLARE @LastUpdUser VARCHAR(50)
SELECT @LastUpdUser = LastUpdUser FROM inserted
-- Stores possible column names
CREATE TABLE #Columns (
name varchar(100)
)
-- Stores only updated columns
CREATE TABLE #Changes (
Id sql_variant,
FieldName sysname,
FieldValue_OLD sql_variant,
FieldValue_NEW sql_variant,
DateChanged datetime DEFAULT (GETDATE()),
LastUpdUser varchar(50),
GroupNumber int
)
-- Gathers names of all possible updated columns (excluding generic)
INSERT INTO #columns
SELECT Name
FROM sys.columns
WHERE object_id = @tableId
AND Name NOT IN ('LastUpdUser', 'LastUpdDate', 'CreatedByUser', 'CreatedDate', 'ConcurrencyId')
-- Builds 2 dynamic strings of columns to use in pivot
DECLARE @castFields nvarchar(max) -- List of columns being cast to sql_variant
DECLARE @listOfFields nvarchar(max) -- List of columns for unpivot
SELECT @castFields = COALESCE(@castFields + ', ', '') + ('CAST(' + QUOTENAME(Name) + ' AS sql_variant) [' + Name + ']') FROM #columns
SELECT @listOfFields = COALESCE(@listOfFields + ', ', '') + QUOTENAME(Name) FROM #columns WHERE Name <> 'Id'
-- Inserting deleted/inserted data into temp tables
SELECT * into #deleted FROM deleted
SELECT * into #inserted FROM inserted
SELECT @sql = ';WITH unpvt_deleted AS (
SELECT Id, FieldName, FieldValue
FROM
(SELECT ' + @castFields + '
FROM #deleted) p
UNPIVOT
(FieldValue FOR FieldName IN
(' + @listOfFields + ')
) AS deleted_unpivot
),
unpvt_inserted AS (
SELECT Id, FieldName, FieldValue
FROM
(SELECT ' + @castFields + '
FROM #inserted) p
UNPIVOT
(FieldValue FOR FieldName IN
(' + @listOfFields + ')
) AS inserted_unpivot
)
INSERT INTO #Changes (Id, FieldName, FieldValue_OLD, FieldValue_NEW, LastUpdUser, GroupNumber)
SELECT COALESCE(D.Id, I.Id) Id
, COALESCE(D.FieldName, I.FieldName) FieldName
, D.FieldValue AS FieldValue_OLD
, I.FieldValue AS FieldValue_NEW
, ''' + @LastUpdUser + '''
, DENSE_RANK() OVER(ORDER BY I.Id) AS GroupNumber
FROM unpvt_deleted D
FULL OUTER JOIN unpvt_inserted I ON D.Id = I.Id AND D.FieldName = I.FieldName
WHERE D.FieldValue <> I.FieldValue
DECLARE @i INT = 1
DECLARE @lastGroup INT
SELECT @lastGroup = MAX(GroupNumber) FROM #Changes
WHILE @i <= @lastGroup
BEGIN
DECLARE @Changes VARCHAR(MAX)
SELECT @Changes = COALESCE(@Changes + ''; '', '''')
+ UPPER(CAST(FieldName AS VARCHAR)) + '': ''
+ '''''''' + CAST(FieldValue_OLD AS VARCHAR) + '''''' to ''
+ '''''''' + CAST(FieldValue_NEW AS VARCHAR) + ''''''''
FROM #Changes
WHERE GroupNumber = @i
ORDER BY GroupNumber
INSERT INTO tblChangeLog (Message, TableName, PrimaryKey, Activity, CreatedByUser, CreatedDate)
SELECT Distinct @Changes, ''' + @tableName + ''', CONVERT(VARCHAR, Id), ''' + @activity + ''', LastUpdUser, DateChanged
FROM #Changes
WHERE GroupNumber = @i
SET @Changes = NULL
SET @i += 1
END
DROP TABLE #Changes
DROP TABLE #columns
DROP TABLE #deleted
DROP TABLE #inserted'
exec sp_executesql @sql
END
END
Чтобы сделать это еще более универсальным, мой администратор баз данных разработал изящный скрипт и фиктивную таблицу, которые будут хранить этот триггер в виде строки в фиктивной таблице. Скрипт использует курсор и находит каждую таблицу в базе данных и создает триггер для каждой таблицы на лету. Мы также проверили это, и оно работает как шарм, не беспокоясь о том, чтобы публиковать это здесь в это время.