Friday, April 16, 2010

Kill All SPIDs for given Databases

So as promised in my last post, I'm going to include a script that will loop over a list of databases and kill all SPIDS connected to them. If you want to use this, you'll need to install the SP_WHO3 proc and the StringToTable function that I posted in the past two posts.


DECLARE @DBsToKill VARCHAR(MAX) = 'DB1,DB2,DB3' --List of DBs here, comma delimited
,@KillTest VARCHAR(20) = 'TEST' --TEST=print SPIDs; KILL=Kill SPIDs
,@SQL VARCHAR(MAX)

SET @SQL = '
SET NOCOUNT ON
CREATE TABLE #SpidsToKill (
SPID VARCHAR(5)
,[Status] VARCHAR(max)
,[login] VARCHAR(max)
,[HostName] VARCHAR(max)
,BlkBy VARCHAR(max)
,DBName VARCHAR(100)
,Command VARCHAR(max)
,CPUTime VARCHAR(max)
,DiskIO VARCHAR(max)
,LastBatch VARCHAR(MAX)
,ProgramName VARCHAR(MAX)
,SPID2 VARCHAR(max)
,RequestID VARCHAR(max)
)
'
DECLARE @DBName VARCHAR(100)

DECLARE Z_CURSOR CURSOR FOR
SELECT TableValue
FROM dbo.StringToTable(@DBsToKill,',')

OPEN Z_CURSOR
FETCH NEXT FROM Z_CURSOR
INTO @DBName
WHILE @@FETCH_STATUS = 0
BEGIN

SET @SQL += '
INSERT INTO #SpidsToKill

( SPID
,Status
,login
,HostName
,BlkBy
,DBName
,Command
,CPUTime
,DiskIO
,LastBatch
,ProgramName
,SPID2
,RequestID
)
EXEC sp_who3 ''' + @DBName + '''
'

FETCH NEXT FROM Z_CURSOR
INTO @DBName
END
CLOSE Z_CURSOR
DEALLOCATE Z_CURSOR

