Login or Sign Up to become a member!

EXPERTS, INFORMATION, IDEAS & KNOWLEDGE

Social bookmarker Add this

Your profile

Search

July 2009
Mon Tue Wed Thu Fri Sat Sun
 << <   > >>
    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    

XML Feeds

Authors

« SQL Server Rounding MethodsSQL Friday, The Best SQL Server Links Of The Past Week Episode 1 »
The Data Management Journal

The Ten Most Asked SQL Server Questions And Their Answers

by SQLDenis


Permalink 10 Dec 2008 10:09 , Categories: Data Modelling & Design Tags: answers, questions, sql server 2000, sql server 2005, sql server 2008

If you are active in the SQL Server newsgroups and forums as I am, you will notice that the same questions keep popping up all the time. I picked ten of them which I see daily. Since this became a pretty long blogpost I have linked all the questions below to jump to the question itself.

1) Selecting all the values from a table for a particular date
2) Search all columns in all the tables in a database for a specific value
3) Splitting string values
4) Select all rows from one table that don't exist in another table
5) Getting all rows from one table and only the latest from the child table
6) Getting all characters until a specific character
7) Return all rows with NULL values in a column
8) Row values to column (PIVOT)
9) Pad or remove leading zeroes from numbers
10) Concatenate Values From Multiple Rows Into One Column



1 Selecting all the values from a table for a particular date

This is a very popular question and people sometimes answer that you need to use between. There is a problem with between if you happen to have a value at exactly midnight. Let us take a look, first create this table.

  1. CREATE TABLE SomeDates (DateColumn DATETIME)

Insert 2 values

  1. INSERT INTO SomeDates VALUES('2008-10-02 00:00:00.000')
  2. INSERT INTO SomeDates VALUES('2008-10-01 00:00:00.000')

Return everything between '2008-10-01' and '2008-10-02'

  1. SELECT *
  2. FROM SomeDates
  3. WHERE DateColumn BETWEEN '20081001' AND '20081002'
  4. ORDER BY DateColumn

This works without a problem, we get this returned

(results)
2008-10-01 00:00:00.000
2008-10-02 00:00:00.000

Let's add some more dates including the time portion

  1. INSERT INTO SomeDates VALUES('2008-10-02 00:01:00.000')
  2. INSERT INTO SomeDates VALUES('2008-10-02 00:00:59.000')
  3. INSERT INTO SomeDates VALUES('2008-10-02 00:00:01.000')
  4. INSERT INTO SomeDates VALUES('2008-10-01 00:01:00.000')
  5. INSERT INTO SomeDates VALUES('2008-10-01 00:12:00.000')
  6. INSERT INTO SomeDates VALUES('2008-10-01 23:00:00.000')

Return everything between '2008-10-01' and '2008-10-02'

  1. SELECT *
  2. FROM SomeDates
  3. WHERE DateColumn BETWEEN '20081001' AND '20081002'
  4. ORDER BY DateColumn

(results)
2008-10-01 00:00:00.000
2008-10-01 00:01:00.000
2008-10-01 00:12:00.000
2008-10-01 23:00:00.000
2008-10-02 00:00:00.000

Here is where it goes wrong; for 2008-10-02 only the midnight value is returned the other ones are ignored

Now if we change 2008-10-02 to 2008-10-03 we get what we want

  1. SELECT *
  2. FROM SomeDates
  3. WHERE DateColumn BETWEEN '20081001' AND '20081003'
  4. ORDER BY DateColumn

(results)
2008-10-01 00:00:00.000
2008-10-01 00:01:00.000
2008-10-01 00:12:00.000
2008-10-01 23:00:00.000
2008-10-02 00:00:00.000
2008-10-02 00:00:01.000
2008-10-02 00:00:59.000
2008-10-02 00:01:00.000

Now insert a value for 2008-10-03 (midnight)

  1. INSERT INTO SomeDates VALUES('2008-10-03 00:00:00.000')

Run the query again

  1. SELECT *
  2. FROM SomeDates
  3. WHERE DateColumn BETWEEN '20081001' AND '20081003'
  4. ORDER BY DateColumn

(results)
2008-10-01 00:00:00.000
2008-10-01 00:01:00.000
2008-10-01 00:12:00.000
2008-10-01 23:00:00.000
2008-10-02 00:00:00.000
2008-10-02 00:00:01.000
2008-10-02 00:00:59.000
2008-10-02 00:01:00.000
2008-10-03 00:00:00.000

We get back 2008-10-03 00:00:00.000, between will return the date if it is exactly midnight

If you use >= and < then you get exactly what you need

  1. SELECT *
  2. FROM SomeDates
  3. WHERE DateColumn >= '20081001' AND DateColumn < '20081003'
  4. ORDER BY DateColumn

(results)
2008-10-01 00:00:00.000
2008-10-01 00:01:00.000
2008-10-01 00:12:00.000
2008-10-01 23:00:00.000
2008-10-02 00:00:00.000
2008-10-02 00:00:01.000
2008-10-02 00:00:59.000
2008-10-02 00:01:00.000

