Tag: T-SQL

  • A Basic Recursive CTE and a Money Lesson

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

    When I was a six or seven year old, my Mom asked me a question. She asked if I’d rather have $1,000,000 at the end of the month, or a penny on day 1, with the note that each day of the month, she’d double what I’d gotten the first day. Doing quick math in my head, $0.01, $0.02, $0.04, etc, I said a million.

    Was I right? Let’s build a recursive CTE.

    Recursion is an interesting computer science technique that stumps lots of people. When I was learning programming, it seemed that recursion (in Pascal) and pointers (in C), were the weed out topics.

    However, they aren’t that bad, and with CTEs, we can write recursion in T-SQL. I won’t cover where this might be used in this post, though I will give you a simple CTE to view.

    There are two parts you need: the anchor and the recursive member. These are connected with a UNION ALL. There can be multiple items, but we’ll keep things simple.

    I want to first build an anchor, which is the base for my query. In my case, I want to start with the day of the month, which I’ll represent with a [d]. I also need the amount to be paid that day, which is represented with [v]. I’ll include the $1,000,000 as a scalar at the end. My anchor looks like this:

    WITH myWealth ( d, v)
    AS (

    — anchor, day 1
    SELECT
    ‘d’ = 1
    , ‘v’ = CAST( 0.01 AS numeric(38,2))
    UNION ALL

    Now I need to add in the recursive part. In this part, I’ll query the CTE itself, calling myWealth as part of the code. For my query, I want to increment the day by 1 with each call, so I’ll add one to that value.

    SELECT
    myWealth.d + 1

    For the payment that day, it’s a simple doubling of the previous day. So I can do this a few days: addition or multiplication. I’ll use multiplication since it’s easier to read.

    SELECT
    myWealth.d + 1
    , myWealth.v * 2

    My FROM clause is the CTE itself. However I need a way to stop the recursion. In my case, I want to stop after 31 days. So I’ll add that.

    UPDATE: The original code (<= 31) went to 32 days. This has been corrected to stop at 31 days.

    FROM
    myWealth
    WHERE
    myWealth.d <= 30

    Now let’s see it all together, with a little fun at the end for the outer query.

    WITH  myWealth ( d, v )
    AS (
    — anchor, day 1)
    SELECT
    ‘d’ = 1
    , ‘v’ = CAST(0.01 AS NUMERIC(38, 2))
    UNION ALL
    — recursive part, get double the next value, end at one month
    SELECT
    myWealth.d + 1
    , myWealth.v * 2
    FROM
    myWealth
    WHERE
    myWealth.d <= 31
    )
    SELECT
    ‘day’ = myWealth.d
    , ‘payment’ = myWealth.v
    , ‘lump sum’ = 1000000
    , ‘decision’ = CASE WHEN myWealth.v < 1000000 THEN ‘Good Decision’
    ELSE ‘Bad decision’
    END
    FROM
    myWealth;

    When I run this, I get some results:

    2016-05-17 18_48_04-Start

    Did I make a good choice? Let’s look for the last few days of the month.

    2016-05-17 18_48_16-Start

    That $1,000,000 isn’t looking too good. If I added a running total, it would be worse.

    SQLNewBlogger

    If you want to try this yourself, add the running total and explain how it works.

  • Changing a Computed Column–#SQLNewBlogger

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

    I was working with a computed column the other day, and realized I had the wrong definition. In this case, I was performing some large calculation, and the result was larger than an int. However the first part of the formula was an int, which resulted in an implicit conversion to an int.

    I needed to change the formula, and then realized that plenty of people might not work with computed columns much, and not realize how you alter a computed column.

    You don’t.

    In fact, you need to drop the column and add it back. In my case, this was what I did. Here was my table:

    CREATE TABLE SiteStats
    (
    StatID INT IDENTITY(1,1) PRIMARY KEY NONCLUSTERED
    , StateDate DATE DEFAULT SYSDATETIME()
    , StatMonth TINYINT
    , StatYear int
    , PageVisits INT
    , TimeOnSite TIME
    , Engagement AS (PageVisits * DATEDIFF(SECOND, CAST(’00:00:00′ AS TIME), TimeOnSite))
    )

    I wanted to cast the PageVisits part of the column to a bigint to solve the issue. I first needed to do this:

    ALTER TABLE dbo.SiteStats
    DROP COLUMN Engagement

    Once that’s done, I can do this:

    ALTER TABLE dbo.SiteStats
      ADD Engagement AS (CAST(PageVisits AS BIGINT) * DATEDIFF(SECOND, CAST(’00:00:00′ AS TIME), TimeOnSite));
    GO

    Now I have a new definition that works great.

    Some of you might realize that this could be an issue with columns in the middle of the table, and it is. However you shouldn’t worry about column order. Select the columns explicitly and you can order them anyway you want.

    SQLNewBlogger

    A quick post, five minutes. Even if you had to search for how this works, you could do this in 10-15 minutes, tops. Research, write why you did this and potential issues with your system.

  • Am I a sysadmin?–#SQLNewBlogger

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

    I was doing some security testing and wondered if I was a sysadmin. There are a few ways to check this, but I thought there should be a function to tell me.

    There’s this code, of course:

    SELECT
    ServerRole = rp.name,
    PrincipalName = SP.name
    FROM sys.server_role_members rm
    Inner JOIN sys.server_principals rp
    ON rm.role_principal_id = rp.principal_id
    Inner JOIN sys.server_principals SP
    ON rm.member_principal_id = SP.principal_id
    where sp.name = SUSER_SNAME()
    and rp.name = ‘sysadmin’

    That lets me know if my login is a sysadmin. However, there is a function that you can use. IS_SRVROLEMEMBER() is a function that you can use, passing in a server role as a parameter. The code I’d use to check on sysadmin membership is this:

    SELECT IS_SRVROLEMEMBER(‘sysadmin’);

    If I run this, I get a 1 if I’m a member, or a 0 if I’m not.

    2016-04-12 11_38_34-Settings

    Using this function in your code allows you to make decisions based on role membership for the users involved, and perhaps alert them of needs for certain rights.

    SQLNewBlogger

    This was a quick one, really about 10 minutes to organize and write. Most of the time was writing the code to join system tables. If you tackle this subject, talk about how you  might use this, or where this type of check could come in handy in your code (maybe before taking some action).

  • Explicitly using tempdb with ##tables

    I had someone ask me last night if this statement would create a permanent table in tempdb with a strange name or a global temp table:

    CREATE TABLE tempdb..##mytable
    ( id int
    );

    My suspicion was that this would always be a temp table, but since I usually don’t include tempdb, I decided to test things. I quickly opened up SSMS and tried it. I got this message:

    2016-04-21 13_55_14-Microsoft Solitaire Collection

    That seems to imply what I suspected. The ## override everything and determine the table type. When I look in the list of tables, I see my table there as a temporary one.

    2016-04-21 13_56_21-Start

    This is the behavior I’d expect, and it acts the same in SQL 2012, 2014, and 2016 (RC2).

    I don’t think there’s an advantage to using tempdb..##mytable, or even creating a permanent table (until restart) using tempdb.dbo.mytable, but if there is, please note something in the comments.