Skip to content

Alter Index All Tables

This script comes in handy when needing to alter all indexes in a database and rebuild them. This will only work on SQL Server 2005+. It utilizes the ALL keyword in the Alter index statement to rebuild all the indexes for a particular table. This script retrieves all base tables and stores them into a temporary table, then loops through rebuilding everything.

USE AdventureWorksLT2008
GO

SELECT
	RowNum = ROW_NUMBER() OVER(ORDER BY t.TABLE_NAME)
	,TableName = t.TABLE_SCHEMA + '.' + t.TABLE_NAME
	,AlterMe = 'ALTER INDEX ALL ON ' + t.TABLE_SCHEMA + '.' + t.TABLE_NAME + ' REBUILD;'
INTO #Reindex_Tables
FROM INFORMATION_SCHEMA.TABLES t
WHERE TABLE_TYPE = 'BASE TABLE'

DECLARE @Iter INT
DECLARE @MaxIndex INT
DECLARE @ExecMe VARCHAR(MAX)

SET @Iter = 1
SET @MaxIndex =
(
	SELECT COUNT(1)
	FROM #Reindex_Tables
)

WHILE @Iter < @MaxIndex
BEGIN
	SET @ExecMe =
	(
		SELECT AlterMe
		FROM #Reindex_Tables
		WHERE RowNum = @Iter
	)

	EXEC (@ExecMe)
	PRINT @ExecMe + ' Executed'

	SET @Iter = @Iter + 1
END

SQL Date Comparison

When comparing the datetime datatype in SQL Server, it is important to maintain consistency in order to gaurd against SQL interpreting a date differently than you intend. In at least one occasion I have seen someone specify a short format for a date, like (1/4/08) only to find that SQL interpreted the month as the year. A couple incidences similar to this has scared me enough to be very careful in my SQL Date comparisons.

Most of the time when hard coding a date I define it out the way SQL Server defines it (at least according to SQL Profiler). Like this:

DECLARE @MyDate datetime

SET @MyDate = '2000-01-04T00:00:00.000'

SELECT *
FROM Customers
WHERE ModifiedDate = @MyDate

When defined this way, SQL will have no option to interpret in any other way that what is presented. The downside is, yes, it’s long. However when hard coding dates, I’d personally rather be safe that sorry. I haven’t run into it, nor have I tested it, but I’d hate to have all my dates coded in a system changed just because the next version of SQL or any other system decided to interpret things differently.

The other safe way to compare dates is to use SQL Server’s datepart function. Utilizing this method, you can never go wrong either.

SELECT *
FROM Customers
WHERE
-- Compare year
DATEPART(yyyy, ModifiedDate) = 2000
-- compare month
AND DATEPART(mm, ModifiedDate) = 08
-- compare day
AND DATEPART(dd, ModifiedDate) = 01

Though this is a little more of a pain, and there may be a fraction of a fraction time difference for the conversion, it’s worth the peace of mind.

Rownum in SQL Server

Prior to SQL Server 2005, there was no inherent function to generate a rownumber within a row. There is a workaround however for SQL 2000. If you are on SQL 2005+, then you will utilize the following function:

Method 1

-- SQL 2005+
SELECT
    RowNumber = ROW_NUMBER() OVER (ORDER BY c.CustomerID ASC)
    ,c.*
FROM SalesLT.Customer c

Method 2

There are two ways to do this in SQL 2000. The easiest way is to join the table on itself and count the number of rows that precede the primary key.

-- SQL 2000+
SELECT Rownum =
(
	SELECT COUNT(1)
	FROM SalesLT.Customer
	WHERE CustomerID <= c.CustomerID
), *
FROM SalesLT.Customer c
ORDER BY CustomerID

Method 3

The other way for SQL 2000 is to use the IDENTITY function in order to generate a Rownum. The downside is that you must insert the results into a temp table, then select from the temp table in order to retrieve the rownum.