So be careful when using between because you might get back rows that you did not expect to get back and it might mess up your reporting if you do counts or sums

Bonus: how to strip the time of a date?

To strip the time portion of a datetime, you can do this and still return a datetime

  1. SELECT * ,DATEADD(dd, DATEDIFF(dd, 0, DateColumn), 0) AS stripped
  2. FROM SomeDates

you can also do a convert to varchar with a style value of 112 and then converting back to datetime

  1. SELECT * ,CONVERT(DATETIME,CONVERT(VARCHAR(8),DateColumn,112)) AS stripped
  2. FROM SomeDates

Both methods return this
-----------------------------------------------

DateColumn              stripped
2008-10-02 00:00:00.000	2008-10-02 00:00:00.000
2008-10-01 00:00:00.000	2008-10-01 00:00:00.000
2008-10-02 00:01:00.000	2008-10-02 00:00:00.000
2008-10-02 00:00:59.000	2008-10-02 00:00:00.000
2008-10-02 00:00:01.000	2008-10-02 00:00:00.000
2008-10-01 00:01:00.000	2008-10-01 00:00:00.000
2008-10-01 00:12:00.000	2008-10-01 00:00:00.000
2008-10-01 23:00:00.000	2008-10-01 00:00:00.000
2008-10-03 00:00:00.000	2008-10-03 00:00:00.000



2 Search all columns in all the tables in a database for a specific value

How can I search all the columns in every table in my database for a specific value?
This question keeps popping up all the time. Unfortunately there is no easy way to do this, you have to loop over all the tables in the database and search for the value.
As you can imagine if you have a huge database with a lot of tables this can take a really long time
There are a couple of checks going on in this proc, if the data is not numeric then we skip all the numeric columns and do not search those columns. This code was created by our own gmmastros and what he did was create 3 different stored procedures, one for dates, numbers and strings. Then he created a 4th stored proc named FindMyData, this proc will only call the appropriate child procs if the datatype is right.
We only list the table name and the column name where the value is stored, we do not return that data!
First create the following 3 stored procedures

Just the date searching proc

  1. CREATE PROCEDURE FindMyData_Date
  2.     @DataToFind DATETIME
  3. AS
  4. SET NOCOUNT ON
  5.  
  6. DECLARE @Temp TABLE(RowId INT IDENTITY(1,1), TableName sysname, ColumnName SysName, DataType VARCHAR(100), DataFound BIT)
  7.  
  8. DECLARE @ISDATE BIT
  9.  
  10.     INSERT  INTO @Temp(TableName, ColumnName, DataType)
  11.     SELECT  C.Table_Name, C.Column_Name, C.Data_Type
  12.     FROM    Information_Schema.Columns AS C
  13.             INNER Join Information_Schema.Tables AS T
  14.                 ON C.Table_Name = T.Table_Name
  15.     WHERE   Table_Type = 'Base Table'
  16.             And (Data_Type = 'DateTime'
  17.             Or (Data_Type = 'SmallDateTime' And @DataToFind >= '19000101' And @DataToFind < '20790607'))
  18.  
  19. DECLARE @i INT
  20. DECLARE @MAX INT
  21. DECLARE @TableName sysname
  22. DECLARE @ColumnName sysname
  23. DECLARE @SQL NVARCHAR(4000)
  24. DECLARE @PARAMETERS NVARCHAR(4000)
  25. DECLARE @DataExists BIT
  26. DECLARE @SQLTemplate NVARCHAR(4000)
  27.  
  28. SELECT  @SQLTemplate = 'If Exists(Select *
  29.                                  From   [ReplaceTableName]
  30.                                  Where  [ReplaceColumnName]
  31.                                               = ''' + CONVERT(VARCHAR(30), @DataToFind, 126) + '''
  32.                                  )
  33.                            Set @DataExists = 1
  34.                        Else
  35.                            Set @DataExists = 0',
  36.         @PARAMETERS = '@DataExists Bit OUTPUT',
  37.         @i = 1
  38.  
  39. SELECT @i = 1, @MAX = MAX(RowId)
  40. FROM   @Temp
  41.  
  42. WHILE @i <= @MAX
  43.     BEGIN
  44.         SELECT  @SQL = REPLACE(REPLACE(@SQLTemplate, 'ReplaceTableName', TableName), 'ReplaceColumnName', ColumnName)
  45.         FROM    @Temp
  46.         WHERE   RowId = @i
  47.  
  48.  
  49.         PRINT @SQL
  50.         EXEC SP_EXECUTESQL @SQL, @PARAMETERS, @DataExists = @DataExists OUTPUT
  51.  
  52.         IF @DataExists =1
  53.             UPDATE @Temp SET DataFound = 1 WHERE RowId = @i
  54.  
  55.         SET @i = @i + 1
  56.     END
  57.  
  58. SELECT  TableName, ColumnName
  59. FROM    @Temp
  60. WHERE   DataFound = 1
  61.  
  62. go

