Category Archives: #SQLServer

All about Microsoft SQL Server

#0267 – SQL Server – IDENTITY columns – Myths – IDENTITY columns cannot have holes or “gaps”


Whenever a pattern evolves into a frequently used practice, it does not take long for myths to start getting associated with it. IDENTITY columns have become such a common pattern that often I hear about these myths from novice developers. Using this series, I hope to bust those myths once and for all.


Busting the Myth – IDENTITY columns cannot have holes


Whenever a break in a series is encountered, it is referred to as a hole. For example, if we have a series of numbers from 1 to 5 such that the number 2 is missing (1, 3, 4, 5), then we have a hole.


I often hear from developers that “Holes cannot be generated in the values of a column defined as the IDENTITY column” is a prime reason why one uses IDENTITY columns. This is in-fact incorrect as I will be proving by this post.


The Test


To provide that holes can be generated in an IDENTITY column, I will use the script shown below. The script:



  1. Prepares the environment by creating a table and adding some test data into it

  2. Check the current identity value of the table using IDENT_CURRENT()

  3. Opens a transaction

  4. Performs an insert into a table which has the IDENTITY property defined

  5. Later, the transaction is rolled back, and the current identity value of the table is queried using IDENT_CURRENT()

  6. To confirm, a record is inserted into the table and the values selected – clearly indicating the presence of the hole

USE tempdb;
GO

SET NOCOUNT ON;

--1. Prepare the environment
--   Create a table, and add some test data into it

--Safety Check
IF OBJECT_ID('tIdentity','U') IS NOT NULL
    DROP TABLE dbo.tIdentity;
GO

--Create a table
CREATE TABLE dbo.tIdentity (IdentityId INT IDENTITY(1,1),
                            IdentityValue VARCHAR(10)
                           );
GO

--Add some test data
INSERT INTO dbo.tIdentity (IdentityValue)
VALUES ('One'),
       ('Two'),
       ('Three');
GO

--2. Check the current identity value of the table using IDENT_CURRENT()
SELECT IDENT_CURRENT('dbo.tIdentity') AS IdentityValueBeforeTest;
GO

--3. Perform the test
--3a. Open a transaction
BEGIN TRANSACTION IdentityHolesCheck
    --3b. Perform an insert
    INSERT INTO dbo.tIdentity (IdentityValue)
    VALUES ('Four');
--3c. Rollback the transaction
ROLLBACK TRANSACTION IdentityHolesCheck;
GO

--4. Check the current identity value of the table using IDENT_CURRENT()
SELECT IDENT_CURRENT('dbo.tIdentity') AS IdentityValueAfterTest;
GO

--5. Confirmatory test
INSERT INTO dbo.tIdentity (IdentityValue)
VALUES ('Five');
GO

--6. Select the values from the table
SELECT ti.IdentityId, ti.IdentityValue
FROM dbo.tIdentity AS ti;
GO

--Cleanup
IF OBJECT_ID('tIdentity','U') IS NOT NULL
    DROP TABLE dbo.tIdentity;
GO

/**********************************************
               RESULTS
**********************************************/
/*
IdentityValueBeforeTest
-----------------------
3

IdentityValueAfterTest
----------------------
4

IdentityId  IdentityValue
----------- -------------
1           One
2           Two
3           Three
5           Five
*/

While this was a simulation, in a practical world this situation is similar to a failed insert. Even if the INSERT did not go through, the IDNETITY value was incremented leaving the hole behind.


Conclusion


The test shown above clearly proves that it is possible to have holes in an IDENTITY column. Any code that expects an uninterrupted series of values may break if this has not been taken into consideration.


Until we meet next time,


Be courteous. Drive responsibly.

#0266 – SQL Server – IDENTITY Columns – @@IDENTITY, SCOPE_IDENTITY, IDENT_CURRENT and $IDENTITY


IDENTITY columns are perhaps the least understood and yet most used features of Microsoft SQL Server.


In this post, I will explore some system functions and keywords that we as developers and administrators can use to get information about IDENTITY columns.