-- SQL 2000+
SELECT
    RowNumber   = IDENTITY(INT,1,1)
    ,c.LastName
    ,c.FirstName
INTO #Customer_RowID
FROM SalesLT.Customer c
ORDER BY c.LastName ASC

SELECT *
FROM #Customer_RowID

Also notice from this example that when we insert the results into the temp table, we are specifying the column names. This is because the identity function will not work if you include the primary key of the table, which is another downside.

SQL Server For Each Row Next

It is difficult for me to write this particular article and I’ll tell you why. If you don’t care then just skip down to the example, but here goes anyway. It is very rare that you should have to perform looping in SQL. There are certain situations that do require it, and they are mostly back-end or maintenance related.

Ok, now that I got that off my chest, here you go. In order to perform an execution which iterates performing a “for each row”, this is the easiest way for me:

SELECT
	RowNum = ROW_NUMBER() OVER(ORDER BY CustomerID)
	,*
INTO #Customers
FROM SalesLT.Customer

DECLARE @MaxRownum int
SET @MaxRownum = (SELECT MAX(RowNum) FROM #Customers)

DECLARE @Iter int
SET @Iter = (SELECT MIN(RowNum) FROM #Customers)

WHILE @Iter &amp;lt;= @MaxRownum
BEGIN
	SELECT *
	FROM #Customers
	WHERE RowNum = @Iter

	-- run your operation here

	SET @Iter = @Iter + 1
END

DROP TABLE #Customers

Once again, take heed, even though this is how to loop, I encourage set-based operations whenever possible.

The EXECUTE permission was denied on the object

This error is pretty self explanatory. If you are here, I’m sure you simply want to know how to correct it, so here goes.

The user context in which you are executing does not have the rights to execute the procedure or function. So first, in order to find out which user you are, simply execute the following query from the connection that is failing.

SELECT CURRENT_USER

Now that you know this, you need to login as a user that has the rights to change the execute permissions. Though it is not likely this same user that cannot execute the procedure also has the rights to change the permissions, you can still try. Otherwise simply login as a user with ‘dbo’ access and execute this:

GRANT EXECUTE ON [dbo].[procname] TO [youruser]

If you see this error:
Cannot grant, deny, or revoke permissions to sa, dbo, entity owner, information_schema, sys, or yourself. Then you are out of luck. You are using SQL 2005+ and need to login as a different user.

SQL Rank

The SQL Rank function was introduced in SQL Server 2005 and is part of a family of ranking functions. In order to explain the SQL Rank function, we need to look at it in context with the other rank functions.

  • RANK
  • DENSE_RANK
  • ROW_NUMBER
  • NTILE

This list may seem overwhelming, however most of the ranking functions are rather similar.

First, the syntax. Each ranking function takes a mandatory parameter of a column or value used in order to “rank”. Let’s look at an example run against the adventureworks light database.

SELECT
	UnitPrice	= UnitPrice
	,RowNumber	= ROW_NUMBER() OVER (ORDER BY UnitPrice)
	,Rank		= RANK() OVER (ORDER BY UnitPrice)
	,DenseRank	= DENSE_RANK() OVER (ORDER BY UnitPrice)
	,NTile		= NTile(100) over (ORDER BY UnitPrice)
FROM SalesLT.SalesOrderDetail

Looking at the results of the query provides the best explanation for the functions.

Results of SQL Rank Functions

Results of SQL Rank Functions

From this list we can compare the UnitPrice in order to determine what each function is doing.

  1. The rownumber function, simply add one number per each row that is returned.
  2. The Rank function will compare the unit price and provide an integer based on it’s comparison with the previous row. If they are equal it will return the same rank. Internally, the rank will be incremented by one for each row (whether it is equal or not) and the next non-equal value will display the next internal rank. (I know this is a tough sentence, just look at the example).
  3. Dense_Rank performs the same as rank, except it does not keep an internal increment and instead simply increments the count by one when the value is not equal
  4. Ntile is a beast of it’s own. It simply takes the entire result set, and divides it equally into the number of segments specified in the parameter. In this case 100.

SQL Server SELECT INTO

The most efficient method of copying a result set into a new table is to use the SELECT INTO method. This method also follows a very simple syntax.

SELECT *
INTO dbo.NewTableName
FROM dbo.ExistingTable

Once the query above is executed, all the columns and data in the table ExistingTable (along with their datatypes) will be copied into a brand new table named: NewTableName. The reason this is so fast is because it makes use an internal SQL Server feature called minimal logging. Minimal logging means that each row that is written to the new table is only written to the data page and is not written to the log file. Instead only a pointer to the data is inserted to the log file. This cuts the IO down by half.

The query shown above is simple, but it can also be as complex as you’d like it to be. Any query that is currently returning a result set, can easily be made into a SELECT INTO query by inserting the INTO keyword after the select list.

The Downside of SELECT INTO

This title may be a little intense, however there are a couple issues that SELECT INTO does not cover. First, it does not copy over any of the constraints, keys, or computed columns. It also does not copy over the indexes. While this may not necessarily be a downside, it should be noted. Many times I have copied a table using SELECT INTO only to find I did not take into account that it does not copy the attributes.

SQL Server Begin Try

The try catch methodology of programming is a great innovation for SQL 2005+. The first question you should ask yourself before using Try/Catch should be “why?”. Why am I going to use Try/Catch?

Personally, I have found a few uses, however I must say I do fall into the category of not being a developer that uses this for every procedure. Generally speaking, I do want my errors to bubble up to the next highest layer (like the web layer, and display exactly as they occur). And while you can use try/catch and still allow errors to bubble up, in a lot of cases I don’t see the need. In fact, in implementing Try/Catch in an improper way, it is possible to accidently hide errors to the extent of no one ever knowing about them.

That said, probably the best use of Try/Catch is to implement your own error logging within your database.

Before we get into an extensive logging example, let’s see an example of a Try Catch block. The following example would be a good use of Try/Catch because it is plausible that a negative number may enter a variable used in the Top Clause:

DECLARE @TopRows int
SET @TopRows = -1

BEGIN TRY

	SELECT top (@TopRows) *
	FROM SalesLT.Customer

END TRY
BEGIN CATCH

	SELECT ERROR_NUMBER(), ERROR_MESSAGE()

END CATCH

When we execute this script, we see that no exceptions are returned. Instead, a second result set is shown. Notice the first result set is also shown (returning no rows). I have not found a way around this, the column meta data will always be returned.

Try Catch Example for Begin Try

Error is Caught

Now that we’ve seen a simple example of Try/Catch, let’s look at a more extensive example. The following logging example was based off an example in the book, “Pro SQL Server 2005
Database Design and Optimization”
by Louis Davidson. Hats off for a great book.

First, let’s look at an example of a complete Try / Catch implementation, first we need to create our supporting objects.

-- Create our custom Schema for Logging
CREATE SCHEMA [Log] Authorization DBO
GO
-- Create our error log table
CREATE TABLE Log.ErrorLog
(
	[Error_Number] int NOT NULL,
	[Error_Location] sysname NOT NULL,
	[Error_Message] varchar(max),
	[SPID] int,
	[Program_Name] varchar(255),
	[Client_Address] varchar(255),
	[Authentication] varchar(50),
	[Error_User_Application] varchar(100),
	[Error_Date] datetime NULL
		CONSTRAINT dfltErrorLog_error_date DEFAULT (GETDATE()),
	[Error_User_System] sysname NOT NULL
		CONSTRAINT dfltErrorLog_error_user_system DEFAULT (SUSER_SNAME())
)
GO

Now we create our stored procedure which inserts the error found:

USE [AdventureWorksLT2008]
GO

CREATE PROCEDURE [Log].[ErrorLog_Ins_Error_Dtl]
(
	@Error_Number int = NULL,
	@Error_Location sysname = NULL,
	@Error_Message varchar(4000) = NULL,
	@UserID int = NULL
)
AS
BEGIN
	BEGIN TRY

		INSERT INTO Log.ErrorLog
		(
			[Error_Number]
			,[Error_Location]
			,[Error_Message]
			,[SPID]
			,[Program_Name]
			,[Client_Address]
			,[Authentication]
			,[Error_User_System]
			,[Error_User_Application]
		)
		SELECT
			[Error_Number]				= ISNULL(@Error_Number,ERROR_NUMBER())
			,[Error_Location]			= ISNULL(@Error_Location,ERROR_MESSAGE())
			,[Error_Message]			= ISNULL(@Error_Message,ERROR_MESSAGE())
			,[SPID]						= @@SPID
			,[Program_Name] 			= ses.program_name
			,[Client_Address]			= con.client_net_address
			,[Authentication]			= con.auth_scheme
			,[Error_User_System]		= SUSER_SNAME()
			,[Error_User_Application]	= @UserID

		FROM sys.dm_exec_sessions ses
		LEFT JOIN sys.dm_exec_connections con
			ON con.session_id = ses.session_id
		WHERE ses.session_id = @@SPID

	END TRY
	BEGIN CATCH
		-- We even failed at the log entry so let's get basic
		INSERT INTO Log.ErrorLog
		(
			ERROR_NUMBER
			,ERROR_LOCATION
			,ERROR_MESSAGE
		)
		VALUES
		(
			-100
			,OBJECT_NAME(@@PROCID)
			,'Error Log Procedure Errored out'
		)
	END CATCH

END
GO

Finally, let’s look at an example of how we implement our error trapping system.


DECLARE @TopRows int
SET @TopRows = -1

BEGIN TRY

	SELECT top (@TopRows) *
	FROM SalesLT.Customer

END TRY
BEGIN CATCH

	EXEC [Log].[ErrorLog_Ins_Error_Dtl]
	--
END CATCH

From the example above, we see that the only action we take after an error has been caught, is to execute our error logging procedure (which then inserts into a table). This method may or may not be ok depending on your application. In most cases I have found that you do want the front end to know about the error. So in order to facilitate that, you can simple use the RAISERROR function after executing the error log procedure.

Find Duplicate Fields in a Table

A common scenario when querying tables is the need to find duplicate fields within the same table. To do this is simple, it requires utilizing the GROUP BY clause and counting the number of recurrences. For example, lets take a customers table. Within the customers table, we want to find all the records where the customers FirstNames are the same. We also want to find which FirstNames are the same and count them.

First off, let’s get a count of how many customers share the same first name:

SELECT FirstName
	,DuplicateCount = COUNT(1)
FROM SalesLT.Customer
GROUP BY FirstName
HAVING COUNT(1) > 1  -- more than one value
ORDER BY COUNT(1) DESC -- sort by most duplicates
Duplicate Listing

Duplicate FirstNames in Customers Table

So here we see the results of the query sorted by the names which are duplicated the most. Using the having clause restricts the result set to only those customers that have duplicates. The order by clause orders the results by those who are duplicated the most.

This method can also be expanded to include multiple columns, like FirstName and LastName. In order to expand the criteria, we simply add the columns to the select list and the group by clause.

SELECT FirstName
	,LastName
	,DuplicateCount = COUNT(1)
FROM SalesLT.Customer
GROUP BY
	FirstName
	,LastName
HAVING COUNT(1) > 1  -- more than one value
ORDER BY COUNT(1) DESC -- sort by most duplicates

Ok, so now that we have found the duplicate items, how do we join that back on the main table so we can see the entire record? There are two methods that may perform differently depending on your result set so in this case I will include them both. Only the EXISTS method however can be used for multiple columns.


-- *********************************
-- * Find duplicates using IN
-- *********************************
SELECT *
FROM SalesLT.Customer
WHERE FirstName IN
(
	SELECT FirstName
	FROM SalesLT.Customer
	GROUP BY
		FirstName
	HAVING COUNT(1) > 1  -- more than one value
)
ORDER BY FirstName

-- *********************************
-- * Find duplicates using EXISTS
-- *********************************
SELECT *
FROM SalesLT.Customer c1
WHERE EXISTS
(
	SELECT 1
	FROM SalesLT.Customer
	WHERE FirstName = c1.FirstName
	GROUP BY
		FirstName
	HAVING COUNT(1) > 1  -- more than one value
)
ORDER BY FirstName

Now what if you want to check to see if there are duplicates for an entire row without having to do a group by you ask? In other words, how do I find the records where the entire row is a duplicate. Yes, there is a way. To do this, we utilize a very handy function named CHECKSUM. Checksum returns a numerical value representing a single “hash” which is unique (mostly) for a multitude of values. The advantage of this method over the group by is that it is much faster.

SELECT *
FROM (
	SELECT
		CustomerID
		,ChkSum =
		CHECKSUM
		(
			FirstName,
			LastName
			-- specify the rest of the columns
		)
	FROM SalesLT.Customer sc
) t1
JOIN
(
	SELECT
		CustomerID
		,ChkSum =
		CHECKSUM
		(
			FirstName,
			LastName
			-- specify the rest of the columns
		)
	FROM SalesLT.Customer
) t2
ON t1.CustomerID != t2.CustomerID
AND t1.ChkSum = t2.ChkSum

Using OpenQuery

The OPENQUERY command is used to initiate an ad-hoc distributed query using a linked-server. It is initiated by specifying OPENQUERY as the table name in the from clause. Essentially, it opens a linked server, then executes a query as if executing from that server.

While executing queries directly and receiving data directly in this way is not bad, there are implications when trying to join the results of OPENQUERY with a local table. Typically joining remote tables with local tables across the network is not the most efficient method of querying. In some cases it may be better to subscribe to a remote table by importing it locally, then joining it locally.

Setup Ad Hoc Distributed Queries

In order to utilize, a server-level configuration needs to be applied to the default configuration in order to allow ‘Ad Hoc Distributed Queries’.

In order to see the current setting, run the following:

EXEC sp_configure

If you see the config_value set to 1 (as the figure below), then the option is already set. Otherwise you need to change the configuration.

OPENQUERY_sp_configure_adHocdistributedqueries

Ad Hoc distributed queries turned on

To set the configuration, simply run the following code below:

-- Enable Ad Hoc Distributed Queries
EXEC sp_configure 'Ad Hoc Distributed Queries',1
RECONFIGURE WITH OVERRIDE
GO

OpenQuery Example

The next step is to find or define the link server required and specify the linked server name as the first parameter in OPENQUERY. This tutorial already assumes you have a linked server setup, so from here we execute the query. The execution is pretty straight forward. OPENQUERY is specified right after the from clause followed by the first parameter as the linked server, and the second parameter as the query to execute.

--****************************
--* Example 1
--* Import all users into new table
--*****************************/
SELECT *
INTO dbo.Users_Import
FROM OPENQUERY(remotelinkedservername, 'SELECT * FROM dbo.Users' )

--****************************
--* Example 2
--* Insert users that do not exist
--* into existing table
--*****************************/
INSERT INTO dbo.Users
(
	UserID,
	UserName,
	FirstName,
	LastName
)
SELECT
	UserID,
	UserName,
	FirstName,
	LastName
FROM OPENQUERY(remotelinkedservername, 'SELECT * FROM dbo.Users' ) ru
WHERE NOT EXISTS
(
	SELECT 1
	FROM dbo.Users
	WHERE UserID = ru.UserID
)

--/****************************
--* Example 3
--* Insert results from remote procedure
--* into new table
--*****************************/
SELECT *
INTO dbo.List
FROM OPENQUERY(remotelinkedservername, 'set fmtonly off exec dbo.GetList')