Posts Tagged ‘tsql’

What if you want to PIVOT against a text column?

If you’ve ever worked with or researched SQL Server’s PIVOT function, you probably noticed most of the samples pivot against an id column.  Typically an int column like EmployeeID, or StoreID.  That’s fine and dandy, but what happens when you want to PIVOT against a varchar column?  If you’ve been in this need you know this is a bit of a task.

I had this need on an app recently and built a little dynamic sql action that does just this.  The example below however, uses the the DatabaseLog table in the AdventureWorks sample database to return a count of Events logged for each Schema.  Before jumping into the PIVOT, here’s a simple query that gives you the same information, all Schemas, Events, and Event counts.

SELECT      [Schema], [Event], COUNT( [Event] ) AS 'event_count'
FROM        DatabaseLog
GROUP BY    [Schema], [Event]
ORDER BY    [Schema]

Running this query should give you a long result looking something like this.

Data is there, format isn't nice like PIVOT

While this query returns the same information to you, I don’t like this format as much as using PIVOT.  This query result is long and requires a bit of manipulation to get into a readable format.

Now let’s have a look at retrieving the same information using the PIVOT function.

/*
Example of a dynamic PIVOT against a varchar column from the Adventureworks database

References :
PIVOT & UNPIVOT function

http://msdn.microsoft.com/en-us/library/ms177410.aspx

AdventureWorks sample Databases

http://msdn.microsoft.com/en-us/library/ms124501(v=SQL.100).aspx

AdventreWorks.DatabaseLog

http://msdn.microsoft.com/en-us/library/ms124872.aspx

*/

USE AdventureWorks

-- populate temp Event table
SELECT DISTINCT [Event] as 'Event'
INTO	#events
FROM	DatabaseLog

-- this var will hold a comma delimited list of [Event]
DECLARE	@eventList nvarchar(max)

-- create a flattened [Event], list for the PIVOT statement
SELECT	@eventList = COALESCE( @eventList + ', ', '') + CAST( QUOTENAME( [Event] ) AS VARCHAR(1000) )
FROM	#events
ORDER BY [Event]

-- drop table var since our data now lives in @eventList
DROP TABLE #events

-- this var will hold the dynamic PIVOT sql
DECLARE @pvt_sql nvarchar(max)

-- NOTE : we're using dynamic sql here because PIVOT
-- does not support sub SELECT in the 'FOR Event IN ( )'
-- part of the query.
-- If we don't use dynamic SQL here, the PIVOT function
-- requires you to hard code each 'Event'
-- Using SELECT * here so the [Event] columns are auto included
SET @pvt_sql = 'SELECT	*
                FROM
                (
                    SELECT	[Event], [Schema]
                    FROM	DatabaseLog
                ) AS data
                PIVOT
                (
                    COUNT( Event )
                    FOR Event IN
                    ( ' + @eventList + ' )
                ) AS pvt'

-- run the query
EXEC sp_executesql @pvt_sql

Assuming you have the AdventureWorks database installed on your server, running this sql should give you a result looking something like this.

Dynamic PIVOT on text column Event

Show all Schemas and count of each Event type

This query result was truncated to fit in this post, but just know the query above creates a column for every Event in the Databaselog table.

