Tag: T-SQL

  • Using sp_executesql Parameters –#SQLNewBlogger

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

    I haven’t used sp_executesql much. Instead, my habitually way of executing dynamic SQL has been with EXEC(). There are a few differences between these commands, but I had to look at sp_executesql recently and realized I didn’t know much about it.

    One of the neat things with sp_executesql is that you can pass in parameters.  That’s pretty cool. I hadn’t ever bothered, but if you read the docs, you’ll see that if you execute the same code over and over, with different parameters, you might get the same execution plan. This can be a performance boost.

    NOTE: THIS IS NOT ALWAYS BETTER. It can be.

    I’m not going to delve into deep details, but Kimberly Tripp does so read her post (and then write your own thoughts).

    The Code

    Here’s some code to demonstrate. I have a simple table with 3 columns to insert. In this case, here’s my insert:

    INSERT EventLogger VALUES (@m, @d, @u)

    Now, I went to use this over and over, but with different values for the parameters. Obviously I can just do this:

    …

    SET @m = ‘Error Message’

    INSERT EventLogger VALUES (@m, @d, @u)

    SET @m = ‘New Error Message’

    INSERT EventLogger VALUES (@m, @d, @u)

    …

    However, imagine that I’m building this INSERT string dynamically because it’s more complex. How do I execute this over and over with new values? With EXEC(), I rebuild the string. With sp_executesql, I do this:

    DECLARE @cmd NVARCHAR(MAX)
    DECLARE @dt DATETIME = GETDATE();
    DECLARE @msg VARCHAR(200) = ‘An error occured’;
    DECLARE @usr VARCHAR(10) = ‘Steve’;
    DECLARE @p NVARCHAR(500);

    SELECT @cmd = N’INSERT EventLogger VALUES (@m, @d, @u)’

    SELECT @p = N’@m varchar(200), @d datetime, @u varchar(10)’

    EXEC sp_executesql @cmd, @p, @m = @msg, @d = @dt, @u = @usr;

    SELECT @dt = GETDATE()
         , @msg = ‘A new error occured’
         , @usr = ‘Bob’;

    EXEC sp_executesql @cmd, @p, @m = @msg, @d = @dt, @u = @usr;
    GO
    SELECT top 10
      *
    FROM dbo.EventLogger AS el

    Now, I check the table:
    2016-06-22 15_00_08-Settings

    I thought that was cool.

    SQLNewBlogger

    This isn’t a deep post. It’s a light look, with a little explanation. I’ll do more later. However, I’m hoping this serves as a way to show you how to start investigating a topic. I’ve spent a bit of time experimenting and learning. I’m fairly confident I could play and use sp_executesql more.

    You could do this as well, start digging into a topic and then show how you’re learning.

  • Basic XML Node Query–#SQLNewBlogger

     

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

    I saw a question recently about querying an XML document. Certainly avoid this in the database if you can, but there are times you need to. Rather than link to the post, I wanted to show the basics of how you query a node.

    Let’s suppose I have an XML document like this:

    <Order>
      <OrderID>4FB9</OrderID>
      <ORderDate>2019-07-20-00.31.23.000000</ORderDate>
      <Status>Open</Status>
      <Customer>
        <CustomerName Type=”Individual”>
          <FirstName>Jon</FirstName>
          <LastName>Doe</LastName>
        </CustomerName>
      </Customer>
      <Customer Type = “Company”>
        <CustomerName>
          <CompanyName>Acme</CompanyName>
          <Account>12345</Account>
        </CustomerName>
      </Customer>
      </Order>

    Now, I saw someone query this with code like this to get the OrderID.

    DECLARE @xml XML;
    SET @xml = N’
    <Order>
      <OrderID>4FB9</OrderID>
      <ORderDate>2019-07-20-00.31.23.000000</ORderDate>
      <Status>Open</Status>
      <Customer>
        <CustomerName Type=”Individual”>
          <FirstName>Jon</FirstName>
          <LastName>Doe</LastName>
        </CustomerName>
      </Customer>
      <Customer Type = “Company”>
        <CustomerName>
          <CompanyName>Acme</CompanyName>
          <Account>12345</Account>
        </CustomerName>
      </Customer>
      </Order>
    ‘;

    SELECT
          t.b.value(‘(ORDERID)[1]’, ‘NVARCHAR(100)’) AS MSGID
      FROM
        @xml.nodes(‘/Order’) t(b);

    This doesn’t work.

    2016-06-15 13_09_07-Photos

    The reason this doesn’t work is that XML is case sensitive. Meaning ORDERID != OrderID. The former is in the query, the latter in the XML document. If I change the query, this works (note I have OrderID below).

    2016-06-15 13_11_23-Photos

    This would also apply to the .Nodes call. If I had .ORDER, this also wouldn’t work.

    2016-06-15 13_11_54-Photos

    The @xml.nodes() call determines the root at which I’ve essentially set the document. I could have this as /Order/Customer if I wanted. In that case, I couldn’t access the OrderID. The OrderID isn’t below the Customer node.

    2016-06-15 13_13_21-Photos

    However, from below Customer, I can get to the names.

    2016-06-15 13_14_05-Photos

    There is a lot more to know about XML, but you can experiment with the various nesting levels by including different paths. I’ll show a few more things in another post.

    SQLNewBlogger

    Querying XML is hard, and can be frustrating as the document size grows and complexity grows. However, this is a good way to showcase your skills (or build them), but tackling different query questions or challenges and writing about them.

    Hint: this will also help solidify your XML skills.

  • Getting the Previous Row Value before SQL Server 2012

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

    I ran across a post where someone that was trying to access the previous value in a table for some criteria. This is a common issue, and  one that’s very easily solved in SQL Server 2012+ with the windowing functions.

    However, what about in SQL Server 2008 R2-?

    NOTE: I’m solving this quickly, the way many people do, but this is an inefficient solution. I’ll show that in another post. However, I’m showing how you can describe and solve a problem here. If you need to solve this, look for a temp table solution (or find a later post from me).

    Setup

    It’s pretty easy. Let’s get some data together. I’ll use a big sample since that’s easier to see the differences.

    CREATE TABLE MyID
    ( myid INT
    , myvalue INT
    );
    GO
    INSERT MyID
    VALUES (1, 10 ),
            (1, 20 ),
            (2, 400),
            (2, 500),
            (2, 600),
            (3, 8000),
            (3, 9000),
            (3, 10000),
            (3, 11000);

    Now, what I want is something that returns the previous row, assuming we’re ordering by the ID and value. If there is no previous value, let’s return a zero. Essentially what we want is something like this:

    select MyID        , MyValue       , MyPrevValue = ISNULL( x, 0)
    from …

    That’s the pseudocode. Obviously I need to fill in blanks. However, let’s build a test. Why? Well, I can then see my result data, and I can re-run the test over and over as I experiment with the query. It’s not hard, I promise.

    EXEC tsqlt.NewTestClass
      @ClassName = N'WindowTests';
      go
    CREATE PROCEDURE [WindowTests].[test check the previous row value for MyID]
    AS
    BEGIN
    -- assemble
    CREATE TABLE #expected (id INT, myvalue INT, PrevValue int) INSERT #expected
    VALUES (1, 10  , 0  ),
            (1, 20  , 10 ),
            (2, 400 , 20 ),
            (2, 500 , 400),
            (2, 600 , 500),
            (3, 8000 , 600),
            (3, 9000 , 8000),
            (3, 10000 , 9000),
            (3, 11000 , 10000) SELECT *
    INTO #actual
      FROM #expected AS e
      WHERE 1 = 0 -- act
    INSERT #actual
    EXEC dummyquery;
    -- assert
    EXEC tsqlt.AssertEqualsTable
      @Expected = N'#expected'
    , @Actual = N'#actual'
    , @FailMsg = N'Incorrect query' END

    If you examine the test, you’ll see that I create a table, insert the results I expect, and then call some procedure. I compare the results of the procedure with the table I built.

    That’s it. A simple test, but I’ll let the computer compare the result sets rather than trusting my eyes.

    Last thing, I’ll build my dummy procedure, which can look like this:

    CREATE PROCEDURE dummyquery
    -- alter procedure dummyquery
    AS
    BEGIN select MyID   , MyValue , PrevValue = MyValue from MyID
      END

    Now I have the outline of what I need. If I run the test now, I’ll get this:

    2016-06-07 10_18_23-Photos

    The test output tells me it has failed, the values in the #expected table (with a <), and the values from my query in the #actual table (with a >).

    Now I can debug and work on this.

    Solving the Problem

    First, I want to order the data and get a number that counts the order. The ROW_NUMBER function does this, which is available in SQL Server 2005+. I won’t go into SQL 2000- solutions because, well they’re more complex and there should be very few SQL 2000 instances left coming up with new problems.

    I can do this with this code:

    2016-06-07 10_21_40-Photos

    Note that I have a sequential counter that lets me order every row with an index. Now, I can access the previous row, since I know the MyKey value will be one less than the current row.

    With this in mind, let’s turn this into a CTE (removing the previous value). Outside of the CTE, I’m going to self-join the CTE to itself. I’ll use a LEFT JOIN since not every row will have a previous row. In fact, the first row won’t.

    The join condition, which you can play with, will be on the outer table’s ID being one less than the first table’s key. You could reverse the math as well, but that’s up to you.

    2016-06-07 10_29_37-Photos

    One last issue. Add an ISNULL to the previous value to return a 0 if there is no match. Now, let’s run the test.

    2016-06-07 10_31_58-Photos

    SQLNewBlogger

    This was a slightly longer post, where I tried to explain how I setup the problem and solved it. I included a test, which didn’t add much coding time. In fact, the writing took far longer than the coding itself.

    This is the type of problem I’d encourage you to solve on your blog. If you want to repeat this, look for a solution with temp tables, as the CTE incurs a lot of reads. This isn’t really what you’d like to do in production code.

  • LAST_VALUE–The Basics of Framing

    I did some work a 3-4 years ago, learning about the Windowing functions and enjoying them so much I built a few presentations on them. In learning about them, and trying to understand them, I found some challenges, and it took some experimentation to actually understand how the functions work in small data sets.

    I noticed last week that SQLServerCentral had re-run a great piece from Kathi Kellenberger on LAST_VALUE, which is worth the read. There’s a lot in there to understand, so I thought I’d break things down a bit.

    Framing

    The important thing to understand with window functions is that there is a frame at any point in time when the data is being scanned or processed. I’m not sure what the best term to use is.

    Let’s look at the same data set Kathi used. For simplicity, I’ll use a few images of her dataset, but I’ll examine the SalesOrderID. I think that can be easier than looking at the amounts.

    Here’s the base dataset for two customers, separated by CustomerID and ordered by the OrderDate. I’ve included amount, but it’s really not important.

    2016-06-06 13_38_55-Phone

    Now, if I do something like query for LAST_VALUE with a partition of CustomerID and ordered by OrderDate, I get this set. The partition divides the set up into the two customer sets. Without an ORDER BY, these sets would exist as the red set and blue set, but in no particular order. The ORDER BY functions as it does in any query, guaranteeing the same order every time.

    2016-06-06 13_46_36-Movies & TV

    Now, let’s look at the framing of the partition. I have a few choices, but at any point, I have the current row. So my processing looks like this, with the arrow representing the current row.

    2016-06-06 13_49_22-Movies & TV

    The next row is this one:

    2016-06-06 13_49_33-Movies & TV

    Then this one (the last one for this customer)

    2016-06-06 13_49_44-Movies & TV

    Then we move to the next customer.

    2016-06-06 13_49_54-Movies & TV

    When I look at any row, if I use “current row” in my framing, then I’m looking at, and including, the current row. The rest of my frame depends on what else I have. I could have UNBOUNDED PRECEEDING and UNBOUNDED FOLLOWING in there.

    If I used UNBOUNDED PRECEEDING and CURRENT ROW, I’d have this frame, in green, for the first row. It’s slightly offset to show the difference.

    2016-06-06 13_53_22-Movies & TV

    However, if I had CURRENT ROW and UNBOUNDED FOLLOWING, I’d have this frame (in green).

    2016-06-06 13_54_21-Movies & TV

    In this last case, the frame is the entire partition.

    What’s the last value? In the first case, the last part of that frame is the current SalesOrderID (43793). That’s the only row in the frame. In the second frame, the last one is 57418, the last row in the frame, and partition.

    What if we move to the next row? Let’s look at both frames. First, UNBOUNDED PRECEEDING and CURRENT ROW.

    2016-06-06 13_56_16-Movies & TV

    Now the frame is the first two rows. In this case, the last value is again the current row (51522). Below, we switch to CURRENT ROW and UNBOUNDED FOLLOWING.

    2016-06-06 13_56_29-Movies & TV

    Now the frame is just the last two rows of the partition and the last value is the same (57418).

    There’s a lot more to the window functions, and I certainly would recommend either Kathi’s book (Expert T-SQL Window Functions in SQL Server) or Itzik’s book (Microsoft SQL Server 2012 High-Performance T-SQL Using Window Functions). Either one will help. We’ve also got some good articles at SQLServerCentral on windowing functions.