Author: way0utwest

  • Heading to SQL Bits 2025

    I’ll be at SQL Bits tomorrow, Saturday Jun 20, 2025 for the final day of the conference. I wasn’t selected to speak, but since I’m in Cambridge next week I came a couple of days early to stop by the event.

    As this publishes, I’m probably just about to land at Heathrow and I am looking forward to a fairly quiet day in London.

    Tomorrow should be fun. I have lightly looked at the schedule and I see a few AI sessions that might be fun to watch, but mostly I’m looking forward to catching up with friends.

    If you see me, please don’t hesitate to say hi.

  • SQL Server 2025 Excitement

    Are you looking forward to SQL Server 2025? Or perhaps you think this is just another release, or perhaps you are not looking for new features or capabilities in your environment. Maybe you don’t care about new things, but are looking for enhancements to features introduced in 2017/2019/2022. There is certainly no shortage of things that can be improved from previous versions (cough graph *cough).

    I ran across an article on the five things that one person is looking forward to in SQL Server 2025. It’s a good list, and the things included make me consider an upgrade. Certainly, any improvements in the performance area, especially with all the investments made in Intelligent Query Processing over the last few versions, are worth evaluating. They might help your workload, or they might not, but if they do, then upgrade.

    However, test, test, test. I can’t stress that enough. Test with your workload, not some random queries. Spend some time setting up WorkloadTools or find some other way to replay a set of queries from multiple clients to see if performance improves. It’s far too easy to look at a query in isolation and make a snap decision. With a load, sometimes performance looks different.

    The HA improvements are also enticing, especially the idea of offloading backups more easily. Of course, this means you need to ensure you can and know how to, restore a complex set of backups in an emergency situation. Distributed systems are complex, and backups from multiple nodes (remember, you might get unexpected failovers) are a distributed system. Make sure you consolidate those, and plan for potential disruptions if your backup system/share/location is gone. Local backups are always nice, but Murphy’s law might cause you problems in multiple ways with multiple nodes and backups moving across them.

    Again, test, test, test, and consider weird situations taking place. They will occur, and you should ensure your staff has a simple way to deal with them.

    We’ve had a few SQL Server versions that leaped forward. SQL Server 2005 changed the paradigm, and I think SQL Server 2016 was another time of dramatic growth. Will SQL Server 2025 be one of those versions, or is it one that has a few incremental improvements?

    Let me know your thoughts today.

    Steve Jones

    Listen to the podcast at Libsyn, Spotify, or iTunes.

    Note, podcasts are only available for a limited time online.

  • Better Trigger Design: #SQLNewBlogger

    I had someone ask me about using triggers to detect changes in their tables. This is the third post in the series. The first one

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

    The Setup

    We’re using the same table from the first post.  This is the dbo.Customer table with a PK and 5 other fields. Here is the data in the table:

    2025-05_0231

    I had this trigger in the last post, and showed how it captured updates to the ContactEmail field.

    CREATE OR ALTER TRIGGER Customer_tru ON dbo.Customer FOR UPDATE
    AS
    BEGIN
         IF UPDATE(CustomerName)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.CustomerName changed')
         IF UPDATE(AddressKey)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.AddressKey changed')
         IF UPDATE(CustomerStatus)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.CustomerStatus changed')
         IF UPDATE(CustomerContact)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.CustomerContact changed')
         IF UPDATE(ContactEmail)
         BEGIN
             INSERT dbo.logger (logdate, logmsg)
             SELECT GETDATE(), 'ContactEmail updated from ' + d.ContactEmail + ' to ' + i.ContactEmail
              FROM inserted i
              INNER JOIN Deleted d ON i.CustomerID = d.CustomerID
         END
    END

    This seemed to work, but did it really?

    The Problem

    Let’s illustrate the big problem with this change. I’ll run this code:

    UPDATE dbo.Customer
      SET ContactEmail = ‘andy@sqlservercentral.com’
      WHERE CustomerID = 2;

    If I do this, here are the results:

    2025-05_0232

    I get a NULL? Why, the original value is null and when I concatenate null with other values, I get NULL. Not ideal.

    Let’s fix this problem. I’ll use a function to handle null values. Note, I need to do this for both the inserted and deleted tables. Here’s the new trigger.

    CREATE OR ALTER TRIGGER Customer_tru ON dbo.Customer FOR UPDATE
    AS
    BEGIN
         IF UPDATE(CustomerName)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.CustomerName changed')
         IF UPDATE(AddressKey)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.AddressKey changed')
         IF UPDATE(CustomerStatus)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.CustomerStatus changed')
         IF UPDATE(CustomerContact)
             INSERT dbo.logger (logdate, logmsg) VALUES (DEFAULT, 'dbo.Customer.CustomerContact changed')
         IF UPDATE(ContactEmail)
         BEGIN
             INSERT dbo.logger (logdate, logmsg)
             SELECT GETDATE(), 'ContactEmail updated from ' + COALESCE(d.ContactEmail, 'null') + ' to ' + COALESCE(i.ContactEmail, 'null')
              FROM inserted i
              INNER JOIN Deleted d ON i.CustomerID = d.CustomerID
         END
    END

    We can see this handles the null appropriately.

    2025-05_0233

    In this case I’ve chosen to replace a NULL value with the word ‘null’. This means something to me, but I could have just as well replaced this with “blank” or any other word. In many applications a developer might display a null value as a blank, so choose what works for you.

    I’m also including an example to show this works for multiple rows. Here I’ll update multiple rows and we can see each is inserted into my log.

    2025-05_0234

    This is a good reason to audit certain activities, as people will sometimes make these mistakes and updates lots of data.

    This trigger is slightly more useful, and handles the NULL cases, but it still isn’t perfect. Imagine I need to parse out changes, or generate the reverse transactions, or even search for certain changes. Stuffing a lot of data into a single field is overloading it, and making it less useful over time. What we’d really want to do is separate pertinent data into different fields. In a NoSQL world, we might do this by using a JSON schema to track the before and after.

    We could do that here, just stuff JSON into the log message and read it back out and de-serialize it.

    SQL New Blogger

    This post modified our trigger to address a previous design problem: not handling nulls. We also showed how to test the trigger with multiple rows. This shows I’ve added knowledge to my skillset and can test what I’m trying to do.

    Write your own blogs that might examine what you’ve done poorly in the past and how to fix the problems you’ve identified, even in simple code. Many of us do this a lot.

    This was a 20-30 minute post for me. You could likely do it in a similar amount of time.

  • Patching the Patch

    I had to make a few changes to a SQL Saturday event recently. The repo is public, and some of the organizers submit PRs for their changes, and others send me an email/message/text/etc. for a change. In this case, an organizer just asked for a couple of image updates to their site. I opened VS Code, created a branch, added a URL for the images, and submitted my own PR. After the build, I deployed it.

    And it didn’t work.

    I had a broken image. I checked the URL in code and realized I had “events” copied before the URL, which wasn’t valid. Ok, edit the URL to be correct and repeat: new PR, build, merge, deploy.

    And it didn’t work.

    I was looking at the code live on the site, the code in the repo, and I was trying to reconcile paths and file names and keys and values and a few other things.

    I realized the world for a developer hadn’t changed a lot, and in fact, I was in the age-old loop: deploy, patch, patch the patch, fix the patch for the patch, and so on. I don’t even know that I could have gotten better here with testing, as these were one-off data changes that affected the site for users. If I enter the wrong data, it’s wrong. I can’t easily test for this.

    I have written code that was wrong, and a few simple tests would have caught my issues. I’ve also written code that isn’t easy to test. If I am adding or changing data, it’s hard to test that. Often, I might do some copy/pasting between the code and the test to generate the test. If I’ve typoed something, the typo continues through the test (in some cases). Even using a code generator or an AI to produce the INSERT or UPDATE code might not solve the problem. They might read my typos in a prompt.

    One of the best things to help code quality in the last few decades is continuous integration (CI), where we have automated systems that compile code, test it, and run it. It’s not perfect, but it does help reduce the silly mistakes many of us likely make every day when writing code. These can’t prevent typos and issues, but if we are testing intermediate systems, hopefully somewhere along the way, a human or AI agent tries to verify that the things we were typing exist and can catch a typo.

    In this case, I had to find where I’d mistyped the line and realized that I had the path wrong. The image was in a subfolder and I needed to add that to the img url.

    Working with data is hard, and it’s a constant source of simple mistakes. I don’t know we’ll ever get away from patching the patch when data manipulation is involved.

    Steve Jones

    Listen to the podcast at Libsyn, Spotify, or iTunes.

    Note, podcasts are only available for a limited time online.