Category: Blog

  • A Very Cool Benefit in SSMS 21

    I saw a post from Erin that Preview 2 is available. I’d gotten a message when I started SSMS v21 this morning, but got distracted.

    I made a short video showing me getting the update, which is way, way easier and more convenient than it used to be.

  • AI Helping with an API

    Can an AI help me with some database API work? Let’s see.

    This is part of a series of experiments with AI systems.

    The Scenario

    One of the things that helped me support a number of development teams with their database work was having code generate code. I had a piece of code that could read the system tables in SQL Server and produce insert, update, and delete procedures.

    I wondered if an AI could do this easily.

    Using Copilot

    I decided to try this first in the Edge Copilot window. I wrote this prompt:Create the T-sQL code for insert, update, and delete procedures for these tables:

    I then pasted in the code for 5 tables from Northwind, such as this one:

    CREATE TABLE "Employees" ( "EmployeeID" "int" IDENTITY (1, 1) NOT NULL , "LastName" nvarchar (20) NOT NULL , "FirstName" nvarchar (10) NOT NULL , "Title" nvarchar (30) NULL , "TitleOfCourtesy" nvarchar (25) NULL , "BirthDate" "datetime" NULL , "HireDate" "datetime" NULL , "Address" nvarchar (60) NULL , "City" nvarchar (15) NULL , "Region" nvarchar (15) NULL , "PostalCode" nvarchar (10) NULL , "Country" nvarchar (15) NULL , "HomePhone" nvarchar (24) NULL , "Extension" nvarchar (4) NULL , "Photo" "image" NULL , "Notes" "ntext" NULL , "ReportsTo" "int" NULL , "PhotoPath" nvarchar (255) NULL , CONSTRAINT "PK_Employees" PRIMARY KEY CLUSTERED ( "EmployeeID" ), CONSTRAINT "FK_Employees_Employees" FOREIGN KEY ( "ReportsTo" ) REFERENCES "dbo"."Employees" ( "EmployeeID" ), CONSTRAINT "CK_Birthdate" CHECK (BirthDate < getdate()) ) GO CREATE INDEX "LastName" ON "dbo"."Employees"("LastName") GO CREATE INDEX "PostalCode" ON "dbo"."Employees"("PostalCode") GO

    I got this result:

    -- Insert Procedure
    CREATE PROCEDURE InsertEmployee
         @LastName nvarchar(20),
         @FirstName nvarchar(10),
         @Title nvarchar(30),
         @TitleOfCourtesy nvarchar(25),
         @BirthDate datetime,
         @HireDate datetime,
         @Address nvarchar(60),
         @City nvarchar(15),
         @Region nvarchar(15),
         @PostalCode nvarchar(10),
         @Country nvarchar(15),
         @HomePhone nvarchar(24),
         @Extension nvarchar(4),
         @Photo image,
         @Notes ntext,
         @ReportsTo int,
         @PhotoPath nvarchar(255)
    AS
    BEGIN
         INSERT INTO Employees (LastName, FirstName, Title, TitleOfCourtesy, BirthDate, HireDate, Address, City, Region, PostalCode, Country, HomePhone, Extension, Photo, Notes, ReportsTo, PhotoPath)
         VALUES (@LastName, @FirstName, @Title, @TitleOfCourtesy, @BirthDate, @HireDate, @Address, @City, @Region, @PostalCode, @Country, @HomePhone, @Extension, @Photo, @Notes, @ReportsTo, @PhotoPath)
    END
    GO
    -- Update Procedure
    CREATE PROCEDURE UpdateEmployee
         @EmployeeID int,
         @LastName nvarchar(20),
         @FirstName nvarchar(10),
         @Title nvarchar(30),
         @TitleOfCourtesy nvarchar(25),
         @BirthDate datetime,
         @HireDate datetime,
         @Address nvarchar(60),
         @City nvarchar(15),
         @Region nvarchar(15),
         @PostalCode nvarchar(10),
         @Country nvarchar(15),
         @HomePhone nvarchar(24),
         @Extension nvarchar(4),
         @Photo image,
         @Notes ntext,
         @ReportsTo int,
         @PhotoPath nvarchar(255)
    AS
    BEGIN
         UPDATE Employees
         SET LastName = @LastName, FirstName = @FirstName, Title = @Title, TitleOfCourtesy = @TitleOfCourtesy, BirthDate = @BirthDate, HireDate = @HireDate, Address = @Address, City = @City, Region = @Region, PostalCode = @PostalCode, Country = @Country, HomePhone = @HomePhone, Extension = @Extension, Photo = @Photo, Notes = @Notes, ReportsTo = @ReportsTo, PhotoPath = @PhotoPath
         WHERE EmployeeID = @EmployeeID
    END
    GO
    
    
    
    
    -- Delete Procedure
    CREATE PROCEDURE DeleteEmployee
         @EmployeeID int
    AS
    BEGIN
         DELETE FROM Employees WHERE EmployeeID = @EmployeeID
    END
    GO
    
    

    Here’s an image of what I saw:

    2024-11_0126

    You might ask if this is better than what I’d write. I wondered myself, so I asked it to change the update procedure slightly. Here’s the prompt and result:

    2024-11_0127

    -- Update Procedure
    CREATE PROCEDURE UpdateEmployee
         @EmployeeID int,
         @LastName nvarchar(20) = NULL,
         @FirstName nvarchar(10) = NULL,
         @Title nvarchar(30) = NULL,
         @TitleOfCourtesy nvarchar(25) = NULL,
         @BirthDate datetime = NULL,
         @HireDate datetime = NULL,
         @Address nvarchar(60) = NULL,
         @City nvarchar(15) = NULL,
         @Region nvarchar(15) = NULL,
         @PostalCode nvarchar(10) = NULL,
         @Country nvarchar(15) = NULL,
         @HomePhone nvarchar(24) = NULL,
         @Extension nvarchar(4) = NULL,
         @Photo image = NULL,
         @Notes ntext = NULL,
         @ReportsTo int = NULL,
         @PhotoPath nvarchar(255) = NULL
    AS
    BEGIN
         UPDATE Employees
         SET LastName = COALESCE(@LastName, LastName),
             FirstName = COALESCE(@FirstName, FirstName),
             Title = COALESCE(@Title, Title),
             TitleOfCourtesy = COALESCE(@TitleOfCourtesy, TitleOfCourtesy),
             BirthDate = COALESCE(@BirthDate, BirthDate),
             HireDate = COALESCE(@HireDate, HireDate),
             Address = COALESCE(@Address, Address),
             City = COALESCE(@City, City),
             Region = COALESCE(@Region, Region),
             PostalCode = COALESCE(@PostalCode, PostalCode),
             Country = COALESCE(@Country, Country),
             HomePhone = COALESCE(@HomePhone, HomePhone),
             Extension = COALESCE(@Extension, Extension),
             Photo = COALESCE(@Photo, Photo),
             Notes = COALESCE(@Notes, Notes),
             ReportsTo = COALESCE(@ReportsTo, ReportsTo),
             PhotoPath = COALESCE(@PhotoPath, PhotoPath)
         WHERE EmployeeID = @EmployeeID
    END
    GO

    That’s interesting. If I pass in nulls for everything, I get an update in place. Not quite what I’d do, as I’d check if the param was null and not include it in an update, which could reduce logging, but this is likely just as efficient.

    Easy code to write for sure, and I could likely put together a bit of code to do this for me, but it’s nice that I can quickly ask an AI to do this. I think this is the type of thing that might be easier for developers to do, and maybe more likely. They get to play with AI and feel more productive. They can even alter a table and “re-ask” for new procedures where they can interact with the table.

    What’s more, the INSERT procedures have the explicit column lists, so this means I could rev the underlying table without breaking this code. It’s a good coding practice, but far too many people would just embed a this in their code:

    insert Employee values ‘Jones’, ‘Steve’, ‘Editor’, …

    A quick thing, but Copilot seemed to work well here.

  • Creating a “Real” Copy of a View: #SQLNewBlogger

    I saw a post where a developer was trying to read the Information Schema views to create a copy of a view as a “real” table, a user table. This posts shows an easy way to do this.

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

    The Scenario

    Imagine you have a view, for example, I have this one:

    CREATE   VIEW [dbo].[City] 
    AS
    SELECT TOP 10
      cn.CityNameID, cn.CityName
      FROM dbo.CityName AS cn
      WITH CHECK OPTION
    GO

    The structure of the underlying table is:

    2024-11_0122

    I have data being returned from this view as well, as you can see here:

    2024-11_0118

    If I want a copy of this view, I can certainly look in the information_schema views and see some data. Below, I have the column information for this view, which can be used to structure a create table statement.

    2024-11_0119

    However, there’s a better way.

    Quickly Copying a View

    The INTO clause is very valuable and helpful here. Many of us use this to copy a table or part of a table, but it work with views. Here is how I create an empty copy of my view.

    SELECT * 
      INTO dbo.MyCities
      FROM dbo.city
      WHERE 1 = 0;

    This will actually create a new table, as you can see in my Table list when I refresh after running the command.

    2024-11_0120

    The table looks like the structure of the view above. The PK isn’t set, but there isn’t necessarily a PK in a view as it can combine data from multiple tables. If I wanted data, I can run the same statement above without the WHERE clause. I’ve done that below and then selected data from the new table so show this.

    2024-11_0123

    If I needed to add some constraints or other items, I could easily add those with ALTER TABLE statements.

    SQL New Blogger

    This post required about 20 minutes for me to setup a demo, test, and then write with some screenshots. It wasn’t a hard post to write, but it shows a quick technique for doing something I’ve commonly seen from others.

    This is the type of post you can write that might get an interviewer interested in you and perhaps ask you a question. You could add some context as to why you did this, or why you like (or don’t like) this technique.

  • T-SQL Tuesday #181: A Technology Present

    It’s the last T-SQL Tuesday of the year, and it’s amazing to think we’ve gotten to #181. That’s over 15 years of monthly blog parties.

    This month we have a slightly different invite from Kevin Chant. Kevin usually participates in the Festive Tech Calendar, which I’ve never been a part of. He wanted to combine those to together in a crossover, which is an interesting idea. I wonder how it will go.

    In any case, there’s a bit of explanation, but the essential invite is this: My invitation to yourselves is to write about a Microsoft Data Platform announcement that you considered to be as amazing as a present. In other words, something which made you go “wow”.

    It’s a good invite for the end of the year and my answer is below.

    Think about a VCS Diff

    I could say git diff since git has won the VCS battle, but in any tech work, it’s important to be able to tell what things have changed and evaluate if the change is helpful or hurtful.

    Lots of tools, especially visual ones, haven’t always considered this. They’ve often built a tool that doesn’t easily make comparing code easy. The first example of this was DTS/SSIS (SQL Server Integration Services), where this was a visual tool. The configuration was stored in an XML file, but every visual change created a change in the XML, and the structure was fluid enough that it was often hard to determine what changed between versions.

    Microsoft has a lot of tools that did this, including Power BI. However, a preview mode of the code was announced earlier last year. Power BI Developer mode includes Git integration. This gives us a way to edit the file as code, something that should be required of all tech tools. The visual stuff is great, but give us a code option.

    You can enable this in the PBI Desktop options.

    2024-12_0143

    To me, this is fantastic as it enables this to really work as a code tool, which it is. More importantly, as we get changes made by Copilot or other AIs, we need to easily see the differences that exist between versions. That’s important for troubleshooting and governance.

    To me, getting a Power BI project file that works in a VCS is a great present.