Monday, March 26, 2007

Output clause and Auditing of data

There is very common scenario to auditing of data in a table using triggers. In this case, the trigger uses information from the inserted and updated tables to add rows into the audit tables. MS has introduced a new OUTPUT clause in SQL Server 2005 to audit data instead of using triggers to insert rows into an audit table.

CREATE TABLE test (col INT NOT NULL);
CREATE TABLE test_audit ( old_col INT NOT NULL, new_col INT NULL);
INSERT INTO test (col) values( 1 );
INSERT INTO test (col) values( 2 );

UPDATE test
SET col = col + 1
OUTPUT deleted.col ,inserted.col into test_audit
WHERE col = 1;

DELETE FROM test
OUTPUT deleted.col, NULL into test_audit
WHERE col = 2;

SELECT * FROM test
SELECT * FROM test_audit;

Wednesday, March 21, 2007

Detaching and Attaching database

At this time I'd like to share some scripts that used undocumented (sorry) but very popular among DBA's sp_MSforeachdb stored procedure. If you have hundred of databases on your server and you was told to move those databases to another server the quick way to do the job is performing the following scripts. First of all we detach all user databases from the server and the second query will output an attach
script. The thing is that the second query will output the script that attach the database that has more than one LOG file.

I'm sure that you have backuped the user databases before performing such tasks.