If you want to test just this proc, try this

  1. EXEC FindMyData_Date '20070615'

This is the string proc

  1. CREATE PROCEDURE FindMyData_String
  2.     @DataToFind NVARCHAR(4000),
  3.     @ExactMatch BIT = 0
  4. AS
  5. SET NOCOUNT ON
  6.  
  7. DECLARE @Temp TABLE(RowId INT IDENTITY(1,1), TableName sysname, ColumnName SysName, DataType VARCHAR(100), DataFound BIT)
  8.  
  9.     INSERT  INTO @Temp(TableName, ColumnName, DataType)
  10.     SELECT  C.Table_Name, C.Column_Name, C.Data_Type
  11.     FROM    Information_Schema.Columns AS C
  12.             INNER Join Information_Schema.Tables AS T
  13.                 ON C.Table_Name = T.Table_Name
  14.     WHERE   Table_Type = 'Base Table'
  15.             And Data_Type In ('ntext','text','nvarchar','nchar','varchar','char')
  16.  
  17.  
  18. DECLARE @i INT
  19. DECLARE @MAX INT
  20. DECLARE @TableName sysname
  21. DECLARE @ColumnName sysname
  22. DECLARE @SQL NVARCHAR(4000)
  23. DECLARE @PARAMETERS NVARCHAR(4000)
  24. DECLARE @DataExists BIT
  25. DECLARE @SQLTemplate NVARCHAR(4000)
  26.  
  27. SELECT  @SQLTemplate = CASE WHEN @ExactMatch = 1
  28.                             THEN 'If Exists(Select *
  29.                                            From   [ReplaceTableName]
  30.                                            Where  Convert(nVarChar(4000), [ReplaceColumnName])
  31.                                                         = ''' + @DataToFind + '''
  32.                                            )
  33.                                       Set @DataExists = 1
  34.                                   Else
  35.                                       Set @DataExists = 0'
  36.                             ELSE 'If Exists(Select *
  37.                                            From   [ReplaceTableName]
  38.                                            Where  Convert(nVarChar(4000), [ReplaceColumnName])
  39.                                                         Like ''%' + @DataToFind + '%''
  40.                                            )
  41.                                       Set @DataExists = 1
  42.                                   Else
  43.                                       Set @DataExists = 0'
  44.                             END,
  45.         @PARAMETERS = '@DataExists Bit OUTPUT',
  46.         @i = 1
  47.  
  48. SELECT @i = 1, @MAX = MAX(RowId)
  49. FROM   @Temp
  50.  
  51. WHILE @i <= @MAX
  52.     BEGIN
  53.         SELECT  @SQL = REPLACE(REPLACE(@SQLTemplate, 'ReplaceTableName', TableName), 'ReplaceColumnName', ColumnName)
  54.         FROM    @Temp
  55.         WHERE   RowId = @i
  56.  
  57.  
  58.         PRINT @SQL
  59.         EXEC SP_EXECUTESQL @SQL, @PARAMETERS, @DataExists = @DataExists OUTPUT
  60.  
  61.         IF @DataExists =1
  62.             UPDATE @Temp SET DataFound = 1 WHERE RowId = @i
  63.  
  64.         SET @i = @i + 1
  65.     END
  66.  
  67. SELECT  TableName, ColumnName
  68. FROM    @Temp
  69. WHERE   DataFound = 1
  70.  
  71. go

If you want to test just this proc, try this

  1. EXEC FindMyData_string 'google', 0

