Showing posts with label SQL Server. Show all posts
Showing posts with label SQL Server. Show all posts

Saturday, September 2, 2017

Converting an Existing SQL Server Table to a Temporal Table

One of the very useful features added in SQL Server 2016 were temporal tables.  With a temporal table, SQL Server will automatically record a history of all changed data rows to a history table associated with the temporal table.  Further, SQL Server gives us some new syntax to be able to easily query what the data in the table looked like at any point in time or to show the entire history of a row in a table.  If you want more details, you can check out this earlier blog post I wrote on temporal tables.

However, what if we have an existing table in our database that we want to convert to a temporal table?  Lets take a look at how we do that.

For this example, lets assume that we have the following table that already exists in our database.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
CREATE TABLE Employees
(
    EmployeeId    INT          NOT NULL,
    FirstName     VARCHAR(20)  NOT NULL,
    LastName      VARCHAR(20)  NOT NULL,
    Email         VARCHAR(50)  NOT NULL,
    Phone         VARCHAR(20)  NULL,
    CONSTRAINT PK_Employees
        PRIMARY KEY (EmployeeId)
);


To convert this table to a temporal table, it is a two step process.  The first step is that we need to add our ValidFrom/ValidTo columns to the table to represent when the row was active in the table.  So we can run the following statement to do this.


1
2
3
4
5
6
ALTER TABLE Employees ADD 
    ValidFrom DATETIME2(3) GENERATED ALWAYS AS ROW START 
        NOT NULL DEFAULT '1900-01-01 00:00:00.000',
    ValidTo   DATETIME2(3)  GENERATED ALWAYS AS ROW END 
        NOT NULL DEFAULT '9999-12-31 23:59:59.999',
    PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo);

Here we are adding the two columns needed for when the row is valid and the PERIOD that is required by a temporal table.  Some things to note:

  • We could name the columns ValidFrom and ValidTo anything that we want to, these are just the names that I chose.
  • These columns must be of a DATETIME2 data type.  In this case, I am using DATETIME2(3) to go down to millisecond precision.
  • We need to provide default values for these columns in order to populate the existing rows on the table.  For my ValidFrom I chose 1/1/1900 as a default starting date.  The ending date for the rows in ValidTo column must be the maximum date/time value for our data type, so in this case, 12/31/999 at 23:59:99.999.
  • Otherwise, the syntax for the columns look much like the syntax for the columns in the CREATE TABLE statement.
Then, we need to run step 2 of the process:


1
2
ALTER TABLE Employees			
    SET (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.EmployeesHistory));

Here, we turn on SYSTEM_VERSIONING for the table so the ValidFrom and ValidTo dates will be auto-generated and define the name of the history table to use.

And that is all there is to it.  Now, your table has been converted to a temporal table and any changes to your table will be tracked in the history table.  






Saturday, April 29, 2017

Chicago Code Camp Slides and Resources

Thanks to everyone who attended my presentation at Chicago Code Camp.  The slides and other resources from my talk are below.

Slides


Pluralsight Course


Sample Database


DMV Queries


Saturday, April 15, 2017

JSON Functionality in SQL Server

SQL Server 2016 gives us the ability to work with JSON data directly in SQL Server, which is a very useful.  Traditionally, we've thought of relational databases and NoSQL databases as distinct entities, but with databases like SQL Server implementing JSON functionality directly in the database engine, we can start to think about using both relational and no-sql concepts side by side, which opens up a lot of exciting possibilities.

There is enough to cover that I will split this topic into multiple blog posts.  This is post number one, where I will cover how to store JSON in your tables and how to interact with scalar values.  The other posts are (will be updated as I go):