A quick explanation of what’s happening in this sql

  1. First you fill a table variable ( #events ) with all Events from DatabaseLog
  2. Next create a comma delimited list of the Events inside of the table variable
  3. Drop the table variable now that we’ve got our delimited list of Events
  4. Build the PIVOT statement as a string so you can inject the Events list
  5. Fire the dynamic SQL via EXEC

Dynamic SQL is something that comes in handy from time to time, but I do my best to only use it if I absolutely have to.  In this case we’re using it because the PIVOT function does not allow sub SELECT statements.  This is also why we create a specially formatted delimited list of Events prior to building the dynamic sql.

So there you have it, one example of using PIVOT against a varchar column instead of an integer column.  Also, this is a pretty good example of a dynamic PIVOT since it’s pretty simple.  I hope this makes sense, and if you have any suggestions of better techniques, I’d love to hear it.

Incorrect syntax near the keyword ‘table’ in TSQL

Ran into something little that I know I’m going to forget if I don’t write down. It appears that when using a TABLE variable in tsql ( SQL Server 2005 ), you must DECLARE that variable on it’s own line, as opposed to inline with your other @variables.

Typically in my sprocs or sql scripts I do my best to have a main DECLARE block and seperate my @variables with a comma like this.

Typically I DECLARE=

If you're using a TABLE variable, put it on it's own DECLARE line

After some mucking around, it turns out moving the TABLE @variable to it’s own DECLARE line fixes this issue.

DECLARE TABLE @variables on their own line

DECLARE TABLE @variables on their own line

I haven’t found this info in SQL BOL, so I hope this helps somebody else.

What happens in EXEC, stays in EXEC. Lifespan of a MSSQL table variable

One of my all time favorite features of MSSQL 2005+ is being able to create table variables on the fly from SELECT statements. This isn’t a lesson in what table variables are, but here is an easy sample in case this is a new concept.

Running this query

SELECT * INTO #myTableVar FROM YourTable

Gives you a new table variable named myTableVar. Table variables are scoped to the active connection, so running this will work.

// make table var
SELECT * INTO #myTableVar FROM YourTable
// show me the data
SELECT * FROM #myTableVar
// you can drop it if you wish
DROP TABLE #myTableVar

However, let’s say you have an aspx page or a sproc that runs this query.

SELECT * INTO #myTableVar FROM YourTable

You can not access myTableVar in a separate connection to the database because as soon as the first query’s connection closes, myTableVar gets dropped.   Here are a few other scenarios that also demonstrate the scoping of a table variable.

-- FAILS
EXEC ('SELECT * INTO #tmp FROM MyTable;');
-- #tmp does not exist
SELECT * FROM #tmp
#tmp only exists inside of EXEC

Table variable #tmp lives inside of EXEC

Here we see that the table variable #tmp only lives for the life of the statement inside of EXEC. The second SELECT * calls is outside of the EXEC statement.

-- #tmp2 works inside of EXEC statement
EXEC ('SELECT * INTO #tmp2 FROM MyTable; SELECT * FROM #tmp2');
table variables in EXEC live in EXEC

What happens in EXEC, stays in EXEC

Here #tmp2 works because it’s being used inside of the EXEC statement. This is worth knowing if you work with dynamic sql statements and exec.

-- works!
SELECT * INTO #tmp FROM MyTable;
-- #tmp exists
SELECT * FROM #tmp
typical sample of using mssql table variable

typical sample of using mssql table variable

This is a typical example that you may use inside a sproc, trigger, script, etc. Both sql calls live in the same space, so #tmp exists.

Select random value from a range of values

Earlier I blogged about creating random numbers using tsql functions.  Here are two techniques for selecting a random value from a pre-defined range of values in a tsql script.  The first technique uses a table variable ( MSSQL 2000 + ), and the second uses a Common Table Expression or CTE ( MSSQL 2005+ ).

Select a random value using a table variable


-- var to hold random integer
declare @field_val int

-- create table var to hold value range [ 0, 512, 1024, 2048, 4096 ]
-- inserting the first value sets the structure for the table variable
SELECT 0 AS 'num'
INTO #temp

-- insert data into table var
INSERT INTO #temp VALUES ( 512 )
INSERT INTO #temp VALUES ( 1024 )
INSERT INTO #temp VALUES ( 2048 )
INSERT INTO #temp VALUES ( 4096 )

-- assign random value
SELECT TOP 1 @field_val = num FROM #temp ORDER BY NEWID()

-- show value
SELECT @field_val

-- drop the table variable
DROP TABLE #temp

Select a random value using a CTE

-- define our data table
WITH data( car )
AS
(
	-- UNION together our range of values
	SELECT 'audi' AS 'car'
	UNION
	SELECT 'bmw' AS 'car'
	UNION
	SELECT 'infinity' AS 'car'
	UNION
	SELECT 'lexus' AS 'car'
	UNION
	SELECT 'porsche' AS 'car'
)
-- select a random value
SELECT TOP 1 car FROM data
ORDER BY NEWID()

Both of these techniques can be used with numbers or text. Just be sure to mind your quotes, and variable datatypes.  Being able to pick a random value in data generation scripts has proven very useful.  I hope this helps somebody else out as well.