Tag Archives: Administration

Articles related to Microsoft SQL Server Administration

#0353 – SQL Server – Locate an object in a SQL Server Instance (using documented features)


A while back, I wrote a post on locating an object in a SQL Server instance (post #351). One of the items I received as part of the feedback was to implement the logic using documented (and therefore supported) features.

As you may know, I had used the sp_MSforeachDB system stored procedure in my earlier post. This is an undocumented procedure and therefore the recommendation is to assume that it is unsupported and not use it in production environments. In today’s post, I present before you a script that achieves the same objective (i.e. locate an object in a SQL Server instance), but using fully documented (and therefore fully supported) features.

Improvements over the script using sp_MSforeachDB:

  • Uses fully documented features
  • Does a comparison on both – the schema name and the object name
  • Search is only limited to user databases by default (system databases can be easily reintroduced by commenting out appropriate filter conditions in the query below)

The entire script is provided below for your reference:

USE master;
GO

SET NOCOUNT ON;

--Declarations
BEGIN
    --User to fill in
    DECLARE @schemaToSearch NVARCHAR(255) 
                            = N'dbo';
    DECLARE @objectToSearch NVARCHAR(255) 
                            = N'uspGetBillOfMaterials';

    --Internal variables
    DECLARE @databaseInformation TABLE 
                (DatabaseId INT NOT NULL,
                 DatabaseName NVARCHAR(255),
                 IsProcessed BIT NOT NULL
                );
    DECLARE @databaseId INT = 0;
    DECLARE @databaseName NVARCHAR(255) = N'';
    DECLARE @sqlStmnt NVARCHAR(1000) = N'';
END

--Fetch all user databases into temporary table variable
--for convenience
BEGIN
    INSERT INTO @databaseInformation (DatabaseId, 
                                      DatabaseName, 
                                      IsProcessed)
    SELECT sdb.database_id AS DatabaseId,
           sdb.name AS DatabaseName,
           0 AS IsProcessed
    FROM sys.databases AS sdb 
    WHERE CAST((CASE WHEN SUBSTRING(sdb.name,0,14) 
                          IN ('master',
                              'msdb',
                              'model',
                              'tempdb',
                              'ReportServer$') 
                          THEN 1 
                     ELSE sdb.is_distributor 
                END) AS BIT) = 0;
END

--Begin processing one database after another
WHILE EXISTS (SELECT dbi.DatabaseId 
              FROM @databaseInformation AS dbi 
              WHERE dbi.IsProcessed = 0
             )
BEGIN
    SELECT TOP 1
           @databaseId = dbi.DatabaseId,
           @databaseName = dbi.DatabaseName
    FROM @databaseInformation AS dbi
    WHERE dbi.IsProcessed = 0;

    --Build the statement to fetch the objectId of an object 
    --and then check for it's existence
    SET @sqlStmnt = N'USE ' + QUOTENAME(@databaseName) + N';
    DECLARE @objectId INT;
    SET @objectId = OBJECT_ID(''' 
                + QUOTENAME(@schemaToSearch) 
                + N'.' 
                + QUOTENAME(@objectToSearch) 
                + N''')
    IF EXISTS (SELECT *
               FROM sys.objects AS so
               WHERE so.is_ms_shipped = 0
               AND so.object_id = @objectId
             )
    BEGIN
        SELECT DB_NAME() AS DatabaseName;
    END;'

    --Execute the query
    --PRINT @sqlStmnt;
    EXEC sp_executesql @sqlStmnt = @sqlStmnt;

    --Mark the database as done
    UPDATE dbi
    SET dbi.IsProcessed = 1
    FROM @databaseInformation AS dbi
    WHERE dbi.DatabaseId = @databaseId
      AND dbi.DatabaseName = @databaseName; 

END
GO

Until we meet next time,
Be courteous. Drive responsibly.

#0352 – SQL Server – Working with Temporary Tables (L100) – Scope


In this post, I provide a Level 100 primer on the scope related considerations for local temporary tables.

While I have written about temporary tables a lot in the past (and the same has also been done by other writers), there are some questions that I keep encountering from the team or in various forums on the Internet which is why I am writing this post.

Temporary tables use the tempdb and come in two variants:

  1. Local Temporary Tables
    • As the name implies, they are local to the connection that created them
    • Therefore, if multiple connections request creation of a temporary table with the same name, each connection gets it’s own unique copy of the temporary table
    • The local temporary tables are implicitly deleted when the connection is closed
    • To create a local temporary table, prefix the desired table name with a single hash (#) mark, e.g. A local temporary table for employees might be named #Employee or #HumanResources
  2. Global Temporary Tables
    • These are global to the entire SQL Server instance and are therefore shared by all connections to the server
    • They are implicitly deleted when the last connection referencing them is closed
    • To create a global temporary table, prefix the desired table name with a double hash (##) mark, e.g. A global temporary table for employees might be named ##Employee or ##HumanResources

A demo on local temporary tables

The most important point that is missed out by most novice developers is that temporary tables are session specific. To briefly demonstrate this, allow me to follow the following steps:

  1. Open SQL Server Management Studio and launch two (2) query editor windows
    • In each window, connect to the same database and same SQL Server instance
  2. Run the following in window #1. This snippet creates a local temporary table and populates some records into it
--Window 01
USE AdventureWorks2012;
GO

--Create the local temporary table if it does not exist
IF OBJECT_ID('tempdb..#BasicEmployeeList','U') IS NULL
BEGIN
CREATE TABLE #BasicEmployeeList 
    ( BusinessEntityID INT          NOT NULL,
      JobTitle         NVARCHAR(50) NOT NULL,
      Title            NVARCHAR(8)      NULL,
      FirstName        NVARCHAR(50) NOT NULL,
      MiddleName       NVARCHAR(50)     NULL,
      LastName         NVARCHAR(50) NOT NULL
    );
END;
GO

INSERT INTO #BasicEmployeeList
    (BusinessEntityID,
     JobTitle,
     Title,
     FirstName,
     MiddleName,
     LastName
    )
SELECT p.BusinessEntityID,
       e.JobTitle,
       p.Title,
       p.FirstName,
       p.MiddleName,
       p.LastName
FROM Person.Person AS p
INNER JOIN HumanResources.Employee AS e
ON p.BusinessEntityID = e.BusinessEntityID;
GO
  1. Without closing window #1, Switch over to window #2
  2. Attempt to run the statement shown below
    • The system will encounter an error
USE AdventureWorks2012;
GO
SELECT bel.BusinessEntityID,
       bel.JobTitle,
       bel.Title,
       bel.FirstName,
       bel.MiddleName,
       bel.LastName
FROM #BasicEmployeeList AS bel;
GO

/* ERROR
Msg 208, Level 16, State 0, Line 1
Invalid object name '#BasicEmployeeList'.
*/
  1. The error is received because the temporary table was created as a local temporary table and is therefore inaccessible to other connections

Important points to note

  • Because temporary tables are persisted in the tempdb, the tempdb can see significantly large I/O operations if your application makes use of a lot of temporary storage
  • SQL Server gives Local Temporary tables an auto-generated name which is transparent to the caller to allow multiple connections to the use local temporary tables with the same name [Link]
  • Local temporary tables can be shared to referencing stored procedures as long as they use the same connection
    • This is documented in my post [Link]
  • Creating named constraints on temporary tables prevent them from being used concurrently
  • Temporary tables do not support:
    • Foreign key constraints [Links]
    • Triggers

Until we meet next time,

Be courteous. Drive responsibly.

#0351 – SQL Server – Locate an object in a SQL Server instance


Interacting with people on a SQL Server forum is always something that I look forward to. It gives me an insight into the challenges that are being faced out in the field – some of which I may have already encountered & addressed myself in the past, but thought it to be something trivial. A recent question was before me which prompted this post.

A friend of mine was involved in a knowledge transfer of a legacy product from one team to another. Documentation was insufficient and most of the product knowledge was in the brains of the people who had originally developed the system. One of the points covered in the transfer that all databases of that product would have a particular database object (a table or a stored procedure). If that object did not exist, the database was not a valid database for that product. The question therefore was:

“Given a SQL Server instance, is it possible to check for existence of a particular database object across all databases in the instance?”

The simple answer is yes.

However, the answer involves the use of a undocumented (and therefore unsupported) system stored procedure – sp_MSforeachdb.

USE master ;
GO
EXEC sp_MSforeachdb 
       N'USE ?;
         IF EXISTS (SELECT *
                    FROM sys.objects AS so
                    WHERE so.is_ms_shipped = 0
                      AND so.name = ''uspGetBillOfMaterials''
                   )
            SELECT ''?'' AS DatabaseName;' ;
GO

/*RESULTS

DatabaseName
------------------
AdventureWorks2012

*/

Disclaimer: Because of the use of unsupported system stored procedure, it is recommended that the script provided below is limited to use in the development and quality assurance environments only.

Further Reading

Until we meet next time,

Be courteous. Drive responsibly.

#0350 – SQL Server – CREATE INDEX – DROP_EXISTING option and Msg 7999


Normally, when we need to perform maintenance operations on any database object as part of a database deployment, we would first check for it’s existence. If the object exists, the query would be required to first drop the object and then recreate it (or simply modify it).

For indexes, this would result in a script that is similar to the one shown below:

--Creating Indexes (the traditional/common way)
IF EXISTS (SELECT * 
           FROM sys.indexes AS si 
           WHERE si.name = 'ncidx_ProductName')
    DROP INDEX ncidx_ProductName ON dbo.Product;
GO

CREATE UNIQUE NONCLUSTERED INDEX ncidx_ProductName
       ON dbo.Product (ProductName);
GO

However, as I was reading the Books On Line the other day, I noticed an interesting option in the CREATE INDEX statement – the DROP_EXISTING option. Here’s an example demonstrating the usage of this option:

--Creating Indexes using the DROP_EXISTING option
CREATE UNIQUE NONCLUSTERED INDEX ncidx_ProductName 
    ON dbo.Product (ProductName) WITH (DROP_EXISTING = ON);
GO

DROP_EXISTING allows us to request the database engine to drop and rebuild the index with the new definition. However, the index must exist before the DROP_EXISTING can be used. If DROP_EXISTING is used with the CREATE INDEX statement for an index that does not exist, the following error is seen.

Msg 7999, Level 16, State 9, Line 2
Could not find any index named ‘ncidx_ProductName’ for table ‘dbo.Product’.

Summary

Although I am yet to use the DROP_EXISTING option in my maintenance operations, it does appear to be lucrative due to the reduced number of lines involved in recreating the index. A couple of points need to be kept in mind, though:

  • The index must be existing when the DROP_EXISTING option is used
  • We can use DROP_EXISTING when changing the definition of the index
  • Index type (clustered to non-clustered or vice-versa) cannot be changed when DROP_EXISTING is used

Further Reading

Until we meet next time,

Be courteous. Drive responsibly.

#0348 – SQL Server – Msg 1946 – The index entry exceeds maximum length of 900 bytes


Having indexes on character columns is fairly common in OLTP systems that deal with multiple look-up values. However, indexes on character columns come with a small catch.

The maximum size of an index is 900 bytes. With character data forming part of the index columns, 900 bytes can fill up very fast depending upon the overall size of the character columns involved. While an index on the character columns can still be created if the existing data in the columns does not exceed 900 bytes, the system may reject an insert/update because the total size of the columns being inserted/updated exceed 900 bytes.

While I have never actually experienced this situation, I have seen SQL Server warning about index length exceeding 900 bytes. This prompted me to do a small test, which I am sharing via this post.

The scenario

To simulate the scenario, I will create a test table (for representational purposes) with a couple of columns which will be used on an index.

USE tempdb ;
GO

--(C) SQLTwins, nakulvachhrajani.com

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

--Create the test table
--NOTE: For representational purposes only!
CREATE TABLE dbo.IndexLengthTest
    (
      KeyValue1 NVARCHAR(250),
      KeyValue2 NVARCHAR(250),
      KeyValue3 NVARCHAR(250),
      KeyValue4 UNIQUEIDENTIFIER
    )
GO

--Create the test Index
--NOTE: For representational purposes only!
CREATE UNIQUE NONCLUSTERED INDEX uncidx_IndexLengthTest 
    ON dbo.IndexLengthTest ( KeyValue1, KeyValue2, KeyValue3, KeyValue4 ) ;
GO

NOTE that when the index is created, the following warning is seen in the “Messages” tab of the SQL Server Management Studio (SSMS).

Warning! The maximum key length is 900 bytes. The index ‘uncidx_IndexLengthTest’ has maximum length of 1516 bytes. For some combination of large values, the insert/update operation will fail.

The warning clearly indicates that  the maximum possible size of the key in the proposed index is 1516 byes [(2*250)*3 + 16 = 1516 bytes]. Because the existing data in the table is less than 900 bytes, SQL Server will go ahead and create an index on the selected columns.

The Test

I will now  attempt to insert a record in the table whose total size exceeds 900 bytes.

INSERT  INTO dbo.IndexLengthTest
        (
          KeyValue1,
          KeyValue2,
          KeyValue3,
          KeyValue4
        )
VALUES  (
          REPLICATE(N'a', 250),
          REPLICATE(N'b', 250),
          REPLICATE(N'c', 250),
          NEWID()
        ) ;
GO

SQL Server immediately returns the following error:

Msg 1946, Level 16, State 3, Line 1
Operation failed. The index entry of length 1516 bytes for the index ‘uncidx_IndexLengthTest’ exceeds the maximum length of 900 bytes.

To summarize, SQL Server prevents the user from inserting records that violate the index key length which is why caution needs to be exercised when designing indexes on character columns.

Further Reading

Until we meet next time,

Be courteous. Drive responsibly.