Tag: T-SQL

  • Detecting Database Option Changes with DDL Triggers

    One of the keys to managing a large production SQL Server environment is being aware of changes that are taking place in the environment, and preventing potentially harmful changes. There are any number of ways to do this: triggers, auditing, PMB, third party monitoring, and more.

    In this post, I want to look at a quick way to detect issues with databases using DDL triggers. Specifically, I will build a quick trigger that responds to the ALTER DATABASE event.

    Tracking Changes

    In general, I try to avoid those options that severely limit my flexibility. I dislike trying to enforce every possible rule I create since I understand that IT environments often evolve and change, and the hard rules you have today may not apply tomorrow. I also realize that most of the time the hard rules have exceptions to them for various reasons and it’s much easier to manage a set of instances if you expect that the might not have the same requirements.

    I do want to be informed of changes, and one option is a DDL trigger that responds to a particular event. DDL triggers have a large list of events that will trigger them, of which the ALTER DATABASE is one.

    We build the trigger by giving it a name, scope and an event. In this case, we’ll start with this template

    CREATE TRIGGER [name]

    ON ALL [scope]

    FOR [event]

    You can change the scope and events as needed. For me, I need the Server level scope (database changes are a server instance event) and then the ALTER_DATABASE event. From there, it’s pretty much normal T-SQL coding.

    The data comes back from the EventData() function as an XML fragment, so in the trigger, I need to parse out the particulars that I care about.

    The code I’ll use is this:

    CREATE TRIGGER DBAAudit_ALTER_Database
    ON ALL Server
    FOR ALTER_Database  -- Captures a Create Database Event
    AS
         
    DECLARE
      @EventTime datetime
    , @ServerName varchar(200)
    , @LoginName varchar(200)
    , @DatabaseName varchar(200)
    , @TSQL varchar(2000)
    , @event XML
    
    select @event = EVENTDATA()
    
    SELECT @ServerName = @event.value('(/EVENT_INSTANCE/ServerName)[1]', 'varchar(200)')
    SELECT @EventTime = @event.value('(/EVENT_INSTANCE/PostTime)[1]','datetime' )
    SELECT @LoginName = @event.value('(/EVENT_INSTANCE/LoginName)[1]','varchar(200)' )
    SELECT @DatabaseName = @event.value('(/EVENT_INSTANCE/DatabaseName)[1]','varchar(200)' )
    SELECT @TSQL = @event.value('(/EVENT_INSTANCE/TSQLCommand)[1]','varchar(2000)' )
    
    Print 'Database ' + @Servername + '.' + @DatabaseName + ' was altered by ' + @LoginName
    Print 'Command: ' + @TSQL
    
    

    When I create this on my instance, it is stored in the Server Objects \ Triggers area on my server. Just like any other object, I can right click and perform all kinds of actions in SSMS.

    ddltrigger_a

    When I execute a change on a database, such as setting a database to read only with this:

    alter database dba_admin set READ_ONLY
    

    I get this in the messages tab (from the Print statement)

    Database DKRSQL2012.dba_admin was altered by DKRSQL2012\Steve

    Command: alter database dba_admin set READ_ONLY

    If I set the database back to read_write, I get this:

    Database DKRSQL2012.dba_admin was altered by DKRSQL2012\Steve

    Command: alter database dba_admin set READ_Write

    This is a nice example, but in a real system, I’d use a database for tracking these changes and store the information from the event in a table. Instead of a PRINT, I’d insert data into a table that tracks changes.

  • The Tally Table and Expanding Rows

    Suppose you had some data like this:

    Class           Limit
    ————— ———–
    Calculus        5
    History         4
    Physics         2

    But what you really want is this:

    Class           Student
    ————— ———–
    Calculus       
    Calculus       
    Calculus       
    Calculus       
    Calculus       
    History        
    History        
    History        
    History        
    Physics        

    Physics        

    Where you have a placeholder for each student. This is a little contrived, but for the sake of the scenario, how do you actually expand the data in the first set to the second?

    An easy way is a cursor, but suppose you had a large school environment and you were doing this regularly, you might not want the performance hit of a cursor. Suppose you have some similar scenario, like slotting inventory or holding places for some large report? You would want a better solution.

    Setup

    Let’s first get a table ready for this.

    declare @Table table (
      Class varchar(15)
    , Limit int
    )
    ;
    
    insert into @Table 
      values ('Calculas',5)
           , ('History', 6)
           , ('Physics', 2)
    ;       
    

    A Tally Table

    I first saw this when Jeff Moden talked about this in his “The Numbers or Tally Table” article. It seemed like a neartidea, and he improved upon is in his next piece, finding a more efficient way to generate the data. An even more flexible idea that I like came from his Test Data Generator article, where he uses sys.columns to build a list as large as he would like. I found it to be a great idea, and this problem is a great way to apply it.

    Here’s a short look at the code that makes this work.

    SELECT TOP (4) RowNum = ROW_NUMBER() OVER(ORDER BY a.[Name])
     FROM sys.columns a, sys.columns b

    In this code, a cross join occurs between sys.columns and itself. With the addition of the ROW_NUMBER() function, and an order by, you can easily get a list of numbers. If I run this. I get:

    RowNum

    ——————–

    1

    2

    3

    4

    If I change my TOP value, I can get different rows:

    SELECT TOP (6) RowNum = ROW_NUMBER() OVER(ORDER BY a.[Name])
     FROM sys.columns a, sys.columns b

    RowNum

    ——————–

    1

    2

    3

    4

    5

    6

    To make this work for me in the original problem, I will use the limit value for my TOP in a subquery. However I can’t do a straight join, I need a CROSS APPLY, that will execute the right side of the CROSS apply for each input row from the left table.. It’s intended to be for table valued functions, but in this case, we’ll just return a table from the right side.

    The code looks like this:

    SELECT
      c.Class
    , 'Student' = ' '
    FROM (
    SELECT Class,Limit
    FROM @Table
    ) c
    CROSS APPLY (
    SELECT TOP (Limit) RowNum = ROW_NUMBER() OVER(ORDER BY a.[name])
    FROM sys.columns a, sys.columns b
    ) n

    What happens is that for each row, the derived table on the right side of the CROSS APPLY, returns a set of numbers which are then linked to the row on the left side of the CROSS APPLY.

    For the first row in @table, we have “Calculus” and “5”. When we apply this row to the subquery on the right side (SELECT TOP()…), we get these results:

    1

    2

    3

    4

    5

    When this is joined with the row and the c.Class returned, we end up with the same value being returned 5 times. That results in

    Calculus

    Calculus

    Calculus

    Calculus

    Calculus

    If this is repeated for each of the other rows, we get 4 and 2 rows from the right side of the CROSS APPLY returned, respectively.

    The final results are what we get at the top of this post.

  • From the Labs of SQL Prompt

    I love SQL Prompt as an add-in for SSMS. The intellisense is very handy for me and I’ve gotten used to certain shortcut combinations that make it easy for me to write T-SQL quickly and get information on parameters without opening Books Online. It’s works better than the native intellisense for me, though perhaps I’ve just gotten used to it. When it’s not installed on an instance in one of my VMs, writing code is a chore.

    When I was in Cambridge recently, I had the chance to sit down with one of the developers of SQL Prompt and he showed me a few things I had never seen.

    For the most part when I install SQL Prompt, I leave it with the defaults. There are a few snippets that I change quickly, like the ssf snippet. This normally produces a “SELECT * FROM” and I add a “TOP 10” to it in order to reduce the amount of data I bring back.

    However there are a few features in SQL Prompt that are “experimental” in nature. They are complete, but not deployed into the product by default. You can access them from the SQL Prompt menu in Management Studio.

    prompt3

    This brings up the Experimental Features tab in the options dialog, which you can see below. There aren’t a lot of features, but these are ideas that have been suggested, or are working, but they developers aren’t sure if they are completely spec’d out.

    prompt4

    You can enable a few of these to see if you really want to see how they work. For example, I’ve enabled “Automatic Refresh Suggestions”. Since I tend to work in one database at a time and create lots of objects, I want this to happen. I can ALT+S, Enter for this, but I’d like to tool to do it for me.

    These items change periodically, and some link to related tools (like SQL Tab Magic), and they give you a chance to test the way the feature works and provide feedback. If you are a SQL Prompt user, you might check out this tab.

    If you’d like to see what SQL Prompt can do for you, download a free trial and give it a try:

    14-day-free-trial

  • Creating a Sequence in SQL Server 2012

    Sequences are a new object in SQL Server 2012 that generate just what the name implies: a sequence of numbers.

    To create a sequence, you use standard DDL with the CREATE SEQUENCE command. This command takes a type, a starting value, a min, max, and a few other parameters. You can read about the meanings of the parameters in Books Online, and learn how to use the parameters.

    When I first head about sequences, I thought this was neat, but not terribly useful. The idea of a custom sequence seems more like an edge case. However the more I’ve looked at them, the more it starts to make some sense.

    Let’s look at a real world example where we use a sequence since we often use these as a basis for some computer algorithm. Suppose I have a group of kids and I need to assign them to groups. In the real world, we line up the kids and we have them count off to some number. It’s often “1”, “2”, “1”, “2”, but it could be something else. In a youth group a few years ago, we counted off into 5 groups to divide the kids up.

    In this case, suppose I have kids and I want to get them into these groups:

    sequence1

    In order to get that in T-SQL, it can be cumbersome, especially if there isn’t a data point that cycles things, but rather a set counter. Here I can create a simple sequence:

    create sequence RoundRobin
     Start with 1
     increment by 1
      minvalue 1
      maxvalue 3
     cycle
    ;
    

    I have 10 rows in my table, being the list of kids:

    create table Kids
    ( Kid varchar(10)
    )
    go
    insert Kids
     values
      ('Emma')
    , ('Tabitha')
    , ('Kendall')
    , ('Delaney')
    , ('Kyle')
    , ('Jessica')
    , ('Josh')
    , ('Kirsten')
    , ('Amanda')
    , ('Jimmy')
    ;
    go
    

    To integrate the sequence, I do this:

    select
      'Group' = next value for dbo.RoundRobin
    , Kid
     from Kids
    ;
    

    That returns the results above.

    It’s not necessarily a common solution, but there might be places where you want custom counters, perhaps counting by a certain value. Suppose you want to alternate counters among two different people in a table. An identity doesn’t allow this, but you could set up two sequences, say one for even numbers, and one for odd numbers, and let each person use the appropriate sequence for inserts.

    Sequences are simple, but they can be useful in some cases. I’ll write a bit more about a few gotchas I’ve run into with sequences soon.