Tag: SQLNewBlogger

  • Using Framing for a Running Total–#SQLNewBlogger

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

    I’ve been writing about window functions, which I find very handy. In this post, I want to build a running total using the framing of the window. This continues from my last post on aggregates.

    I’ve been looking at baseball data. Imagine that someone wants to know how many total home runs a player had at each stage of his career. In this case, if a player hit 1, then 4, then 5 home runs, we’d expect a running total to show:

    • Year 1: 1
    • Year 2: 5
    • Year 3: 10

    This is a cumbersome query without window functions, and inefficient, as I really need a subtotal query for each of the main rows. It’s difficult to write, read, and it’s slow. With a window function, however, I can use this query. You can see the framing with the ROWS section in the OVER() clause.

    SELECT
              yearid
            , hr
            , SUM (b.hr) OVER (ORDER BY b.yearID
                               ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) AS TotalHR
    FROM     dbo.batting AS b
    WHERE    b.playerID = 'griffke02'
    ORDER BY b.yearID;

    This gives me results that look like this:

    2021-07-23 15_53_08-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    Simple and easy to see and read.

    This works as the window is scanned and totaled using the OVER() clause. In this case, the partition is the entire window, meaning all rows for this player. I’ve set the ordering to be the year, so as we move through the years, the SUM() is calculated using the part of the window that goes from the beginning, with the UNBOUNDED PRECEDING marker, until the current row.

    You can think of this as we scan from year 1989 first. In this case, the entire preceding section is nothing, and the current row is 1989. Therefore the sum is 16.

    Next we look at year 1990. There is a preceding row (1989) and this current row. We sum those to get 16+22=38. We repeat this with each row, always going back to the first row.

    For the ROWS clause, we can use the between to determine the start and end portion of the partition that we scan. This means we can use:

    • unbounded preceding
    • unbounded following
    • current row
    • an integer

    We can combine these 4 choices to get what we need. If we were looking only for a best 3 year time frame of home runs, we could get a sum like this:

    SELECT
              yearid
            , hr
            , SUM (b.hr) OVER (ORDER BY b.yearID
                               ROWS BETWEEN 1 PRECEDING AND 1 FOLLOWING) AS TotalHR
    FROM     dbo.batting AS b
    WHERE    b.playerID = 'griffke02'
    ORDER BY b.yearID;

    This gives me a grouping of the SUM for the current 1, as well as 1 before and after, for each row.  My results are shown below. For the first year, there is no preceding row, so we sum the current and next row, 16+22 for 38. For the second row, we have 16 preceding, 22 current, and 22 next, which sum to 60. You can check the math for others.

    2021-07-23 16_01_12-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    There’s more to this, but for now, a quick running total is using SUM() and then the ROWS BETWEEN UNBOUNDED PRECEDING and CURRENT ROW clause.

    SQLNewBlogger

    This was a quick 15 minutes to write. I took part of what I’d done in the last post, changed the query quickly, and then started to explain part of a clause. I’ll keep this around and use it to expand on some other places where the framing can be useful and affect how I work with data.

    A good chance for you to also show how you might build a running total, or even running count, with your own data.

  • Using Aggregates in Calculations with Other Columns Functions–#SQLNewBlogger

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

    In the last post on Window functions, I looked at ROW_NUMBER, and how I can use this to order rows. In this one, I want to look at one of the advantages of Window functions in trying to combine data and aggregates together.

    A Scenario

    In the last post, I examined the career of Ken Griffey, Jr., showing his home runs with some ordering. That wasn’t a very realistic case of using data, so let’s look at another one. Suppose I want to know the percentage of his career home runs he hit during each one of his seasons. That’s an interesting question, showing some idea of how much he improved or declined. If I try a to start combining a “normal” aggregate with other data, I can’t do it without a GROUP BY.

    2021-07-19 15_27_35-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    Not a huge big deal, as I can put the sum in a CTE and use it later. Here’s the code:

    WITH cteHR (PlayerID, TotalHR)
    AS (   SELECT
                     b.playerID
                   , SUM (hr)
            FROM     dbo.batting AS b
            WHERE    b.playerID = 'griffke02'
            GROUP BY b.playerID)
    SELECT
                    b.yearID
                  , b.teamID
                  , hr
                  , TotalHR
                  , ROUND((hr * 1.0) / TotalHR * 100, 2) AS percentofCareer
    FROM
                    dbo.batting AS b
         INNER JOIN cteHR
             ON cteHR.PlayerID = b.playerID
    WHERE          b.playerID = 'griffke02'
    ORDER BY       b.yearID;

    And the results.

    2021-07-19 15_30_52-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    That’s OK, but the code is complex, and if I wanted to break the home runs by team or some other group, it would get more complex quickly.

    Window Functions Simplify Things

    Here’s a simpler query. I’ve just added the SUM with an OVER() clause, without any order. I just want all rows summed. I added this to the query to see the value.

    SELECT
              b.yearID
            , TeamID
            , HR
            , SUM (b.hr) OVER (ORDER BY (SELECT NULL)) AS TotalHR
            , ROUND ((b.HR * 1.0) / SUM (b.hr) OVER (ORDER BY (SELECT NULL))  * 100, 2) AS TeamPercentofCareer
    FROM     dbo.batting AS b
    WHERE    b.playerID = 'griffke02'
    ORDER BY b.yearID;

    The results are the same as the other query, but it’s easy to see.

    What if I wanted to change this and order this by the highest percentage years of his career. In other words, when was he the most productive. I can easily add an ORDER BY to both queries to see this, but I lose some context.

    Look at these results. How do I know if 1997 was closed to the beginning or end of his career?

    2021-07-19 15_35_33-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    I want to add some context with the span of his career. I can do that easily with a few more Window functions. Here’s the result I want, with the years showing his career first. I moved some of the other data to the end.

    2021-07-19 15_38_57-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    Without window functions, this would be complex, as the MIN() and MAX() would be from different columns, so I’d need another CTE. Here, I can use this code:

    SELECT
            CAST(MIN (b.yearID) OVER (ORDER BY (SELECT NULL)) AS CHAR(4)) 
            + '-'
            + CAST(MAX (b.yearID) OVER (ORDER BY (SELECT NULL)) AS CHAR(4)) AS CareerSpan
            , b.yearID
            , ROUND ((b.HR * 1.0) / SUM (b.hr) OVER (ORDER BY (SELECT NULL))  * 100, 2) AS TeamPercentofCareer
            , TeamID
            , HR
            , SUM (b.hr) OVER (ORDER BY (SELECT NULL)) AS TotalHR
    FROM     dbo.batting AS b
    WHERE    b.playerID = 'griffke02'
    ORDER BY TeamPercentofCareer desc;

    If I wanted to add some math, like how many years into his career was he, I could easily do that. Here I’ve added the year number to his career, which comes from ROW_NUMBER().

    2021-07-19 15_42_31-window_queries.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (57))_ - Microsoft

    The code? I just add the aggregates I need, which in this case are ones containing the entire set. I mix MIN(), MAX(), COUNT() and ROW_NUMBER(), and use a partition of the entire data set.

    SELECT
            CAST(MIN (b.yearID) OVER (ORDER BY (SELECT NULL)) AS CHAR(4)) 
            + '-'
            + CAST(MAX (b.yearID) OVER (ORDER BY (SELECT NULL)) AS CHAR(4)) AS CareerSpan
            , b.yearID
            , ROUND ((b.HR * 1.0) / SUM (b.hr) OVER (ORDER BY (SELECT NULL))  * 100, 2) AS TeamPercentofCareer
            , RTRIM(CAST(ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS CHAR(2)) )
            + ' of ' 
            + CAST( COUNT(yearid) OVER(ORDER BY (SELECT NULL)) AS CHAR(2))
            AS YearInCareer
            , TeamID
            , HR
            , SUM (b.hr) OVER (ORDER BY (SELECT NULL)) AS TotalHR
    FROM     dbo.batting AS b
    WHERE    b.playerID = 'griffke02'
    ORDER BY TeamPercentofCareer desc;

    Try doing that without window functions. It’s a nightmare to write in T-SQL.

    SQLNewBlogger

    This was pretty easy to write. The hard part was thinking of the questions I might ask of this data set and setting up the queries. Duplicating this without window functions was fun, and took more time. But it was good practice for me, and helped me to better understand why I like window functions.

    This took me about 30 minutes, and it’s a good showcase of learning a new technique and applying it. You should do this if writing reports and aggregates is part of your job and you might want to showcase this to your next potential employer.

  • Getting Row Numbers with Window Functions–#SQLNewBlogger

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

    In a recent post, I started looking at some basics for window functions. This post continues with a look at one of the most commonly used ones: row_number().

    Rows in a table aren’t in any particular order. They can be physically stored in order by the clustered index, but in a SELECT, there is no guarantee of any particular order unless you have an ORDER BY clause. However, even when you get a set or rows, there isn’t any number for the rows that is given.

    Many of us would like to have some number that allows us to know this is row 1, row 2, etc.

    We can do that with ROW_NUMBER(), which is a window function that assigns sequential numbers to rows. In the previous post, I used some baseball data, so I’ll continue with that today, but I’ll use another amazing batter, Ken Griffey Jr.

    If I just get the list of batting records for Ken, I see this results (abbreviated) below. Note that there is no ordering I can count on here. The row number to the right is added by SSMS, but isn’t in the result set:

    2021-07-13 11_34_04-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    If I want to ensure every client has a row number, I can use that function with an OVER() clause. Note that I need to include something in the OVER() clause.

    2021-07-13 11_35_33-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    Let’s fix that. I’ll order by year and then ensure I show the SSMS number added by the GUI. I see the numbers seem to correspond to the years. What if I order the entire query by team?

    2021-07-13 11_38_32-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    This appear to have reversed the numbers. However, note that rows 11 and 12 are the 22 and 23. The window function applied the numbers based on the ordering of years for the entire set, then the rows were re-ordered for the query based on the ORDER BY. We see this more clearly with an ORDER BY using the HR column.

    2021-07-13 11_41_59-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    What about just an ordering for the results? I do need an ORDER BY I can use this trick.

    SELECT   TOP 100
              ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS Rowsetnumber
              , teamid
              , yearID
              , HR
    FROM     batting
    WHERE    playerID = 'griffke02'
    ORDER BY hr

    This allows me to just apply the ROW_NUMBER to whatever the query is doing. Here’s the result, ordered by HR.

    2021-07-13 11_45_14-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    If I go back to my ordering by team, I see this:

    2021-07-13 11_46_48-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    I can also add a PARTITION BY, and get numbering inside the partition or group. Here I’ll partition by team. I’ll go back to ordering by year, since that makes sense. I’ll use this query.

    SELECT   TOP 100
              ROW_NUMBER() OVER(PARTITION BY teamID ORDER BY (SELECT NULL)) AS Rowsetnumber
              , teamid
              , yearID
              , HR
    FROM     batting
    WHERE    playerID = 'griffke02'
    ORDER BY yearID

    The results are then shown with the numbering restarting with each team.

    2021-07-13 11_49_01-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    The one strange thing to note here is that since Ken went back to Seattle late in his career, the numbering for his final two years show a continuation of the numbers from earlier with SEA.

    2021-07-13 11_49_12-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (75))_ - Microsoft SQL S

    The ROW_NUMBER() function is very powerful and useful when you need some ranking and ordering to show to the client for the rows. As with all data, you need to ensure you understand the data set and be aware of how your partition (grouping) and ordering in the OVER() clause apply to the data, but the final results are dependent on the query’s ORDER BY. This can cause some confusion, so be sure you understand the difference and inform your clients.

    SQLNewBlogger

    This was a quick 10 minute post. I’ve done a lot of work with Window functions and presented on them, so this was a portion of a presentation I’d given, where I took part if a demo and wrote it up.

    However, you can experiment in 15-20 minutes and then spend 10-15 minutes structuring a post on this topic. How have you used ROW_NUMBER(), or if you’ve just learned it, what does it mean to you. Come up with some examples, ensure you understand them, and then explain them back. Might be an easy interview question to answer at some point if they find it on your blog.

  • Basic Window Functions–#SQLNewBlogger

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

    Window functions are a class of functions that dramatically improve the performance of certain types of queries. We often think of these as aggregates, but there are ranking and analytic functions that can be used as well.

    This is a basic post looking at the outline of these functions and their structure. I’ll look at a few more details in future posts.

    The OVER() Clause

    The idea in many of these functions is to create a window on which a function can operate and perform some computation over a row in the window. When I first heard of this, I wasn’t sure this was that powerful, but now I find these functions to be incredibly useful for many aggregates, especially because I can get things like a SUM() without requiring a GROUP BY.

    The main way we define the window is with the OVER() clause. This comes after the function and defines a partition and ordering for the rowset, one which the window is then applied. That sounds more complex than it really is, so let’s look at a simple example.

    I have a number of baseball statistics in a table. I’ll look at one player, Barry Bonds, and his career across a couple teams. If I look at the data, I see:

    2021-07-06 12_58_18-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (51))_ - Microsoft SQL S

    If I want to sum something like home runs, I can do that with the team easily like this:

    2021-07-06 13_00_35-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (51))_ - Microsoft SQL S

    I have a sum and a team, and a GROUP BY. What if I wanted to add something in there, like the at bats by year. Maybe I want to add some other text fields, which is easy to do, but every time I add a non-aggregate to the column list, I need to also add it to the GROUP BY. That’s a pain, and it’s cumbersome. Not only that, I’m limited to looking at the data in the same way. I might want an overall average number of at-bats, or maybe an average by team.

    With the OVER clause, I can have different views of the rowset as applied to the function. In this query below, I order one rowset, but not the other. I could have partitioned by different values if I wanted, but that doesn’t help here.

    2021-07-06 13_07_16-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (51))_ - Microsoft SQL S

    Here the average is calculated on the partition, not on the entire group, which is what I’d have before the OVER() clause. I could even add other detail data, like the league, and I wouldn’t have to alter multiple clauses.

    There are two key concepts here for the basic OVER clause that I want to discuss, and more details will come in other posts.

    Partition BY

    The partition by separates the rowset into groups, much like the GROUP BY does. In this case, we note that we want to break our data into groups based on the value of a column (or columns).

    This can be different for each OVER() clause, as you can see below. Now the average is teh same for all rows, as Mr. Bonds only played in one league.

    2021-07-06 13_12_26-SQLQuery1.sql - ARISTOTLE.BaseballData (ARISTOTLE_Steve (51))_ - Microsoft SQL S

    More details in a future post.

    Order By (Inside OVER)

    The ORDER BY is the ordering of the rows inside of the partition. In this case, the order doesn’t matter, but in some cases it might. With some functions it matters, so this can change the way your query works.

    This works just like the ORDER BY clause at the end of a SELECT query, but when inside the parenthesis, this applies only to the partitions.

    Summary

    This is a very basic look at Window functions. This is the basic view of what these are, and how they might be used in queries. There are more options, and more to know, but this is a basic look at how you might think about building aggregate queries without a GROUP BY, better performance, and in my mind, easier to read.

    SQLNewBlogger

    I had to write a query recently and chose an window function because it simplified the code. It also performed very well. This is a start of a few posts based on that work, showing that I have some knowledge on how to use these features in SQL.

    This post took about 10 minutes to write, mostly me structuring the example from a dataset I have, and then about 5 minutes to pull some things out of this post to focus on the basic part of building a query. I can expand on this in other posts.

    This is a pattern you can easily follow in your own blog to show that you understand some concept in T-SQL, in Azure, or really any topic. Enough of these and they’ll help drive your interview to subjects you know something about.