Whenever a record is inserted to a table using an IDENTITY column via an INSERT, SELECT INTO or bulk copy command, the identity values on the affected tables are incremented. Microsoft SQL Server gives us three system functions that can provide the last used identity value – depending upon the scope and session.


IDENT_CURRENT()


Given a table name, this system function returns the last identity value used for the given table or view. Since this function works on a particular table or view, this value is independent of the session or the scope.


SCOPE_IDENTITY()


This system function provides the last used IDENTITY value within the same scope.


Statements inside the same stored procedure, trigger or batch comprise of the same scope – those within triggers fired as a result of a statement inside a stored procedure aren’t.


If no statement has been executed which affects an IDENTITY value, this function returns NULL.


@@IDENTITY


The @@IDENTITY system function contains the last value that was generated by the statement – irrespective of the scope.


If no statement has been executed which affects an IDENTITY value, this function returns NULL. However, please note that this function reports the identity value last generated by the statement – irrespective of the scope. This means that if a statement causes execution of a trigger which generates identity values in other tables, calling the @@IDENTITY function immediately after this statement will return the last IDENTITY value generated by the trigger.


Let’s look at each of these via an example:

USE AdventureWorks2008R2;
GO
SET NOCOUNT ON ;

SELECT  IDENT_CURRENT('Purchasing.PurchaseOrderDetail') AS PODetailIdentity,
        IDENT_CURRENT('Production.TransactionHistory') AS TransactionlIdentity,
        SCOPE_IDENTITY() AS ScopeIdentityValue,
        @@IDENTITY AS LastUsedIdentity;

    --Perform the INSERT into the table: Purchasing.PurchaseOrderDetail
    --The insert trigger on this table: Purchasing.iPurchaseOrderDetail, causes a record
    --to be inserted into Production.TransactionHistory, thereby incrementing it's IDENTITY column - TransactionID
    INSERT  INTO Purchasing.PurchaseOrderDetail
            (
              PurchaseOrderID,
              DueDate,
              OrderQty,
              ProductID,
              UnitPrice,
             --LineTotal,
              ReceivedQty,
              RejectedQty,
             --StockedQty,
              ModifiedDate
            )
            SELECT  1 AS PurchaseOrderId,
                    '2005-05-31' AS DueDate,
                    1 AS OrderQty,
                    4 AS ProductId,
                    57.02 AS UnitPrice,
                    --258.06 AS LineTotal,
                    1 AS ReceivedQty,
                    0 AS RejectedQty,
                    --1 AS StockedQty,
                    '2013-05-09' AS ModifedDate ;
    GO

SELECT  IDENT_CURRENT('Purchasing.PurchaseOrderDetail') AS PODetailIdentity,
        IDENT_CURRENT('Production.TransactionHistory') AS TransactionlIdentity,
        SCOPE_IDENTITY() AS ScopeIdentityValue,
        @@IDENTITY AS LastUsedIdentity;

/****RESULTS****/
/*
        PODetailIdentity    TransactionlIdentity    ScopeIdentityValue    LastUsedIdentity
Before: 8847                213450                  NULL                  NULL  
After : 8848                213451                  8848                  213451
*/

Inserting a new record in the table: Purchasing.PurchaseOrderDetail causes the insert trigger on this table: Purchasing.iPurchaseOrderDetail to be executed. This trigger maintains a history of the PurchaseOrder Detail transactions in the table – Production.TransactionHistory, thereby incrementing it’s IDENTITY column – TransactionID.


As can be seen in the output:



  • IDENT_CURRENT() provides us the last used identity value for the given table

  • SCOPE_IDENTITY() provides us the last used identity value within the scope of the batch

  • @@IDENTITY provides us the last used identity value irrespective of the scope

$IDENTITY


The $IDENTITY keyword allows us to refer to an identity column in a table without using the corresponding column name. Here’s an example:

USE AdventureWorks2008R2 ;
GO
SELECT  $IDENTITY
FROM    Purchasing.PurchaseOrderDetail ;
GO

The query above will return the value of the [PurchaseOrderDetailId] in the table [Purchasing].[PurchaseOrderDetail] since it’s marked as an identity column.