Just the number proc

  1. CREATE PROCEDURE FindMyData_Number
  2.     @DataToFind NVARCHAR(4000),
  3.     @ExactMatch BIT = 0
  4. AS
  5. SET NOCOUNT ON
  6. DECLARE @Temp TABLE(RowId INT IDENTITY(1,1), TableName sysname, ColumnName SysName, DataType VARCHAR(100), DataFound BIT)
  7.  
  8. DECLARE @IsNumber BIT
  9. DECLARE @ISDATE BIT
  10.  
  11. IF ISNUMERIC(CONVERT(VARCHAR(20), @DataToFind)) = 1
  12.     SET @IsNumber = 1
  13. ELSE
  14.     SET @IsNumber = 0
  15.  
  16.     INSERT  INTO @Temp(TableName, ColumnName, DataType)
  17.     SELECT  C.Table_Name, C.Column_Name, C.Data_Type
  18.     FROM    Information_Schema.Columns AS C
  19.             INNER Join Information_Schema.Tables AS T
  20.                 ON C.Table_Name = T.Table_Name
  21.     WHERE   Table_Type = 'Base Table'
  22.             And Data_Type In ('float','real','decimal','money','smallmoney','bigint','int','smallint','tinyint','bit')
  23.  
  24.  
  25. DECLARE @i INT
  26. DECLARE @MAX INT
  27. DECLARE @TableName sysname
  28. DECLARE @ColumnName sysname
  29. DECLARE @SQL NVARCHAR(4000)
  30. DECLARE @PARAMETERS NVARCHAR(4000)
  31. DECLARE @DataExists BIT
  32. DECLARE @SQLTemplate NVARCHAR(4000)
  33.  
  34. SELECT  @SQLTemplate = CASE WHEN @ExactMatch = 1
  35.                             THEN 'If Exists(Select *
  36.                                            From   [ReplaceTableName]
  37.                                            Where  Convert(VarChar(40), [ReplaceColumnName])
  38.                                                         = ''' + @DataToFind + '''
  39.                                            )
  40.                                       Set @DataExists = 1
  41.                                   Else
  42.                                       Set @DataExists = 0'
  43.                             ELSE 'If Exists(Select *
  44.                                            From   [ReplaceTableName]
  45.                                            Where  Convert(VarChar(40), [ReplaceColumnName])
  46.                                                         Like ''%' + @DataToFind + '%''
  47.                                            )
  48.                                       Set @DataExists = 1
  49.                                   Else
  50.                                       Set @DataExists = 0'
  51.                             END,
  52.         @PARAMETERS = '@DataExists Bit OUTPUT',
  53.         @i = 1
  54.  
  55. SELECT @i = 1, @MAX = MAX(RowId)
  56. FROM   @Temp
  57.  
  58. WHILE @i <= @MAX
  59.     BEGIN
  60.         SELECT  @SQL = REPLACE(REPLACE(@SQLTemplate, 'ReplaceTableName', TableName), 'ReplaceColumnName', ColumnName)
  61.         FROM    @Temp
  62.         WHERE   RowId = @i
  63.  
  64.  
  65.         PRINT @SQL
  66.         EXEC SP_EXECUTESQL @SQL, @PARAMETERS, @DataExists = @DataExists OUTPUT
  67.  
  68.         IF @DataExists =1
  69.             UPDATE @Temp SET DataFound = 1 WHERE RowId = @i
  70.  
  71.         SET @i = @i + 1
  72.     END
  73.  
  74. SELECT  TableName, ColumnName
  75. FROM    @Temp
  76. WHERE   DataFound = 1
  77.  
  78. go

If you want to test just this proc, try this

  1. EXEC FindMyData_Number '562', 1

The mother of all procs!

  1. CREATE PROCEDURE FindMyData
  2.     @DataToFind NVARCHAR(4000),
  3.     @ExactMatch BIT = 0
  4. AS
  5. SET NOCOUNT ON
  6.  
  7. CREATE TABLE #Output(TableName sysname, ColumnName sysname)
  8.  
  9. IF ISDATE(@DataToFind) = 1
  10.     INSERT INTO #Output EXEC FindMyData_Date @DataToFind
  11.  
  12. IF ISNUMERIC(@DataToFind) = 1
  13.     INSERT INTO #Output EXEC FindMyData_Number @DataToFind, @Exactmatch
  14.  
  15. INSERT INTO #Output EXEC FindMyData_String @DataToFind, @ExactMatch
  16.  
  17. SELECT TableName, ColumnName
  18. FROM   #Output
  19. ORDER BY TableName, ColumnName
  20. go

Here are some proc calls to test it out

  1. EXEC FindMyData 'google', 0
  2. EXEC FindMyData 1, 0
  3. EXEC FindMyData '20081201', 0
  1. EXEC FindMyData 'sysobjects', 0



3 Splitting string values

The fastest way to split a comma delimited string is by using a number table. If you do not have a number table in your database then use this code to create one

  1. -- Create our Pivot table ** do this only once
  2.     CREATE TABLE NumberPivot (NumberID INT PRIMARY KEY)
  3.    
  4.        INSERT INTO NumberPivot
  5.        SELECT number FROM master..spt_values
  6.     WHERE type = 'P'
  7.    
  8.     GO

Now you can run the following code to return each of the values in the comma delimited string in its own row

  1. DECLARE @SplitString VARCHAR(1000)
  2.     SELECT @SplitString ='1,4,77,88,4546,234,2,3,54,87,9,6,4,36,6,9,9,6,4,4,68,9,0,5,3,2,'
  3.      
  4.     SELECT SUBSTRING(',' + @SplitString + ',', NumberID + 1,
  5.     CHARINDEX(',', ',' + @SplitString + ',', NumberID + 1) - NumberID -1)AS VALUE
  6.     FROM NumberPivot
  7.     WHERE NumberID <= LEN(',' + @SplitString + ',') - 1
  8.     AND SUBSTRING(',' + @SplitString + ',', NumberID, 1) = ','
  9.     GO

