Category: Blog

  • Traveling in Wales

    It’s SQL Relay Cardiff day today. This is my first time in Wales, and it’s a busy day at SQL Relay. It’s a bit of deja vu with me delivering the same session on Version Control today that I presented 18 ours ago at SQL Relay Bristol.

    It’s interesting being on part of the SQL Relay tour, with some of the same speakers, some different, the rapid travel from one city to the next, and orienting myself in a new environment. The venues are much different, with a set of hotel conference rooms yesterday and a large arena facility (the Motorpoint Arena) today.  It’s surprisingly confusing trying to find toilets, speaker rooms, and more.

    However the people are nice, and friendly, although quiet. I got lots of good questions both days, on a semi-confusing topic, and that will help me revisit the session and make some changes over the next few days.

    I get to help on a second session today as we have a speaker that’s ill. Then it’s some work as I prep for travel back to London and SQL in the City in London on Friday.

     

  • A tSQLt Mistake – Debugging a Test

    While I was working on a test the other day, it kept failing. Not a big surprise, but I couldn’t figure out why. When I looked at tsqlt.testresults, I saw extra rows. Double rows in fact, and that threw me.

    This was my Assemble code.

    -- Assemble
    CREATE TABLE #Expected (
    yearnum int
    , monthnum TINYINT
    , salestotal money
    )


    INSERT INTO #Expected
    ( yearnum
    , monthnum
    , salestotal
    )
    VALUES
    ( 2012, 11, 2500.23 )
    , ( 2012, 12, 2200.15 )
    , ( 2013, 1, 2656.75 )

    SELECT *
    INTO #actual
    FROM #Expected AS e

    EXEC tsqlt.FakeTable @TableName = N'MonthlySales', @SchemaName='dbo';

    INSERT dbo.MonthlySales
    VALUES
    ( 11, 1000.00)
    , ( 11, 1500.23)
    , ( 12, 2200.15)
    , ( 13, 1000.00)
    , ( 13, 1656.00)
    , ( 13, 0000.75);

    Here was the output (ignoring the failure messages):

    [tArticles].[test sum of sales by month for multiple months] failed: (Failure) The calculations are incorrect

    |_m_|yearnum|monthnum|salestotal|

    +—+——-+——–+———-+

    |=  |2012   |11      |2500.2300 |

    |=  |2012   |12      |2200.1500 |

    |=  |2013   |1       |2656.7500 |

    |>  |2013   |1       |2656.7500 |

    |>  |2012   |12      |2200.1500 |

    |>  |2012   |11      |2500.2300 |

     

    Hmmm. What’s going on? Why don’t the rows match? If I run the query, I see the results I expect. Is it the query or test?

    In this case, you read the results as showing that I have 3 rows that are the same in my expected and actual tables (@expected and @actual variables in the assert). However I also have 3 extra rows in the actual table, which appear to be duplicates.

    If I go back to the Assemble, I see a pattern that’s a problem. Some people might think these hassles are a way to give up on testing. Some might build a better pattern. I’ll do the latter.

    In this case I create the expected table and then I insert the expected results. Then I create my actual table from the expected one to keep the schema the same and avoid repeating code. However in this case I have a bug.

    The bug is I’m moving the expected results to actual. If I asserted at this point, I’d pass. However then I run the query and insert the results, which happen to be the same as the expected results (my query works). If the query didn’t work, I might really spend a lot of time debugging it, but here I can tell my test code is buggy.

    I have two choices to fix this.

    1. Add a WHERE clause of WHERE 1 = 0 (no rows inserted)
    2. Move the creation of the actual table.

    My first thought was to adjust the pattern to this:

    CREATE TABLE #Expected (
    yearnum int
    , monthnum TINYINT
    , salestotal money
    )

    SELECT *
    INTO #actual
    FROM #Expected AS e


    INSERT INTO #Expected
    ( yearnum
    , monthnum
    , salestotal
    )
    VALUES
    ( 2012, 11, 2500.23 )
    , ( 2012, 12, 2200.15 )
    , ( 2013, 1, 2656.75 )

    I move the #Actual and #Expected tables together, so that once I get the results set, I immediately create the #Actual copy. I could leave things and do this:

    CREATE TABLE #Expected (
    yearnum int
    , monthnum TINYINT
    , salestotal money
    )


    INSERT INTO #Expected
    ( yearnum
    , monthnum
    , salestotal
    )
    VALUES
    ( 2012, 11, 2500.23 )
    , ( 2012, 12, 2200.15 )
    , ( 2013, 1, 2656.75 )

    SELECT *
    INTO #actual
    FROM #Expected AS e
    WHERE 1 = 0

    EXEC tsqlt.FakeTable @TableName = N'MonthlySales', @SchemaName='dbo';

    Maybe the best thing is to be careful and do this:

    -- Assemble
    CREATE TABLE #Expected (
    yearnum int
    , monthnum TINYINT
    , salestotal money
    )

    SELECT *
    INTO #actual
    FROM #Expected AS e
    WHERE 1 = 0

    INSERT INTO #Expected
    ( yearnum
    , monthnum
    , salestotal
    )
    VALUES
    ( 2012, 11, 2500.23 )
    , ( 2012, 12, 2200.15 )
    , ( 2013, 1, 2656.75 )

    Combine the ideas and keep this insulated from refactoring moving or adding code in there.

    Remember, tests are code. This is why they should fail first, so that you have some confidence in your code working and causing a test to pass.

  • A Long Trip Ahead

    This is my last day at home for a long time. At least long by my standards. I head to the airport tomorrow for a ten day trip, not returning to CO until Saturday, Oct 17. I rarely travel more than 4 or 5 days at the most, so this is one of my longer ones.

    My first stop is Orlando. I’m heading over to help teach a DLM workshop for Redgate Software on Friday. This is our Database Source Control workshop that covers some in depth work with SQL Source Control and version control systems. I’ve done a few of these, so this should be easy for me.

    Saturday is SQL Saturday #442 in Orlando. I haven’t been to a SQL Saturday in Orlando in a long time, so I’m excited to get back to the place where these all started. I’ve got one talk on Saturday, talking Encryption, around which I’ll be hanging out with friends and trying to learn a few SQL things along the way.

    Sunday I travel, though at a relaxed pace. I’ll spend the day making my way to Houston before an overnight flight to London on the Dreamliner. It’s a leisurely day, where I’ll probably spend time catching up on Python work because Monday is crazy.

    Monday is a day I dread a bit. I land in London and immediately drive to Cambridge for a few meetings. I’ve got some SQL in the City rehearsals planned before I turn around and head back to London to catch the fun bus to Bristol for SQL Relay. If you map this out, it seems silly, but that’s what I got myself talked into somehow.

    Tuesday is SQL Relay in Bristol. I’ll be previewing my talk for SQL in the City, so I’ll apologize in advance if things aren’t 100% set. However after a day at the conference, I’ll be heading over to Cardiff where I’ll get dinner and try to fix all the things I did wrong during the talk.

    Wednesday is SQL Relay Cardiff.  A repeat of Tuesday in a new city. I’m not sure if everything is the same, but I’ll be (hopefully) delivering a better talk on Wednesday. Wednesday night Grant and I aren’t doing anything, so it’s a few hours to unwind.

    Thursday morning we make our way back to London. Hopefully we manage the train system fine because we have lunchtime and afternoon meetings with people coming down from Redgate during the day. This is the final SQL in the City prep time, as well as a few other in person events, including seeing my boss for only the 3rd time this year.

    Friday is SQL in the City 2015 London. Redgate puts on a great event, and I’m looking forward to another exciting day. Three times on stage for me, so I’m sure when things wrap up around 5 I’ll be quite tired. However no rest, I head to Heathrow for a night in my 5th hotel on this trip.

    10 days. Orlando, Cambridge, Bristol, Cardiff, London.

    I have the feeling I won’t be doing much on Saturday night or Sunday when I return.

  • Testing Sum By Month

    I’ve been on a testing kick, trying to formalize the ad hoc queries I’ve run into something that’s easier to track. As a result, when I look to solve a problem, I’ve written a test to verify that what I think will happen, actually happens.

    The Problem

    I saw a post recently where someone wasn’t sure how to get the sum of a series of data items by month, so I decided to help them. They asked for a year number, a month number, and a total, so something like this:

    Year   Month   Sales

    2012       1   1500.23

    2012       2   1480.00

    2012       3   1945.00

    2015       7   8933.11

    They mentioned, however, that the had sales data stored as an integer. Not as 201201, but as 1, 2, 3, with a base date being Jan 1, 2012. That’s strange, but it’s a good place to write a test.

    I like to start with the results, since if I don’t know the results, how can I tell if my query works? Let’s get a test going. I’ll start by created my expected results. I’ve come to like using temporary tables, and limited data. I also like to test some boundaries, so Iet’s cross a year.

    CREATE PROCEDURE [tArticles].[test sum of sales by month for multiple months]
    AS
    BEGIN
    -- Assemble
    CREATE TABLE #Expected (
    yearnum INT
    , monthnum TINYINT
    , salestotal NUMERIC(10,2)
    )


    SELECT *
    INTO #actual
    FROM #Expected AS e



     

    INSERT INTO #Expected
    ( yearnum
    , monthnum
    , salestotal
    )
    VALUES
    ( 2012, 11, 2500.23 )
    , ( 2012, 12, 2200.15 )
    , ( 2013, 1, 2656.75 )

    I like to create the actual results table here as well, which allows me to then easily insert into this table from a procedure as well as a query. In this case, I’ll use a query, but I could use insert..exec.

    Once I have results, I need to setup my test data. In this case, I’d probably go grab the rows from a specific period and put them in a temp table and use Data Compare to get them. Or make them up. It doesn’t matter. I just need the data that allows me to test my query.


    EXEC tsqlt.FakeTable @TableName = N'MonthlySales';

    INSERT MothlySales
    VALUES
    ( 11, 1000.00)
    , ( 11, 1500.23)
    , ( 12, 2200.15)
    , ( 13, 1000.00)
    , ( 13, 1656.00)
    , ( 13, 0000.75);

    I don’t try to make this hard. I use easy math, giving myself a few cases. One, two, three rows of data for the months. If I think this isn’t representative, I can add a few more. I don’t try to be difficult, I’m testing a query. If I had rows that might not matter, or I wanted to test if 0 rows are ignored, I could do that.

    Now I need a query. Something simple, a SUM() with a GROUP by is needed. However I need to also change 11 into 2012 11, so that’s an algorithm.

    An easy way to do this is start with a base date. I’d prefer this is in a table, but I can do it inline.

    INSERT #actual

    SELECT
    yearnum = DATEPART( YEAR, DATEADD( MONTH, datenum, '20120101'))
    , MONTHNUM = DATEPART(MONTH, DATEADD( MONTH, DATENUM, '20120101'))
    , SALESTOTAL = SUM(ms.salesamount)
    FROM dbo.MonthlySales AS ms
    GROUP BY
    DATEPART( YEAR, DATEADD( MONTH, datenum, '20120101'))
    , DATEPART(MONTH, DATEADD( MONTH, DATENUM, '20120101'))
    ORDER BY
    DATEPART( YEAR, DATEADD( MONTH, datenum, '20120101'))
    , DATEPART(MONTH, DATEADD( MONTH, DATENUM, '20120101'))
    ;
    GO

    I’ll insert this data into #actual, which tests my query.

    The final step is to assert my tables are equal.

    -- Assert
    EXEC tsqlt.AssertEqualsTable
    @Expected = N'#EXPECTED',
    @Actual = N'#actual',
    @FailMsg = N'The calculations are incorrect';

    The Test

    What happens when I execute this test? I can use tsqlt.run, or my SQL Test plugin.

    2015-09-28 16_07_49-Photos

    In either case, I’ll get a failure.

    2015-09-28 16_08_11-Photos

    When I check the messages, I see the output from tSQLt. In this case, none of my totals seem to match.

    2015-09-28 16_15_23-Photos

    What’s wrong? In my case, I’m adding the integer to the base month, but that means a 1 means 2012 02, not 2012 01. I’m a month off. Let’s adjust the query.

    -- Act
    INSERT #actual
    SELECT

    yearnum = DATEPART( YEAR, DATEADD( MONTH, datenum, '20111201'))
    , MONTHNUM = DATEPART(MONTH, DATEADD( MONTH, DATENUM, '20111201'))
    , SALESTOTAL = SUM(ms.salesamount)
    FROM dbo.MonthlySales AS ms
    GROUP BY
    DATEPART( YEAR, DATEADD( MONTH, datenum, '20111201'))
    , DATEPART(MONTH, DATEADD( MONTH, DATENUM, '20111201'))
    ORDER BY
    DATEPART( YEAR, DATEADD( MONTH, datenum, '20111201'))
    , DATEPART(MONTH, DATEADD( MONTH, DATENUM, '20111201'))

    Now when I run my test, it passes.

    2015-09-28 16_18_09-Photos

    Why Bother?

    This seems trivial, right? What’s the point of this test? After all, I can easily check this with a couple quick queries.

    Well, let’s imagine that we decide to move this base date into a table, or that we alter it. We want our queries to continue to work. I can have this test as part of an automated routine that ensures this test will run each time the CI process runs. Or each time a developer executes a tsqlt.runall in this database (shared or populated from a VCS). I prevent refactoring queries.

    More importantly, I can take results and alter them first, say if someone decides to change this to a windowing query. I could plug a new query in the test (or better yet, use a proc and put that call in the test) , and if I change code, I can verify it still works.

    Write tests. You need them anyway, so why not formalize them? The code around this query, mocking test data, is something I do anyway, so this gets me a few more minutes to verify that the code works. I can tune the query, alter indexes, perf test, and be sure that code is still running cleanly.

    http://www.sqwhere lservercentral.com/Forums/Topic1716471-1292-1.aspx#bm1716535