You can find the other parts of the series here (I'll update as I go)

  1. Accessing scalar properties on JSON objects stored in SQL Server (this post)
  2. Accessing array data in JSON objects stored in SQL Server


Also, if you want to recreate the database I have here and try the samples out for yourself, you can find everything you need on Github ar:



So lets take a look at what we can do.

Storing JSON Data in a Table
SQL Server does not contain a dedicated data type for JSON data, you simply just store your data in a VARCHAR (or NVARCHAR) data type of the appropriate size.

Below is a table that I have defined that is going to hold some weather data.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
CREATE TABLE WeatherDataJson
(
    ObservationId     INT IDENTITY(1,1)  NOT NULL,
    StationCode       VARCHAR(10)        NOT NULL,
    City              VARCHAR(30)        NOT NULL,
    State             VARCHAR(2)         NOT NULL,
    ObservationDate   DATETIME           NOT NULL,
    ObservationData   VARCHAR(4000)      NOT NULL,
    CONSTRAINT PK_WeatherDataJson
        PRIMARY KEY (ObservationId)
)

Our JSON will be stored in the column named ObservationData.  The data type for this field is simply a VARCHAR(4000) field, which is sufficient to hold the JSON objects we''l be storing in it.  If you have larger objects, you can use a VARCHAR(MAX) field, and indeed we'll se an example of this later.

This is a sample of the JSON we'll be storing in this column.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
{
  "stationCode": "04825",
  "location": {
    "city": "Appleton",
    "state": "WI",
    "stationName": "OUTAGAMIE CO RGNL AIRPORT"
  },
  "observationDate": "20160701",
  "observationTime": "1245",
  "observationDateTime": "2016-07-01T12:45:00",
  "skyCondition": "SCT045",
  "visibility": 10,
  "dryBulbFarenheit": 66,
  "dryBulbCelsius": 19,
  "wetBulbFarenheit": 55,
  "wetBulbCelsius": 12.6,
  "dewpointFarenheit": 45,
  "dewpointCelsius": 7,
  "relativeHumidity": 47,
  "windSpeed": 7,
  "windDirection": "360",
  "stationPressure": 29.13,
  "seaLevelPressure": null,
  "recordType": " ",
  "hourlyPrecip": null,
  "altimeter": 30.11
}

So we see we have a number of weather related properties off of the main object and a nested object for the location.  Our data in this column may be for different cities, but it all has this same format, which sets us up for the next step, querying the data.

Querying Scalar Values in JSON Data
First lets look at if we run a plain old SQL Query what we get back.   Here is our initial query:

1
2
3
4
5
6
SELECT * 
    FROM WeatherDataJson
    WHERE City = 'Appleton'
        AND State = 'WI'
        AND ObservationDate > '2016-07-01'
        AND ObservationDate < '2016-07-02'

And our results:

We can clearly see there is JSON stored in the ObservationData column.  But now we want to do something with it.  Lets pull out the temperature and humidity readings from each observation and make those columns in our result set.

To do this, we use a new function in SQL Server called JSON_VALUE.  JSON_VALUE takes two arguments:

  • A JSON expression, which is typically a the name of a column that contains JSON text, but could also be a T-SQL variable containing JSON.
  • A path expression, which describes how to navigate to the scalar value you want to extract out of the JSON.
Path expressions start with the $ sign and then use dot notation to navigate to the property that you want the value of.  So if I wanted to get the station name of where the measurement was made in our JSON above, I would use the expression '$.location.stationName'.  To get the temperature and humidity, we'll use the expressions '$.dryBulbFarenheit' and '$.relativeHumidity' respectively.

So now, we are going to rewrite our query to extract these fields out as columns like this.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
SELECT 
        ObservationId,
        StationCode,
        City,
        State,
        ObservationDate,
        JSON_VALUE(ObservationData, '$.dryBulbFarenheit') As Temperature,
        JSON_VALUE(ObservationData, '$.relativeHumidity') As Humidity,
        ObservationData
    FROM WeatherDataJson
    WHERE City = 'Appleton'
        AND State = 'WI'
        AND ObservationDate > '2016-07-01'
        AND ObservationDate < '2016-07-02';

And here are our results.

We see that SQL Server used the JSON_VALUE function to dynamically pull those values out of our JSON so we can see those as columns in our result set.  And now we can interact with them like we would any other column in a table.  So we can keep this data stored as JSON, but interact with it in a relational manner when that makes sense, which is pretty cool.  Traditionally, we would have needed an ETL process that would have parsed this data out and placed each element in a column, but now we can do that on the fly.

The Power of Virtual (Computed) Columns
With our weather data, it would be pretty cool if I didn't have to write that syntax each time I queried the table but could just get a couple of these key fields like temperature and humidity.  By combining the JSON_VALUE function with SQL Server's computed columns feature, we can create a set of virtual columns on our table which will do just that, pulling the data out of the JSON each time so it appears as just another column on the table.  Here is what the syntax looks like.

1
2
3
4
5
ALTER TABLE WeatherDataJson
    ADD Temperature AS JSON_VALUE(ObservationData, '$.dryBulbFarenheit');

ALTER TABLE WeatherDataJson
    ADD Humidity AS JSON_VALUE(ObservationData, '$.relativeHumidity');

Now, if we enter our original SELECT * query against the table, we will see that these two columns just show up in our result set like any other columns.  And of course, we could also refer to them by name, either in a SELECT clause, WHERE clause or JOIN condition.  The function just like any other column, even though what is happening is real time, SQL Server is parsing the JSON and retrieving this value for us.

That is an important point if we want to use these columns in a WHERE clause or a JOIN condition.  We know that for a table of any size, if we query the table by a column that is not indexed, our performance is going to be very slow because SQL Server has to read all of the rows of the table from disk and find the columns we are looking for.  Here, our results would probably be even worse if we tried to filter by one of these JSON virtual columns, because not only is SQL Server going to have to scan the whole table, it will also have to parse the JSON for each row.  Not good!

Indexing Computed Columns
However, SQL Server allows you to create an index over a computed (virtual) column.  When you do this, the values of the computed columns are computed at index creation time and then persisted in the index so they can be searched quickly like values of any other column.  In this way, but using the JSON_VALUE function in conjunction with a computed column and an index over that computed column, we can quickly search values in our JSON data like we would any other column in our table.  Lets look at an example.

Imagine we redefined our table from above to be about as simple as could be, a surrogate key ObservationId for the primary key and a column to hold our JSON data so that our CREATE TABLE statement now looks like this.

1
2
3
4
5
6
7
CREATE TABLE BasicWeatherDataJson
(
    ObservationId     INT IDENTITY(1,1)  NOT NULL,
    ObservationData   VARCHAR(4000)      NOT NULL,
    CONSTRAINT PK_BasicWeatherDataJson
        PRIMARY KEY (ObservationId)
)

This is about as simple as storing data can get.  All of our data is inside the ObservationData  column.  This includes fields that we will probably want later in order to look the data up by, things like the observation time, the city, the state and the station code.  We could write a query like this...


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
SELECT 
        ObservationId,
        JSON_VALUE(ObservationData, '$.stationCode') As StationCode,
        JSON_VALUE(ObservationData, '$.location.city') As City,  
        JSON_VALUE(ObservationData, '$.location.state') As State,
        CONVERT(datetime2(3), JSON_VALUE(ObservationData, '$.observationDateTime'), 126) As ObservationDate,  
        JSON_VALUE(ObservationData, '$.dryBulbFarenheit') As Temperature,
        JSON_VALUE(ObservationData, '$.relativeHumidity') As Humidity
    FROM BasicWeatherDataJson
    WHERE
        JSON_VALUE(ObservationData, '$.location.city') = 'Appleton'
        AND JSON_VALUE(ObservationData, '$.location.state') = 'WI'
        AND CONVERT(datetime2(3), JSON_VALUE(ObservationData, '$.observationDateTime'), 126) > '2016-07-01'
        AND CONVERT(datetime2(3), JSON_VALUE(ObservationData, '$.observationDateTime'), 126) < '2016-07-02'

Not exactly the paragon of simplicity.  Further, the execution plan confirms, this query is doing a full table scan to go and find the data, so it is both slow and resource intensive.


So first we are going to create some virtual columns to one, make the table easier to work with and two, so that we can create an index in the next step.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
ALTER TABLE BasicWeatherDataJson
    ADD StationCode AS JSON_VALUE(ObservationData, '$.stationCode');

ALTER TABLE BasicWeatherDataJson
    ADD City AS JSON_VALUE(ObservationData, '$.location.city');
     
ALTER TABLE BasicWeatherDataJson
    ADD State AS JSON_VALUE(ObservationData, '$.location.state');

ALTER TABLE BasicWeatherDataJson
    ADD ObservationDate AS CONVERT(datetime2(3), JSON_VALUE(ObservationData, '$.observationDateTime'), 126);

ALTER TABLE BasicWeatherDataJson
    ADD Temperature AS JSON_VALUE(ObservationData, '$.dryBulbFarenheit');

ALTER TABLE BasicWeatherDataJson
    ADD Humidity AS JSON_VALUE(ObservationData, '$.relativeHumidity');

And now for the indexes.

1
2
3
4
5
CREATE INDEX IX_BasicWeatherJson_ObservationDate_StationCode
    ON BasicWeatherDataJson (ObservationDate, StationCode);

CREATE INDEX IX_BasicWeatherJson_ObservationDate_State_City
    ON BasicWeatherDataJson (ObservationDate, State, City);

When you create these indexes, you will get a warning that you may be exceeding the maximum non-clustered index key size, because theoretically you could be pulling a very large string out of the JSON.  If you have larger strings, you want to make sure that these will fit under the 1700 byte limit for the key size of an index, and in our case, we are well below that, so we are fine to continue.

So now, we can rewrite our query so it looks like this:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
SELECT 
        ObservationId,
        StationCode,
        City,  
        State,
        ObservationDate,  
        Temperature,
        Humidity
    FROM BasicWeatherDataJson
    WHERE
        City = 'Appleton'
        AND State = 'WI'
        AND ObservationDate > '2016-07-01'
        AND ObservationDate < '2016-07-02';

This is much simpler and performs much better, as now the query is using one of our indexes.  So again, pretty cool that we can not just reach into the JSON to grab the values out, but we can also index any key properties we have so we can quickly search through the data that we have.

Summary
So there are the basics of storing JSON in SQL Server and interacting with scalar values.  in part 2, we'll take a look at interacting with arrays that may be stored in your JSON data.

Friday, April 14, 2017

SQL Server Temporal Tables

One of the newest and most useful features of SQL Server is Temporal Tables, which are available in SQL Server 2016 and SQL Azure.  In a nutshell, temporal tables give you a simple way to capture all of the changes that are made to rows in a table.  Of course you could do this in prior versions of SQL Server by defining your own history table and trigger, but now this functionality is built directly into SQL Server.

Why is this useful?  Have you ever had to go back and audit when a piece of data changed in a table?  Maybe you have needed to see the history of all of the changes to a certain record in the table?  Or needed to recreate the table as it was at a certain point of time.  If any of these apply, then temporal tables can be a big help.

Defining a Temporal Table
The syntax for defining a temporal table is straightforward and shown below.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
 CREATE TABLE Contacts
 (
     ContactID        INT IDENTITY(1,1)      NOT NULL,
     FirstName        VARCHAR(30)            NOT NULL,
     LastName         VARCHAR(30)            NOT NULL,
     CompanyName      VARCHAR(30)            NULL,
     PhoneNumber      VARCHAR(20)            NULL,
     Email            VARCHAR(50)            NULL,
     ValidFrom        DATETIME2(3) GENERATED ALWAYS AS ROW START,
     ValidTo          DATETIME2(3) GENERATED ALWAYS AS ROW END,
     PERIOD FOR SYSTEM_TIME (ValidFrom, ValidTo), 
     CONSTRAINT PK_Contacts PRIMARY KEY (ContactId)
 )
 WITH (SYSTEM_VERSIONING = ON (HISTORY_TABLE = dbo.ContactsHistory));

Lines 2 through 8 contain our normal column definitions for the table, in this case, a simple table to manage contact information.

We then need to define two columns that represent when the column is start and end times that the row is considered active, and this is done on lines 9 and 10 with the ValidFrom and ValidTo columns.  You can use any names for these columns you want, its just a good idea to make sure the names convey what these columns represent, the time the row is active.  These columns will have a DATETIME data type, and in this case I am using a DATETIME2(3) data type so we will store these values down to millisecond precision.  Finally, you need to include the text GENERATED ALWAYS AS ROW START and GENERATED ALWAYS AS ROW END for your respective start and stop columns.  This signals to SQL Server to automatically populate these columns with the UTC value of the system time when a row is inserted, updated or deleted.

On line 11, we have some syntax that indicates that our ValidFrom and ValidTo columns represent a period in time.  Finally, at the end of our table definition on line 14, we tell SQL Server that we need to have System Versioning of this table on and give SQL Server the name of the history table that we want to use.

If you don't want users to see the ValidFrom and ValidTo columns, you can hide these columns like so:

1
2
3
4
5
ALTER TABLE Contacts   
   ALTER COLUMN ValidFrom ADD HIDDEN;   

ALTER TABLE Contacts   
   ALTER COLUMN ValidTo ADD HIDDEN; 

If you change your mind and want to have them show up again, then simply do this.

1
2
3
4
5
ALTER TABLE Contacts   
   ALTER COLUMN ValidFrom DROP HIDDEN;   

ALTER TABLE Contacts   
   ALTER COLUMN ValidTo DROP HIDDEN;  
That just drops the hidden attribute, not the column itself.

When you execute this statement, SQL Server will create both this table and the specified history table.  Where things get interesting is when you start inserting and updating data in your table.

Working With Temporal Tables
You insert, update and delete data in your temporal table just like you would any other table.  The only thing you need to know is that you do not specify values for the ValidFrom or ValidTo columns.  SQL Server will take care of populating these columns for you.

So what happens when we perform DML operations against out temporal table?


Operation Description
INSERT The row will be inserted into the primary table (in this case contacts).

The ValidFrom column is populated with the current system time in UTC

The ValidTo field is populated with '9999-12-31 23:59:59.999' (the system max time)

No entry is made in the history table.

UPDATE The existing row will be moved from the primary table to the history table.  When this is done, its ValidTo time is populated with the current system time in UTC.

The existing row is then replaced by the new row in the primary table.  The new row will have a ValidFrom of the current system time in UTC and the ValidTo field will be the system max time.

DELETE The existing row will be moved from the primary table to the history table with its ValidTo time populated as the current system time in UTC

No row will be present in the primary table, since the row has been deleted.


Querying Data
SQL Server also gives us some new constructs to query data out of our temporal tables and see what has changed in time or what the state of the data was at any point in time.  What is nice about these constructs is that we don't have to query the primary table and the history table individually and then union them together in order to get a complete view of our data over time.  SQL Server does this for us behind the scenes, leaving us with a much simpler query syntax.  So lets take a look.

First of all, we can query the table normally which will show us all of the rows that are currently active.

1
2
3
-- Plain old SQL
SELECT * 
    FROM Contacts;

No surprises here in terms of the data that comes back, just what rows are currently in the primary table


We can see these rows have been inserted at different times over a few days, and we see that all of the ValidTo dates are set to the system max time.  But more has been going on with this table, so lets check that out.

Show Complete History of a Table
If you want to look at every instance of every row that has been in a table, you can use the FOR SYSTEM TIME ALL clause immediately after your table name in your FROM clause like this:

1
2
3
4
5
-- Gets all records
SELECT *
    FROM Contacts
        FOR SYSTEM_TIME    
            ALL;

Now lets take a look at these results.

So now we see that there have indeed been some changes to the data in our table over time.  We see the 5 active rows that we saw before, but we also see some additional rows.  This view shows that we had two contacts that were deleted, contact number 4 for Garfield Arbuckle and contact number 6, Odie Arbuckle.  In addition, we see that the record for contact number 1 Charlie Brown has changed, and specifically it looks like his phone number changed in the row.  And by looking at the ValidTo date, we can tell when each of these events occurred.


What Did the Data Look Like at a Point In Time?
We can use the FOR SYSTEM TIME AS OF clause to view the data in a table as it appeared in a point in time as follows.

1
2
3
4
SELECT *
    FROM Contacts
        FOR SYSTEM_TIME    
            AS OF '2017-04-12 14:00:00.0000000';

This query will view the table as it was as of April 12, 2017 at 14:00 UTC.  Remember when specifying this time that you need to use UTC time as that is what is contained in the ValidFrom and ValidTo tables and not your system local time.

It should also be pointed out that we could very well attach a WHERE clause to this query to get just a subset of rows or even a single row.  In our case though, here are the results that we get.

This shows us that at this time, records for Sally Brown, Franklin Armstrong and Odie Arbuckle had not even been added yet and that the record for Garfield Arbuckle was still active.  This can be extremely useful when you are trying to recreate a report that was run at a certain time, because you can see exactly what the data was in the table at that instant in time.

Querying All Records Active for a Period of Time
Finally, we have some new syntax which will show us any record that was active during a given time period.

  • FROM <start date> TO <end date> - Shows all rows that were active during this time period, but excludes rows that became active on the boundary
  • FOR SYSTEM TIME BETWEEN <start date> AND <<end date> - Shows all rows active in this time period, including rows that became active on the upper boundary of the time period
  • FOR SYSTEM TIME CONTAINED IN (<start date>, <end data>) - Gets all the rows that opened and closed during the specified time period

Summary
I've found temporal tables to be one of the best additions to SQL Server.  They are super easy to use and being able to go back and look at all of the changes are to your table is extremely helpful.  They are so easy to use that I find almost no reason not to use them.  So next time you need to create a table, think about making the table a temporal table.  The first time you need to look up what has changed about a record, you will be glad that you did.


Further Reading
Official Microsoft Documentation
https://docs.microsoft.com/en-us/sql/relational-databases/tables/temporal-tables


Wednesday, November 9, 2016

Why I Avoid Using Hints in my Database Queries

For those who have watched my Pluralsight courses on either Oracle or SQL Server performance tuning, you will notice that I don't talk about database hints in either course.  This is intentional on my part.  I tend not to use hints in any of my production SQL statements and overall I only use hints in very limited cases.

I made the decision not to cover hints in my courses because too often times, I have seen hints used incorrectly, often by someone who didn't understand what the hint was doing.  I've seen cases where someone read about hints "on the Internet" and thought by dropping a hint into their SQL statement, it would act as some sort of magical performance booster for their statement.  Unfortunately, there is no magic going on, and like any technology or technique, the result can actually be a worse outcome if you don't understand what is going on.

So lets explain what a little bit about hints.

The Query Optimization Process
When you submit a SQL statement to any database, a piece of software called the Query Optimizer parses the SQL statement and determines the fastest way to process that statement.  It is the query optimizer that determines if an index can be used or a table scan should be performed.  It also determines how to perform joins between tables and in what order the tables should be joined if you have multiple join conditions.

To determine the most efficient way to process your statement, the optimizer looks at the statistics of the tables involved in the statement, including the total number of rows and the distribution of those rows in the table.  It also looks at the indexes on the table and the number of unique keys in the index and matches all of this data up with the where clauses and join criteria you have specified in your statement.  Using these statistics, the optimizer can estimate the cost of all of the different ways to perform the SQL statement, and it will pick the lowest cost combination of these operations.

All of this usually happens in 100 milliseconds or less.  And optimizers today are really, really good at picking the right execution plan that will result in the fastest way to execute your statement.

What Does a Hint Do
When you supply a database hint, you are taking control of how the statement will execute and taking this control away from the query optimizer.  The problem is that the best way to execute a statement may change over time.  Maybe a table gets more data or the distribution of the data in a table changes.  Under normal circumstances, the query optimizer can adjust to these changes and come up with a new plan which is most efficient for the current data set.

When you provide a hint, you are taking this flexibility away from the optimizer.  So now as things change in your database, the optimizer cannot adjust.  So now, you have a sub-optimal plan, and many times that plan is much less efficient than the plan the query optimizer would have come up with on its own.  The problem is that while you can figure out the right hint for the way the data looks today, you have no way of knowing what the data will look like tomorrow.  Yet by providing a hint, you are really committing to a specific execution plan, even if that plan is wildly inefficient for tomorrows problem.

Is There Ever a Time to Use Hints
Sometimes I am surprised when I get a certain execution plan.  For example, I may have expected the optimizer to use a different index than what it did.  So in my SQL editor, I'll use a hint to look at that different version of the plan.  In almost every case, the plan produced by the optimizer is less expensive than the plan with the hint.  Its not that I don't trust the optimizer, but sometimes being able to contrast the two execution plans helps me understand why the optimizer is making the decisions it is.

The other time to use a hint would be if you were instructed to do so by Oracle or Microsoft technical support.  I've never had to do this, and I'm guessing these instances are very few and very far between.

Summary
I really feel like it is best to avoid using hints in any production SQL statements your application might run.  At first, they may seem magical.  But the optimizers included in today's database products are really, really good.  When you start including hints in your statement, you are taking control away from the optimizer and its ability to find the best plan for the current state of the table.  This almost always results in a less efficient plan and therefore slower running SQL statement.  So I encourage you to avoid using hints in any production SQL you might run.

Saturday, October 29, 2016

Slides from my MKE Dot Net talk on SQL Server Performance

Thanks to everyone who attended my talk at MKE Dot Net today.  The slides are available by clicking on the image or the link below.



MKE Dot Net - What Every Developer Should Know About SQL Server Performance - Slides

Thanks again for attending.

Sunday, July 24, 2016

What About Indexing SQL Server Tables That do Not Have a Primary Key or Cluster Key

I had a question in the comments section of my recent Pluralsight course, and the basic premise of the question was, "If your table does not have a primary key or cluster key, is it still worth it to create an index on the table?".  The answer to this question is absolutely, yes.  I want to take this opportunity to discuss this topic further though.

The Most Common Scenario - A Review
In my course, I describe a the most typical setup in SQL Server, such that when you create a table with a primary key, the primary key column(s) are used as the cluster key of the table, and the cluster key is what the data in the table is physically sorted by.  What this means is that the actual table data in SQL Server is stored in a B-tree structure something like what you see below.



In computer science, a B-tree structure can be searched very rapidly, as only a handful of comparisons are required to get you to the lowest level of the table (the leaf level) where the data is stored.  This is why looking up data in SQL Server by the primary key value is so fast, because by default, the primary key is also the cluster key, and then SQL Server can take advantage of this tree structure to rapidly find the associated row of data.

Typically though, you need to search for data in a table on some other field or fields than the primary key.  For example, you might not know a student's id number, but you know their first and last name.  Without an index, SQL Server would have to scan through all of the rows of the table, and this would not just take a lot of time, but also be very resource intensive in terms of system resources (CPU and disk IO).  So what we do is create an index on these columns since we commonly use them to search for data in the table.  This index uses the same B-tree structure, but now this tree structure is organized (sorted) by the columns in the index key -- last name and first name in our case.  And in the leaf nodes of the index (the bottom level), the index doesn't contain the data for the row, but instead the value of the primary key of the table.



So what SQL Server will do is first traverse the index, which again is very fast because we are traversing a tree structure, and find all of the index keys that match the input criteria (WHERE clause) specified.  Then, it will get the primary key values out of the index and go over to the table and look those values up by their primary key.  And as we said before, these lookups in the table are very fast because the table is stored in a tree structure organized by the primary key.

How We Got Here
What I did not mention in the course are some of the other scenarios that can occur.  There is always a dilemma when putting together a course about what to put in and what to leave out, and I chose not to cover the less common scenarios because I felt like it was more important to make sure the viewer had a good understanding of the most common scenario.  What I will do it go over those other scenarios here.

A Table With No Cluster Key
It is possible in SQL Server to create a table with no cluster key.  In this case, the rows of the table are stored in what is called a heap, which is just a way of saying that some space is allocated on disk and the rows of the table are stored in no particular order within that allocated area.  So now we don't have our rows organized in a nice tree structure, they are just stored in effectively random order in whatever pages on disk the table is using.

In this case, you can still (and should) create indexes on your table over the columns you search the table by.  In this case though, the record in the index doesn't contain the primary key value, because that wouldn't really help us since our data isn't organized by primary key any more.  Instead, it will contain a row pointer value that points tells SQL Server where it can find the corresponding row in the table.  This row pointer value will include the the page the where the data is stored as well as a row identifier so it can find the row in the page.  With this information, SQL Server can very quickly locate where the actual data for the row is, read it off of disk and return it to you.

A Table Key with a Cluster Key Different Than The Primary Key
It is also possible to create a table that has a cluster key that is not the primary key.  That is, in a fictional students table, my primary key is a column named StudentId, but I am going to make my cluster key on the columns LastName and FirstName.  This would mean that the data I store in my table would be physically organized (sorted) by the combination of LastName and FirstName.

You might think that would be a good idea, because after all, if I commonly search for students by their first and last name, then having the data organized like this would be making searching super fast, and I'd avoid the lookup step where you have to go look the actual row up by the student id after you have found the entry in the index.  But before you do that, you should read this superbly written article by Michelle Ufford.

Effective Clustered Indexes - https://www.simple-talk.com/sql/learn-sql-server/effective-clustered-indexes/

The problem is that as you insert data into the table, you are going to need to be inserting data in the middle of the table.  That will lead to page splits, which will over time degrade your performance.  Since our data is stored in a tree structure, it is much better if we are inserting new data at the end of the tree, not somewhere in the middle.

Further, we want our cluster key be unique, since SQL Server has to have some way to uniquely identify rows.  If you choose a cluster key that is not unique, SQL Server will have to add a unique identifier value to the cluster key such that it can uniquely identify the rows.

Back to our question though, and lets say you have a table that has a cluster key different than the primary key and further, the cluster key is not unique.  Should you still create indexes on the table?  Absolutely, because otherwise you will have to scan each and every row of the table to locate your data.  You will pay a small price because the cluster key is not unique, but have appropriate indexes will still be very beneficial to the overall performance of your application.

In Summary
The rule still holds that for a table of any size, you want to make sure that your SQL Statement is using an index whenever accessing the table.  The execution plan and resulting data paths might look a little different, but a well thought out index will still provide a major performance improvement for your SQL statements.









Sunday, April 24, 2016

DMV Queries From my Pluralsight Course "What Every Developer Should Know About SQL Server Performance"

I've actually broken these queries up into separate blog posts, so here are the links to the individual posts where you will find the queries.

Exposing SQL Server DMV Data to Developers and other non-DBA Users

Using DMV's to Find SQL Server Connection and Session Info

Which Statements Are Currently Running in my SQL Server Database

Finding the Most Expensive, Longest Running Queries in SQL Server

Analyzing Index Usage in SQL Server
(Contains queries for both missing indexes and index usage stats)

Using DMV's to Find SQL Server Connection and Session Info

It is often times useful to understand how your application is connecting to SQL Server and any other connections that may be present to your application's database.  This can be understood by lookng at the sys.dm_exec_sessions view.  Here is a sample query to use:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
SELECT
    database_id,    -- SQL Server 2012 and after only
    session_id,
    status,
    login_time,
    cpu_time,
    memory_usage,
    reads,
    writes,
    logical_reads,
    host_name,
    program_name,
    host_process_id,
    client_interface_name,
    login_name as database_login_name,
    last_request_start_time
FROM sys.dm_exec_sessions
WHERE is_user_process = 1
ORDER BY cpu_time DESC;


This simply lists all of the current connections to SQL Server, but we also get some useful information with each connection.  We see we have fields like cpu_time (in milliseconds), memory_usage (in 8 KB pages), reads, writes and logical_reads.  So if we have a session that is consuming a lot of resources in SQL Server, we can immediately see that by looking at these fields.

We also have columns like host_name, program_name and host_process_id which can help us identify where a connection is coming from.  You would be surprised how many times you look at a production database and see users connecting directly to the database from applications like Management Studio or even Excel.

We can write a little bit different query to get a different look at this data:


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
SELECT
    login_name,
    host_name,
    host_process_id,
    COUNT(1) As LoginCount
FROM sys.dm_exec_sessions
WHERE is_user_process = 1
GROUP BY
    login_name,
    host_name,
    host_process_id;

This counts up all of the logins by login name and client process.  So if you have an ASP.NET web application, you would see each worker process with the number of connections the worker process has to the database, plus any other processes that might be connected up.  Sometimes this is a useful view, because you can validate that indeed all of the machines in your web cluster are indeed talking to the database and you can get a feel for how many connections they have in their connection pools.

It might seem simple to know who is logged in, but what this can help you do is understand any connectivity issues you may be having and see if there is a session using a lot of resources, so for those reasons, these simple queries against the dm_exec_sessions view can tell you quite a bit.


Saturday, April 23, 2016

Exposing SQL Server DMV Data to developer and other non-DBA Users

As a developer, it is very useful to have access to a number of SQL Server's Dynamic Management Views so you can view performance related statistics about how your application is interacting with the database.  This includes finding which of your SQL Statements are taking the longest, if SQL Server thinks there are any missing indexes and usage data for your existing indexes.

Unfortunately, access to DMV's in SQL Server requires the VIEW SERVER STATE permission.  Many DBA's are reluctant to grant this permission to members of the development team because of the amount of data that one can see when they have this level of access.  Unfortunately, this removes an important set of performance tuning tools from the developer's toolbox or forces them to ask a DBA for help whenever they need this information.  There has to be a better way.

What we need is a way that we can grant to a developer or any other non-DBA type user access to a select subset of DMV's information.  For example, it is useful for a developer to see query execution statistics, missing index information and index usage data.  Further, we would like to limit the information to just the database a developer is working in.  So how do we do this?

You would think this would be easy to do--create a view as a dba user that exposes only the information you want a developer to access and then grant the developer SELECT permission on that view.  This fails though with the following message.

Msg 300, Level 14, State 1, Line 1 VIEW SERVER STATE permission was denied on object 'server', database 'master'. Msg 297, Level 16, State 1, Line 1 The user does not have permission to perform this action.


The next thought you might have is to create a table valued function with EXECUTE AS set to a user that has permissions to view the DMV's.  However, this also fails with an error message complaining about the module not being digitally signed.

There is a solution, but it turned out to be much more complex than I would have initially thought.  My approach is based on this blog post by Aaron Betrand, though I had to do things a little bit differently than he did.

The Solution

What really helps is to visualize the solution before we start going through the individual steps, so here is what it looks like.



What we have to do is create a database (called PerfDB in this example) that is going to be a holding area for a set of table valued functions and views that we create to expose our DMV information.  This database will have the option TRUSTWORTHY ON option set, which will allow our table valued function to execute as a user with VIEW SERVER STATE permissions and access the DMV's (this gets around the error about function not being digitally signed that was mentioned above).  Then, we can create the rest of our objects to expose the DMv information we want to.  This approach gives us a way to expose just the information we want, which is what we are after.

Let's walk through the individual steps we need to take for all of this to work.


Step 1 - Create your PerfDB container database
So to get started, we'll create the PerfDB database.  I did this in SQL Server Management Studio using the UI, mainly because that is the easiest way for me to do it.

Once the database is created, I open a query window and run the following command to set the TRUSTWORTHY option to ON.

1
ALTER DATABASE PerfDB SET TRUSTWORTHY ON;

Some DBA's might have some concerns about setting TRUSTWORTHY to ON, but that is the reason for using a separate database that is just a container for the objects we create.  By separating these objects out into a separate database, we've limited any security risks we might have.


Step 2 - Create the Custom DMV Performance Functions in the PerfView Database
Now we are ready to create a table valued functions which will query DMV's of interest for us.  In this example, I am creating a function that will return back the query execution statistics in SQL Server.  You would create one of these functions for each piece of DMV info you wanted to expose.

The function basically just executes a query against a DMV or DMVs and returns that data in a table object.  In this function, you can see I am taking in a parameter of the database id so that in the next step, I'll be able to able to limit access for a user to only seeing data for one database.

You will need to create this function as a user that has the VIEW SERVER STATE privilege, so this could be an existing user or you could create a separate, dedicated user that owns this function and others like it.  What is important is that this function has the WITH EXECUTE AS SELF option set, so that it will run as the user who created the function.  This will allow this function to query the DMV's even if the calling user does not have permission to see the DMV's.


 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
CREATE FUNCTION dbo.GetSqlExecutionStatistics(@database_id INT)
    RETURNS @x TABLE 
    (
        DatabaseId            INT,
        DatabaseName          VARCHAR(100), 
        ExecutionCount        BIGINT, 
        CpuPerExecution       BIGINT,
        TotalCpu              BIGINT,
        IOPerExecution        BIGINT,
        TotalIO               BIGINT,
        AverageElapsedTime    BIGINT,
        AverageTimeBlocked    BIGINT,
        AverageRowsReturned   BIGINT,
        TotalRowsReturned     BIGINT,
        QueryText             NVARCHAR(max),
        ParentQuery           NVARCHAR(max),
        ExecutionPlan         XML,
        CreationTime          DATETIME,
        LastExecutionTime     DATETIME
    )
    WITH EXECUTE AS OWNER
    AS
BEGIN

    INSERT @x (DatabaseId, DatabaseName, ExecutionCount, CpuPerExecution, TotalCpu, IOPerExecution, TotalIO, 
     AverageElapsedTime, AverageTimeBlocked, AverageRowsReturned, TotalRowsReturned, 
  QueryText, ParentQuery, ExecutionPlan, CreationTime, LastExecutionTime)
 SELECT
        [DatabaseId] = CONVERT(int, epa.value),
        [DatabaseName] = DB_NAME(CONVERT(int, epa.value)), 
        [ExecutionCount] = qs.execution_count,
        [CpuPerExecution] = total_worker_time / qs.execution_count ,
        [TotalCpu] = total_worker_time,
        [IOPerExecution] = (total_logical_reads + total_logical_writes) / qs.execution_count ,
        [TotalIO] = (total_logical_reads + total_logical_writes) ,
        [AverageElapsedTime] = total_elapsed_time / qs.execution_count,
        [AverageTimeBlocked] = (total_elapsed_time - total_worker_time) / qs.execution_count,
        [AverageRowsReturned] = total_rows / qs.execution_count,    
  [TotalRowsReturned] = total_rows,
        [QueryText] = SUBSTRING(qt.text,qs.statement_start_offset/2 +1, 
            (CASE WHEN qs.statement_end_offset = -1 
                THEN LEN(CONVERT(nvarchar(max), qt.text)) * 2 
                ELSE qs.statement_end_offset end - qs.statement_start_offset)
            /2),
        [ParentQuery] = qt.text,
        [ExecutionPlan] = p.query_plan,
        [CreationTime] = qs.creation_time,
        [LastExecutionTime] = qs.last_execution_time   
    FROM sys.dm_exec_query_stats qs
    CROSS APPLY sys.dm_exec_sql_text(qs.sql_handle) as qt
    OUTER APPLY sys.dm_exec_query_plan(qs.plan_handle) p
    OUTER APPLY sys.dm_exec_plan_attributes(plan_handle) AS epa
    WHERE epa.attribute = 'dbid'
     AND CONVERT(int, epa.value) = @database_id;

 RETURN;
END


Step 3 - Create a Database Specific View to Wrap Your Function
I'll create a view specific to the database I want to expose data for and pass in the appropriate database id to the function created in step 2.  The reason I am doing this is because ultimately, I'll give my users SELECT access on this view and not directly on the table valued function.  This way I have the logic (my query) in just one place (the function) that I can use over and over again, but what my users will see is their specific view for their specific database.


1
2
CREATE VIEW AppDatabaseName_SqlExecutionStatistics AS
    SELECT * FROM GetSqlExecutionStatistics( <<application database id>> );

You would replace AppDatabaseName in the name of this view with the name of your application database.  You would also create a view for each different app database you wanted developers to have access to.

If you need to know the database id of your database, this information can be obtained from the sys.databases view in the master database.


Step 4 - Grant Permission for Users to Query From the View
For your user's to be able to query this information, we need to grant them the SELECT privilege to the view we just created in step 4.  But before we do that, we need to map their login to a user in the PerfDB database.  Basically, they have to have access to the database before they can even see the view, so we can do this in the Management Studio GUI or by running the following command.


1
2
3
USE PerfDB;

CREATE <<username>> FROM LOGIN <<user login name>>;

For every user you want to have access to this information, you need to do this.  So that means if you have five developers who need access to this information, you'll need to give all five of those developer logins access to the PerfDB database in this way.

Then, we can grant permission so those user's can query the view.


1
2
GRANT SELECT ON AppDatabaseName_SqlExecutionStatistics 
    TO <<username>>;

You will probably want to create a role that has select permissions for all of the DMV views you create for a database, and then put user's inside of that view.  The important point though is that you have to get the user's permission to be able to select from the view.


Step 5 - Querying the Data
Querying the data is as simple as Querying the view we just created, and this can be done by our non-DBA user who does not have VIEW SERVER STATE permission.


1
2
SELECT * 
    FROM PerfDb.dbo.AppDatabaseName_SqlExecutionStatistics;

If you want to make things a little easier, you can create a synonym in the application database so user's don't have to fully qualify the object name in their queries.


1
2
3
CREATE SYNONYM [dbo].[QueryExecutionStatistics] 
    FOR [PerfDb].[dbo].[AppDatabaseName_SqlExecutionStatistics]
GO 

And now users can simply run this query


1
SELECT * FROM QueryExecutionStatistics;

Now, normal user's who have access to this view can access the information contained within.  This also allows you to selectively choose what information is exposed from your DMV's since you control the query inside of the table valued function and you can limit the results to data from just a single database.

Its unfortunate there isn't a more straightforward way to do this, but at least it can be done.  And of course all of this work can be incorporated into scripts, making it much easier to run.