You can also return distinct values by using DISTINCT

  1. DECLARE @SplitString VARCHAR(1000)
  2.     SELECT @SplitString ='1,4,77,88,4546,234,2,3,54,87,9,6,4,36,6,9,9,6,4,4,68,9,0,5,3,2'
  3.      
  4.     SELECT DISTINCT SUBSTRING(',' + @SplitString + ',', NumberID + 1,
  5.     CHARINDEX(',', ',' + @SplitString + ',', NumberID + 1) - NumberID -1)AS VALUE
  6.     FROM NumberPivot
  7.     WHERE NumberID <= LEN(',' + @SplitString + ',') - 1
  8.     AND SUBSTRING(',' + @SplitString + ',', NumberID, 1) = ','

You now can dump the result into a table and then you can join one of your real tables with that table which will execute much faster



4 Select all rows from one table that don't exist in another table

There are at least 5 ways to return data from one table which is not in another table. Two of these are SQL Server 2005 and greater only

NOT IN
NOT EXISTS
LEFT and RIGHT JOIN
OUTER APLY (2005+)
EXCEPT (2005+)

First Create these two tables

  1. CREATE TABLE testnulls (ID INT)
  2.     INSERT INTO testnulls VALUES (1)
  3.     INSERT INTO testnulls VALUES (2)
  4.     INSERT INTO testnulls VALUES (null)
  5.  
  6.     CREATE TABLE testjoin (ID INT)
  7.     INSERT INTO testjoin VALUES (1)
  8.     INSERT INTO testjoin VALUES (3)

NOT IN

  1. SELECT * FROM testjoin WHERE ID NOT IN(SELECT ID FROM testnulls)

What happened? Nothing gets returned! The reason is because the subquery returns a NULL and you can't compare a NULL to anything

Now run this

  1. SELECT * FROM testjoin WHERE ID NOT IN(SELECT ID FROM testnulls WHERE ID IS NOT NULL)

That worked because we eliminated the NULL values in the subquery

NOT EXISTS

NOT EXISTS doesn't have the problem that NOT IN has. Run the following code

  1. SELECT * FROM testjoin j
  2.     WHERE NOT EXISTS (SELECT n.ID
  3.     FROM testnulls n
  4.     WHERE n.ID = j.ID)

Everything worked as expected

LEFT and RIGHT JOIN

Plain vanilla LEFT and RIGHT JOINS

  1. SELECT j.* FROM testjoin j
  2.     LEFT OUTER JOIN testnulls n ON n.ID = j.ID
  3.     WHERE n.ID IS NULL
  4.  
  5.     SELECT j.* FROM  testnulls n
  6.     RIGHT OUTER JOIN testjoin j  ON n.ID = j.ID
  7.     WHERE n.ID IS NULL

OUTER APPLY (SQL 2005 +)

OUTER APPLY is something that got added to SQL 2005

  1. SELECT j.* FROM testjoin j
  2.     OUTER APPLY
  3.     (SELECT id  FROM testnulls n
  4.     WHERE n.ID = j.ID) a
  5.     WHERE a.ID IS NULL

EXCEPT(SQL 2005 +)
EXCEPT is something that got added to SQL 2005. It basically returns everything from the top table which is not in the bottom table

  1. SELECT * FROM testjoin
  2.     EXCEPT
  3.     SELECT * FROM testnulls

INTERSECT
INTERSECT returns what ever is in both tables(like a regular join)

  1. SELECT * FROM testjoin
  2.     INTERSECT
  3.     SELECT * FROM testnulls



5 Getting all rows from one table and only the latest from the child table

  1. CREATE TABLE #MaxVal(id INT,VALUE INT,SomeDate DATETIME)
  2.     INSERT #MaxVal VALUES(1,1,'20010101')
  3.     INSERT #MaxVal VALUES(1,2,'20020101')
  4.     INSERT #MaxVal VALUES(1,3,'20080101')
  5.     INSERT #MaxVal VALUES(2,1,'20010101')
  6.     INSERT #MaxVal VALUES(2,2,'20060101')
  7.     INSERT #MaxVal VALUES(2,3,'20080101')
  8.     INSERT #MaxVal VALUES(3,1,'20010101')
  9.     INSERT #MaxVal VALUES(3,2,'20080101')
  1. SELECT id,MAX(SomeDate) AS VALUE
  2.     FROM #MaxVal
  3.     GROUP BY id
  1. SELECT t.* FROM(
  2.     SELECT id,MAX(SomeDate) AS MaxValue
  3.     FROM #MaxVal
  4.     GROUP BY id) x
  5.     JOIN #MaxVal t ON x.id =t.id
  6.     AND x.MaxValue =t.SomeDate



6 Getting all characters until a specific character (charindex + left)

There are two built in functions that you can use in SQL Server to return the first position in a column of the character you are looking for. These functions are PATINDEX and CHARINDEX. Let's take a look at how PATINDEX works. First create this table.

  1. CREATE TABLE #SomeTable2
  2. (SomeValue VARCHAR(49))
  3. INSERT INTO #SomeTable2 VALUES ('one two three')
  4. INSERT INTO #SomeTable2 VALUES ('1 2 3')
  5. INSERT INTO #SomeTable2 VALUES ('abc def ghi')
  6. INSERT INTO #SomeTable2 VALUES ('one two')
  7. INSERT INTO #SomeTable2 VALUES ('one two three four')
  8. INSERT INTO #SomeTable2 VALUES ('one two three four five')

