Tag: SQLNewBlogger

  • 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.

  • When were statistics updated?–#SQLNewBlogger

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

    I ran across the STATS_DATE function recently, and it’s one that I hadn’t used in production code. I’m not sure how this escaped me, as it was added in SQL Server 2008, but I rarely see it written about, so it’s not just me.

    This function takes an object_id and a stats_id, and returns the date the statistics were last updated. The statistics id is the id from sys.stats and doesn’t necessarily correspond to the index ID.

    As a quick example, if you look at the Sales.SalesOrderHeader table in AdventureWorks2012, you can run this:

    SELECT STATS_DATE ( 1266103551 , 2) 

    This should return a simple date. I don’t know if you’ll have the same date in your database, but I assume this is the default date for the sample database.

    2016-06-06 14_07_56-Phone

    Obviously these stats are out of date.

    Or are they? I don’t use this database a lot and haven’t changed the data in this table that I’m aware of. In that case, they may be up to date.

    This can be a handy function, but remember, the age of stats only matters if you’ve had data changes. However with having an understanding of both pieces of information, you might use this to accelerate statistics rebuilds ahead of what AUTO STATISTICS might do.

    SQLNewBlogger

    This was a good chance to dig into and look at how a function works in SQL and how I might use it. You could write this easily.

  • OBJECT_ID()–#SQLNewBlogger

     

    One of the things that is needed in quite a few functions is the object_id of a particular table/view/procedure/function in SQL Server. For example, I was looking at STATS_DATE recently, and it has this definition.

    STATS_DATE (object_id, stats_id)

    In the past, I’d run something like this:

    DECLARE @i INT
    SELECT @i = object_id
     FROM sys.objects 
     WHERE name = 'SalesOrderHeader'
    SELECT STATS_DATE ( @i , 2)

    Actually, I’d really do this as two batches.

    SELECT * FROM sys.objects WHERE name = 'SalesOrderHeader'
    SELECT STATS_DATE ( 1266103551 , 2)  
    

    I’d run the first, get the ID, and paste it into the second. However I’ve learned that isn’t the best way to do this. In fact, when I started doing  a lot of encryption testing and research, I started to take advantage of functions like OBJECT_ID.

    Now, here’s what I’d do:

    SELECT STATS_DATE ( OBJECT_ID(‘Sales.SalesOrderHeader’) , 2) 

    Simple, easy, and I can do this inline. With SQL Prompt, I’m also pretty quick getting this out. Of course, I do need to remember to include the schema, because this won’t work:

    SELECT STATS_DATE ( OBJECT_ID(‘SalesOrderHeader’) , 2) 

    Three warnings. First, qualify your objects. In this case, I should have used Sales.SalesOrderHeader to be sure I get the correct object. There are people that use schemas with the same object in multiple schemas (etl.SalesOrderHeader, audit.SalesOrderHeaders, etc.).

    Second, the object_id() isn’t guaranteed to be unique across databases. I should have pointed that out.

    SQLNewBlogger

    When I find quick tricks or techniques I use often, I try to make a note and then write about them later. It helps me remember, but it also lets me share things with others.

    Perhaps most important, it shows I’m doing and learning things in my career. Winking smile