Other functions – IDENT_INCR() and IDENT_SEED()


If one needs to look at the value of the seed and increment values used in the identity column definition, the IDENT_INCR() and IDENT_SEED() functions can be used.

USE AdventureWorks2008R2;
GO
SELECT IDENT_SEED('Production.TransactionHistory') AS IdentityIncrement,
       IDENT_INCR('Production.TransactionHistory') AS IdentitySeed;
GO
/* RESULTS */
/*
IdentityIncrement   IdentitySeed
100000              1
*/

Checking for Identity columns in a table


The Catalog View sys.identity_columns has one record for each identity column and provides information like the seed, increment, last value generated for that column and other meta-data information.


Usage is shown in the query below:

USE AdventureWorks2008R2 ;
GO
SELECT * FROM sys.identity_columns
WHERE object_id = OBJECT_ID('Production.TransactionHistory');
GO

Until we meet next time,


Be courteous. Drive responsibly.

#0265 – SQL Server – An introduction to IDENTITY columns


One of the most frequently used, but perhaps one of the least understood feature of Microsoft SQL Server has to be the IDENTITY columns.


IDENTITY columns are any columns that are used as identifier columns. They create an automatically incrementing identification number on a given table.


Over the course of time, I have seen many novice engineers make statements that imply certain common implementations of IDENTITY to be the default and only behavior that these columns exhibit. For example, it’s a common implementation to have the IDENTITY value increment by 1, but it is not the only implementation possible. As we will see in the upcoming posts, increments can have a lot of different flavours.


An Introduction


In order for Microsoft SQL Server to create an automatically incrementing identification number the IDENTITY property consists of two attributes:



  • SEED – this specifies the value that should be used by the first record inserted into the table

  • INCREMENT – this specifies the increment or the value to be added to the seed to determine successive identity numbers

Here are some general rules around IDENTITY columns:



  • A table may have only one column defined with the IDENTITY property

  • The column must be defined as NOT NULL and must not have a DEFAULT constraint defined on it

  • Valid data types for IDENTITY are decimal, int, bigint, numeric, smallint and tinyint

  • Default value for both seed and increment is 1

The question therefore comes up is:



Why are IDENTITY columns useful?


Why IDENTITY?


Generally, IDENTITY columns are used as primary key values. When I created my first table that used the IDENTITY property, I was unaware of its significance – I had used it because it was part of the design standard used by the product. Upon study, I later realized the following:




  • By their very nature, IDENTITY values make for “non-intelligent” keys


  • IDENTITY values are auto-generated when a record is inserted. What this means is that IDENTITY ensures that the values generated are naturally ordered – making them ideal candidates for a clustered index



    • Generating the clustered index on an IDENTITY column essentially means that the logical ordering of records essentially remains the same


    • Similarly, record retrieval is faster and key management also becomes easier


  • Because IDENTITY can be used on BIGINT datatypes, it has a very vast addressing capacity – generally sufficient for most OLTP systems

These are the primary reasons why IDENTITY columns have become an important and commonly used design choice for integer-based primary key values.



From my next post, I will be presenting a series on the various aspects of IDENTITY and myths that surround the IDENTITY column. Are there any questions that come to your mind when working with IDENTITY columns? Do leave a note and I will try my best to answer them as I move along.


Until we meet next time,


Be courteous. Drive responsibly.

#0264 – SQL Server – How to check if a database belongs to your product?


One question that I often face from teams is:



Given a set of SQL server databases, how do we identify that a given database belong to our product?


