Category: Blog

  • SQL Prompt Gets Even Better

    SQL Prompt v9 came out recently, and just when you thought they couldn’t make it better, they found a way. If you click the menu for SQL Prompt, you’ll see a couple new items.

    2017-11-29 14_55_40-SQLQuery2.sql - (local)_SQL2016.AdventureWorks2014 (PLATO_Steve (66)) - Microsof

    Code Analysis!!! Finally, a first step towards some sort of better analysis of code. These are a set of rules from our SQL Code Guard acquisition and we’ve integrated these into Prompt. Now you’ll get some green squiggly’s to alert you to potential issues.

    For example, I get two in this short, poorly written code. The first item is a TOP without an ORDER BY, which usually isn’t a good thing.

    2017-11-29 14_59_07-SQLQuery2.sql - (local)_SQL2016.AdventureWorks2014 (PLATO_Steve (66))_ - Microso

    The second item is one of those that I would like to put in all CAPs for developers. No asterisks.

    2017-11-29 14_59_11-SQLQuery2.sql - (local)_SQL2016.AdventureWorks2014 (PLATO_Steve (66))_ - Microso

    Right now there are a set of static rules, but we are working to add more and allow you to customize these items. We’re also looking for feedback on how you might like to apply or surface these rules to your staff. Please, if you have comments or questions, ask at the Redgate Hub.

    For now, you can disable analysis, or disable selected rules. Click the Manage link, and you’ll see a list of rules, which you can turn off as needed.

    2017-11-29 14_55_51-Sql Prompt - Code analysis rules

    Static analysis of SQL code has a long way to go, but I am looking forward to seeing more improvements appear in SQL Prompt and our other products over time.

  • Testing a Birthday Month Query

    I ran across a post from a tester, wondering  how to write a query for birthday months. This makes sense, as I’m sure some businesses want to notify or track customers that have birthdays this month and give them something.

    The post was good, with DDL and test data, and ignoring the discussion about database design, how would you test htis? If you look through the test data, surely you might decide to check for Jan, or Feb, etc., but are you use as the query might change that you’re checking everything?

    This is where testing can help. I’d start with a simple test. One that uses some data and checks that nothing is returned.

    CREATE OR ALTER PROCEDURE tTSQLTests.[test birthday queries for a Jan with no birthdays in range]
    /*
    Description:
    
    Changes
    11/22/2017
    */
    AS
    BEGIN
         -------------
         -- Assemble
         -------------
         DECLARE @begin DATETIME = '1994-01-01' ,
                 @end DATETIME = '2000-01-01' ,
                 @month TINYINT = 1;
    
        EXEC tSQLt.FakeTable @TableName = 'birthdays', @SchemaName = 'dbo';
         INSERT dbo.birthdays
         (
             cust_id ,
             cust_fname ,
             cust_lname ,
             cust_dob
         )
         VALUES
         (94, N'Jamie', N'Aguiar', '2017-06-02 00:00:00.000') ,
         (346, N'Keith', N'Brady', '1993-03-29 00:00:00.000') ,
         (361, N'Kelsea', N'Britto', '1994-03-25 00:00:00.000') ,
         (715, N'Tia', N'Delguidice', '1999-02-04 00:00:00.000') ,
         (994, N'Holly', N'Hamilton', '2017-11-12 00:00:00.000') ,
         (1110, N'ISABELLE', N'HYDER', '1993-04-06 00:00:00.000') ,
         (1295, N'RAELYN', N'LITTLE', '1995-02-15 00:00:00.000') ,
         (1403, N'ALLISON', N'RIPA', '1993-10-14 00:00:00.000') ,
         (1486, N'Rayvon', N'Miller', '1984-11-09 00:00:00.000') ,
         (1559, N'Alexandra', N'Sousa', '1989-09-17 00:00:00.000') ,
         (1897, N'Patrick', N'Snow', '1976-10-10 00:00:00.000') ,
         (1749, N'Justine', N'Zienowicz', '1998-03-12 00:00:00.000'),
         (2209,N'Brittany',N'Kosboski','1987-01-22 00:00:00.000')
         ;
    
        CREATE TABLE #Expected
         (
             cust_id INTEGER NOT NULL PRIMARY KEY ,
             cust_fname NVARCHAR(50) NOT NULL ,
             cust_lname NVARCHAR(50) NOT NULL ,
             cust_dob DATETIME NOT NULL
         );
    
    
    
    
        SELECT TOP 0
             cust_id ,
             cust_fname ,
             cust_lname ,
             cust_dob
         INTO #Actual
         FROM #Expected;
    
        ---------------
         -- Act
         ---------------
         INSERT #Actual
         SELECT cust_id ,
                cust_fname ,
                cust_lname ,
                cust_dob
         FROM birthdays
         WHERE cust_dob >= @begin
               AND cust_dob < @end
               AND MONTH(cust_dob) = @month;
    
        ---------------
         -- Assert    
         ---------------
         EXEC tSQLt.AssertEqualsTable @Expected = N'#expected' ,
                                      @Actual = N'#actual' ,
                                      @Message = N'The query doesn''t work';
    
    END;
    
    
    GO
    -- GRANT EXECUTE ON tTSQLTests.[test birthday queries for a month] to userrole
    EXEC tsqlt.run 'tTSQLTests.[test birthday queries for a Jan with no birthdays in range]'

    Then I’d test for one birthday.

    CREATE OR ALTER PROCEDURE tTSQLTests.[test birthday queries for a Jan with one birthdays in range]
    /*
    Description:
    
    Changes
    11/22/2017
    */
    AS
    BEGIN
         -------------
         -- Assemble
         -------------
         DECLARE @begin DATETIME = '1984-01-01' ,
                 @end DATETIME = '2000-01-01' ,
                 @month TINYINT = 1;
    
        EXEC tSQLt.FakeTable @TableName = 'birthdays', @SchemaName = 'dbo';
         INSERT dbo.birthdays
         (
             cust_id ,
             cust_fname ,
             cust_lname ,
             cust_dob
         )
         VALUES
         (94, N'Jamie', N'Aguiar', '2017-06-02 00:00:00.000') ,
         (346, N'Keith', N'Brady', '1993-03-29 00:00:00.000') ,
         (361, N'Kelsea', N'Britto', '1994-03-25 00:00:00.000') ,
         (715, N'Tia', N'Delguidice', '1999-02-04 00:00:00.000') ,
         (994, N'Holly', N'Hamilton', '2017-11-12 00:00:00.000') ,
         (1110, N'ISABELLE', N'HYDER', '1993-04-06 00:00:00.000') ,
         (1295, N'RAELYN', N'LITTLE', '1995-02-15 00:00:00.000') ,
         (1403, N'ALLISON', N'RIPA', '1993-10-14 00:00:00.000') ,
         (1486, N'Rayvon', N'Miller', '1984-11-09 00:00:00.000') ,
         (1559, N'Alexandra', N'Sousa', '1989-09-17 00:00:00.000') ,
         (1897, N'Patrick', N'Snow', '1976-10-10 00:00:00.000') ,
         (1749, N'Justine', N'Zienowicz', '1998-03-12 00:00:00.000') ,
         (2209, N'Brittany', N'Kosboski', '1987-01-22 00:00:00.000');
    
        CREATE TABLE #Expected
         (
             cust_id INTEGER NOT NULL PRIMARY KEY ,
             cust_fname NVARCHAR(50) NOT NULL ,
             cust_lname NVARCHAR(50) NOT NULL ,
             cust_dob DATETIME NOT NULL
         );
    
        INSERT #Expected
         (
             cust_id ,
             cust_fname ,
             cust_lname ,
             cust_dob
         )
         VALUES
         (2209, N'Brittany', N'Kosboski', '1987-01-22 00:00:00.000');
    
        SELECT TOP 0
             cust_id ,
             cust_fname ,
             cust_lname ,
             cust_dob
         INTO #Actual
         FROM #Expected;
    
        ---------------
         -- Act
         ---------------
         INSERT #Actual
         SELECT cust_id ,
                cust_fname ,
                cust_lname ,
                cust_dob
         FROM birthdays
         WHERE cust_dob >= @begin
               AND cust_dob < @end
               AND MONTH(cust_dob) = @month;
    
        ---------------
         -- Assert    
         ---------------
         EXEC tSQLt.AssertEqualsTable @Expected = N'#expected' ,
                                      @Actual = N'#actual' ,
                                      @Message = N'The query doesn''t work';
    
    END;
    
    
    GO
    -- GRANT EXECUTE ON tTSQLTests.[test birthday queries for a month] to userrole
    EXEC tSQLt.Run 'tTSQLTests.[test birthday queries for a Jan with no birthdays in range]';
    EXEC tSQLt.Run 'tTSQLTests.[test birthday queries for a Jan with one birthdays in range]';

    Next, I’d add a few more tests for other cases, perhaps checking that leap years, etc. run correctly.

    Is this hard to setup? Well, I might argue that the time you spend examining result sets and checking random queries is about the same. Once I’ve written this test, which took about 10 minutes with a template, I can easily copy/paste the test, change the name and move data around. Ideally I’d stick this query in some procedure instead and run the test that way, which makes it easy for me to alter the query and re-run a ton of tests quickly.

    The time saved isn’t in the initial development, but in the checking when I touch this code again, or tune it. What if I decided to replace the MONTH() check with a computed column, as suggested by some responders? Then my tests should still pass with a new query.

    Testing builds better software. Not perfect, but better. And I can get better as developing over time by adding more tests.

  • Automating SQL Clone Creation with PoSh

    I think SQL Clone is one of the game changing products from Redgate. This product really fits into a DevOps mindset, allowing me to quickly and easily build (and rebuild) a dev database.

    While the agent web pages make this easy, they’re slightly cumbersome and the PowerShell cmdlets fit better with a DevOps flow. In setting up a Query Store demo, I found myself changing some data and needing to reset my database rapidly, so I built a quick PoSh function to do this for me.

    Here’s my function:

    function Add-ADWClone {

    param([Parameter(Mandatory=$true)][string] $CloneName)

    $mycredential = Get-Credential

    Connect-SqlClone -ServerUrl ‘http://socrates:14145&#8217; -Credential $mycredential

    # remove the image if it exists

    $SqlServerInstance = Get-SqlCloneSqlServerInstance -MachineName Plato -InstanceName SQL2016

    $Clones = Get-SqlClone -Location $SqlServerInstance

    if ($Clones.Name -contains $CloneName) {

    $CloneToDelete = Get-SqlClone -Location $SqlServerInstance –Name $CloneName

    Remove-SqlClone -Clone $CloneToDelete | Wait-SqlCloneOperation

    }

    # Create the new image

    $image = Get-SqlCloneImage -Name ‘Adw2014Base’

    $sqlServerInstance = Get-SqlCloneSqlServerInstance -MachineName Plato -InstanceName SQL2016

    $image | New-SqlClone -Name $CloneName -Location $sqlServerInstance | Wait-SqlCloneOperation

    }

    In this function, I’m taking the name of a cloned database. I’ll use that to check if the clone exists, and if so, remove it. I do this by using Get-SqlClone. Once that’s done, I get the image, which is static here (this is a function for a specific project) and then I’ll create the new clone.

    This works great. If I run the command, I’ll get a clone being created. You can see my client in the back creating the clone database.

    2017-11-28 14_46_08-SQL Clone

    If I re-run the command, I’ll see the delete.

    2017-11-28 14_48_22-SQL Clone

    And then the clone create again.

    Changing Development

    When I first saw a prototype a few years ago, I could immediately think back to being a full time developer and the hassles of manipulating my development database, making data changes to test code, trying to reset them, writing scripts to undo changes and more. At some point I tried to perform backups and restores of a standard database, but I’d keep forgetting to update things.

    SQL Clone makes this easier, and together with some way to push/pull code from a VCS to your database, it means that I can quickly reset a database back to a known state. One of the things that I’d like to easily do is whack my development database, recreate a new one, and then move on with writing code. If I make a mistake, I repeat the process.

    Moving On

    This is a basic Proof of Concept, something I just whacked together for a project, so I’ve coded in the image name, and I get the credentials from the user. I could clean this up, and have it as a simple up-arrow, enter from the  PoSh command line that I use as I need to reset my system. Or code in a saved, secure credential that lets me double click some batch file to run this for me.

    Automation speeds up development by removing simple tasks from the developer. SQL Clone makes it easy to reset my database to a known state (the image) and create new databases as needed.

    If you haven’t tried SQL Clone, give it a try today by downloading an evaluation.

  • Hit Refresh

    thI read Satya Nadella’s Hit Refresh recently, and it was a very enjoyable book. He’s had an interesting journey and I do think he’s dramatically transformed Microsoft. The book is recommended.

    Anyway, the Microsoft MVP Program asked is MVPs would share a refresh moment from their lives, so here’s mine:

    Hitting Refresh, Over and Over

    I think I’ve started to wear the finish off the refresh button, but it’s been a great life and career.

    When I entered university at 17, I expected to study computer science and spend a career writing code for some organization. This was the era before the Internet when terminals were hardwired to mainframes, printers were housed in a separate room (with a page budget each semester), and my 300 baud modem allowed me to watch the screen slowly fill with characters. After two years of learning new languages and solving relatively simple problems, I hit refresh and decided finance and business were the direction for me.

    I completed a degree in economics, but injured myself playing rugby during my final year. I hit refresh again, not wanting to spend most of my life draped in a suit and tie, pouring over financial documents at a bank. I took a year off, working, playing volleyball, and surfing at the beach. Midway through this year, I became bored and hit refresh a third time, returning to school to study computer engineering.

    A year later, after securing an internship writing code and managing a Novell network, I hit refresh again, abandoning my schoolwork and going to work fulltime. I discovered SQL Server and hit refresh yet again. I moved into the data world, spending the next fifteen years working with SQL Server in a variety of industries and positions.

    I hit refresh again after starting SQLServerCentral.com and having that become successful enough to support me in a fulltime role. I left employment for my own venture, which was a completely different type of work from writing code for an employer. I became an educator and journalist, seeking to help others grow their SQL Server skills. I was honored to be recognized as a Data Platform MVP by Microsoft for my work here.

    Finally, I hit refresh one last time when Redgate Software purchased my business, and I became an evangelist for them. Today, I continue to educate and train others in Database DevOps, and hopefully won’t hit refresh again until I retire.

    This post is part of the collection of #HitRefresh moments. Read more at
    www.hitrefreshbook.com