IF (@KillTest = 'Kill')
BEGIN
SET @SQL += '
DECLARE @SPID VARCHAR(4)
,@InternalSQL VARCHAR(MAX) = ''''

DECLARE Z_CURSOR CURSOR FOR
SELECT SPID
FROM #SpidsToKill

OPEN Z_CURSOR
FETCH NEXT FROM Z_CURSOR
INTO @SPID
WHILE @@FETCH_STATUS = 0
BEGIN
SET @InternalSQL += ''
KILL '' + @SPID

FETCH NEXT FROM Z_CURSOR
INTO @SPID
END
CLOSE Z_CURSOR
DEALLOCATE Z_CURSOR

exec(@InternalSQL)
--PRINT @InternalSQL
'
END
ELSE
BEGIN
SET @SQL += 'SELECT * FROM #SpidsToKill ORDER BY DBName, SPID'

END
SET @SQL += '
DROP TABLE #SpidsToKill'

--PRINT @SQL

EXEC(@SQL)




Returning SPIDS for a given Database

I took the SP_WHO2 proc and altered it so that it could take in a parameter containing a database name and return only SPIDs that are active in that database. I pretty much just use this when I'm going to be restoring backups and need to have the database without any connections to it. However in my next post, we'll make use of this proc and the string to table proc I discussed in yesterdays post and use them both to create a script to kill all spids in a list of databases.


USE [master]
GO
/****** Object: StoredProcedure [dbo].[sp_who3] Script Date: 04/16/2010 15:23:09 ******/
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO



ALTER procedure [dbo].[sp_who3] --- 1995/11/03 10:16
@dbname sysname = NULL
as

set nocount on

declare
@retcode int

declare
@sidlow varbinary(85)
,@sidhigh varbinary(85)
,@sid1 varbinary(85)
,@dbid INT
,@spidlow int
,@spidhigh int

declare
@charMaxLenLoginName varchar(6)
,@charMaxLenDBName varchar(6)
,@charMaxLenCPUTime varchar(10)
,@charMaxLenDiskIO varchar(10)
,@charMaxLenHostName varchar(10)
,@charMaxLenProgramName varchar(10)
,@charMaxLenLastBatch varchar(10)
,@charMaxLenCommand varchar(10)

declare
@charsidlow varchar(85)
,@charsidhigh varchar(85)
,@charspidlow varchar(11)
,@charspidhigh varchar(11)

-- defaults

select @retcode = 0 -- 0=good ,1=bad.
select @sidlow = convert(varbinary(85), (replicate(char(0), 85)))
select @sidhigh = convert(varbinary(85), (replicate(char(1), 85)))

select
@spidlow = 0
,@spidhigh = 32767

--------------------------------------------------------------
IF (@dbname IS NULL) --Simple default to all LoginNames.
GOTO LABEL_17PARM1EDITED

-- select @sid1 = suser_sid(@dbname)
select @dbid = null
if exists(select * from sys.databases WHERE name = @dbname)
select @dbid = database_id from sys.databases WHERE name = @dbname

IF (@dbid IS NOT NULL) --Parm is a recognized login name.
begin
GOTO LABEL_17PARM1EDITED
end

--------

raiserror(15007,-1,-1,@dbname)
select @retcode = 1
GOTO LABEL_86RETURN


LABEL_17PARM1EDITED:


-------------------- Capture consistent sysprocesses. -------------------

select

spid
,status
,sid
,hostname
,program_name
,cmd
,cpu
,physical_io
,blocked
,dbid
,convert(sysname, rtrim(loginame))
as loginname
,spid as 'spid_sort'

, substring( convert(varchar,last_batch,111) ,6 ,5 ) + ' '
+ substring( convert(varchar,last_batch,113) ,13 ,8 )
as 'last_batch_char'
,request_id

into #tb1_sysprocesses
from sys.sysprocesses with (nolock)

if @@error <> 0
begin
select @retcode = @@error
GOTO LABEL_86RETURN
end


--------Prepare to dynamically optimize column widths.


select
@charsidlow = convert(varchar(85),@sidlow)
,@charsidhigh = convert(varchar(85),@sidhigh)
,@charspidlow = convert(varchar,@spidlow)
,@charspidhigh = convert(varchar,@spidhigh)



select
@charMaxLenLoginName =
convert( varchar
,isnull( max( datalength(loginname)) ,5)
)

,@charMaxLenDBName =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),db_name(dbid))))) ,6)
)

,@charMaxLenCPUTime =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),cpu)))) ,7)
)

,@charMaxLenDiskIO =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),physical_io)))) ,6)
)

,@charMaxLenCommand =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),cmd)))) ,7)
)

,@charMaxLenHostName =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),hostname)))) ,8)
)

,@charMaxLenProgramName =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),program_name)))) ,11)
)

,@charMaxLenLastBatch =
convert( varchar
,isnull( max( datalength( rtrim(convert(varchar(128),last_batch_char)))) ,9)
)
from
#tb1_sysprocesses
where
spid >= @spidlow
and spid <= @spidhigh



--------Output the report.


EXEC(
'
SET nocount off

SELECT
SPID = convert(char(5),spid)

,Status =
CASE lower(status)
When ''sleeping'' Then lower(status)
Else upper(status)
END

,Login = substring(loginname,1,' + @charMaxLenLoginName + ')

,HostName =
CASE hostname
When Null Then '' .''
When '' '' Then '' .''
Else substring(hostname,1,' + @charMaxLenHostName + ')
END

,BlkBy =
CASE isnull(convert(char(5),blocked),''0'')
When ''0'' Then '' .''
Else isnull(convert(char(5),blocked),''0'')
END

,DBName = substring(case when dbid = 0 then null when dbid <> 0 then db_name(dbid) end,1,' + @charMaxLenDBName + ')
,Command = substring(cmd,1,' + @charMaxLenCommand + ')

,CPUTime = substring(convert(varchar,cpu),1,' + @charMaxLenCPUTime + ')
,DiskIO = substring(convert(varchar,physical_io),1,' + @charMaxLenDiskIO + ')

,LastBatch = substring(last_batch_char,1,' + @charMaxLenLastBatch + ')

,ProgramName = substring(program_name,1,' + @charMaxLenProgramName + ')
,SPID = convert(char(5),spid) --Handy extra for right-scrolling users.
,REQUESTID = convert(char(5),request_id)
from
#tb1_sysprocesses --Usually DB qualification is needed in exec().
where
dbid = ' + @dbid + '

-- (Seems always auto sorted.) order by spid_sort

SET nocount on
'
)


LABEL_86RETURN:


if (object_id('tempdb..#tb1_sysprocesses') is not null)
drop table #tb1_sysprocesses

return @retcode -- sp_who2




Thursday, April 15, 2010

Converting a delimited string into a table

This isn't anything new, but we will be building upon this in the next post. At times you want to take a delimited string and have it returned in a table format so you can loop over it or do stuff with it; many people have done this before and I'm sure that I'm drawing on stuff I've seen before. This function works well, you call it like this:

SELECT *
FROM dbo.StringToTable('a,b,c,d',',')


Where the first parameter is the string to be converted and the second is the delimiter


USE [master]
GO
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO

--/***********************************************************************
--***
--*** Function: [StringToTable]
--*** Purpose: Takes a delimited string and returns a table
--***
--***
--*** Author: Eric Zierdt
--*** Date Created: 2010-04-15
--***
--*** Revision History
--*** Date Author Description
--*** 2010-04-15 ez Created
--*** select * from dbo.StringToTable('a,b,c,d',',')
--***********************************************************************/

CREATE FUNCTION [dbo].[StringToTable]
(
@String VARCHAR(MAX),
@Delimiter CHAR(1) = ','
)
RETURNS @ReturnList TABLE
(
ID INT,
TableValue VARCHAR(MAX)
)
AS BEGIN
DECLARE @ArrayList TABLE
(
ID INT, TableValue VARCHAR(MAX)
)
DECLARE @Value VARCHAR(MAX)
DECLARE @NextDelimiterLocation INT

----------------------------
-- REMOVE LEADING DELIMITER --
----------------------------
SET @String = LTRIM(RTRIM(@String)) +
CASE WHEN RIGHT(@String, 1) = @Delimiter THEN ''
ELSE @Delimiter
END

-------------------------------------------
-- SET THE LOCATION OF THE FIRST DELIMITER --
-------------------------------------------
SET @NextDelimiterLocation = ISNULL(CHARINDEX(@Delimiter, @String, 1), 0)

----------------------------------------
-- IF NO DELIMITER THEN SET THE WHOLE --
-- STRING TO THE FIRST ROW OF THE TABLE --
----------------------------------------
IF @NextDelimiterLocation = 0
BEGIN
INSERT INTO @ArrayList
(
ID
,TableValue
)
SELECT 1, @String
END
ELSE
BEGIN
DECLARE @Counter INT
SET @Counter = 0
WHILE @NextDelimiterLocation > 0
BEGIN
-----------------------------------------------------
-- SET THE VALUE PARAM TO DATA BEFORE THE DELIMITER --
-- THEN LOOP TO DO THE SAME FOR THE REST OF THE LIST --
-----------------------------------------------------
SET @Value = LTRIM(RTRIM(LEFT(@String, @NextDelimiterLocation - 1)))
IF LEN(@String) > 0 AND @NextDelimiterLocation <= LEN(@String)
BEGIN
SET @Counter = @Counter + 1
INSERT INTO @ArrayList ( ID, TableValue )
SELECT @Counter, @Value
END
SET @String = SUBSTRING(@String, @NextDelimiterLocation + LEN(@Delimiter), LEN(@String))
SET @NextDelimiterLocation = CHARINDEX(@Delimiter, @String, 1)
END
END
INSERT @ReturnList
(
ID, TableValue
)
SELECT ID, TableValue
FROM @ArrayList

RETURN
END