Tag: T-SQL

  • Solving Ken’s FizzBuzz 3D–#SQLNewBlogger

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

    I like the FizzBuzz test. It’s cute and fun, and I’ve had my kids solve it, just to think about what it means to structure a simple problem. It’s not a great test of whether you’re a good programmer, but if you can’t solve this, you probably aren’t.

    Ken Fisher set up a challenge to solve FizzBuzz with SQL, but in a 3D manner. He added a few challenge to this, like no modulus operator. I don’t know why you’d deliberately avoid using this, but it’s a programming exercise, so why not. I took the challenges because, well, Ken asked me do.

    SQLNewBlogger

    I’m putting this part first, because this is a good exercise, and a great post for your blog. Solve this yourself first, and write about it. Then you can read my code.

    The coding part of this probably took me 20-25 minutes to do. I worked on it in a few stages, pausing to do other work and let the solution simmer a bit. I realized a few times that I was making it harder, so the breaks were helpful.

    Writing this up was about 30 minutes, but it made me think about what I’d done.

    Building a Solution

    My first thought with this is I need a tally table. Of course, I’m building a set of coordinates from 1 to 100. I started here.

    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)
    )
    , cteCoordinates (x, y, z)
    -- SQL Prompt formatting on
    AS
    (
    SELECT a.n ,
            b.n ,
            c.n
    FROM myTally AS a
         CROSS JOIN myTally AS b
         CROSS JOIN myTally AS c
    )
    SELECT x, y, z
    FROM cteCoordinates;

    This gave me my list of numbers.

    2018-07-02 14_40_48-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    My next step was to think about the FizzBuzz test. I can’t use Modulo, so how can I determine if I am evenly divisible by 3? I decided to look at the simple division operator. I got these results, but notice anything?

    2018-07-02 14_42_55-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    I get a new number every time the division changes. Immediately I think of window functions here. So I decided to do some checking here. Since I’m looking for a change, I went with a LAG function.

    If the LAG isn’t equal to the current value, I’ve had a change. Therefore, a Fizz.

    2018-07-02 14_45_44-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    Let’s clean this up to show Fizz and add the Buzz with a 5.

    2018-07-02 14_47_27-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    I’m getting there, but what about FizzBuzz? Well, the CASE will execute in order, so let’s add that one at the top.

    2018-07-02 14_49_00-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    I’ve mostly solved this, so let’s put this in my query. I’ll substitute the CASE in for each item of the cross join. I get this:

    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)
    ),
    cteCoordinates (x, y, z)
    AS
    ( SELECT
    CASE
         WHEN ( a.n/3 != LAG(a.n/3, 1, 0) OVER (ORDER BY a.n)
           AND  a.n/5 != LAG(a.n/5, 1, 0) OVER (ORDER BY a.n)
             )THEN 'FizzBuzz'
         WHEN a.n/3 != LAG(a.n/3, 1, 0) OVER (ORDER BY a.n) THEN 'Fizz'
         WHEN a.n/5 != LAG(a.n/5, 1, 0) OVER (ORDER BY a.n) THEN 'Buzz'
         ELSE CAST(a.n AS VARCHAR(8)) END,
    case WHEN ( b.n/3 != LAG(b.n/3, 1, 0) OVER (ORDER BY b.n)
           AND   b.n/5 != LAG(b.n/5, 1, 0) OVER (ORDER BY b.n)
             )THEN 'FizzBuzz'
         WHEN b.n/3 != LAG(b.n/3, 1, 0) OVER (ORDER BY b.n) THEN 'Fizz'
         WHEN b.n/5 != LAG(b.n/5, 1, 0) OVER (ORDER BY b.n) THEN 'Buzz'
         ELSE CAST(b.n AS VARCHAR(8)) END,
    case WHEN ( c.n/3 != LAG(c.n/3, 1, 0) OVER (ORDER BY c.n)
           AND  c.n/5 != LAG(c.n/5, 1, 0) OVER (ORDER BY c.n)
             )THEN 'FizzBuzz'
         WHEN c.n/3 != LAG(c.n/3, 1, 0) OVER (ORDER BY c.n) THEN 'Fizz'
         WHEN c.n/5 != LAG(c.n/5, 1, 0) OVER (ORDER BY c.n) THEN 'Buzz'
         ELSE CAST(c.n AS VARCHAR(8)) END
        FROM mytally a
        CROSS JOIN mytally b
        CROSS JOIN mytally c
    )
    SELECT *
      FROM cteCoordinates
      ORDER BY cteCoordinates.x, cteCoordinates.y, cteCoordinates.z

    That doesn’t seem to work. Even forgetting the ordering, I have a mess.

    2018-07-02 14_58_53-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    At this point I took a break. Clearly I’m overlooking something in my five minutes of work.

    Debugging

    Let’s move the items around and get some ideas here. I’ll get the actual numbers and then the FizzBuzz results. When I print the coordinate values along with the decodes, I see this.

    2018-07-02 15_04_06-SQLQuery4.sql - (local)_SQL2016.sandbox (vstsbuild (53))_ - Microsoft SQL Server

    What’s my error? I’m not thinking of LAG() correctly. This is by row, and the lagging for the x and y coordinates (the first two columns), aren’t changing at the same rate.

    Aha.

    Let’s change this. I’ll setup a simpler CTE first, one that takes the values from 1-100 and returns the value as well as the FizzBuzz calculation. This gives me this code:

    WITH myTally (n)
    AS
    (
    SELECT n = ROW_NUMBER() OVER (ORDER BY (SELECT NULL))
    FROM
    -- SQL Prompt formatting off
    (    VALUES (1) ,(2) ,(3) ,(4) ,(5) ,(6) ,(7) ,(8) ,(9) ,(10)) AS a (n)
         CROSS JOIN    ( VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10) ) AS b (n)
         CROSS JOIN    ( VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10) ) AS c (n)
    -- SQL Prompt formatting on
    ) ,
          cteFizzBuzz (x, n)
    AS
    (
    SELECT CASE
                WHEN (n / 3 != LAG(n / 3, 1, 0) OVER (ORDER BY n)) AND (n / 5 != LAG(n / 5, 1, 0) OVER (ORDER BY n))
                   THEN 'FizzBuzz'
                WHEN n / 3 != LAG(n / 3, 1, 0) OVER (ORDER BY n) THEN
                    'Fizz'
                WHEN n / 5 != LAG(n / 5, 1, 0) OVER (ORDER BY n) THEN
                    'Buzz'
                ELSE
                    CAST(n AS VARCHAR(8))
            END, n
    FROM myTally
    ) ,    cteCoordinates (x, y, z)

    Now that I have this, let’s build the coordinates now. I’ll use the first set of code above as an example, and cross join the cteFizzBuzz with itself. I’ll make this the third CTE.

    ) ,    cteFinal (x, y, z, a, b, c)
    AS
    (
    SELECT a.n, b.n, c.n, a.x, b.x, c.x
    FROM           cteFizzBuzz AS a
         CROSS JOIN cteFizzBuzz AS b
         CROSS JOIN cteFizzBuzz AS c
    )

    Note that I’m returning the original values (for sorting) and the calculated FizzBuzz values, which are characters. If I didn’t care about this, I could ignore the first few columns.

    In my outer query, I display the words, but order by the numbers.

    SELECT 
        c.a, c.b, c.c
      FROM cteFinal c
      ORDER BY c.x, c.y, c.z

    This gives me the answer (scrolled down to show a few cases).

    2018-07-02 15_12_18-SQLQuery5.sql - (local)_SQL2016.sandbox (vstsbuild (54))_ - Microsoft SQL Server

    On my machine, this takes about 4s to run. Not bad, and probably not optimal. The query plan is a mess, but this is for fun in a quick run, so let’s leave this  for now.

    2018-07-02 15_14_08-SQLQuery5.sql - (local)_SQL2016.sandbox (vstsbuild (54))_ - Microsoft SQL Server

    There are likely better solutions. I’m not an optimization guy out of the box. I get things done first, then I’ll go back and evaluate some time to tune things. In this case, adding in 3 more values (to 300) takes 1:58s. Going to 500 rows caused an SSMS out of memory error.

    If I send the results to a temp table, things are better:

    • 100x100x100 (1,000,000 rows) – 00:01
    • 300x300x300 (27,000,000 rows) – 00:06
    • 500 x 500 x 500 (125,000,000 rows)  – 00:29
    • 1000x1000x1000 (1,000,000,000 rows) – 6:48

    Note, don’t send results to the client if you don’t need to.

  • Finding Inconsistent Key Values–#SQLNewBlogger

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

    I was reading Iris Classon’s blog recently and ran across a post on her day at work. I think it’s a fantastic post that does help younger people understand what a job is like. I’m looking forward to seeing more.

    One quick thing struck me in the post, which was that she has clients that are missing key value settings in a table. I’ve dealt with this, and want to write more, but a quick post on just finding out that there are missing settings.

    Finding the Data Inconsistencies

    Each client should have a set of key value pairs in a settings table. However, because of application problems, not every client does. In the sample data (below), there are 8 clients, each of which should have 5 values in the GlobalSettings table. However, there are only 20 rows in this table when there should be 40.

    This is the type of issue I’ve had occur in an application, especially one that evolves over time. We add a key-value item to the application, which new clients get, but older ones are never populated. This is the same type of issue I saw in a post by Iris Classon.

    How can I find the items that don’t match? One simple way is to count the values, but include a HAVING clause to limit the results. If I want to see who has all the values, I can do this:

    SELECT gs.ClientID, COUNT(*)
    FROM dbo.GlobalSetting AS gs
    GROUP BY gs.ClientID
      HAVING COUNT(*) = 5

    In my sample data, this returns one row, for Client 1. If I change the HAVING clause to < 5, I get the other seven rows.

    2018-06-29 14_46_00-SQLQuery1.sql - (local)_SQL2016.sandbox (vstsbuild (56))_ - Microsoft SQL Server

    There are other considerations here, and this isn’t the best way that you might ensure you have the values. I might have a table, or a derived table that ensures I’m checking the right 5 values.

    I’ve written more about this in an article at SQLServerCentral.

    The Setup

    I built a couple quick tables and added data with this script. Note that there are 8 clients, and that each has a series of settings in a table. There are 5 possible settings (Position, Height, Weight, Number, College)

    CREATE TABLE Client
    (ClientKey INT IDENTITY (1,1) NOT NULL CONSTRAINT ClientPK PRIMARY KEY 
    , ClientName VARCHAR(200)
    , ClientStatus TINYINT)
    go
    CREATE TABLE GlobalSetting
    ( GlobalSettingKey INT IDENTITY (1,1) NOT NULL CONSTRAINT GlobalSettingPK PRIMARY KEY 
    , ClientID INT NOT NULL CONSTRAINT GlobalSettingFK_Client_ClientID FOREIGN KEY REFERENCES Client
    , GlobalSettingName VARCHAR(100)
    , GlobalSettingValue VARCHAR(500)
    )
    GO
    INSERT client VALUES ('Shaquil', 1), ('Von', 1), ('Bradley', 1), ('Shane', 2), ('Todd', 2), ('Jerrol', 3), ('Jeff', 3), ('Josey', 3)
    
     Position, College, Height, weight, number
    INSERT dbo.GlobalSetting
    (
        ClientID ,
        GlobalSettingName ,
        GlobalSettingValue
    )
    VALUES
      (1, 'Position', 'OLB')
    , (1, 'Weight', '250'),
    (1, 'College', 'CSU') , (1, 'Height', '74') , (1, 'Number', '48') , (2, 'Weight', '250') , (2, 'Number', '58') , (2, 'College', 'Texas A&M') , (3, 'Height', '76') , (3, 'Weight', '269') , (4, 'Position', 'OLB') , (4, 'College', 'Missouri') , (4, 'Height', '75') , (5, 'Weight', '230') , (5, 'Number', '51') , (5, 'College', 'Sacramento St') , (6, 'Weight', '235') , (6, 'Position', 'LB') , (7, 'Weight', '249') , (8, 'Number', '47')

    SQLNewBlogger

    This only took about 10 minutes to write, though I had to build the tables and data. Of these, the data took the longest, because I had to look up the values Winking smile.

    This is a basic example of checking data in a business situation. I might write about this in my job, perhaps showing a daily integrity check or a custom metric that I use to ensure the system is working. You can do the same thing and ensure that your data is correct.

  • Getting All Users

    I saw someone that wanted to get all users from all databases on their instance. Seems like that ought to be simple, right?

    The user wanted to use sp_MSforeachdb to query users, but wanted a single result set for all databases.  Why you need this, I’m not sure. I guess some auditing report. Or maybe looking to clean up and remove unnecessary users?

    In any case, this turned out to be easy with a caveat. It’s not in one single statement..

    DECLARE @MyUsers TABLE ( dbname VARCHAR(200), principalname VARCHAR(200), principalSID VARBINARY(MAX))
      INSERT @MyUsers
    EXEC sp_MSforeachdb 'select ''?'', name, sid from [?].sys.sysusers'

    SELECT top 1000
      *
      FROM @myusers

    Use the ? in the query to get the users from every db. Then store this in a table that you can pull back all the data.

    This worked for me, though since I have some database names with a hypen (-) in them, I needed the brackets to get this to work.

  • Defining FKs in CREATE TABLE–#SQLNewBlogger

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

    One of the important things that a database developer can do is to define Foreign Keys (FK) at table creation. This is a good time to do this as the referential integrity gets setup before any data is added and this can prevent issues later.

    This post shows the syntax for defining the FKs and adding them to your tables immediately.

    Build the Reference

    The first step is to ensure you have a table with a Primary Key (PK) that will be referenced. Let’s do that first. I’ve been looking to provide a database of SQL Server Builds, so let’s start with a table of versions.

    CREATE TABLE SQLServerVersion
    ( SQLServerVersionKey INT IDENTITY(1,1)
    , VersionName VARCHAR(200)
    , CONSTRAINT SQLServerVersionPK PRIMARY KEY (SQLServerVersionKey)
    )
    GO

    The PK in this table is what is referenced in the next table. This is required as a FK must refer to a PK in another table.

    Add the Reference

    When you build a child table, you may write the code like this:

    CREATE TABLE [dbo].[SQLServerBuilds]
    (
    [BuildKey] [int] NOT NULL IDENTITY(1, 1),
    [BuildNumber] [varchar] (30)  NULL,
    [BuildDescription] [varchar] (100)  NULL,
    [BuildKBArticleNumber] [varchar] (50) NULL,
    [BuildKBArticleURL] [varchar] (1000)  NULL,
    [SQLServerVersionKey] [int]  NULL
    ) ON [PRIMARY]
    GO

    However, in this case, you’ve ignored the FK that might link this table to the versions table. This means that a value could be entered in this table that doesn’t exist in the SQLServerVersion table.

    You might think this won’t happen with your application, but thousands, maybe millions, of developers have felt the same way. And they have junk data in their databases because of this.

    If there is a strong relationship, add the FK.

    Here’s how we do that in the CREATE TABLE statement. I’ll add a comma at the end and include a CONSTRAINT clause. I add the name and then the FOREIGN KEY keywords. Next I include the column from this table that is the FK with the References and the other table and column.

    CREATE TABLE dbo.SQLServerBuild
    (
         BuildKey INT NOT NULL IDENTITY(1, 1) ,
         BuildNumber VARCHAR(30) NULL ,
         BuildDescription VARCHAR(100) NULL ,
         BuildKBArticleNumber VARCHAR(50) NULL ,
         BuildKBArticleURL VARCHAR(1000) NULL ,
         SQLServerVersionKey INT NULL ,
         CONSTRAINT SQLServerBuild_Version_FK
             FOREIGN KEY (SQLServerVersionKey)
             REFERENCES dbo.SQLServerVersion (SQLServerVersionKey)
    ) ON [PRIMARY];
    GO

    This is the structure I tend to use, though sometimes I’ll move the CONSTRAINT clause directly below the actual column. This lets me see right away this is related to that column.

    I also avoid using this inline in the column as I can’t specify the constraint name, which I always want to do.

    SQLNewBlogger

    This is a core skill that database developers needed. If you know the syntax, this post would take about 10  minutes to structure and write. If not, maybe it’s 10 more to learn a bit.. Write your own and show you understand the design concepts.

    Reference

    Creating Foreign Key Relationships – https://docs.microsoft.com/en-us/sql/relational-databases/tables/create-foreign-key-relationships?view=sql-server-2017