There are various mechanisms that can be used, depending upon how the underlying database design is. I have two mechanisms that I use frequently.




  1. Check for the existance of a particular table (a table that stores your product version or some table which holds product-specific static data)


  2. Many database designs (especially those derived from legacy systems) are such that all tables have a column that is used for various generic purposes (to indicate whether a particular record was modified as part of a data clean-up, or a GUID column to facilitate replication, etc)



    • In such database designs, one can check if all tables have the particular column. Pseudo-code to achieve this check efficiently is shown below:
      USE AdventureWorks2008R2 ;
      GO
      IF NOT EXISTS ( SELECT st.*
      FROM sys.tables AS st
      LEFT OUTER JOIN sys.columns AS sc ON st.object_id = sc.object_id
      AND sc.name = ‘BusinessEntityId’
      WHERE st.type = ‘U’
      AND st.object_id IS NOT NULL
      AND sc.object_id IS NULL )
      BEGIN
      PRINT ‘All tables contain the column – BusinessEntityId.’ ;
      END
      ELSE
      BEGIN
      PRINT ‘Some or all tables do not contain the column – BusinessEntityId.’ ;
      END
      GO


    • The benefit of the above query is that it queries the meta-data and is therefore faster than a custom iterative mechanism

I trust this post was helpful. I would also like to know what is the method you use for detecting your product database?


Until we meet next time,


Be courteous. Drive responsibly.

#0263 – SQL Server – Interview Question – Script to Identify DML Triggers (Active/Disabled) and Trigger Events


For an entry level DBA position, some of the common interview questions around the subject of database DML triggers are:



  • How many triggers exist in your database?

  • Can you classify them into INSERT, UPDATE and DELETE triggers?

This post presents a script that answers all the questions above. The script below involves querying the Catalog Views – sys.triggers and sys.trigger_events to answer these questions:

USE AdventureWorks2008R2 ;
GO
SELECT st.name AS TriggerName,
OBJECT_NAME(st.parent_id) AS ParentTableName,
st.is_ms_shipped AS IsMSShipped,
st.is_disabled AS IsDisabled,
st.is_not_for_replication AS IsNotForReplication,
st.is_instead_of_trigger AS IsInsteadOfTrigger,
te.type AS TypeId,
te.type_desc AS TypeDescription,
te.is_first AS IsTriggerFiredFirst,
te.is_last AS IsTriggerFiredLast
FROM sys.triggers AS st
INNER JOIN sys.trigger_events AS te ON te.object_id = st.object_id
WHERE st.parent_id > 0
AND st.is_ms_shipped = 0
ORDER BY st.parent_id, te.type ASC;
GO

/* Results:
TriggerName ParentTableName IsMS Is IsNotFor IsInstead Type Type IsTrigger IsTrigger
Shipped Disabled Replication Trigger Id Description FiredFirst FiredLast
——————– ——————– ——- ——– ———– ——— —- ———– ———– ———
dVendor Vendor 0 0 1 1 3 DELETE 0 0
iWorkOrder WorkOrder 0 0 0 0 1 INSERT 0 0
uWorkOrder WorkOrder 0 0 0 0 2 UPDATE 0 0
iPurchaseOrderDetail PurchaseOrderDetail 0 0 0 0 1 INSERT 0 0
uPurchaseOrderDetail PurchaseOrderDetail 0 0 0 0 2 UPDATE 0 0
uPurchaseOrderHeader PurchaseOrderHeader 0 0 0 0 2 UPDATE 0 0
iduSalesOrderDetail SalesOrderDetail 0 0 0 0 1 INSERT 0 0
iduSalesOrderDetail SalesOrderDetail 0 0 0 0 2 UPDATE 0 0
iduSalesOrderDetail SalesOrderDetail 0 0 0 0 3 DELETE 0 0
dEmployee Employee 0 0 1 1 3 DELETE 0 0
uSalesOrderHeader SalesOrderHeader 0 0 1 0 2 UPDATE 0 0
iuPerson Person 0 0 1 0 1 INSERT 0 0
iuPerson Person 0 0 1 0 2 UPDATE 0 0
*/


Here are some of the ways in which the query given above can be helpful in addition to answering interview questions:



  • Detemine if a trigger exists on a database

  • The IsDisabled flag shows whether a trigger is active or not

  • Is the trigger an INSTEAD OF trigger? 


Further Reading:



  • sys.triggers: [Link]

  • sys.trigger_events: [Link]

  • Catalog views and DMVs – An introduction: [Link]

  • SQL Server – 2012 – What’s new? – Dynamic Management Views (DMV) and Catalog View changes: [Link]

Until we meet next time,


Be courteous. Drive responsibly.