Tag: T-SQL

  • T-SQL Tuesday #106 – Trigger Headaches or Happiness

    tsqltuesdaySince I took over the T-SQL Tuesday a few months ago, I decided I ought to host again. Especially since I’ve done it twice, but Wayne Sheffield got his third spot last month. Gotta keep up with Mr. Sheffield.

    Triggers, for fun and frustration

    I’ve been working with SQL Server and T-SQL a long time, and across many jobs, I think I’ve ended up using triggers in 0.01% of my tables or less. They can be a useful and helpful construct, but they can also be problematic and difficult, especially in the age of changing business models and rules.

    Since I’ve found triggers to be both helpful and hurtful, I decided to ask you to write about an experience you’ve had with triggers. Either good or bad, but let me know this month what stands out in your mind.

    The Rules

    As always, the rules for this month are simple.

    • Write and publish a post on September 11, 2018, UTC time.
    • Include the T-SQL Tuesday logo (you can grab this above) and link your post back to this invitation.
    • Leave a comment/pingback on this post for me to use to include you in the roundup
    • Have fun.
  • Generating a Constrained Random Date–#SQLNewBlogger

    Another post for me that is simple and hopefully serves as an example for people trying to get blogging as #SQLNewBloggers.

    There have been lots of posts on the topic of generating random values, and some great articles. One of my favorites is Jeff Moden’s Generating Test Data: Part 1 – Generating Random Integers and Floats. Part 2 deals with dates, and that’s actually what I needed, but really I needed part 1.

    In my situation, I was helping a customer generate some random data. They had filled a table, Customers, with some data.

    2018-08-24 13_05_44-Microsoft Edge
    The goal was to populate a child table with some data. The child table had a date column that was supposed to be between the Entered and Exit dates in the Customer table.

    My update would have a join, obviously, and I can reference the enter and exit date, but how to get a date between them? My first thought was that I wanted a DATEADD() function. Something like this:

    UPDATE ce
    SET ce.EventTimeStamp = DATEADD( MINUTE, SomeRandomValue, c.CustomerExitedDateTime)), c.CustomerEnteredDateTime)
    FROM   dbo.Customer AS c
    INNER JOIN dbo.CustomerEvent AS ce
    ON ce.CustomerID = c.CustomerID

    The trick is what random value to use? If you look through Jeff’s article, you will see that the trick is to use a tally table and the NEWID() function. However, this doesn’t work:

     UPDATE ce
    SET ce.EventTimeStamp = DATEADD( MINUTE, NEWID(), c.CustomerEnteredDateTime)
    FROM   dbo.Customer AS c
    INNER JOIN dbo.CustomerEvent AS ce
    ON ce.CustomerID = c.CustomerID
    ;

    What I need to do is convert the GUID to a number. In this case, I added CHECKSUM around it, again, as in Jeff’s article. Then use ABS() to enclose this to get all positive numbers.

     UPDATE ce
    SET ce.EventTimeStamp = DATEADD( MINUTE, ABS(CHECKSUM((NEWID())))), c.CustomerEnteredDateTime)
    FROM   dbo.Customer AS c
    INNER JOIN dbo.CustomerEvent AS ce
    ON ce.CustomerID = c.CustomerID
    ;

    This gives me values, but they aren’t constrained. What I need to do is limit the upper random value so that the end time doesn’t exceed the Customer.CustomerExitDateTime for that row.

    To do this, I can constraint a large set of numbers to some value with the modulo function. This will limit what values can appear. The basic script is this:

    UPDATE ce
    SET ce.EventTimeStamp = DATEADD( MINUTE, ABS(CHECKSUM((NEWID())))) % 10, c.CustomerEnteredDateTime)
    FROM   dbo.Customer AS c
    INNER JOIN dbo.CustomerEvent AS ce
    ON ce.CustomerID = c.CustomerID
    ;

    This would give me values between 1 and 0 minutes after the start time, but this doesn’t mean these values won’t be after the exit time. This is also an unrealistic window if most of the time the enter and exit times vary by hours.

    What I did instead was to use the difference between the enter and exit times, with DATEDIFF() as my modulo function. That gives me:

    WITH myTally (n)
    AS
    -- SQL Prompt formatting off
    (SELECT n = ROW_NUMBER() OVER (ORDER BY (SELECT null))
      FROM (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) a(n)
       CROSS JOIN (VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)) b(n)
    )
    UPDATE ce
    SET ce.EventTimeStamp = DATEADD( MINUTE, ABS(CHECKSUM((NEWID()))) % (DATEDIFF(MINUTE, c.CustomerEnteredDateTime, c.CustomerExitedDateTime)), c.CustomerEnteredDateTime)
    FROM   dbo.Customer AS c
    INNER JOIN dbo.CustomerEvent AS ce
    ON ce.CustomerID = c.CustomerID
    ;

    I run this, and I get the table updated with a random set of values.

    2018-08-24 13_19_08-Microsoft Edge

    SQLNewBlogger

    This was a problem in my daily work. It was a customer, but it could easily be an internal query problem. I spent about 10 minutes grabbing screen shots and taking apart the query I’d built.

    You can do this, too. Show us your mind working with the solutions you write in your own blog.

  • Remember the Default Window

    I ran across a question recently from a user about why they had strange results from a windowing query. This is better explained with an example, so let’s look at one.

    I have some data in a table. This is NFL data, and a sample of it looks like this:

    2018-08-22 18_57_25-SQLQuery1.sql - Plato_SQL2016.NFLAnalysis (PLATO_Steve (52))_ - Microsoft SQL Se

    What I want to do is compare the passing yards each year with the most current value for that player, showing the plus or minus. This means that for Aaron Rodgers, who threw for 1675 yards in 2017, I’d want to show this for the first few years of his career:

      PlayerName  NFLYear PassYards Most Recent Yards Difference
    ------------- ------- --------- ----------------- -----------
    Aaron Rodgers 2005 65 1675 -1610
    Aaron Rodgers 2006 46 1675 -1629
    Aaron Rodgers 2007 218 1675 -1457
    Aaron Rodgers 2008 4038 1675 2363

    This shows
    me an easy view of the years where he was better in his career than he is now. Last year was likely a down year because of injury, but we’ll see this year.

    In any case, if I run this query using LAST_VALUE() for the final year of his career, I don’t get the right results.

    2018-08-22 19_11_16-SQLQuery1.sql - Plato_SQL2016.NFLAnalysis (PLATO_Steve (52))_ - Microsoft SQL Se

    It seems as though in every row, I’m getting the current row as the last value, not the last value of the partition. My partition is by player, so I should only have a window for each player. In this case, I should have the years 2005-2017 for Aaron Rodgers. My ordering is by year, so the last value should be 1675.

    Why isn’t it?

    The reason has to do with the framing. As the window is consumed, the default values for the framing are between

    • start – unbounded preceding
    • end – current row

    That means the first row for 2005 has the range of 2005-2005. The preceding rows are this row, and the current row is this row. For 2006, we have the first row as 2005 and the current row as 2006. The last value in this case is 46.

    What we need to do is specify the entire window if we want that. In this case, we could use the current row as the start, but we certainly need the unbounded following rows.

    2018-08-22 19_18_30-SQLQuery1.sql - Plato_SQL2016.NFLAnalysis (PLATO_Steve (52))_ - Microsoft SQL Se

    This is a common mistake when writing window queries. I’d recommend you always include the partition and the framing to avoid any issues.

  • Will Terminators Be Required?

    I was looking at an article the other day and noticed that there was a CTE sample with the semicolon on the line before the code. I’ve been seeing this convention for years, starting your CTE with a semicolon because people aren’t sure this will get dropped in a batch with other code. It’s not that the CTE needs this, but the previous statement needs to be terminated. There are a few other T-SQL constructs that require any previous statements to be terminated, and as a result, we have a series of strange publishing conventions for sample code.

    I really wish that the language designers had thought this through and stopped trying to overload and reuse keywords. We could have avoided this with a simple CTE language element to indicate the structure. I know, I know, there are other considerations, but this seems annoying. I’m sure that the addition of the CTE fully expected that at some point semicolons would be required for all code.

    Brent wrote about this a few years ago. The Syntax page for T-SQL currently says this about the semicolon: “Transact-SQL statement terminator. Although the semicolon is not required for most statements in this version of SQL Server, it will be required in a future version.” There is no shortage of confusion about where terminators might be required and how to structure code, partially because SQL hasn’t ever used terminators and the evolution of the language has been a bit inconsistent with regard to structure.

    These days it seems that nothing will ever be removed. It appears that nothing else will be deprecated in this age of cloud software and feature toggles.I suspect at this point that we’ll see features wither in the codebase, not receiving future development if Microsoft doesn’t see them as valuable, living in limbo forever.

    I don’t think we’ll ever see terminators required, and as the amount of legacy code grows, it becomes less and less likely they will become mandated.

    Steve Jones

    The Voice of the DBA Podcast

    Listen to the MP3 Audio ( 3.2MB) podcast or subscribe to the feed at iTunes and Libsyn.