Now we want to return everything up to the first space and also everything after the last space. Here is how we do that

  1. SELECT *,LEFT(SomeValue,PATINDEX('% %',SomeValue)-1),
  2. RIGHT(SomeValue,PATINDEX('% %',(REVERSE(SomeValue)))-1)
  3. FROM #SomeTable2

(results)

one two three one three
1 2 3 1 3
abc def ghi abc ghi
one two one two
one two three four one four
one two three four five one five

As you can see to get the last value you can just use the REVERSE function with the RIGHT function.


7 Return all rows with NULL values in a column

People have a hard time with nulls, the first problem with NULLs is that your WHERE clause is not the same as if you would select a blank or a value

First create this simple table

  1. CREATE TABLE #SomeTableNull(SomeValue VARCHAR(49))
  2. INSERT INTO #SomeTableNull VALUES ('1')
  3. INSERT INTO #SomeTableNull VALUES ('')
  4. INSERT INTO #SomeTableNull VALUES ('a')
  5. INSERT INTO #SomeTableNull VALUES (NULL

)

To search a column your WHERE clause uses the = sign

  1. SELECT * FROM #SomeTableNull WHERE SomeValue = ''
  2. SELECT * FROM #SomeTableNull WHERE SomeValue = '1'
  3. SELECT * FROM #SomeTableNull WHERE SomeValue = 'a'

Now let us try that to find a NULL value

  1. SELECT * FROM #SomeTableNull WHERE SomeValue = NULL

What just happened? We got nothing back? The reason is that you cannot compare a NULL value according to ANSI standards.

  1. Take a look AT this
  2. IF NULL = NULL
  3. PRINT 'equal'
  4. ELSE
  5. PRINT 'not so equal'

The bottom print statement got printed, NULL is unknown and you do not know if two unknows are the same

The correct way to return the NULL value is the following

  1. SELECT * FROM #SomeTableNull WHERE SomeValue IS NULL

Just so that you know this, if you turn off ansi nulls then you can use =

  1. SET ansi_nulls OFF
  2.  
  3.  
  4. SELECT * FROM #SomeTableNull WHERE SomeValue = NULL
  5.  
  6. SET ansi_nulls ON

However I do not recommend doing that ever, the default is ON and I would leave it like that



8 Row values to column (PIVOT)

A very frequent request is how to pivot/transpose/crosstab a query. SQL server 2005 introduced PIVOT, this makes life a lot easier compared to the SQL 2000 days. so let's see how this works. First create this table

  1. CREATE TABLE #SomeTable
  2. (SomeName VARCHAR(49), Quantity INT)
  1. INSERT INTO #SomeTable VALUES ('Scarface', 2)
  2. INSERT INTO #SomeTable VALUES ('Scarface', 4)
  3. INSERT INTO #SomeTable VALUES ('LOTR', 5)
  4. INSERT INTO #SomeTable VALUES ('LOTR', 6)
  5. INSERT INTO #SomeTable VALUES ( 'Jaws', 2)
  6. INSERT INTO #SomeTable VALUES ('Blade', 5)
  7. INSERT INTO #SomeTable VALUES ('Saw', 6)
  8. INSERT INTO #SomeTable VALUES ( 'Saw', 2)
  9. INSERT INTO #SomeTable VALUES ( 'Jaws', 12)
  10. INSERT INTO #SomeTable VALUES ('Blade', 5)
  11. INSERT INTO #SomeTable VALUES ('Saw', 6)
  12. INSERT INTO #SomeTable VALUES ( 'Saw', 2)

What we want is too list all the movies in a column and the sum of all quantities for that movie as a value. So in this case we want this output

Scarface LOTR Jaws Saw Blade
6 11 14 16 10

First let's look how we can do this in SQL Server 2000, this BTW will also work in SQL Server 2005/2008

  1. SELECT SUM(CASE SomeName WHEN 'Scarface' THEN Quantity ELSE 0 END) AS Scarface,
  2. SUM(CASE SomeName WHEN 'LOTR' THEN Quantity ELSE 0 END) AS LOTR,
  3. SUM(CASE SomeName WHEN 'Jaws' THEN Quantity ELSE 0 END) AS Jaws,
  4. SUM(CASE SomeName WHEN 'Saw' THEN Quantity ELSE 0 END) AS Saw,
  5. SUM(CASE SomeName WHEN 'Blade' THEN Quantity ELSE 0 END) AS Blade
  6. FROM #SomeTable

