2016-08-23 1 views
1

Я работаю над триггером, чтобы обновить таблицу журналов изменений для таблицы с фактами продаж. Когда кто-то пытается сделать обновление в таблице, они должны написать описание, а затем обновление регистрируется (пользователь, время, таблица, поле, таблица_Unique_ID, значение oldvalue, newvalue)Локальная переменная в UPDATE() Функция триггера T-SQL

Я пытаюсь использовать UPDATE() Функция триггера для локальной переменной @fieldname. Целью этого является то, что я сравниваю только удаленные/вставленные значения для столбцов, которые обновляются, а не циклически, чтобы проверять каждый столбец. Я смог использовать функцию UPDATE(), когда я явно указываю имя столбца, но не если я устанавливаю переменную в имя столбца, которое я хочу проверить.

Я очень ценю любые советы о том, как оптимизировать этот процесс! У меня еще есть чему поучиться, и это моя первая попытка регистрации изменений.

Ниже мой Trigger в целом:

ALTER TRIGGER [dbo].[TR_UpdateLog] 

ON [dbo].[Test_Update_Trigger] 
    AFTER Update 
AS 

Declare @description nvarchar(1000) 
, @UserName nvarchar(128) 
, @oldValue nvarchar(255) 
, @newValue nvarchar(255) 
, @UniqueID nvarchar(255) 
, @fieldname nvarchar(128) 
, @oname NVARCHAR(100) 
, @OldSQL nvarchar(max) 
, @NewSQL nvarchar(max) 
, @i int 
, @c int 
, @numcolumns int 
, @numrows int; 
DECLARE @updated_table TABLE ( 
idx int Primary Key identity(1,1) 
, uniqueID nvarchar(255) NULL) 
; 


--Require the User to submit a description for the update 
Set @description = (SELECT * FROM [dbo].[Fact_Bookings_Audit_Description]) 
TRUNCATE TABLE [dbo].[Fact_Bookings_Audit_Description] 

IF @description is null 
BEGIN 
    PRINT 'You must provide a description. Use the following text: 

    TRUNCATE TABLE [dbo].[Fact_Bookings_Audit_Description] 
    INSERT INTO [dbo].[Fact_Bookings_Audit_Description] 
    SELECT "Your Text Here--use Single Quotes" 

    Copy and paste above your query, type your description, and run the update again. 
    '; 
    ROLLBACK TRANSACTION; 
END 

--Set User and Table name 
Set @Username = system_User 
SET @oname = (SELECT OBJECT_NAME(parent_id) from sys.triggers where object_ID = @@PROCID) 


------------------------------------------------------------- 
---create tables of updated items to reference for old/new values 
INSERT INTO @updated_table 
SELECT distinct Table_ID from DELETED 

SELECT * INTO dbo.tempDelete FROM DEleted 
SELECT * INTO dbo.tempInsert FROM inserted 
-------------------------------------------------- 
--Set variables for loop through updated items 
Set @i = 1 
Set @numrows = (SELECT count(*) from @updated_table) 
------------------------------------------------------------ 
---Set number of columns for loop through each field 
Set @numcolumns = (SELECT MAx(ordinal_position) FROM information_schema.columns where table_name = @oname) 

--------------------------------------------------------------- 
--If there was an update 
IF @numrows > 0 
BEGIN 
    --loop through each individual updated row 
    WHILE (@i <= (select max(idx) from @updated_table)) 
    BEGIN 
     --reset the column variable 
     Set @c = 1 
     --loop through each column 
     WHILE (@c <= @numcolumns) 
     BEGIN 
       Set @fieldname = (SELECT Column_name FROM information_schema.columns 
             Where table_name = @oname AND ORDINAL_POSITION = @c) 

        /**This is what I want to use-- 
        I would only like to do the comparison on columns which were updated, not cycle through every column**/ 

        --IF UPDATE(@fieldname) 
        --BEGIN 

        --set values for old and new values as well as unique ID for log table 
        Set @UniqueID = (SELECT uniqueID from @updated_table u WHERE idx = @i) 
        Set @OldSQL = 'SELECT @oldvalue = ' + @fieldname + ' from dbo.tempdelete d WHERE d.Table_ID = ' + @UniqueID; 
        Set @NewSQL = 'SELECT @newvalue = ' + @fieldname + ' from dbo.tempInsert i WHERE i.Table_ID = ' + @UniqueID; 

        EXEC sp_executesql @oldSQL, N'@oldvalue nvarchar(128) output', @oldValue = @oldValue output 
        EXEC sp_executesql @newSQL, N'@newvalue nvarchar(128) output', @newValue = @newValue output 

        ; 
         --Insert into log table if value is changed 
         IF isnull(@oldvalue,0) <> isnull(@newvalue,0) 
         BEGIN 
          INSERT INTO dbo.Fact_Bookings_ChangeAudit (Change_Date, Change_Time, [User], Table_Name, Field, Table_ID, Oldvalue, Newvalue, [Description]) 
          VALUES(cast(datediff(DAY,0,getdate())as datetime),cast(getdate() as datetime),@UserName, @oname, @fieldname, @UniqueID, @oldValue, @newValue, @description); 
         END 
        --END 
       --next column 
       Set @c = @c + 1 
     END 
    --next record 
    Set @i = @i + 1 
    END 

