Columns_Updated() in update trigger reverses the bitmask?

  • I have some significant strangeness going on with columns_updated() in an update trigger. It appears they inverted the bytes in the bitmasking, making it difficult to locate fields.

    Setup:

    /*

    DROP TRIGGER TR_UpdTestTrig

    GO

    DROP TABLE UpdateTest

    GO

    */

    CREATE TABLE UpdateTest

    (ColIDINTIDENTITY(1,1),

    ColAVARCHAR(10),

    ColBVARCHAR(10),

    ColCVARCHAR(10),

    ColDVARCHAR(10),

    ColEVARCHAR(10),

    ColFVARCHAR(10),

    ColGVARCHAR(10),

    ColHVARCHAR(10),

    ColIVARCHAR(10),

    ColJVARCHAR(10),

    ColKVARCHAR(10),

    ColLVARCHAR(10)

    )

    GO

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    GO

    Here's where things go odd. According to Books online for SQL 2k5:

    From: ms-help://MS.SQLCC.v9/MS.SQLSVR.v9.en/tsqlref9/html/765fde44-1f95-4015-80a4-45388f18a42c.htm

    In SQL Server 2005, the ORDINAL_POSITION column of the INFORMATION_SCHEMA.COLUMNS view is not compatible with the bit pattern of columns returned by COLUMNS_UPDATED. To obtain a bit pattern compatible with COLUMNS_UPDATED, reference the ColumnID property of the COLUMNPROPERTY system function when you query the INFORMATION_SCHEMA.COLUMNS view, as shown in the following example:

    It recommends running this (which I did):

    SELECT TABLE_NAME, COLUMN_NAME,

    COLUMNPROPERTY(OBJECT_ID(TABLE_SCHEMA + '.' + TABLE_NAME),

    COLUMN_NAME, 'ColumnID') AS COLUMN_ID

    FROM INFORMATION_SCHEMA.COLUMNS

    WHERE TABLE_NAME = 'UpdateTest';

    GO

    Now, theoretically, the results here SHOULD match the bitmapping from the columns, right? Well, next, drop in this trigger:

    CREATE TRIGGER TR_UpdTestTrig ON UpdateTest FOR UPDATE AS

    PRINT CAST( columns_updated() AS INT)

    print columns_updated() & 1

    GO

    It does nothing except report what the columns_updated() is reporting. The second one in there is for confirmation that there wasn't anything funky going on with converting the varbinary to int for some reason, which confirms with one of the updates below.

    When you run this, it says ColA's Column_ID is '2', and ColB is '3'. Theoretically, bitmapped to 2 and 4 respectively. ColH is Column_ID: 9

    UPDATE UpdateTest

    SETColA = 'xx'

    where ColID = 1

    UPDATE UpdateTest

    SETColB = 'yy'

    where ColID = 2

    UPDATE UpdateTest

    SETColC = 'zz'

    where ColID = 3

    UPDATE UpdateTest

    SETColE = 'qq'

    wherecolID = 4

    UPDATE UpdateTest

    SET ColH = 'pp'

    where colID = 5

    The results from setting colA/B/C/E/H are: 512/1024/2048/8192/1. Note, you count 8 DOWN to find the start of the previous bit, you don't even reverse count. Field #9 is the first item. I've also done a test expanding to three bytes worth of fields, and the pattern persists. (Fields 1-8 are in byte 3, 9-16 in byte 2, 17 alone in byte 1).

    So, the pattern is there, but there is no way to easily locate this, books online is wrong about the location method, and it's incredibly counter-intuitive.

    Did I miss a memo somewhere? Is there an intuitive equivalent of columns_updated that takes the field names? Do I need to mess with the mechanic somehow? The structure works just fine for 8 fields, or less.


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • Data this type of thing is stored as the Least Significant Byte First (on the "left"). That's why it appears to be backwords. Then, within each byte, the least significant BIT is on the "right".

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (9/7/2010)


    Data this type of thing is stored as the Least Significant Byte First (on the "left"). That's why it appears to be backwards. Then, within each byte, the least significant BIT is on the "right".

    Sorry Jeff, but that appears inaccurate, or I'm misunderstanding what you're saying. Least Significant byte is on right, from all appearances, at least via the following test:

    DECLARE @blah AS VARBINARY(4)

    SET @Blah = CAST( 260 AS VARBINARY(4))

    --print cast( @blah AS INT)

    --print @blah & 4

    --print @Blah & 256

    PRINT substring(@blah, 1, 1) & 4

    PRINT substring(@blah, 2, 1) & 4

    PRINT substring(@blah, 3, 1) & 4

    PRINT substring(@blah, 4, 1) & 4

    PRINT substring(@blah, 1, 1) & 1

    PRINT substring(@blah, 2, 1) & 1

    PRINT substring(@blah, 3, 1) & 1

    PRINT substring(@blah, 4, 1) & 1

    You'll find the (4) in byte 4 and the (256) in the 1 spot in byte 3.

    However, while your reply is appreciated, it doesn't really answer the resultant question(s).

    Either SQL Books is wrong or I'm missing a hotfix of somekind that allows it to be right. I'm patched to SQL Server 9.00.3042.00 (SP2 with SSIS).

    Failing that, since BOL is wrong (Amazing! I know!), I need a more intuitive way of dealing with the bitmasking for dealing with column update locations. At the moment we're using an equality where clause check to determine changes between inserted and deleted which, while functional, is slower.

    While I could create code to deal with byte level confusion, the average junior/midlevel dba I would hand that off to might have an anneurism if it exploded at 3 in the morning. Please note, I'm a consultant. When they decide to change this code I've probably moved on through 2-3 jobs by then. I have to be careful about the intricate things I leave behind.


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • At the moment we're using an equality where clause check to determine changes between inserted and deleted which, while functional, is slower.

    i think that's the only way to do it;

    remember the UPDATE() function and the COLUMNS_UPDATED() function just returns whether the column was actually referenced in the insert/update statement, NOT whether the column changed it's value;

    --cols a thru L are in the update() functions

    INSERT INTO UpdateTest (ColA, ColB, ColC, ColD, ColE, ColF, ColG, ColH, ColI, ColJ, ColK, ColL)

    VALUES ('aa','bb','cc','dd','aa','bb','cc','dd','aa','bb','cc','dd')

    --only cola and colb is in the UPDATE, both return true, but only cola changed it's value.

    UPDATE UpdateTest set cola = 'aaa',colb = 'bb' WHERE cola = 'aa'

    Lowell


    --help us help you! If you post a question, make sure you include a CREATE TABLE... statement and INSERT INTO... statement into that table to give the volunteers here representative data. with your description of the problem, we can provide a tested, verifiable solution to your question! asking the question the right way gets you a tested answer the fastest way possible!

  • Lowell (9/7/2010)


    remember the UPDATE() function and the COLUMNS_UPDATED() function just returns whether the column was actually referenced in the insert/update statement, NOT whether the column changed it's value;

    If I'd known that I'd forgotten it. You're absolutely correct after I tested your example myself. Thanks Lowell. We're using a single stored proc to deal with this and if the param's null it isnull()'s to itself, so they'll always have an 'update'. That kills the entire adventure in general.

    Well, my questions are answered, but I'm more then happy to continue discussing why Microsoft decided to use reverse byte association and not detect a way to use it with their built in functions. 🙂


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • I always felt those two functions should be renamed, as they are really misleading; something like ISREFERENCED would be a much better name .

    Lowell


    --help us help you! If you post a question, make sure you include a CREATE TABLE... statement and INSERT INTO... statement into that table to give the volunteers here representative data. with your description of the problem, we can provide a tested, verifiable solution to your question! asking the question the right way gets you a tested answer the fastest way possible!

  • I always felt those two functions should be renamed, as they are really misleading; something like ISREFERENCED would be a much better name

    Lowell


    --help us help you! If you post a question, make sure you include a CREATE TABLE... statement and INSERT INTO... statement into that table to give the volunteers here representative data. with your description of the problem, we can provide a tested, verifiable solution to your question! asking the question the right way gets you a tested answer the fastest way possible!

  • Craig Farrell (9/7/2010)


    or I'm misunderstanding what you're saying.

    That may be true... from BOL 2k (had a handy reference there)...

    F. Use COLUMNS_UPDATED to test more than 8 columns

    If you must test for updates that affect columns other than the first 8 columns in a table, you must use the SUBSTRING function to test the proper bit returned by COLUMNS_UPDATED. This example tests for updates that affect columns 3, 5, or 9 in the Northwind.dbo.Customers table.

    USE Northwind

    DROP TRIGGER tr1

    GO

    CREATE TRIGGER tr1 ON Customers

    FOR UPDATE AS

    IF ( (SUBSTRING(COLUMNS_UPDATED(),1,1)=power(2,(3-1))

    + power(2,(5-1)))

    AND (SUBSTRING(COLUMNS_UPDATED(),2,1)=power(2,(1-1)))

    )

    PRINT 'Columns 3, 5 and 9 updated'

    GO

    UPDATE Customers

    SET ContactName=ContactName,

    Address=Address,

    Country=Country

    GO

    Like I said, "Least significant byte is first". And it's not a Microsoft anomoly... that's the way data is stored for numbers at the machine language level.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (9/8/2010)


    That may be true... from BOL 2k (had a handy reference there)...

    IF ( (SUBSTRING(COLUMNS_UPDATED(),1,1)=power(2,(3-1))

    + power(2,(5-1)))

    AND (SUBSTRING(COLUMNS_UPDATED(),2,1)=power(2,(1-1)))

    )

    PRINT 'Columns 3, 5 and 9 updated'

    GO

    Like I said, "Least significant byte is first". And it's not a Microsoft anomoly... that's the way data is stored for numbers at the machine language level.

    I would agree, if my test code above didn't show that least significant byte is last in a varbinary. BoL is inaccurate here, unfortunately, at least for 2k5.


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • Your test code is working with Varbinary... not with Columns_Updated.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Please note the sample build code in the original post, where it shows that the first set of columns (for more then 8 fields) are in the most significant byte. If you have 10 columns, columns 1-8 are in the first byte (agreed that substring(1,1) works for this), but that byte for hard value is the 256-32768 bit set. The second byte, which works for columns 9-16, is the 1-128 bit set.

    The varbinary acts the same way, I just used it in the second set of code to help simplify the example.


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • Craig Farrell (9/8/2010)


    Please note the sample build code in the original post, where it shows that the first set of columns (for more then 8 fields) are in the most significant byte. If you have 10 columns, columns 1-8 are in the first byte (agreed that substring(1,1) works for this), but that byte for hard value is the 256-32768 bit set. The second byte, which works for columns 9-16, is the 1-128 bit set.

    The varbinary acts the same way, I just used it in the second set of code to help simplify the example.

    Heh... whatever. My point is that BOL isn't wrong for the item I posted. 🙂

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

  • Jeff Moden (9/8/2010)


    Heh... whatever. My point is that BOL isn't wrong for the item I posted. 🙂

    For the item you posted, no, but since we're discussing 2k5 and that BOL is wrong for 2k5 because of the new method to locating the bitmapping, as described in the article from which I referenced earler (which is from 2k5) and then proved with sample code inaccurate, I guess this is appropriate:

    Heh... whatever. 🙂

    I know it exists now, I'll deal with it.


    - Craig Farrell

    Never stop learning, even if it hurts. Ego bruises are practically mandatory as you learn unless you've never risked enough to make a mistake.

    For better assistance in answering your questions[/url] | Forum Netiquette
    For index/tuning help, follow these directions.[/url] |Tally Tables[/url]

    Twitter: @AnyWayDBA

  • Craig Farrell (9/9/2010)


    Jeff Moden (9/8/2010)


    Heh... whatever. My point is that BOL isn't wrong for the item I posted. 🙂

    For the item you posted, no, but since we're discussing 2k5 and that BOL is wrong for 2k5 because of the new method to locating the bitmapping, as described in the article from which I referenced earler (which is from 2k5) and then proved with sample code inaccurate, I guess this is appropriate:

    Heh... whatever. 🙂

    I know it exists now, I'll deal with it.

    Ah sorry... my apologies. Not sure why I was hung up on the 2k thing.

    --Jeff Moden


    RBAR is pronounced "ree-bar" and is a "Modenism" for Row-By-Agonizing-Row.
    First step towards the paradigm shift of writing Set Based code:
    ________Stop thinking about what you want to do to a ROW... think, instead, of what you want to do to a COLUMN.

    Change is inevitable... Change for the better is not.


    Helpful Links:
    How to post code problems
    How to Post Performance Problems
    Create a Tally Function (fnTally)

Viewing 14 posts - 1 through 13 (of 13 total)

You must be logged in to reply to this topic. Login to reply