Saturday, May 31, 2008

Useful Stored Procedures

Simulating undocumented Procedures

Sometimes you may want to run a query against each database

Suppose you want to find all dabase names where particular table exists. You can use undocumented procedure

EXEC sp_msforeachdb 'SELECT table_catalog FROM ?.INFORMATION_SCHEMA.TABLES where table_name=''your_table'''

Because they are undocmented, you cant always rely on them.

Alternatively you can use the following methods

1 WHILE LOOP

declare
@dbname varchar(100), @database_id int,@table_name varchar(100)
select @dbname='', @database_id=1,@table_name='your_table'

while exists(Select * from sys.databases where database_id>@database_id)
Begin
select @dbname
=name,@database_id=database_id from sys.databases where database_id=@database_id
EXEC
('SELECT * FROM '+@dbname+'.INFORMATION_SCHEMA.TABLES where table_name='''+@table_name+'''')
select @database_id=min(database_id) from sys.databases where database_id>@database_id
End

2 Concatenated SQL

declare @sql varchar(max), @table_name varchar(100)
select @sql='', @table_name='your_table'
select @sql=@sql+ 'SELECT table_catalog FROM '+name+'.INFORMATION_SCHEMA.TABLES where table_name='''+@table_name+'''' fromsys.databases
exec(@sql)

Object Catalog Views in SQL Server 2005

In addition to INFORMATION_SCHEMA VIEWS, in SQL Server 2005, we can use Object Catalog Views to know more informations about the objects

Refer this http://msdn.microsoft.com/en-us/library/ms189783.aspx

Populating sample data

Sometimes you may need some sample data for testing purpose

The following may help you in generating some sample data of different datatypes

select 

abs(checksum(newid()))%10000 as intcol,

abs(checksum(newid()))*rand()/100 as float_col,

dateadd(day,0,abs(checksum(newid()))%100000) as date_col,

substring(replace(cast(newid() as varchar(36)),'-',''),1,abs(checksum(newid()))%15) as varchar_col,

abs(checksum(newid()))%2 as bit_col

from 

master..spt_values

where 

type='p' and

Union Vs Union All

Well. Most of you know the diffrence between the two.

http://blog.sqlauthority.com/2007/03/10/sql-server-union-vs-union-all-which-is-better-for-performance/

http://www.codethinked.com/post/2007/11/UNION-versus-UNION-ALL2c-lessons-in-minutiae.aspx

Here is one of the differences that is not known by newbies

When you use UNION, you cant include any column of type TEXT and NTEXT. But it is possible if you use UNION ALL

Declare @test table(i int, text_col text)

insert into @test

 
select 1,'some test string'union all

select 2,'some other test string'

--Error

 
select i,text_col from @test where i=1union 

select i,text_col from @test where i=1

 

--No Error

select i,text_col from @test where i=1union all

select i,text_col from @test where i=1

Splitting csv to columns

Suppose you have string in csv format and want to split into seperate columns. You can use parsename function as long as you have maximum four values. The following would work for any number of values

declare @s varchar(2000),@data varchar(2000)

select @s='this,is,test'

select @data=''''+replace(@s,',',''',''')+''''

exec('select '+@data)

Different ways to know structure of a table

You can use one of the following to know the structure of a table

1 Generate SQL Script option from Enterprise Manager/Management Studio

2 select * from information_schema.columns where table_name='table_name'

3 EXEC sp_help 'table_name'

4 EXEC sp_columns 'table_name'

5 In Query Analyser type the name of the table, highlight it and press Alt+F1

Understanding Single quotes

This is for newbies who struggle to understand how single quotes work in SQL Server

I have seen newbies worrying why the following doesnt work

SELECT columns from mytable where col ='Lifco's'

When you specify a value which has single quote, you need to double it

SELECT columns from mytable where col ='Lifco''s'

The following may be helpful (Run and see the result)

SELECT '','''','''''','''''''',''''''''''

When you use a static sql and express a value in a single quote then first and last sigle quotes specify that the value is a string. Then within those single quotes every double single quotes represent a single single quote

When you use a Dynamic sql then first and last sigle quotes specify that it is a dynamic sql. Then within those single quotes every double single quotes specify that it is a string.Then within those single quotes every four single quotes represent a single single quote

Run and see the result

EXEC('SELECT '''','''''''','''''''''''','''''''''''''''',''''''''''''''''''''')

Forcing integrity between tables and procedures

You can force integerity between tables and views with schemabinding when you create views so that tables cant be dropped until views that bind on tables are dropped. Unfortunately it is not possible to force this integrity between tables and procedures. Tables can be dropped though there are procedures referencing them

But if you use SQL Server 2005 or higher version, it is possible with DDL trigger which is one of finest features available

CREATE TABLE test_table(code int, names varchar(100))

 

GO

 

CREATE TRIGGER alert_me

ON DATABASE

FOR DROP_TABLE

 

AS

declare @table_name varchar(100)

select @table_name= right(ddl,charindex(' ',reverse(ddl))-1) from

(

select EVENTDATA().value('(/EVENT_INSTANCE/TSQLCommand/CommandText)[1]','varchar(100)') as DDL

) as t

 
 

If exists

(

select * from information_schema.routines

where routine_definition like '% '+@table_name+'%'

)

 

begin

Raiserror('One or more procedures depend on this table and cant be dropped' , 16, 1)

Rollback

end

 

GO

create procedure test_sp

as

select * from test_table

 

GO

drop table test_table

 

Now you would get error as test_sp is referencing the table

Beware of Implicit conversions

Sometimes you may wonder why integer divisions are not giving exact result

Consider this example

Select 3/4

Select 4/3

Because both numerator and denominators are INTegers, results 0.75, 1.3333 are truncated to integers resulting 0 and 1 respectively

Suppose you want to find out percentage number of orders taken for each shipcity against total orders

select shipcity,count(*)/(select count(*) from northwind..orders)*100 as percentage

from northwind..orders

group by shipcity

 
 
The result of percentage column is 0

To avoid this, you need to convert one of the openrands by float ,decimal or multiply by 1.0

Select 3*1.0/4

Select 4*1.0/3

select shipcity,count(*)*1.0/(select count(*) from northwind..orders)*100 as percentage

from northwind..orders

group by shipcity

 
 

Remove duplicate characters from a string

Pinal Dave in his weblog posted about Remove Duplicate Chars From String

Here is its alternate method with Number table approach

create procedure remove_duplicate_characters(@string varchar(100))

as

Declare @result varchar(100)

set @result=''

select @result=@result+min(substring(@string ,number,1)) from

(

select number from master..spt_values where type='p' and number between 1 and len(@string )

) as t

group by substring(@string,number,1)

order by min(number)

 

select @result

 
GO

EXEC remove_duplicate_characters 'aasssarrrty'

returns

asrty

 
 

Count number of words in a string

Tony Rogerson in his weblog posted about Counting the number of words in a string

Here are two more methods

1 Using the method I posted already about Squeeze Function to remove multiple spaces

 
 

Declare @s varchar(100)

set @s=' See how many words this has '

Select len(string)-len(replace(string,' ',''))+1 as no_of_words from

(

Select replace(replace(replace(ltrim(@s),' ',' ~!@#'),'~!@# ',''),'~!@#','') as string

) as t

2 Using Number table approach used to Extract only numbers from a String

Declare @s varchar(100),@result varchar(100)

set @s=' See how many words this has '

set @result=''

Select @s=replace(replace(replace(ltrim(@s),' ',' ~!@#'),'~!@# ',''),'~!@#','')

select count(*)+1 as no_of_words from

(

select substring(@s,number,1) as number from

(

select number from master..spt_values where type='p' and number between 1 and len(@s)

) as t

) as t

where number=''

 

Extract only numbers from a String

Sometimes we may need to extract only numbers from a string. Usually I see people using while loop to check each and every character to see if it is a number and extract it

Here is a different appraoch

Declare @s varchar(100),@result varchar(100)

set @s='as4khd0939sdf78'

set @result=''

select

@result=@result+case when number like '[0-9]' then number else '' end

from

(

select substring(@s,number,1) as number from

(

select number from master..spt_values where type='p' and number between 1 and len(@s)

) as t

) as t

 

select @result as only_numbers

 


Result

4093978


Script out Procedures and Functions - Part 2

Part 1 uses information_Schema.routines view and this is based on sp_helptext

declare @sps table(texts varchar(8000))

insert into @sps

select 'sp_helptext '''+name+'''' from sysobjects where xtype in ('p','fn')

order by xtype,name

 

create table scripts(sps varchar(8000))

 

declare @texts varchar(1000)

 
 

select @texts=min(texts)from @sps

while @texts>''

Begin

EXEC('insert into scripts(sps) EXEC '+ @texts)

insert into scripts(sps) select 'GO'

select @texts=min(texts)from @sps where texts>@texts

End

EXEC master..xp_cmdshell 'bcp "select * from dbname..scripts" queryout "c:\scripts.txt" -c'

 

drop table scripts

 

Squeeze Function

The Squeeze function is used to remove the multiple occurences of spaces into one occurence. In SQL Server there is no function to do the same. I needed to write this in my application to remove unwanted spaces in the string.

Run the following and see the result

declare @t table(string_col varchar(100))

insert into @t

select 'a b c' union all

select 'a b c' union all

select 'a b c' union all

select 'a b c' union all

select 'a b c'

 

select

string_col,

replace(replace(replace(string_col,' ',' ~!#$^'),'~!#$^ ',''),'~!#$^','') as new_string_col

from

@t

 

If you use front end application (VB,.NET,jave,etc), you can also simulate the same approach there

Column length and Data length

When you use character datatypes(char,varchar,nchar,etc),

always make sure that column has enough length to have data.

Otherwise sometimes you would get errors and sometimes you wont.

 
 

Consider the following example

Declare @t table(i int, test_col varchar(2))

Insert into @t select 1,'test'

Select i,test_col from @t

Well. You get the error

Msg 8152, Level 16, State 14, Line 4

String or binary data would be truncated.

The statement has been terminated.

 
 

Because the column length of test_col is not enough to have the value 'test'

But only when you insert data to a table, you get this error.

In other cases you dont

Declare @v varchar(2)

set @v='test'

select @v

 

Now there is no error but the value is truncated and

only first two characters are assigned to the variable.

The same case is applied when you use a stored procedure with input parameter

create procedure test(@test_param varchar(2))

as

Select @test_param

Go

EXEC test1 'test'

Go

drop procedure test

 

Now see what happens when you dont specify the length

 

Declare @v varchar

set @v='test'

select @v

 

By default the length is 1

 

Consider another example

 

Select cast('This has more than thirty characters' as varchar)

The result is This has more than thirty char

When you cast a literal to character type without specifying the length,

by default it is 30.

So you need to specify the enough column length when you use character datatypes

Select columns from (EXEC procedure_name) - Is this possible?

Well.I see many users asking this question in forums. "I have a procedure that returns single resultset. How do I filter the result returned from the procedure? There are, at least, two ways to acheive this

Let us create this procedure

create procedure get_orders

as

select* from northwind..orders

 

1 Create a temporary table whose structure is identical to the result of the procedure and query on this table

Create table #orders(Orderid int,..................)

Insert into #orders EXEC get_orders

Select* from #orders where orderdate>='19960101' and orderdate<'19970101'

2 Use OPENROWSET

Select

*

from

OPENROWSET('SQLOLEDB','Data Source=test;Trusted_Connection=yes;

Integrated Security=SSPI','Execute yourdb..get_orders')

 

Now you can easily filter the resultset

Select 

employeeid,orderid,orderdate

from

OPENROWSET('SQLOLEDB','Data Source=test;Trusted_Connection=yes;

Integrated Security=SSPI','Execute yourdb..get_orders')

where

orderdate>='19960101' and orderdate<'19970101'

I prefer using method 2 as you dont need to create temp table and
also you dont need to worry about the structure of the procedure

 

Random Password Generator

This is one of the methods to generate 8 characters password randomly (combination of alphabets,numbers and special characters)

declare @password varchar(8)

set @password=''

select @password=@password+char(n) from

(

select top 8 number as n from master..spt_values

where type='p' and number between 48 and 122

order by newid()

) as t

 

select @password

 
 

Should alias names be preceded by AS?

If you ask me, I would say Yes Smile

When you write select statements and use alias names, always make sure alias names are preceded by the keyword AS. Oherwise you may get results which are unexpected and sometimes difficult to debug.

Consider the following example

use northwind

select employeeid,orderdate,shippeddate,shipcity from orders

Well. You see the correct resultset

Now run this

select employeeid,orderdate,shippeddate shipcity from orders

and see the resultset.Shippeddate's column name becomes shipcity because there is a missing comma after shippeddate that forces shipcity to be alias for shippeddate. If there are tens of columns and if you miss out a comma, it would become tedius to debug if the resultset is sent to the client application

Consider another example


select count(*) from orders

It returns the count as 830

Now what happens by mistake if you omit the keyword from


select count(*) orders

Now it returns count as 1 with alias name orders.

If SQL Server forces you to use AS before alias name, you would get error for the select statements that returns unexpected result in the above example. So I always ask developers to use AS before alias name which certainly makes the statements more readable and easy to debug

Simple Multiply Function

There is no direct way to multiply all the values of a column as we do summation using SUM(col). But it is possible with the following methods

create table test (i int, amount float)

insert into test(i,amount)

select

abs(cast(cast(newid() as varbinary(100)) as smallint)),

abs(cast(cast(newid() as varbinary(100)) as smallint))/1000

from

sysobjects s1 cross join sysobjects s2

 
 

Method 1

 
 

select

i,exp(sum(log(cast(amount as float)))) AS multiplied_value

from

test

where

amount<>0

group by i

order by i

 
 

Method 2

use user defined function

 

create function dbo.multiply(@number int)

returns float as

begin

declare @i float

set @i=1.0

select @i=@i*amount from test where i

return @i

end

 
 

select i,dbo.multiply(i) as multiplied_value from

(

select distinct i from test

) as t

order by i

Easy way to generate number table

Often you may require number table for various purposes (parsing csv, string manipulation,etc)

If you use SQL Server 2005, then it is very easy than any other method you use in lower versions

Here are two methods of generating number table with 10000 numbers

(1) Use Row_number() funtion

Declare @numbers table(number int)

insert into @numbers(number)

select number from

(

select row_number() over (order by s1.name) as number

from sysobjects s1 cross join sysobjects s2

) as numbers

where number between 1 and 10000

 

select number from @numbers

 
 

(2) Use CTE

 
 

Declare @numbers table(number int)

with numbers(number) as

(

select 1 as number

union all

select number+1 from numbers where number<10000

)

 

insert into @numbers(number)

select number from numbers option(maxrecursion 10000)

select number from @numbers

Different ways to count rows from a table

The most reliable and suggested method is to use count(*)

1 Select count(*) from table_name

But other than that you can also use one of the followings


2 select sum(1) from table_name
3 select count(1) from table_name
4 select rows from sysindexes where object_name(id)='table_name' and indid<2
5 exec sp_spaceused 'table_name'
6 DBCC CHECKTABLE('table_name')

PS

To update rows column of sysindexes table, you need to run this before executing methods 4 and 5

DBCC UPDATEUSAGE ('database_name','table_name') WITH COUNT_ROWS.

Undocumented DATE and TIME related functions

It is quite surprising to know the undocumented date and time related functions that work in both sql server 2000 and 2005

Run these and see(I think they are self-explanatory Smile)

select {fn current_date()}
select {fn current_time()}
select {fn now()}
select {fn extract(hour from getdate())}
select {fn extract(minute from getdate())}
select {fn extract(second from getdate())}
select {fn extract(day from getdate())}
select {fn extract(month from getdate())}
select {fn extract(year from getdate())}
select {fn dayname(GetDate())}
select {fn monthname(GetDate())}
select {fn month(GetDate())}
select {fn year(GetDate())}

Write Function in SQL Server 2005

One of the functions available in SQL Server 2005 that allows you to update a character column of size max is WRITE Function. In fact we can consider this as alternate to STUFF function which wont work for columns with Max length

declare @t table(v nvarchar(max))

insert into @t select 'test is test'

update @t set v=stuff(v,1,4,'This')

select v from @t

 
 

GO

 
 

declare @t table(v nvarchar(max))

insert into @t select 'test is test'

update @t set v.write('This',0,4)

 

select v from @t

 
PS

You can use STUFF function in both SELECT and UPDATE Statement but WRITE function can be used only in UPDAET statement preceded by Column name.

Behaviour Changes in SQL Server 2005

If you run the following query in both SQL Server 2000 and 2005 , you can see the difference


select random,random,random
from
(
select rand() as random
) as t

Those behaviour changes are documented here

http://msdn2.microsoft.com/en-us/library/ms143359.aspx

Generating Random Numbers - Part II

There are many methods to generate random numbers. Here are some of them

select number from

(

select top 10000 abs(checksum(newid())) as number

from sysobjects s1 cross join sysobjects s2

) as T

 

select number from

(

select top 1000 abs(cast(cast(newid() as varbinary(10)) as int)) as number

from sysobjects s1 cross join sysobjects s2

) as T

 

If you want to generate 4 digits random number, then use

 

select number/power(10,len(number)-4) as number from

(

select top 10000 abs(checksum(newid())) as number

from sysobjects s1 cross join sysobjects s2

) as T

 
 

If you want to generate 2 digits random number, then use

select number/power(10,len(number)-2) as number from

(

select top 10000 abs(checksum(newid())) as number

from sysobjects s1 cross join sysobjects s2

) as T

 
 

If you use SQL Server 2005, you can also apply the same approach on the following query

select row_number() over (order by s1.name) as number

from sysobjects s1 cross join sysobjects s2

ISNULL() or COALESCE()?


Both the functions are used to show/assign different value if the parameter is null.

select isnull(null,'test'),coalesce(null,'test')

But there are diiferences between the two

isnull() is sql server specific
The datatype/length of the result depends on the parameter

ex

declare @s varchar(1)
Select isnull(@s,'test')

Note that the result is t and not test
The name isnull sounds that it should return 0 or 1 like other functions isdate and isnumeric

But coalesce has advantages over isnull

It is defined by ANSI standard
It will accept more than one parameters
The result doesnt directly depend on the parameter

ex

declare @s varchar(1)
Select coalesce(@s,'test')

and note that the result is test

Although it is proved that isnull is faster than coalesce here (http://sqljunkies.com/WebLog/amachanic/archive/2004/11/30/5311.aspx), for the above reasons I prefer using coalesce

Handle ISDATE() with care

Now-a-days it becomes common practice for newbies to use varchar datatype to store dates. My first advice is to use proper DATETIME datatype. Sometimes it is preferred to use varchar when importing dates from other systems(to avoid any errors if the source dates are in different format). In that case it is required to see if values are actually dates. To check if a value is proper date, it is suggested to use ISDATE()

eg

select isdate('2007-10-12'),isdate('12-12-2006'),isdate('March 12, 2007')

Now it should be noted that the following return 1


select isdate(2007),isdate('2007')

becuase the values in a ISDATE functions are first casted to datetime

select cast(2007 as datetime)

------------------------------------------------------
1905-07-01 00:00:00.000

select cast('2007' as datetime)
------------------------------------------------------
2007-01-01 00:00:00.000

Which are valid dates

So if varchar datatype is used to stored formatted dates (ex YYYYMMDD), to check if it is valid date, you need not only to use ISDATE() function, but also use LEN() function

Consider the following set of data and see the difference


declare @dates table (dates varchar(8))
insert into @dates(dates)
Select '20071201' union all
Select '20071111' union all
Select '2007' union all
Select 2007 union all
Select '1800'


select dates from @dates
where ISDATE(dates) =1

--Result
dates
--------
20071201
20071111
2007
2007
1800

--Result

select dates from @dates
where ISDATE(dates) =1 and LEN(dates)=8


dates
--------
20071201
20071111

Find Nth Maximum value


"How do I find Nth maximum value?" is one of the most asked questions

Here are some methods

I explain how to find 5th Maximum value

Create table number (num integer)
Insert into number values(3456)
Insert into number values(846)
Insert into number values(1456)
Insert into number values(3098)
Insert into number values(34)
Insert into number values(67856)
Insert into number values(906)
Insert into number values(34656)
Insert into number values(9056)
Insert into number values(3036)

(1) Use Inner Join

select t1.num from number t1 inner join number t2 on t1.num<=t2.num
group by t1.num having count(t1.num)=5

(2) Use Top Operator

Select top 1 num from(
Select top 5 num from number order by num desc) T
order by num asc

(3) Generate Serial No based on descending order of the values

select num from (
Select (select count(*) from number where num>=T.num)as Sno ,num
from number T ) temp where Sno=5

(4) Use Aggregate Function

Select min(num) from (select top 5 num from number order by num desc) T

Now you can replace 5 to the number that you want to find the maximum value

Import/Export to Excel

Apart from using DTS and Import/Export wizard, we can also use this query to export data from SQL Server2000 to Excel and vice versa

To export data from SQL Server table to Excel file, create an Excel file named testing having the headers same as that of table columns and use this query

insert into OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel 8.0;Database=D:\testing.xls;',
'SELECT * FROM [SheetName$]') select * from SQLServerTable

To export data from Excel to new SQL Server table,

select *
into SQLServerTable FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel8.0;Database=D:\testing.xls;HDR=YES',
'SELECT * FROM [Sheet1$]')

To export data from Excel to existing SQL Server table,

Insert into SQLServerTable Select * FROM OPENROWSET('Microsoft.Jet.OLEDB.4.0',
'Excel8.0;Database=D:\testing.xls;HDR=YES',
'SELECT * FROM [SheetName$]')

Generate SQL Script

--This procedure will generate the structure of the table

Create Procedure GenerateScript (
@tableName varchar(100))
as
If exists (Select * from Information_Schema.COLUMNS where Table_Name= @tableName)
Begin
declare @sql varchar(8000)
declare @table varchar(100)
declare @cols table (datatype varchar(50))
insert into @cols values('bit')
insert into @cols values('binary')
insert into @cols values('bigint')
insert into @cols values('int')
insert into @cols values('float')
insert into @cols values('datetime')
insert into @cols values('text')
insert into @cols values('image')
insert into @cols values('uniqueidentifier')
insert into @cols values('smalldatetime')
insert into @cols values('tinyint')
insert into @cols values('smallint')
insert into @cols values('sql_variant')

set @sql=''
Select @sql=@sql+
case when charindex('(',@sql,1)<=0 then '(' else '' end +Column_Name + ' ' +Data_Type +
case when Data_Type in (Select datatype from @cols) then '' else '(' end+
case when data_type in ('real','money','decimal','numeric') then cast(isnull(numeric_precision,'') as varchar)+','+
case when data_type in ('real','money','decimal','numeric') then cast(isnull(Numeric_Scale,'') as varchar) end
when data_type in ('char','nvarchar','varchar','nchar') then cast(isnull(Character_Maximum_Length,'') as varchar) else '' end+
case when Data_Type in (Select datatype from @cols)then '' else ')' end+
case when Is_Nullable='No' then ' Not null,' else ' null,' end
from Information_Schema.COLUMNS where Table_Name=@tableName


select @table= 'Create table ' + table_Name from Information_Schema.COLUMNS where table_Name=@tableName
select @sql=@table + substring(@sql,1,len(@sql)-1) +' )'
select @sql as DDL

End

Else


Select 'The table '+@tableName + ' does not exist'

Select Data from Top N Columns

Suppose you have table with many columns and often you need data from first 15 or 20 columns. You have to specify all the columns in your select statement. This procedure will select top N columns you want. All you have to do is to supply table name and number of columns you want

Here is the procedure


CREATE procedure TopNcolumns (@tableName varchar(100),@n int)
as
Declare @s varchar(2000)
set @s=''
If @n>=0
Begin
set rowcount @n
Select @s=@s+','+ column_name from information_schema.columns
where table_name=@tablename order by ordinal_position
Set rowcount 0
Set @s=substring(@s,2,len(@s)-1)
Exec('Select '+@s+' from '+@tablename)
End
else
Select 'Negative values are not allowed' as Error

If you execute TopNColumns 'Mytable',12 then first 12 columns with data from the table Mytable will be displayed

Backup Database

This query will backup the current database


declare @sql varchar(1000)
select @sql = 'BACKUP DATABASE '+(select db_name())+' TO DISK = ''E:\'+(select db_name())+
convert(varchar,GETDATE(),112)+'.bak'''
Exec(@sql)

If the database name is Test then executing this query will backup Test database and name of that backup file is Test20050817.bak. Current date with the format yyyymmdd will be suffixed to the DBName

If you schedule this query to run daily, then backup will be generated daily as a seperate file

Generate Random Numbers

This procedure will display n random numbers selected from specified range

Pass Starting Number, Ending Number and the number of numbers you want to select randomly


Create Procedure RandomNumbers (@min bigint, @max bigint, @count int)
as
Set Nocount On

Declare @t table(num bigint)
Declare @start bigint
Declare @End bigint

While @min<=@max

Begin
Insert into @t values(@min)
set @min=@min+1
End

Set Rowcount @count
Select num from @t order by newid()
Set Rowcount 0

If you run the below code

Exec RandomNumbers 100,300,10

10 random numbers between 100 and 300 will be displayed


Scripts Stored Procedures and Functions through query

This query will script the Procedures and Functions

All you have to do is replace DBname by the actual Database Name


exec master..xp_cmdshell
'bcp "Select routine_definition from DBname.information_Schema.routines order by routine_name" queryout "C:\scripts.sql" -c'

After it runs successfully, the file C:\scripts.sql will have the scripts

BCP - Export data to Text File

Here is a simple method of exporting all the data from SQL Server table to a Text File

CREATE Procedure BCP_Text_File
(
@table varchar(100),
@FileName varchar(100)
)
as
If exists(Select * from information_Schema.tables where table_name=@table)
Begin
Declare @str varchar(1000)
set @str='Exec Master..xp_Cmdshell ''bcp "Select * from '+db_name()+'..'+@table+'" queryout "'+@FileName+'" -c'''
Exec(@str)
end
else
Select 'The table '+@table+' does not exist in the database'


Execute this procedure by giving Table Name and the File Name

EXEC BCP_Text_File 'Employee','C:\emp.txt'

Now all the data from Employee table will be exported to the text file which will be located at C:\emp.txt


Get IP Address

Here is a method of getting the IP Address of a system using Stored Procedure


Create Procedure Get_IPAddress
(@sysName varchar(30))
as
Set Nocount On

Create table #t (data varchar(100))
Declare @sql varchar(100)
Set @sql='Ping -n 1 '+@sysname

Insert into #t
Exec Master..xp_cmdShell @sql

If exists (select * from #t where data like '%Unknown%')
Select 'The system '+@sysName+' doesnt exist' as Error
else
Select Substring(data,charindex('[',data,1)+1,charindex(']',data,1)-(charindex('[',data,1)+1))
as IPAddress from #t where data like '%bytes of data%'

drop table #t
Set Nocount Off

Run this procedure as

EXEC Get_IPAddress 'systemName'

Storing Images

I have read many questions in the Forums asking "How do I store images in the table?". The simplest answer would be to store only the physical path of the images in the table and storing the images in the server's physical directory. To retrieve the image use Presentation layer's File System object. This makes easy the process of storing and retrieving the images.Otherwise AppendChunk and GetChunk methods should be used in the Front End Application.

Here are the complete explanations on how to handle images

http://www.microsoft.com/technet/prodtechnol/sql/2000/reskit/part3/c1161.mspx

http://support.microsoft.com/default.aspx?scid=kb;en-us;317016

CSV to Multiple Columns



If the table is denormalised and has Comma Seperate Values in a
column, this code will copy it to Multiple columns of a
Normalised table
declare @DeNormalisedTable table(data varchar(8000))
 
insert into @DeNormalisedTable 
select '1,Davolio,Nancy' union all
select '2,Fuller,Andrew' union all
select '3,Leverling,Janet' union all
select '4,Peacock,Margaret' union all
select '5,Buchanan,Steven' union all
select '6,Suyama,Michael' union all
select '7,King,Robert' union all
select '8,Callahan,Laura' union all
select '9,Dodsworth,Anne'
 
select * from @DeNormalisedTable -- Comma Seperated Values
declare @s varchar(8000), @data varchar(8000)
Create table #NormalisedTable (Code int, FirstName varchar(100), 
LastName varchar(100))
 
select @s=''
 
while exists (Select * from @DeNormalisedTable where data>@s)
Begin
        Select @s=min(data) from @DeNormalisedTable where data>@s
        select @data=''''+replace(@s,',',''',''')+''''
        insert into #NormalisedTable            
        exec('select '+@data)
End
 
select * from #NormalisedTable -- Data in Normalised Table
 
drop table #NormalisedTable


Enhanced ISNUMERIC() function

It seems often users want to check whether the data has only numbers in a varchar type column. The commonly suggested one is to make use of ISNUMERIC() function. But the problem in using that function is that it will treat some alphabets, $, char(10),etc as numbers.

Cosider this example

declare @test table(strings varchar(50))
Insert into @test
Select '12d3' union all
Select '87234.45' union all
Select '$123,456.00' union all
Select ' 12 ' union all
Select char(10) union all
Select '$'
select strings,isnumeric(strings)as valid from @test


The result is

strings valid
-------------------------------------------------- -----------
12d3 1
87234.45 1
$123,456.00 1
12 1

1
$ 1


Note that only 87234.45 should be considered as numeric and not others

The following function will solve this problem

CREATE Function Is_numeric(@value varchar(25))
Returns bit
as
Begin
Return
(
case when @value not like '%[^-0-9.]%' and len(@value)-len(replace(@value,'.',''))<2
and
1=
(
case when charindex('-',@value)>0 then
case when left(@value,1)='-' and len(@value)-len(replace(@value,'-',''))<2>1 then
1
else
0
end
else
1
end
) then

1
else
0
end
)
End

Now execute this select statement

select strings, dbo.is_numeric(strings)as valid from @test

The result is

strings valid
-------------------------------------------------- -----
12d3 0
87234.45 1
$123,456.00 0
12 0

0
$ 0

SQLAnswers

This freeware tool is used to execute sql,procedures,etc and export the result to EXCEL,PDF,HTML,etc format

Try it here

http://www.sqlanswers.com/Software/

Dynamic Crosstab with multiple PIVOT Columns

Jeff Smith in his weblog showed how to generate Crosstab reports using a stored procedure. It works only for one PIVOT Column.I had a requirement to generate crosstab reports with more than one pivot column. So I used the same approach he used and modified his procedure as shown below

CREATE procedure CrossTab
(
@select varchar(2000),
@PivotCol varchar(100),
@Summaries varchar(100),
@GroupBy varchar(100),
@OtherCols varchar(100) = Null
)

AS

set nocount on
set ansi_warnings off

declare @sql varchar(8000)

Select @sql = ''
Select @OtherCols= isNull(', ' + @OtherCols,'')


create table #pivot_columns (pivot_column_name varchar(100))
Select @sql='select ''' + replace( + @PivotCol,',',''' as pivot_column_name union all select ''')+''''
insert into #pivot_columns
exec(@sql)

select @sql=''

create table #pivot_columns_data (pivot_column_name varchar(100),pivot_column_data varchar(100))

Select @PivotCol=''
Select @PivotCol=min(pivot_column_name) from #pivot_columns
While @PivotCol>''
Begin
insert into #pivot_columns_data(pivot_column_name,pivot_column_data)
exec (
'select distinct ''' + @PivotCol +''' as pivot_column_name, convert(varchar(100),' + @PivotCol + ') as pivot_column_data from
('+
@select
+'
) T'
)
Select @PivotCol=min(pivot_column_name) from #pivot_columns where pivot_column_name>@PivotCol

end


select @sql = @sql + ', ' +
replace(
replace(
@Summaries,'(','(CASE WHEN ' + Pivot_Column_name + '=''' +
pivot_column_data + ''' THEN '
),
')[', ' END) as [' + pivot_column_data
)
from #pivot_columns_data
order by pivot_column_name

exec ( 'select ' + @GroupBy +@OtherCols +@sql +

' from (
'+
@select
+'
) T
GROUP BY ' + @GroupBy)


drop table #pivot_columns
drop table #pivot_columns_data

set nocount off
set ansi_warnings on

Now execute the procedure by supplying two pivot columns shipcountry and Year(Orderdate)


EXEC CrossTab
'SELECT LastName, OrderDate,shipcountry FROM northwind..Employees Employees
INNER JOIN northwind..Orders Orders ON (Employees.EmployeeID=Orders.EmployeeID) ',
'shipcountry,Year(OrderDate)',
'Count(LastName)[]',
'LastName'

Multipurpose Row_Number() Function

One of the features available in SQL Server 2005 is Row_Number() function. It is not only used to generate row number for each row but also used for other purposes as well. I breifly explain how it can be used for various purposes

Consider the following data

Declare @t table(item varchar(100), price float)

insert into @t
select 'item1', 20000 union all
select 'item1', 20000 union all
select 'item1', 20700 union all
select 'item2', 57600 union all
select 'item2', 80120 union all
select 'item3', 89760 union all
select 'item3', 87680 union all
select 'item4', 87680 union all
select 'item4', 43220 union all
select 'item4', 43220


(1)

Generate Serial No or replicate identity column
select row_number() over(order by item) as row_number, * from @t

--Result

row_number item price
-------------------- ---------------------- ------------------------------------------
1 item1 20000.0
2 item1 20000.0
3 item1 20700.0
4 item2 57600.0
5 item2 80120.0
6 item3 89760.0
7 item3 87680.0
8 item4 87680.0
9 item4 43220.0
10 item4 43220.0

(2)

Generate Serial No and reset in each group
select row_number() over(partition by item order by item) as row_number, * from @t

--Result

row_number item price
-------------------- ---------------------- ----------------------------------------
1 item1 20000.0
2 item1 20000.0
3 item1 20700.0
1 item2 57600.0
2 item2 80120.0
1 item3 89760.0
2 item3 87680.0
1 item4 87680.0
2 item4 43220.0
3 item4 43220.0

(3)

Select top N data for each group
Select * from
(
select row_number() over(partition by item order by item) as row_number, * from @t
) T
where row_number<=N

where N is a positive integer value

--Result (when N=2)

row_number item price
-------------------- ---------------------- ------------------------------------------
1 item1 20000.0
2 item1 20000.0
1 item2 57600.0
2 item2 80120.0
1 item3 89760.0
2 item3 87680.0
1 item4 87680.0
2 item4 43220.0

(4)

Pagination
Select item,price from
(
select row_number() over(order by item) as row_number, * from @t
) T
where row_number between 1 and 5

--Result

item price
------------------------- ------------------------------------------
item1 20000.0
item1 20000.0
item1 20700.0
item2 57600.0
item2 80120.0

(5)

Find Nth Maximum value

Select price from
(
select row_number() over(order by price desc) as row_number, price from(select distinct price from @t) d
) T
where row_number=N

--Result(where N=2)

price
------------------------------------------
87680.0

(6)

Delete duplicates
delete T from
(
select row_number() over(order by item) as row_number, * from @t
) T
where row_number not in
(
select min(row_number) from
(select row_number() over(order by item) as row_number, * from @t) T
group by item
)

or

delete T from
(
select row_number() over(partition by item order by item) as row_number, * from @t
) T
where row_number>1

select * from @t

--Result

item price
------------------------- ------------------------------------------
item1 20000.0
item2 57600.0
item3 89760.0
item4 87680.0

Truncate All Tables - Part II


There are many methods to truncate all the tables in a database
Part I of my post http://sqlblogcasts.com/blogs/madhivanan/archive/2007/08/27/truncate-all-tables-part-i.aspx shows executing the concatenated truncate table
statements. This is other method

Set NoCount ON

 
 

Declare @tableName varchar(200)

set @tableName=''

 
 

While exists

(

--Find all child tables and those which have no relations

select T.table_name from INFORMATION_SCHEMA.TABLES T

left outer join INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC

on T.table_name=TC.table_name

where (TC.constraint_Type ='Foreign Key'or TC.constraint_Type is NULL) and

T.table_name not in ('dtproperties','sysconstraints','syssegments')and

Table_type='BASE TABLE' and T.table_name > @TableName)

 
 

Begin

Select @tableName=min(T.table_name) from INFORMATION_SCHEMA.TABLES T

left outer join INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC

on T.table_name=TC.table_name

where (TC.constraint_Type ='Foreign Key'or TC.constraint_Type is NULL) and

T.table_name not in ('dtproperties','sysconstraints','syssegments') and

Table_type='BASE TABLE' and T.table_name > @TableName

 
    --Truncate the table

Exec('Truncate table '+@tablename)

End

set @TableName=''

While exists

(

--Find all Parent tables

select T.table_name from INFORMATION_SCHEMA.TABLES T

left outer join INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC

on T.table_name=TC.table_name

where TC.constraint_Type ='Primary Key'and T.table_name <>'dtproperties' and

Table_type='BASE TABLE' and T.table_name > @TableName )

 
 

Begin

Select @tableName=min(T.table_name) from INFORMATION_SCHEMA.TABLES T

left outer join INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC on T.table_name=TC.table_name

where TC.constraint_Type ='Primary Key' and T.table_name <>'dtproperties' and

Table_type='BASE TABLE' and T.table_name > @TableName

--Delete the table

Exec('Delete from '+ @tableName)

--Reset identity column

 

If exists

(

select * from information_schema.columns

where COLUMNPROPERTY(OBJECT_ID(QUOTENAME(table_schema)+'.'+

QUOTENAME(@tableName)), column_name,'IsIdentity')=1

)

 
 

DBCC CHECKIDENT (@tableName, RESEED, 1)

End

 

Set NoCount Off

 

Truncate All Tables - Part I


This procedure will truncate all the tables in the database
To truncate all tables,I have seen the programmers using Cursors to get all tables and truncate them.This will avoid Cursors. Running this procedure will make SQL Server to slow down other tasks. So test this with test server and make sure that you are not running any other SQL Server related processes


 
 

Create Procedure Truncate_All_Tables

as

Declare @t table(query varchar(1000),tables varchar(50))

Insert into @t

select 'Truncate table ['+T.table_name+']', T.Table_Name from

INFORMATION_SCHEMA.TABLES T

left outer join INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC

on T.table_name=TC.table_name

where (TC.constraint_Type ='Foreign Key' or TC.constraint_Type is NULL) and

T.table_name not in ('dtproperties','sysconstraints','syssegments') and

Table_type='BASE TABLE'

 

Insert into @t

select 'delete table ['+T.table_name+']', T.Table_Name from

INFORMATION_SCHEMA.TABLES T

left outer join INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC

on T.table_name=TC.table_name where TC.constraint_Type ='Primary Key' and

T.table_name <>'dtproperties'and Table_type='BASE TABLE'

Declare @sql varchar(8000)

 

Select @sql=IsNull(@sql+' ','')+ query from @t

Exec(@sql)

 

GO

Run this procedure as

EXEC Truncate_All_Tables