21 October 2009 09:45 tonyrogerson

Detecting changed rows in a trigger using HashBytes and without EVENTDATA and OR's

Say you have a table and that table has a secondary history table so you can do windowing, for instance the settings on a specifc entity like the date when we started retaining fees on a debtor account say.

Ordinarily most people would resort to a large series of OR's to check the inserted and deleted tables to see what has changed.

Instead, you can use GROUP BY, DISTINCT and HashBytes to work out what rows (what key) has changed and actually requires sorting (updating the end date on the base table inserting a new row with the new settings).

The trigger below only shows the SELECT statement that gives you the complete logic I'm talking about, of course - this is test only - you should no longer be putting SELECT's in triggers but it highlights the behaviour...

create table some_base_table (

 

    batch_id        int not null primary key clustered,

 

    batch_name      varchar(250) not null,

    some_parm       int null,

    some_parm2      int null

)

 

insert some_base_table ( batch_id, batch_name, some_parm, some_parm2 )

    values( 1, 'hello fred', 1, 2 )

insert some_base_table ( batch_id, batch_name, some_parm, some_parm2 )

    values( 2, 'hello fred', 1, 2 )

insert some_base_table ( batch_id, batch_name, some_parm, some_parm2 )

    values( 3, 'hello fred', 1, 2 )

insert some_base_table ( batch_id, batch_name, some_parm, some_parm2 )

    values( 4, 'hello fred', 1, 2 )

insert some_base_table ( batch_id, batch_name, some_parm, some_parm2 )

    values( 5, 'hello fred', 1, 2 )

insert some_base_table ( batch_id, batch_name, some_parm, some_parm2 )

    values( 6, 'hello fred', 1, 2 )

 

create trigger trg_some_base_table_check_for_changes on some_base_table for update

as

begin

 

    select batch_id

    from (

        select distinct batch_id, hash_combined = hashbytes( 'sha1', combined )

        from (  select batch_id,

                       combined =(  select batch_id, batch_name, some_parm, some_parm2

                                    from deleted c       --  need old values

                                    where c.batch_id = d.batch_id

                                    for xml path( '' ) )

                from deleted d

                union all

                select batch_id,

                       combined =(  select batch_id, batch_name, some_parm, some_parm2

                                    from some_base_table c       --  need current values (could use inserted here)

                                    where c.batch_id = d.batch_id

                                    for xml path( '' ) )

                from deleted d

            ) as r

        ) as c

    group by batch_id

    having count(*) > 1

 

end

go

 

 

--  test

 

update some_base_table

    set batch_name = lower( batch_name )

where batch_id = 2


 

update some_base_table

    set batch_name = lower( batch_name )

Update 20091022:

HashBytes only takes 8000 bytes so if you have a long row with lots of column you break this and get a binary truncation error; so - instead of using HashBytes use CHECKSUM like discussed earlier in the comments, CHECKSUM works across the varchar(max) the for xml gives you. The important thing here is the use of FOR XML to get around the incompatible data type problem.

 

 

Filed under: ,

Comments

# re: Detecting changed rows in a trigger using HashBytes and without EVENTDATA and OR's

21 October 2009 11:35 by AndyC London

Can't you use Checksum(*) to do the same thing as your hash_combined  technique?

# re: Detecting changed rows in a trigger using HashBytes and without EVENTDATA and OR's

21 October 2009 12:00 by tonyrogerson

Absolutely - however, SHA1 gives more unique possibilities - I think its unique to something like 200 million executions or something like that where as CHECKSUM isn't anywhere near that.

The overall principal is that you hash the text somehow and that unique hash then gives duplicates because of the UNION ALL if there has been a change.

# re: Detecting changed rows in a trigger using HashBytes and without EVENTDATA and OR's

21 October 2009 22:31 by guercheLE

Hi Tony,

After I read your posting, I tried to accomplish same goal but with different a different approach, which is available at http://codepaste.net/2udhoe

I'd really appreciate your comments about it.

Regards,

# re: Detecting changed rows in a trigger using HashBytes and without EVENTDATA and OR's

22 October 2009 08:50 by tonyrogerson

It would have been nice if you had credited the technique on your post, anyway - that aside; binary_checksum doesn't work for text and ntext columns so is actually limited compared to do FOR XML route which gives you the text representation as XML of the columns which then allows you to create a hash over with HashBytes or CHECKSUM.

# re: Detecting changed rows in a trigger using HashBytes and without EVENTDATA and OR's

30 October 2009 01:41 by sqlity

This is a great article. But there are several cases where this approach will not work. Look at www2.sqlity.net/.../working-table-compare-approach for a discussion of this and how to get around it.

# Basic Triggering Techniques 1 | Running Leisure Knowledge

Pingback from  Basic Triggering Techniques 1 | Running Leisure Knowledge