In SQL Server 2005/2008 we can use PIVOT, here is how we can use it

  1. SELECT Scarface, LOTR, Jaws, Saw,Blade
  2. FROM
  3. (SELECT SomeName,Quantity
  4. FROM #SomeTable) AS pivTemp
  5. PIVOT
  6. (   SUM(Quantity)
  7.     FOR SomeName IN (Scarface, LOTR, Jaws, Saw,Blade)
  8. ) AS pivTable

That looks a little bit neater than the SQL 2000 version.
If you are interested in Column To Row take a look at Column To Row (UNPIVOT) on our wiki



9 Pad or remove leading zeroes from numbers


To pad a number with leading zeroes you use as many zeroes as you want the output to be, then use concatenation plus the right function to display it
For example if you want to pad a 6 digit column with zeroes you would do something like this
RIGHT('000000' + CONVERT(VARCHAR(6),ColumnName),6)

Here is some code that will show this, first create this table
USE tempdb
go

  1. CREATE TABLE testpadding(id VARCHAR(50),id2 INT)
  2. INSERT testpadding VALUES('000001',1)
  3. INSERT testpadding VALUES('000134',134)
  4. INSERT testpadding VALUES('002232',2232)
  5. INSERT testpadding VALUES('000002',2)

Now run

  1. SELECT RIGHT('000000' + CONVERT(VARCHAR(6),id2),6)
  2. FROM testpadding

(results)
000001
000134
002232
000002

what about the id columns and stripping the zeroes from that?
No problem do this

  1. SELECT CONVERT(INT,id)
  2. FROM testpadding

(results)
1
134
2232
2

Beautiful right? Not so fast, insert this row

  1. INSERT testpadding VALUES('02222222222222222222200002',2)
  1. SELECT CONVERT(INT,id)
  2. FROM testpadding

Server: Msg 248, Level 16, State 1, Line 1
The conversion of the varchar value '02222222222222222222200002' overflowed an int column.

Okay we can do a bigint instead

  1. SELECT CONVERT(BIGINT,id)
  2. FROM testpadding

Server: Msg 8115, Level 16, State 2, Line 1
Arithmetic overflow error converting expression to data type bigint.

Nope, even that doesn't fit, now what?

  1. SELECT REPLACE(LTRIM(REPLACE(id,'0',' ')),' ','0')
  2. FROM testpadding

(results)
1
134
2232
2
2222222222222222222200002

So how does that work? First you replace all the zeroes with spaces, then you trim it to get rid of the leading spaces, after you replace the space back to zeroes again, the leading zeroes don't exists anymore because they were trimmed



10 Concatenate Values From Multiple Rows Into One Column

If you want to concatenate values from multiple rows into one and you want to order it then you have to use FOR XML PATH. The ORDER BY is -not- guaranteed to be processed before the concatenation occurs, if you use that technique. However, it -is- guaranteed if you use FOR XML PATH('').

Let's take a look. First create these tables

  1. USE TEMPDB
  2. GO
  3. CREATE TABLE Authors (Id INT, LastName VARCHAR(100), FirstName VARCHAR(100))
  4. GO
  5. INSERT Authors VALUES(1,'Nielsen','Paul')
  6. INSERT Authors VALUES(2,'King','Stephen')
  7. INSERT Authors VALUES(3,'Stephenson','Neal')
  8. GO
  9.  
  10. CREATE TABLE Books (ID INT,AuthorID INT, BookName VARCHAR(200))
  11. GO
  12. INSERT Books VALUES(1,1,'SQL Server 2005 Bible')
  13. INSERT Books VALUES(2,1,'SQL Server 2008 Bible')
  14.  
  15. INSERT Books VALUES(3,2,'It')
  16. INSERT Books VALUES(4,2,'The Stand')
  17. INSERT Books VALUES(5,2,'Thinner')
  18. INSERT Books VALUES(6,2,'Salems Lot')
  19.  
  20.  
  21. INSERT Books VALUES(7,3,'Snow Crash')
  22. INSERT Books VALUES(8,3,'The Diamond Age')
  23. INSERT Books VALUES(9,3,'Cryptonomicon')
  24. GO

This is the old style function, it will run on SQL Server 2000 and up

  1. CREATE FUNCTION fnGetBooks2 (@AuthorID INT)
  2. RETURNS VARCHAR(8000)
  3. AS
  4. BEGIN
  5.         DECLARE @BookList VARCHAR(8000)
  6.         SELECT @BookList = ''
  7.         SELECT @BookList = @BookList + BookName +','
  8.         FROM Books
  9.         WHERE AuthorID = @AuthorID
  10.         AND BookName IS NOT NULL
  11.         ORDER BY BookName --yes we can sort
  12.  
  13. RETURN LEFT(@BookList,(LEN(@BookList) -1))
  14. END
  15. GO

This is the same function using XML Path, so SQL Server 2005 and up

  1. CREATE FUNCTION fnGetBooks (@AuthorID INT)
  2.  
  3. RETURNS VARCHAR(8000)
  4. AS
  5. BEGIN
  6.        
  7.  DECLARE @BookList VARCHAR(8000)
  8.  SELECT @BookList =(
  9.  
  10.         SELECT  BookName + ', ' AS [TEXT()]
  11.  
  12.      FROM    Books
  13.  WHERE AuthorID = @AuthorID
  14.         AND BookName IS NOT NULL
  15.      ORDER BY BookName
  16.  
  17.      FOR XML PATH('') )
  18.        
  19.  
  20. RETURN LEFT(@BookList,(LEN(@BookList) -1))
  21. END
  22. GO

Here are the calls to the functions

  1. SELECT *,dbo.fnGetBooks(id) AS Books
  2. FROM Authors
  1. SELECT *,dbo.fnGetBooks2(id) AS Books
  2. FROM Authors


(results)

Id LastName FirstName Books
1 Nielsen Paul SQL Server 2005 Bible,SQL Server 2008 Bible
2 King Stephen It,Salems Lot,The Stand,Thinner
3 Stephenson Neal Cryptonomicon,Snow Crash,The Diamond Age

The results are the same but I have been assured by people in the private SQL Server MVP group that FOR XML PATH will preserve the order while the other function might not

In case you want some more of this stuff we have over 80 hacks, tips and trick on our SQL Server Programming Hacks wiki page

*** Remember, if you have a SQL related question try our Microsoft SQL Server Programming forum or our Microsoft SQL Server Admin forum

16 comments »Send a trackback » 11997 views

Trackback address for this post

Trackback URL (right click and copy shortcut/link location)

16 comments

Comment from: chrissie1 [Member] Email
*****
Cool, long but cool.
12/10/08 @ 10:18
Comment from: AlexCuse [Member] Email
*****
Great post man, I don't think you made it long enough though! You could have milked this for ten posts for sure ;)
12/10/08 @ 10:44
Comment from: Impal3r [Member] Email
*****
Damn, I just wore out my scroll wheel trying to read the whole thing. If I print it half the Amazon will be gone :-(
12/10/08 @ 10:46
Comment from: sirrocco [Visitor]
*****
Great job man. Congrats.
12/11/08 @ 04:47
Comment from: Scott Swank [Visitor]
For #5 you can cut your i/o in half by using window functions.

select id, value, SomeDate
from (
select m.*,
row_number() over (
partition by id
order by SomeDate desc) as rn
from #MaxVal m
) m2
where m2.rn = 1;
12/11/08 @ 11:14
Comment from: SQLDenis [Member] Email
Scott,

True, thanks for the comment, my solution is slower but still 2000 compatible
12/11/08 @ 11:27
Comment from: sqlsister [Member] Email
*****
You forgot - Why is my cursor so slow?

12/11/08 @ 12:23
Comment from: SQLDenis [Member] Email
*****
sqlsister ,

I don't know what cursors have to do with SQL, I move them on my screen with the mouse ;-)
12/11/08 @ 12:25
Comment from: kaht [Member] Email
*****
You forgot the most famous SQL question of all time (that's not really a question):

"NEED TO GO FASTER"
12/11/08 @ 14:15
Comment from: Andy [Visitor]
*****
Another way to strip the time out of a date:

SELECT *, CONVERT(datetime,FLOOR(CONVERT(float,DateColumn,0)),0) AS stripped
FROM SomeDates
12/11/08 @ 19:31
Comment from: Jane [Visitor] Email · http://jane.dallaway.com/blog
****-
Re item 1, and specifically getting the date element without the time (prior to SQL Server 2008)

A colleague of mine discovered
SELECT CONVERT(DATETIME,FLOOR(CONVERT(FLOAT,GETDATE())))

which seems to be more efficient than converting to varchar, something I attempted to prove at http://jane.dallaway.com/blog/2008/07/tsql-timings.html
12/12/08 @ 04:39
Comment from: Sainbayar G [Visitor] · http://sainbayar.wordpress.com
*****
first two questions are really creative.
12/14/08 @ 23:04
Comment from: SQLSam [Visitor]
*****
Great job, I had been looking for padding info elsewhere and came across a long winded way around it,
I knew there must have been a better way.

Also might be good to mention that when searching for columns that may include nulls, if you want to include
nulls you actually have to specify it. Took me a while to realise this.
12/23/08 @ 16:20
Comment from: Rizwan Javed [Visitor] · http://www.dubaihits.com
*****
GREAT JOB !!!
Thank you for sharing such an amazing material, I have learn a lot from this article.

Regards,
Rizwan Javed
05/23/09 @ 01:20
Comment from: hondageoff [Member] Email
This is what I've been looking for. Great post!
06/11/09 @ 14:16
Comment from: Naomi [Visitor]
Can you please list all other variations of a solution to #5 problem - then I can simply point to this blog.
06/12/09 @ 06:32

Leave a comment


Your email address will not be revealed on this site.

Your URL will be displayed.
PoorExcellent
(Line breaks become <br />)
(Name, email & website)
(Allow users to contact you through a message form (your email will not be revealed.)