END 
DROP TABLE dbo.tempDelete 
DROP table dbo.tempInsert 

GO 
+0

Я думаю, что во всем есть плохая идея. Тег 'mysql' вам также не нужен. Я знаю, что это ваш первый вопрос, но, вероятно, слишком много для комментариев. – shawnt00

+1

По-моему, этот вопрос относится к двум сайтам для стека, это один и обзор кода. Возможно, вы захотите опубликовать его, так как ищете оптимизацию. http://codereview.stackexchange.com/ – scsimon

+0

Спасибо, @ shawnt00. Я удалил тег Mysql. Все еще учится прямо сейчас. Если у вас есть хорошие статьи, связанные с отслеживанием изменений, я был бы признателен! –

ответ

1
with i as (
    select 
     case when updated(colA) 
      then coalesce(cast(ColA as varchar(32)), 'null') end as newColA, 
     case when updated(colB) 
      then coalesce(cast(ColB as varchar(32)), 'null') end as newColB, 
     ... 
    from inserted 
), d as (
    select 
     case when updated(colA) 
      then coalesce(cast(ColA as varchar(32)), 'null') end as oldColA, 
     case when updated(colB) 
      then coalesce(cast(ColB as varchar(32)), 'null') end as oldColB, 
     ... 
    from deleted 
), old as (
    select c.Id, Col, Change 
    from i unpivot (Change for Col in (newColA, newColB, ...)) 
    where Change is not null 
), new as (
    select c.Id, Col, Change 
    from d unpivot (Change for Col in (oldColA, oldColB, ...)) 
    where Change is not null 
) 
-- insert into Log 
select ... 
from 
    old o inner join new n on n.Id = o.Id and n.Col = o.Col 

Листинг столбцов является правильный способ сделать это. Я знаю, что вы надеялись использовать цикл над таблицами информационных схем. Я просто не думаю, что все эти динамические sql - хорошая идея.

Я думаю, что указанный выше запрос может быть отправной точкой для создания списка строк, которые вы хотите зарегистрировать. Я не пытался его протестировать. Это может быть медленным, если вы попытаетесь сделать массовое обновление.

Это может быть быстрее при массовых обновлениях, чем при использовании соединения?

with ... 
, new as (select 'i' as src ...), 
, old as (select 'd' as src ...), 
, combined as (select * from new union all select * from old) 
select 
    Id, Col 
    min(case when src = 'i' then Change end) as newVal, 
    min(case when src = 'd' then Change end) as oldVal, 
from combined 
group by Id, Col; 
+0

Хорошо, это отличная отправная точка для поиска другой стратегии! Я дам вам знать, как оно идет. Я ценю отзывы. Просто любопытно, каковы потенциальные недостатки, которые вы видите в динамическом sql? –

+0

Динамический SQL имеет множество недостатков, особенно с безопасностью, и триггеры могут быть достаточно сложными. Если вы будете искать вокруг, я думаю, что вы найдете некоторые ошибки и странное поведение, когда вы это делаете. С головы до ног я не могу назвать ни одного. Но в основном я думаю, что производительность будет большой проблемой, если у вас есть все эти дополнительные запросы, которые выходят прямо в середине простого обновления. – shawnt00

+0

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

Смежные вопросы