--Detaching
EXEC sp_MSforeachdb
'IF N''?'' NOT IN(''model'' ,''master'', ''msdb'', ''tempdb'')
BEGIN
SELECT ''EXEC sp_detach_db ''''?'''',''''true''''''
END'


--Attaching
EXEC sp_msforeachdb
'SELECT ''EXEC sp_attach_db ''''?'''',
'''''' + RTRIM(filename) + '''''',''
FROM ?..sysfiles
WHERE fileid = (SELECT MIN(fileid) FROM ?..sysfiles)
UNION ALL
SELECT '''''''' + RTRIM(filename) + ''''''''
FROM ?..sysfiles
WHERE fileid > (SELECT MIN(fileid) FROM ?..sysfiles) AND
fileid < (SELECT MAX(fileid) FROM ?..sysfiles)
UNION ALL
SELECT '''''''' + RTRIM(filename) + ''''''''
FROM ?..sysfiles
WHERE fileid = (SELECT MAX(fileid) FROM ?..sysfiles)'

Wednesday, March 14, 2007

String manipulation (searching)

Recently I have seen some post in the news group regarding seraching a string in the table or even part of string. Yes , there are lots of resources in the internet how to do that. I would like to shere some ideas also here.


Consider the following structure. We need to find in col1 a part of string. Let us find occurence of '23'.

CREATE TABLE #Test
(
col INT,
col1 VARCHAR(100)
)
INSERT INTO #Test VALUES (1,'12,23,44,56')
INSERT INTO #Test VALUES (2,'26,45,22,32')
INSERT INTO #Test VALUES (3,'45,22,34,23')

DECLARE @st VARCHAR(10)
SET @st='23'
SELECT *
FROM #test
WHERE ','+col1+',' LIKE '%,'+@st+',%'


Now, take a look at following table
CREATE TABLE #Test (contact VARCHAR(20) PRIMARY KEY)

INSERT INTO #Test VALUES ('Bill Clinton')
INSERT INTO #Test VALUES ('George W. Bush')
INSERT INTO #Test VALUES ('John F Kennedy')
INSERT INTO #Test VALUES ('Smith')

1)
SELECT contact
FROM #Test
WHERE contact LIKE '%[A-Z] [A-Z][^A-Z]%[A-Z]'
----
2)
SELECT contact
FROM #Test
WHERE contact LIKE '% _ %' or contact LIKE '% _. %'

The first one returns all people that have a middle name. The second one returns
people who has not '.' in this name after middle name.

Sunday, March 11, 2007

Script Objects SS2K

Well, there are still lots of people/businesses use SQL Server 2000 I'd like to put the following script using SQL DMO object library. You have to open a new project with VB6 and put the code in the new form.
The cool thing is that you get the script without default COLLATION to be specify.
See if you need to run the script on another server/s or database/s that have different collation.

Also, Tibor Karaszi has great info at his web site
http://www.karaszi.com/SQLServer/info_generate_script.asp


Sub ScriptDB(strLogin As String, strPwd As String, _
strDataBase As String, StrFilePath As String)


Dim sql As Object
Dim db As Object
Dim objTrigger As Object
Dim intOptions As Long
Dim genObj

Set sql = CreateObject("SQLDMO.SQLServer")
Set db = CreateObject("SQLDMO.Database")
Set objTrigger = CreateObject("SQLDMO.Trigger")

Const sDrops As Integer = 1
Const sIncludeHeaders As Long = 131072
Const sDefault As Integer = 4
Const sAppendToFile As Integer = 256
Const sBindings As Integer = 128
' No collation to be specify
Const SQLDMOScript2_NoCollation As Long = 8388608

' We need to specify multiple behaviors
' for the ScriptType argument, you use "Or" to combine these.
intOptions = sDrops Or sIncludeHeaders Or _
sDefault Or sAppendToFile Or sBindings Or SQLDMOScript2_NoCollation

' Connect to server
sql.Connect "(local)", strLogin, strPwd
Set db = sql.Databases(strDataBase, "dbo")

' Script User Defined Data Types
For Each genObj In db.UserDefinedDatatypes
genObj.Script intOptions, StrFilePath
Next

' Script Tables and Triggers, ignoring system
' tables and system generated triggers
For Each genObj In db.Tables
If genObj.SystemObject = False Then
genObj.Script intOptions, StrFilePath,,SQLDMOScript2_NoCollation
For Each objTrigger In genObj.Triggers
If objTrigger.SystemObject = False Then
objTrigger.Script intOptions, StrFilePath
End If
Next
End If
Next

' Script Rules
For Each genObj In db.Rules
genObj.Script intOptions, StrFilePath
Next

' Script Defaults
For Each genObj In db.Defaults
genObj.Script intOptions, StrFilePath
Next

' Script Sprocs, ignoring system sprocs
For Each genObj In db.StoredProcedures
If genObj.SystemObject = False Then
genObj.Script intOptions, StrFilePath
End If
Next

' Script Views, ignoring system views and informational schemas
For Each genObj In db.Views
If genObj.SystemObject = False Then
genObj.Script intOptions, StrFilePath
End If
Next


End Sub

To call the procedure save the module and supply the parameters.
Call ScriptDB("UserName","Password","DatabaseName","C:\DBScript.SQL")

Getting the value of Constraint

Recently I was asked by my colleague to return tha actual value of CHECK constaint that he has defined on the table.
I think it can be usefull for some reports that we've done on the database.
Tested on SQL Server 2000(SP3a) and SQL Server 2005(SP2a)

CREATE TABLE Test
(
col INT,
coldatetime DATETIME
)

ALTER TABLE Test
ADD CONSTRAINT coldatetime_check
CHECK ( coldatetime > '20070101')

SELECT text FROM syscomments
WHERE id =OBJECT_ID('coldatetime_check')

UNIQUE Constraint vs NULL's

This is another well known question get asked , how to insert more than one NULL value in the column which has an unique constraint defined on?

Ok, you can create a view WITH SCHEMABINDING that issues SELECT column FROM Table WHERE column IS NOT NULL. Then you need to create an unique index on view (column).

Another method I learned from Steve Kass a long time ago and would like to publish here is that we create a computed column called nullbuster that based on primary key and another column. Now that we have this column so we create a UNIQUE Constraint on two columns. See how it works.

CREATE TABLE dupNulls (
pk int identity(1,1) primary key,
X int NULL,
nullbuster as (case when X is null then pk else 0 end),
CONSTRAINT dupNulls_uqX UNIQUE (X,nullbuster)
)

INSERT INTO dupNulls(X) VALUES (1)
INSERT INTO dupNulls(X) VALUES (NULL)
INSERT INTO dupNulls(X) VALUES (NULL)
GO
SELECT pk, X, nullbuster FROM dupNulls
UPDATE dupNulls SET X = 1 WHERE pk = 2
GO
SELECT pk, X, nullbuster FROM dupNulls
UPDATE dupNulls SET X = 2 WHERE pk = 2
SELECT pk, X, nullbuster FROM dupNulls
DROP TABLE dupNulls

Tuesday, March 6, 2007

Sort techniques

I have seen very often people ask for sorting alphabetically and moreover if it has a number as a part of string that also should be sorted .
Take a look at this example. I used PATINDEX function to find a position at which 'numeric' part begins along with SUBSTRING started from first postion thrue the whole string.RIGHT function just adds '00000' to string , so if it has '0' at the beginnig then it will be sorted by ascending for example.

CREATE TABLE #Test
(
col VARCHAR(10)not null primary key
)
INSERT INTO #Test VALUES ('A104')
INSERT INTO #Test VALUES ('A152')
INSERT INTO #Test VALUES ('A1010')
INSERT INTO #Test VALUES ('A15')
INSERT INTO #Test VALUES ('A17')
INSERT INTO #Test VALUES ('AA130')
INSERT INTO #Test VALUES ('B11')
INSERT INTO #Test VALUES ('B30')

SELECT * FROM #test ORDER BY
SUBSTRING (col, 0,PATINDEX('%[0-9]%',col))+RIGHT('00000' + SUBSTRING (col,
PATINDEX('%[0-9]%',col) , LEN(col)),5)

Monday, March 5, 2007

Column Description

I have seen many people asking how can I scipt out column's description. Well, there is fn_listextendedproperty system function , you can read about in the BOL.

Here is another example how to return column desciption.

--SQL Server 2000
SELECT s2.id, s1.name,
( SELECT s3.value
FROM sysproperties s3
WHERE s3.id = s1.id
AND s3.smallid = s1.colid ) AS "col desc"
FROM syscolumns s1
INNER JOIN sysobjects s2
ON s1.id = s2.id
WHERE s2.name = 'tablename';

I know that sysproperties is undocumented and it is no longer exists in SQL Server 2005.
We can use either the system TVF fn_listextendedproperty or
SELECT value
FROM sys.extended_properties
WHERE major_id=OBJECT_ID('dbo.tbl')

Try ..Catch constructs

There are lots of companies,business have already upgraded to SQL Server 2005 ,definetly it makes sense because MS has introduced lots of new featuers. One of the many is how we can handle errors in SQL Server 2005


This procedure can be executed in your CATCH block.

CREATE PROCEDURE ErrorDetails
AS
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_SEVERITY() AS ErrorSeverity,
ERROR_STATE() AS ErrorState,
ERROR_PROCEDURE() AS ErrorProcedure,
ERROR_LINE() AS ErrorLine,
ERROR_MESSAGE() AS ErrorMessage;
GO


Connect to Object Explorer and execute the following code

BEGIN TRY
SELECT GETDATE()
SELECT 1/0
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE()
EXEC ErrorDetails
END CATCH;

Great thing that we can catch more errors by usig TRY ...CATCH block and what's more important to get an error message out.

Thursday, March 1, 2007

Find gaps

Well ,there are many questions in the newgroup I have seen with this request
You can have a table with sequential numbers and LEFT join with your table to find the gaps.
The following techniques I collected from Steve Kass,Joe Celko, Itzik Ben-Gan.

CREATE TABLE X (C INT NOT NULL PRIMARY KEY)
INSERT X SELECT 1 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 7 UNION ALL
SELECT 10

SELECT * FROM x
SELECT
c+n FROM x, (
SELECT 0 n UNION ALL SELECT 1
) T
GROUP BY c+n
HAVING MIN(n) = 1 and c+n < (SELECT MAX(c) FROM x)
-----------------------------------

CREATE TABLE Foobar
(seq INTEGER NOT NULL PRIMARY KEY
CHECK(seq > 0));
INSERT INTO Foobar VALUES (3);
INSERT INTO Foobar VALUES (7);
INSERT INTO Foobar VALUES (8);
INSERT INTO Foobar VALUES (10);
INSERT INTO Foobar VALUES (11);


SELECT (F1.seq +1) AS start, (F2.seq -1) AS fini
FROM (SELECT seq FROM Foobar
UNION ALL
SELECT 0) AS F1,
Foobar AS F2
WHERE (F1.seq +1) BETWEEN 0 AND (F2.seq-1)
AND NOT EXISTs
(SELECT *
FROM Foobar AS F3
WHERE F3.seq BETWEEN (F1.seq+1) AND (F2.seq-1));
-----------------------
SELECT n+1 as startgap, nextn-1 as endgap
FROM(SELECT n,
(SELECT MIN(n)
FROM t1 as b
WHERE b.n > a.n) as nextn
FROM t1 as a) as d
WHERE nextn - n > 1
----------------------------------
SELECT
MIN(i) as low,
MAX(i) as high
FROM(
SELECT
N1.num,
COUNT(N2.num) - N1.num
FROM Numbers AS N1, Numbers AS N2
WHERE N2.num <= N1.num
GROUP BY N1.num
) AS N(i,gp)
GROUP BY gp

UPDATE Nulls

Recently I got a 'specific' request from one of our customers to update a column (which allows NULLs).Let see the following script

CREATE TABLE test (col1 INT NOT NULL PRIMARY KEY,col2 DECIMAL(5,2))
GO
INSERT INTO test (col1, col2) VALUES (1, 3.5)
INSERT INTO test (col1, col2) VALUES (2, null)
INSERT INTO test (col1, col2) VALUES (3, null)
INSERT INTO test (col1, col2) VALUES (4, null)
INSERT INTO test (col1, col2) VALUES (5, null)
INSERT INTO test (col1, col2) VALUES (6, 7.4)
INSERT INTO test (col1, col2) VALUES (7, null)
INSERT INTO test (col1, col2) VALUES (8, null)
INSERT INTO test (col1, col2) VALUES (9, null)
INSERT INTO test (col1, col2) VALUES (10, 2.9)
INSERT INTO test (col1, col2) VALUES (11, null)
INSERT INTO test (col1, col2) VALUES (12, null)

So we would like to update NULL's with value till first non-null value
UPDATE test
SET col2 = (SELECT r.col2
FROM test AS r
WHERE r.col2 IS NOT NULL
AND r.col1 = (SELECT MAX(rr.col1)
FROM test AS rr WHERE
rr.col2 IS NOT NULL AND rr.col1 < test.col1))
WHERE col2 IS NULL


SELECT * FROM test