Deadlock when querying INFORMATION_SCHEMA - sql-server-2005

I have a process which dynamically alters my SQL2K5 table structure according to changes in a published meta-data layer.
For example, if a new column needs to be added and the table has NO dependancies - the steps would be:
1. Create scripts using T-SQL for any indexes & primary keys that already exist on the table [these scripts are included below]
2. Drop the table
3. Re-create the table from the meta-layer that has the new column
4. Execute the scripts created in step#1
5. Populate the table using BulkCopy
The above is initiated via a .NET assembly and runs in 3 concurrent streams on a daily basis.
I am receiving a deadlock error in step #1 - when I access the INFORMATION_SCHEMA tables to script out the indexes/keys. I have used the hint WITH(NOLOCK) in these scripts thinking this should prevent any locking when 3 streams of these actions are running concurrently. A table can only be processed (the create or scripting) in 1 stream.
Is there something more I need to do???
Any comments greatly appreciated.
[Scripts]
ALTER Procedure [dbo].[s$spScriptPrimaryKeyForTable]
#Tablename varchar(100)
AS
-- Get all existing primary keys
DECLARE cPK CURSOR FOR
SELECT TABLE_NAME, CONSTRAINT_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS WITH(NOLOCK)
WHERE upper(TABLE_NAME)=upper(#Tablename)
ORDER BY TABLE_NAME
DECLARE #PkTable SYSNAME
DECLARE #PkName SYSNAME
-- Loop through all the primary keys
OPEN cPK
FETCH NEXT FROM cPK INTO #PkTable, #PkName
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #PKSQL NVARCHAR(4000) SET #PKSQL = ''
SET #PKSQL = 'ALTER TABLE ' + #PkTable + ' ADD CONSTRAINT ' + #PkName + ' PRIMARY KEY CLUSTERED ('
-- Get all columns for the current primary key
DECLARE cPKColumn CURSOR FOR
SELECT COLUMN_NAME
FROM INFORMATION_SCHEMA.KEY_COLUMN_USAGE WITH(NOLOCK)
WHERE TABLE_NAME = #PkTable AND CONSTRAINT_NAME = #PkName
ORDER BY ORDINAL_POSITION
OPEN cPKColumn
DECLARE #PkColumn SYSNAME
DECLARE #PkFirstColumn BIT SET #PkFirstColumn = 1
-- Loop through all columns and append the sql statement
FETCH NEXT FROM cPKColumn INTO #PkColumn
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#PkFirstColumn = 1)
SET #PkFirstColumn = 0
ELSE
SET #PKSQL = #PKSQL + ', '
SET #PKSQL = #PKSQL + #PkColumn
FETCH NEXT FROM cPKColumn INTO #PkColumn
END
CLOSE cPKColumn
DEALLOCATE cPKColumn
SET #PKSQL = #PKSQL + ')'
-- Print the primary key statement
-- PRINT #PKSQL
FETCH NEXT FROM cPK INTO #PkTable, #PkName
END
CLOSE cPK
DEALLOCATE cPK
SELECT ISNULL(#PKSQL,' ')
================
ALTER Procedure [dbo].[s$spScriptIndexesForTable]
#Tablename varchar(100)
AS
DECLARE #RetVal varchar(4000)
SET #RetVal = ''
-- Get all existing indexes, but NOT the primary keys
DECLARE cIX CURSOR FOR
SELECT OBJECT_NAME(SI.Object_ID), SI.Object_ID, SI.Name, SI.Index_ID
FROM Sys.Indexes SI WITH(NOLOCK)
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC WITH(NOLOCK) ON SI.Name = TC.CONSTRAINT_NAME AND OBJECT_NAME(SI.Object_ID) = TC.TABLE_NAME
WHERE TC.CONSTRAINT_NAME IS NULL
AND OBJECTPROPERTY(SI.Object_ID, 'IsUserTable') = 1
AND upper(OBJECT_NAME(SI.Object_ID))=upper(#Tablename)
ORDER BY OBJECT_NAME(SI.Object_ID), SI.Index_ID
DECLARE #IxTable SYSNAME
DECLARE #IxTableID INT
DECLARE #IxName SYSNAME
DECLARE #IxID INT
-- Loop through all indexes
OPEN cIX
FETCH NEXT FROM cIX INTO #IxTable, #IxTableID, #IxName, #IxID
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #IXSQL NVARCHAR(4000)
--SET #PKSQL = ''
SET #IXSQL = 'CREATE '
-- Check if the index is unique
IF (INDEXPROPERTY(#IxTableID, #IxName, 'IsUnique') = 1)
SET #IXSQL = #IXSQL + 'UNIQUE '
-- Check if the index is clustered
IF (INDEXPROPERTY(#IxTableID, #IxName, 'IsClustered') = 1)
SET #IXSQL = #IXSQL + 'CLUSTERED '
SET #IXSQL = #IXSQL + 'INDEX ' + #IxName + ' ON [' + #IxTable + '] ('
-- Get all columns of the index
DECLARE cIxColumn CURSOR FOR
SELECT SC.Name,IC.[is_included_column],IC.is_descending_key
FROM Sys.Index_Columns IC WITH(NOLOCK)
JOIN Sys.Columns SC WITH(NOLOCK) ON IC.Object_ID = SC.Object_ID AND IC.Column_ID = SC.Column_ID
WHERE IC.Object_ID = #IxTableID AND Index_ID = #IxID
ORDER BY IC.Index_Column_ID,IC.is_included_column
DECLARE #IxColumn SYSNAME
DECLARE #IxIncl bit
DECLARE #Desc bit
DECLARE #IxIsIncl bit set #IxIsIncl = 0
DECLARE #IxFirstColumn BIT SET #IxFirstColumn = 1
-- Loop throug all columns of the index and append them to the CREATE statement
OPEN cIxColumn
FETCH NEXT FROM cIxColumn INTO #IxColumn, #IxIncl, #Desc
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#IxFirstColumn = 1)
BEGIN
SET #IxFirstColumn = 0
END
ELSE
BEGIN
--check to see if it's an included column
IF ((#IxIsIncl = 0) AND (#IxIncl = 1))
BEGIN
SET #IxIsIncl = 1
SET #IXSQL = #IXSQL + ') INCLUDE ('
END
ELSE
BEGIN
SET #IXSQL = #IXSQL + ', '
END
END
SET #IXSQL = #IXSQL + '[' + #IxColumn + ']'
--check to see if it's DESC
IF #Desc = 1
SET #IXSQL = #IXSQL + ' DESC'
FETCH NEXT FROM cIxColumn INTO #IxColumn, #IxIncl, #Desc
END
CLOSE cIxColumn
DEALLOCATE cIxColumn
SET #IXSQL = #IXSQL + ')'
-- Print out the CREATE statement for the index
--SELECT 'IXSQL: ' + #IXSQL
IF #RetVal IS NULL
SET #RetVal = ''
--SELECT 'Retval: ' + #RetVal
SET #RetVal = #RetVal + #IXSQL + ' '
FETCH NEXT FROM cIX INTO #IxTable, #IxTableID, #IxName, #IxID
END
CLOSE cIX
DEALLOCATE cIX
SELECT ISNULL(#RetVal,' ')

INFORMATION_SCHEMA views are just that - views. You can't update them so they are unlikely to cause any deadlocks. If you want to determine the real source (which I assume has something to do with your alters, or other code within the cursor that you didn't show, or other code you're calling in combination with calling these procedures - since selects against views and then selecting variables can't be the cause), I suggest reading Gail Shaw's blog post on interpreting deadlocks.
In spite of (1) I still suggest using more modern catalog views than INFORMATION_SCHEMA. The same information can be derived from, for example, sys.key_constraints.
You're using the default cursor options; and you're nesting cursors. If you end up still using cursors, you should get in the habit of using a less resource intensive cursor (e.g. LOCAL STATIC FORWARD_ONLY READ_ONLY).
You don't actually need a cursor to do this. Here is how I would re-write the PK table script:
CREATE PROCEDURE dbo.ScriptPKForTable
#TableName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#pkName SYSNAME,
#clustered BIT,
#object_id INT,
#sql NVARCHAR(MAX);
SELECT
#object_id = OBJECT_ID(UPPER(#TableName));
SELECT
#pkName = kc.name,
#clustered = CASE i.[type]
WHEN 1 THEN 1 ELSE 0 END
FROM
sys.key_constraints AS kc
INNER JOIN
sys.indexes AS i
ON kc.parent_object_id = i.[object_id]
AND kc.unique_index_id = i.index_id
WHERE
kc.parent_object_id = #object_id
AND kc.[type] = 'pk';
SET #sql = N'ALTER TABLE ' + QUOTENAME(#TableName)
+ ' ADD CONSTRAINT ' + #pkName
+ ' PRIMARY KEY ' + CASE #clustered
WHEN 1 THEN 'CLUSTERED' ELSE '' END + ' (';
SELECT
#sql = #sql + c.name + ','
FROM
sys.index_columns AS ic
INNER JOIN
sys.indexes AS i
ON ic.index_id = i.index_id
AND ic.[object_id] = i.[object_id]
INNER JOIN
sys.key_constraints AS kc
ON i.[object_id] = kc.[parent_object_id]
AND kc.unique_index_id = i.index_id
INNER JOIN
sys.columns AS c
ON i.[object_id] = c.[object_id]
AND ic.column_id = c.column_id
WHERE
kc.[type] = 'PK'
AND kc.parent_object_id = #object_id
ORDER BY key_ordinal;
SET #sql = LEFT(#sql, LEN(#sql) - 1) + ');';
SELECT COALESCE(#sql, ' ');
END
GO
As for the index creation script, I think there is a better way to do this (again without explicit cursors, not that avoiding the cursor is the goal, but the code is going to be a LOT cleaner). First you need a function to build either key or include columns from the index:
CREATE FUNCTION dbo.BuildIndexColumns
(
#object_id INT,
#index_id INT,
#included_columns BIT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #s NVARCHAR(MAX);
SELECT #s = N'';
SELECT #s = #s + c.name + CASE ic.is_descending_key
WHEN 1 THEN ' DESC' ELSE '' END + ','
FROM sys.index_columns AS ic
INNER JOIN sys.columns AS c
ON ic.[object_id] = c.[object_id]
AND ic.column_id = c.column_id
WHERE c.[object_id] = #object_id
AND ic.[object_id] = #object_id
AND ic.index_id = #index_id
AND ic.is_included_column = #included_columns
ORDER BY ic.key_ordinal;
IF #s > N''
SET #s = LEFT(#s, LEN(#s)-1);
RETURN (NULLIF(#s, N''));
END
GO
With that function in place, a ScriptIndexes procedure is pretty easy:
CREATE PROCEDURE dbo.ScriptIndexesForTable
#TableName SYSNAME
AS
BEGIN
SET NOCOUNT ON;
DECLARE
#sql NVARCHAR(MAX),
#object_id INT;
SELECT #sql = N'', #object_id = OBJECT_ID(UPPER(#TableName));
SELECT #sql = #sql + 'CREATE '
+ CASE i.is_unique WHEN 1 THEN 'UNIQUE ' ELSE '' END
+ CASE i.[type] WHEN 1 THEN 'CLUSTERED ' ELSE '' END
+ ' INDEX ' + i.name + ' ON ' + QUOTENAME(#TableName) + ' ('
+ dbo.BuildIndexColumns(#object_id, i.index_id, 0)
+ ')' + COALESCE(' INCLUDE('
+ dbo.BuildIndexColumns(#object_id, i.index_id, 1)
+ ')', '') + ';' + CHAR(13) + CHAR(10)
FROM
sys.indexes AS i
WHERE
i.[object_id] = #object_id
-- since this will be covered by ScriptPKForTable:
AND i.is_primary_key = 0
ORDER BY i.index_id;
SELECT COALESCE(#sql, ' ');
END
GO
Note that my solution does not assume the PK is clustered (your PK script hard-codes CLUSTERED but then your index script assumes that any of the indexes could be clustered). I also ignore additional properties such as filegroup, partitioning, or filtered indexes (not supported in 2005 anyway).

Related

How to get Column value without knowing column name ? SQL Server

I have table name as #Table_Name
I have column value as #Value but don't have the column name (but that exist at 1st position and can be Seek_id or prov_id ...I have to compare my value with this id )
How can I compare that table column name value ?
I want something like
SELECT * FROM #Table_Name
WHERE Table.Column[1].Value = #Value
for example #Table_Name = bb_match and #Value = 6
Possible this be helpful for you -
Query:
IF OBJECT_ID (N'dbo.bb_match') IS NOT NULL
DROP TABLE dbo.bb_match
CREATE TABLE dbo.bb_match (seek_id INT, prov_id INT)
INSERT INTO dbo.bb_match (seek_id, prov_id)
VALUES (6, 1), (2, 6)
DECLARE
#ColumnID TINYINT
, #Value INT
, #TableName SYSNAME
, #SQL NVARCHAR(500)
SELECT
#ColumnID = 1
, #Value = 6
, #TableName = 'dbo.bb_match'
SELECT #SQL = 'SELECT * FROM ' + #TableName + ' WHERE [' + c.name + '] = ' + CAST(#Value AS NVARCHAR(MAX))
FROM sys.objects o WITH (NOWAIT)
JOIN sys.schemas s WITH (NOWAIT) ON o.[schema_id] = s.[schema_id]
JOIN sys.columns c WITH (NOWAIT) ON o.[object_id] = c.[object_id]
WHERE o.[type] = 'U' -- <-- only for tables columns
AND s.name + '.' + o.name = #TableName
AND c.column_id = #ColumnID
PRINT #SQL
EXEC sp_executesql #SQL
Shorter, but unsafe (sys.columns contains column_name for tables, views, procedures, ...):
SELECT #SQL = 'SELECT * FROM ' + #TableName + ' WHERE [' + c.name + '] = ' + CAST(#Value AS NVARCHAR(MAX))
FROM sys.columns c WITH (NOWAIT)
WHERE c.[object_id] = OBJECT_ID(#TableName)
AND c.column_id = #ColumnID
EXEC sys.sp_executesql #SQL
Output:
SELECT * FROM dbo.bb_match WHERE [seek_id] = 6
Results:
seek_id prov_id
----------- -----------
6 1
declare #sql varchar(MAX)
declare #tablename varchar(100) = 'MyTable' --add your table name here
declare #value varchar(100) = 'SomeValue' -- add your desired value hree
select #sql = 'SELECT * FROM ' + #tablename + ' WHERE '
+ name
+ ' = ''' + #value + ''''
from sys.columns where object_id = object_id(#tablename) and column_id = 1
exec (#sql)
There are three parts to this. First I'm declaring three strings. #sql is where I will build up the query, #tablename and #value are the table and search value to look in/for. I've put in the dummy values MyTable and SomeValue to show what I'm talking about
Next I build up the sql statement. The first line sets the string as SELECT * FROM MyTable WHERE
I then add in the column name by selecting Name from the SQL SERver system table sys.columns, filtering on the first column (column_id = 1) and the table name
The next step is to add the value we want to search for in the column.
Finally, EXEC(#sql) interprets the string as a command and runs it.

Dynamic update statement with variable column names

We're looking to do an update in several SQL Server databases to change all NULL values in a certain table to be empty strings instead of NULL. We're potentially going to be doing this across hundreds of databases. The table name will always be the same, but the column names are variable based on how the front-end application is configured (don't judge... I didn't create this system).
Is there a way to do an update on all of these columns without knowing the column names ahead of time?
You can pass the name of the column in dynamic sql:
declare #sql nvarchar (1000);
set #sql = N'update table set ' + #column_name + '= ''''';
exec sp_executesql #sql;
You can look in the sys.columns table and join on the table name or object_id.
DECLARE #OBJ_ID INT
SELECT #OBJ_ID = OBJECT_ID
FROM SYS.tables
WHERE name = 'YOURTABLE'
SELECT * FROM SYS.columns
WHERE OBJECT_ID = #OBJ_ID
You could use the name field from the sys.columns query as a basis to perform the update on.
Assuming you want all columns of varchar/char types only (or change the type filter to whatever you need):
DECLARE #tableName varchar(10)
SET #tableName = 'yourtablenamehere'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = '''' WHERE ' + c.name + ' IS NULL ;'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE t.name = #tableName AND y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
EXEC (#sql)
This can be achieved with cursors. You first select the column names like #Darren mentioned, then you Set a Cursor with those values and loop:
Open oColumnsCursor
Fetch Next From oColumnscursor
Into #ColumnName
While ##FETCH_STATUS=0
Begin
Set #oQuery = 'Update [DB]..[Table] Set [' + #ColumnName + '] = ''NewValue'' Where [' + #ColumnName + '] = ''OldValue'''
Execute(#oQuery)
Fetch Next From oColumnscursor Into #ColumnName
Set #oCount = #oCount + 1
End
Close oColumnsCursor;
Deallocate oColumnsCursor;
This will work when you know the Table Name:
DECLARE #tableName varchar(10)
SET #tableName = 'Customers'
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + #tableName + ' SET ' + c.name + ' = ISNULL('+ c.name +','''');'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE y.name IN ('varchar', 'nvarchar', 'char', 'nchar')
AND t.name = #tableName;
EXEC(#sql);
And this will iterate all Tables and all Columns in a Db:
DECLARE #sql VARCHAR(MAX)
SET #sql = ''
SELECT #sql = #sql + 'UPDATE ' + t.name + ' SET ' + c.name + ' = ISNULL('+ c.name +','''');'
FROM sys.columns c
INNER JOIN sys.tables t ON c.object_id = t.object_id
INNER JOIN sys.types y ON c.system_type_id = y.system_type_id
WHERE y.name IN ('varchar', 'nvarchar', 'char', 'nchar');
EXEC(#sql);
Below is the procedure.
ALTER PROCEDURE [dbo].[util_db_updateRow]
#colval_name NVARCHAR (30), -- column and values e.g. tax='5.50'
#idf_name NVARCHAR (300), -- column name
#idn_name NVARCHAR (300), -- column value
#tbl_name NVARCHAR (100) -- table name
AS
BEGIN
SET NOCOUNT ON;
DECLARE #sql NVARCHAR(MAX)
-- construct SQL
SET #sql = 'UPDATE ' + #tbl_name + ' SET ' + #colval_name +
' WHERE ' + #idf_name + '=' + #idn_name;
-- execute the SQL
EXEC sp_executesql #sql
SET NOCOUNT OFF
RETURN
END
Below is the stored procedure where you can pass Schema Name, Table Name and list of column names separted by comma.It works only in Sql Server 2016 or higher.
CREATE OR ALTER PROCEDURE UpdateData
(#SchemaName NVARCHAR(Max),#TableName NVARCHAR(MAX),#ColumnNames NVARCHAR(MAX))
AS
BEGIN
DECLARE #DynamicSql NVARCHAR(MAX);
SET #DynamicSql = 'UPDATE ' +'[' +#SchemaName+'].' + '[' +#TableName+']' +' SET ' + STUFF((SELECT ', [' + C.name + '] = ' + '''NEW_VALUE'''
FROM sys.columns C
INNER JOIN sys.tables T ON T.object_id = C.object_id
INNER JOIN sys.schemas S ON T.schema_id = S.schema_id
WHERE
T.name = #TableName
AND S.Name = #SchemaName
AND [C].[name] in (SELECT VALUE FROM string_split(#ColumnNames,','))
FOR XML PATH('')), 1,1, '')
print #DynamicSql;
EXEC (#DynamicSql);
END

Dropping a table with all its dependencies (Microsoft SQL Server)

How can I drop a table with all its dependencies [SPs, Views, etc.] (Microsoft SQL Server) without knowing its dependencies upfront? I know I can display all dependencies in Mangement Studio but I'm searching for utility script that I could simply speficy an object and it would drop this object with all its dependencies.
The best thing to do it is "Generate scripts for Drop"
Select Database -> Right Click -> Tasks -> Generate Scripts - will open wizard for generating scripts
Select the database -> next
Set option 'Script to create' to true (want to create)
Set option 'Script to Drop' to true (want to drop)
Set option 'Generate script for dependent object' to true -> Next
Select the Check box to select objects wish to create script
Select the choice to write script (File, New window, Clipboard)
Execute the script
This way we can customize our script i.e., we can do scripting for selected objects of a database.
I hope this will help you!
Best Wishes, JP
You can use Sp_Depends to find the dependencies. With that you can modify the script from this answer Maybe someone less lazy than me will do that for you.
Note: Each object of course could have its own dependencies so you'll need to process them as well.
Delete a SQL object using its schema-qualified name. For tables, the constraints are dropped first.
Errors are ignored.
create procedure [dbo].[spDropObject] (#fullname nvarchar(520))
as
begin
begin try
declare #type nvarchar(5)
declare #resolvedFullname nvarchar(520)
declare #resolvedName nvarchar(255)
set #type = null
set #resolvedFullname = null
set #resolvedName = null
--find the object
select
#type = o.[type]
,#resolvedFullname = '[' + object_schema_name(o.id) + '].[' + o.[name] + ']'
,#resolvedName = '[' + o.[name] + ']'
from dbo.sysobjects o
where id = object_id(#fullname)
--PROCEDURE
if(#type = 'P')
begin
exec('drop procedure ' + #resolvedFullname);
return;
end
--VIEW
if(#type = 'V')
begin
exec('drop view ' + #resolvedFullname);
return;
end
--FUNCTION
if(#type = 'FN' or #type = 'TF')
begin
exec('drop function ' + #resolvedFullname);
return;
end
--TRIGGER
if(#type = 'TF')
begin
exec('drop trigger ' + #resolvedFullname);
return;
end
--CONSTRAINT
if(#type = 'C' or #type = 'UQ' or #type = 'D' or #type = 'F' or #type = 'PK' or #type = 'K')
begin
declare #fullTablename nvarchar(520);
set #fullTablename = null
--find the contraint's table
select #fullTablename ='[' + object_schema_name(t.[object_id]) + '].[' + t.[Name] + ']'
from sys.tables t
join sys.schemas s on t.schema_id = s.schema_id
where t.object_id = (select parent_obj from dbo.sysobjects where id = object_id(#resolvedFullname))
exec('alter table ' + #fullTablename + ' drop constraint ' + #resolvedName);
return;
end
--TABLE (drop all constraints then drop the table)
if(#type = 'U')
begin
--find FK references to the table
declare #fktab table([Name] nvarchar(255))
insert #fktab
select
[Name] = '[' + object_name(fkc.[constraint_object_id]) + ']'
/*
,[Parent] = '[' + object_schema_name(fkc.[parent_object_id]) + '].[' + object_name(fkc.[parent_object_id]) + ']'
,[Ref] = '[' + object_schema_name(fkc.[referenced_object_id]) + '].[' + object_name(fkc.[referenced_object_id]) + ']'
*/
from sys.foreign_key_columns as fkc
where referenced_object_id = object_id(#resolvedFullname)
order by [Name]
--iterate FKs
while(1=1)
begin
declare #constraint nvarchar(255)
set #constraint = null
select top 1
#constraint = [Name]
from #fktab
if(#constraint is not null)
begin
--drop FK constraint
exec [dbo].[spDropObject] #constraint;
delete from #fktab where [Name] = #constraint --remove current record from working table
end
else break;
end
--find constraints for table
declare #constraintTab table ([Name] nvarchar(255));
insert #constraintTab
select [name]
from sys.objects
where parent_object_id = object_id(#resolvedFullname)
order by [name]
--iterate constraints
while(1=1)
begin
set #constraint = null;
select top 1 #constraint = [Name] from #constraintTab
if(#constraint is not null)
begin
--drop constraint
exec [dbo].[spDropObject] #constraint;
delete from #constraintTab where [Name] = #constraint --remove current record from working table
end
else break;
end
--drop table
exec('drop table ' + #resolvedFullname);
return;
end
end try
begin catch
declare #message nvarchar(max)
set #message = error_message( ) ;
print #message
end catch
end
In my case, I specifically wanted to drop a specified table and the tables that depend on that table. It wasn't useful to me to only drop the foreign key constraints that reference it. I wrote a stored procedure to do this
CREATE PROCEDURE DropDependentTables (
#tableName NVARCHAR(64))
AS
-- Find and drop all tables that depend on #tableName
WHILE EXISTS(SELECT *
FROM sys.foreign_keys
WHERE OBJECT_NAME(referenced_object_id) = #tableName AND
OBJECT_NAME(parent_object_id) != #tableName)
BEGIN
DECLARE #dependentTableName NVARCHAR(64)
SELECT TOP 1 #dependentTableName = OBJECT_NAME(parent_object_id)
FROM sys.foreign_keys
WHERE OBJECT_NAME(referenced_object_id) = #tableName AND
OBJECT_NAME(parent_object_id) != #tableName
EXEC DropDependentTables #dependentTableName
END
I'm going to leave a late answer (after around 10 years). I hope you'll find it handy.
In our company, we use this script to properly delete database tables. For each table, we first drop the dependencies (REFERENTIAL_CONSTRAINTS) then delete the table itself.
USE [database-name]
DECLARE #Sql NVARCHAR(500) DECLARE #Cursor CURSOR
SET #Cursor = CURSOR FAST_FORWARD FOR
SELECT DISTINCT sql = 'ALTER TABLE [' + tc2.TABLE_SCHEMA + '].[' + tc2.TABLE_NAME + '] DROP [' + rc1.CONSTRAINT_NAME + '];'
FROM INFORMATION_SCHEMA.REFERENTIAL_CONSTRAINTS rc1
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS tc2 ON tc2.CONSTRAINT_NAME =rc1.CONSTRAINT_NAME
OPEN #Cursor FETCH NEXT FROM #Cursor INTO #Sql
WHILE (##FETCH_STATUS = 0)
BEGIN
Exec sp_executesql #Sql
FETCH NEXT FROM #Cursor INTO #Sql
END
CLOSE #Cursor DEALLOCATE #Cursor
GO
EXEC sp_MSforeachtable 'DROP TABLE ?'
GO
The credit goes to a colleague of mine, Abolfazl Najafzade, for the script.

Preserve SQL Indexes While Altering Column Datatype

I have a smalldatetime column that I need to alter to be a datetime column. This is something that will be part of an install process, so it cannot be a manual procedure. Unfortunately, the column has a few indexes and a not null constraint on it. The indexes are performance related and would need to be retained only using the new data type. Is it possible to write a statement that will allow me to retain the relevant information while still altering the column datatype? If so, how can this be done?
You can not change the datatype from smalldatetime to datetime with the indexes, unique constraints, foreign key constraints or check constraints in place. You will have to drop them all prior to changing the type. Then:
alter table T alter column TestDate datetime not null
Then recreate the constraints and indexes that still apply.
Some different approaches for generating the drop and creates:
1) If you have given explicit names to all indexes and constraints then your installer can run a static script in each environment (dev, test, user acceptance testing, performance testing, etc, production.)
To generate this explicit script you can:
a) Use SSMS (or with SQL Server 2000, enterprise manager) to script the create and drop statements.
b) Work from you source code repository to discover the names and definitions of the dependent objects and put together the appropriate static script.
c) Attempt to run the alter statement. See what it fails on. Look up the definitions and hand write the drop and create. (Personally, this would be great for writing the drop, not so good at the create.)
2) If you have not given explicit names to all indexes and constraints, then your installer will have to query the data dictionary for the appropriate names and use dynamic SQL to run the drops, in the correct order, prior to the alter column statement and then the creates, in the correct order, after the alter column.
This will be simpler if you know that there are no constraints, and just indexes.
There may be tools or libraries that already know how to do this.
Also, if this is a packaged application, you may not be assured that the local DBAs have not added indexes.
NOTE: If there is a unique constraint, it will have built an index, which you will not be able to drop with DROP INDEX.
If you are just changing the size, the Index will still remain on the table.
If you are changing the data type, then you will get an error message stating that objects depend on the column that you are trying to change and therefore you will not be able to change it.
You can script out the indexes in question manually or via script. In SSMS, right click the table and script out the object in question.
If you want programatic index scripting, here is a stored proc that I have been using that I got from an ex colleague of mine.
Drop Proc ScriptIndex
GO
Create Proc ScriptIndex
#TableName VarChar (Max),
#IndexScript VarChar (Max) OUTPUT
AS
-- Get all existing indexes, EXCEPT the primary keys
DECLARE cIX CURSOR FOR
SELECT OBJECT_NAME(SI.Object_ID), SI.Object_ID, SI.Name, SI.Index_ID
FROM Sys.Indexes SI
LEFT JOIN INFORMATION_SCHEMA.TABLE_CONSTRAINTS TC
ON SI.Name = TC.CONSTRAINT_NAME
AND OBJECT_NAME(SI.Object_ID) = TC.TABLE_NAME
WHERE 1=1
AND OBJECT_NAME(SI.Object_ID) = #TableName
AND TC.CONSTRAINT_NAME IS NULL
AND OBJECTPROPERTY(SI.Object_ID, 'IsUserTable') = 1
ORDER BY OBJECT_NAME(SI.Object_ID), SI.Index_ID
DECLARE #IxTable SYSNAME
DECLARE #IxTableID INT
DECLARE #IxName SYSNAME
DECLARE #IxID INT
-- Loop through all indexes
OPEN cIX
FETCH NEXT FROM cIX INTO #IxTable, #IxTableID, #IxName, #IxID
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE #IXSQL NVARCHAR(4000)
DECLARE #PKSQL NVARCHAR(4000)
SET #PKSQL = ''
SET #IXSQL = 'CREATE '
-- Check if the index is unique
IF (INDEXPROPERTY(#IxTableID, #IxName, 'IsUnique') = 1)
SET #IXSQL = #IXSQL + 'UNIQUE '
-- Check if the index is clustered
IF (INDEXPROPERTY(#IxTableID, #IxName, 'IsClustered') = 1)
SET #IXSQL = #IXSQL + 'CLUSTERED '
SET #IXSQL = #IXSQL + 'INDEX ' + #IxName + ' ON ' + #IxTable + '('
-- Get all columns of the index
DECLARE cIxColumn CURSOR FOR
SELECT SC.Name
FROM Sys.Index_Columns IC
JOIN Sys.Columns SC ON IC.Object_ID = SC.Object_ID AND IC.Column_ID = SC.Column_ID
WHERE IC.Object_ID = #IxTableID AND Index_ID = #IxID
ORDER BY IC.Index_Column_ID
DECLARE #IxColumn SYSNAME
DECLARE #IxFirstColumn BIT SET #IxFirstColumn = 1
-- Loop throug all columns of the index and append them to the CREATE statement
OPEN cIxColumn
FETCH NEXT FROM cIxColumn INTO #IxColumn
WHILE (##FETCH_STATUS = 0)
BEGIN
IF (#IxFirstColumn = 1)
SET #IxFirstColumn = 0
ELSE
SET #IXSQL = #IXSQL + ', '
SET #IXSQL = #IXSQL + #IxColumn
FETCH NEXT FROM cIxColumn INTO #IxColumn
END
CLOSE cIxColumn
DEALLOCATE cIxColumn
SET #IXSQL = #IXSQL + ')'
-- Print out the CREATE statement for the index
PRINT #IXSQL
FETCH NEXT FROM cIX INTO #IxTable, #IxTableID, #IxName, #IxID
END
CLOSE cIX
DEALLOCATE cIX
GO
Declare #TableName VarChar (Max), #IndexScript VarChar (Max)
Exec ScriptIndex 'Client', #IndexScript OUTPUT
Print #IndexScript
EDIT: It depends on the original and changed datatype.
If you try to alter a column from varchar to nvarchar, it will fail.
Whereas, if you alter column from varchar(16) to varchar(32), it will succeed.
--Disable Index
ALTER INDEX MyIndex ON MyTable DISABLE
GO
-- Change column datatype
--Enable Index
ALTER INDEX MyIndex ON MyTable REBUILD
GO
If you change the type of a column, then all indexes that use that column will have to be rebuilt.
But unless you have huge volumes of data (or run 24/7), rebuilding indexes is no big deal. Just schedule a maintenance window.
The best thing to do is to create a procedure that returns the index script of a given table / column. So you can remove the indexes just from the column being altered and not all indexes from the table, whereas creating indices can be somewhat expensive.
Stores the result of the procedure in a datatable
Delete the indices of the column
Modify your column
Rebuild the indexes stored in the datatable
-- objective : Generates indices scripting using specified column
-- Parameters :
-- #Tabela -> Name of the table that the column belongs to
-- #Coluna -> Name of the column that will be searched for the indices to generate the script
--Use: proc_ScriptIndexColumn 'TableName', 'ColumnName'
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
Create Proc proc_ScriptIndexColumn (#Tabela VARCHAR(4000), #Coluna VARCHAR(4000))
AS
BEGIN
DECLARE #isql_key VARCHAR(4000),
#isql_incl VARCHAR(4000),
#tableid INT,
#indexid INT
DECLARE #tablename VARCHAR(4000),
#indexname VARCHAR(4000)
DECLARE #isunique INT,
#isclustered INT,
#indexfillfactor INT
DECLARE #srsql VARCHAR(MAX)
DECLARE #ScriptsRetorno TABLE
(Script VARCHAR(MAX))
DECLARE index_cursor CURSOR
FOR
SELECT tablename = OBJECT_NAME(i.[object_id]),
tableid = i.[object_id],
indexid = i.index_id,
indexname = i.name,
isunique = i.is_unique,
CASE I.type_desc
WHEN 'CLUSTERED' THEN 1
ELSE 0
END AS isclustered,
indexfillfactor = i.fill_factor
FROM sys.indexes AS i
INNER JOIN SYSOBJECTS AS O
ON I.[object_id] = O.ID
INNER JOIN sys.index_columns AS ic
ON (ic.column_id > 0
AND (ic.key_ordinal > 0
OR ic.partition_ordinal = 0
OR ic.is_included_column != 0
))
AND ( ic.index_id = CAST(i.index_id AS INT)
AND ic.object_id = i.[object_id]
)
INNER JOIN sys.columns AS sc
ON sc.object_id = ic.object_id
AND sc.column_id = ic.column_id
WHERE O.XTYPE = 'U'
AND i.typE = 2 /*Non clustered*/
AND i.is_unique = 0
AND i.is_hypothetical = 0
AND UPPER(OBJECT_NAME(i.[object_id])) = UPPER(#Tabela)
AND UPPER(sc.name) = UPPER(#Coluna)
OPEN index_cursor
FETCH NEXT FROM index_cursor INTO #tablename,#tableid, #indexid,#indexname ,
#isunique ,#isclustered , #indexfillfactor
WHILE ##fetch_status <> -1
BEGIN
SELECT #isql_key = '',
#isql_incl = ''
SELECT #isql_key = CASE ic.is_included_column
WHEN 0 THEN CASE ic.is_descending_key
WHEN 1 THEN #isql_key +COALESCE(sc.name, '') +
' DESC, '
ELSE #isql_key + COALESCE(sc.name, '')
+ ' ASC, '
END
ELSE #isql_key
END,
--include column
#isql_incl = CASE ic.is_included_column
WHEN 1 THEN CASE ic.is_descending_key
WHEN 1 THEN #isql_incl +
COALESCE(sc.name, '') +
', '
ELSE #isql_incl + COALESCE(sc.name, '')
+ ', '
END
ELSE #isql_incl
END
FROM sysindexes i
INNER JOIN sys.index_columns AS ic
ON (
ic.column_id > 0
AND (
ic.key_ordinal > 0
OR ic.partition_ordinal = 0
OR ic.is_included_column != 0
)
)
AND (ic.index_id = CAST(i.indid AS INT) AND ic.object_id = i.id)
INNER JOIN sys.columns AS sc
ON sc.object_id = ic.object_id
AND sc.column_id = ic.column_id
WHERE i.indid > 0
AND i.indid < 255
AND (i.status & 64) = 0
AND i.id = #tableid
AND i.indid = #indexid
ORDER BY
i.name,
CASE ic.is_included_column
WHEN 1 THEN ic.index_column_id
ELSE ic.key_ordinal
END
IF LEN(#isql_key) > 1
SET #isql_key = LEFT(#isql_key, LEN(#isql_key) -1)
IF LEN(#isql_incl) > 1
SET #isql_incl = LEFT(#isql_incl, LEN(#isql_incl) -1)
SET #srsql = 'CREATE ' + 'INDEX [' + #indexname + ']' + ' ON [' + #tablename
+ '] '
SET #srsql = #srsql + '(' + #isql_key + ')'
IF (#isql_incl <> '')
SET #srsql = #srsql + ' INCLUDE(' + #isql_incl + ')'
IF (#indexfillfactor <> 0)
SET #srsql = #srsql + ' WITH ( FILLFACTOR = ' + CONVERT(VARCHAR(10), #indexfillfactor)
+ ')'
FETCH NEXT FROM index_cursor INTO #tablename,#tableid,#indexid,#indexname,
#isunique ,#isclustered , #indexfillfactor
INSERT INTO #ScriptsRetorno
VALUES
(#srsql)
END
CLOSE index_cursor
DEALLOCATE index_cursor
SELECT *
FROM #ScriptsRetorno
RETURN ##ERROR
END

Search for a string in all tables, rows and columns of a DB

I am lost in a big database and I am not able to find where the data I get comes from. I was wondering if it is possible with SQL Server 2005 to search for a string in all tables, rows and columns of a database?
Does anybody has an idea if it is possible and how?
This code should do it in SQL 2005, but a few caveats:
It is RIDICULOUSLY slow. I tested it on a small database that I have with only a handful of tables and it took many minutes to complete. If your database is so big that you can't understand it then this will probably be unusable anyway.
I wrote this off the cuff. I didn't put in any error handling and there might be some other sloppiness especially since I don't use cursors often. For example, I think there's a way to refresh the columns cursor instead of closing/deallocating/recreating it every time.
If you can't understand the database or don't know where stuff is coming from, then you should probably find someone who does. Even if you can find where the data is, it might be duplicated somewhere or there might be other aspects of the database that you don't understand. If no one in your company understands the database then you're in a pretty big mess.
DECLARE
#search_string VARCHAR(100),
#table_name SYSNAME,
#table_schema SYSNAME,
#column_name SYSNAME,
#sql_string VARCHAR(2000)
SET #search_string = 'Test'
DECLARE tables_cur CURSOR FOR SELECT TABLE_SCHEMA, TABLE_NAME FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_TYPE = 'BASE TABLE'
OPEN tables_cur
FETCH NEXT FROM tables_cur INTO #table_schema, #table_name
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE columns_cur CURSOR FOR SELECT COLUMN_NAME FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_SCHEMA = #table_schema AND TABLE_NAME = #table_name AND COLLATION_NAME IS NOT NULL -- Only strings have this and they always have it
OPEN columns_cur
FETCH NEXT FROM columns_cur INTO #column_name
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #sql_string = 'IF EXISTS (SELECT * FROM ' + QUOTENAME(#table_schema) + '.' + QUOTENAME(#table_name) + ' WHERE ' + QUOTENAME(#column_name) + ' LIKE ''%' + #search_string + '%'') PRINT ''' + QUOTENAME(#table_schema) + '.' + QUOTENAME(#table_name) + ', ' + QUOTENAME(#column_name) + ''''
EXECUTE(#sql_string)
FETCH NEXT FROM columns_cur INTO #column_name
END
CLOSE columns_cur
DEALLOCATE columns_cur
FETCH NEXT FROM tables_cur INTO #table_schema, #table_name
END
CLOSE tables_cur
DEALLOCATE tables_cur
I’d suggest you find yourself a 3rd party tool for this such as ApexSQL Search (there are probably others out there too but I use this one because it’s free).
If you really want to go the SQL way you can try using stored procedure created by
Sorna Kumar Muthuraj – copied code is below. Just execute this stored procedure for all tables in your schema (easy with dynamics SQL)
CREATE PROCEDURE SearchTables
#Tablenames VARCHAR(500)
,#SearchStr NVARCHAR(60)
,#GenerateSQLOnly Bit = 0
AS
/*
Parameters and usage
#Tablenames -- Provide a single table name or multiple table name with comma seperated.
If left blank , it will check for all the tables in the database
#SearchStr -- Provide the search string. Use the '%' to coin the search.
EX : X%--- will give data staring with X
%X--- will give data ending with X
%X%--- will give data containig X
#GenerateSQLOnly -- Provide 1 if you only want to generate the SQL statements without seraching the database.
By default it is 0 and it will search.
Samples :
1. To search data in a table
EXEC SearchTables #Tablenames = 'T1'
,#SearchStr = '%TEST%'
The above sample searches in table T1 with string containing TEST.
2. To search in a multiple table
EXEC SearchTables #Tablenames = 'T2'
,#SearchStr = '%TEST%'
The above sample searches in tables T1 & T2 with string containing TEST.
3. To search in a all table
EXEC SearchTables #Tablenames = '%'
,#SearchStr = '%TEST%'
The above sample searches in all table with string containing TEST.
4. Generate the SQL for the Select statements
EXEC SearchTables #Tablenames = 'T1'
,#SearchStr = '%TEST%'
,#GenerateSQLOnly = 1
*/
SET NOCOUNT ON
DECLARE #CheckTableNames Table
(
Tablename sysname
)
DECLARE #SQLTbl TABLE
(
Tablename SYSNAME
,WHEREClause VARCHAR(MAX)
,SQLStatement VARCHAR(MAX)
,Execstatus BIT
)
DECLARE #sql VARCHAR(MAX)
DECLARE #tmpTblname sysname
IF LTRIM(RTRIM(#Tablenames)) IN ('' ,'%')
BEGIN
INSERT INTO #CheckTableNames
SELECT Name
FROM sys.tables
END
ELSE
BEGIN
SELECT #sql = 'SELECT ''' + REPLACE(#Tablenames,',',''' UNION SELECT ''') + ''''
INSERT INTO #CheckTableNames
EXEC(#sql)
END
INSERT INTO #SQLTbl
( Tablename,WHEREClause)
SELECT SCh.name + '.' + ST.NAME,
(
SELECT '[' + SC.name + ']' + ' LIKE ''' + #SearchStr + ''' OR ' + CHAR(10)
FROM SYS.columns SC
JOIN SYS.types STy
ON STy.system_type_id = SC.system_type_id
AND STy.user_type_id =SC.user_type_id
WHERE STY.name in ('varchar','char','nvarchar','nchar')
AND SC.object_id = ST.object_id
ORDER BY SC.name
FOR XML PATH('')
)
FROM SYS.tables ST
JOIN #CheckTableNames chktbls
ON chktbls.Tablename = ST.name
JOIN SYS.schemas SCh
ON ST.schema_id = SCh.schema_id
WHERE ST.name <> 'SearchTMP'
GROUP BY ST.object_id, SCh.name + '.' + ST.NAME ;
UPDATE #SQLTbl
SET SQLStatement = 'SELECT * INTO SearchTMP FROM ' + Tablename + ' WHERE ' + substring(WHEREClause,1,len(WHEREClause)-5)
DELETE FROM #SQLTbl
WHERE WHEREClause IS NULL
WHILE EXISTS (SELECT 1 FROM #SQLTbl WHERE ISNULL(Execstatus ,0) = 0)
BEGIN
SELECT TOP 1 #tmpTblname = Tablename , #sql = SQLStatement
FROM #SQLTbl
WHERE ISNULL(Execstatus ,0) = 0
IF #GenerateSQLOnly = 0
BEGIN
IF OBJECT_ID('SearchTMP','U') IS NOT NULL
DROP TABLE SearchTMP
EXEC (#SQL)
IF EXISTS(SELECT 1 FROM SearchTMP)
BEGIN
SELECT Tablename=#tmpTblname,* FROM SearchTMP
END
END
ELSE
BEGIN
PRINT REPLICATE('-',100)
PRINT #tmpTblname
PRINT REPLICATE('-',100)
PRINT replace(#sql,'INTO SearchTMP','')
END
UPDATE #SQLTbl
SET Execstatus = 1
WHERE Tablename = #tmpTblname
END
SET NOCOUNT OFF
go
Although the solutions presented before are valid and work, I humbly offer a code that's cleaner, more elegant, and with better performance, at least as I see it.
Firstly, one may ask: Why would anyone ever need a code snippet to globally and blindly look for a string? Hey, they already invented fulltext, don't you know?
My answer: my mainly work is at systems integration projects, and discovering where the data is written is important whenever I'm learning a new and undocummented database, which seldom happens.
Also, the code I present is a stripped down version of a more powerful and dangerous script that searches and REPLACES text throughout the database.
CREATE TABLE #result(
id INT IDENTITY, -- just for register seek order
tblName VARCHAR(255),
colName VARCHAR(255),
qtRows INT
)
go
DECLARE #toLookFor VARCHAR(255)
SET #toLookFor = '[input your search criteria here]'
DECLARE cCursor CURSOR LOCAL FAST_FORWARD FOR
SELECT
'[' + usr.name + '].[' + tbl.name + ']' AS tblName,
'[' + col.name + ']' AS colName,
LOWER(typ.name) AS typName
FROM
sysobjects tbl
INNER JOIN(
syscolumns col
INNER JOIN systypes typ
ON typ.xtype = col.xtype
)
ON col.id = tbl.id
--
LEFT OUTER JOIN sysusers usr
ON usr.uid = tbl.uid
WHERE tbl.xtype = 'U'
AND LOWER(typ.name) IN(
'char', 'nchar',
'varchar', 'nvarchar',
'text', 'ntext'
)
ORDER BY tbl.name, col.colorder
--
DECLARE #tblName VARCHAR(255)
DECLARE #colName VARCHAR(255)
DECLARE #typName VARCHAR(255)
--
DECLARE #sql NVARCHAR(4000)
DECLARE #crlf CHAR(2)
SET #crlf = CHAR(13) + CHAR(10)
OPEN cCursor
FETCH cCursor
INTO #tblName, #colName, #typName
WHILE ##fetch_status = 0
BEGIN
IF #typName IN('text', 'ntext')
BEGIN
SET #sql = ''
SET #sql = #sql + 'INSERT INTO #result(tblName, colName, qtRows)' + #crlf
SET #sql = #sql + 'SELECT #tblName, #colName, COUNT(*)' + #crlf
SET #sql = #sql + 'FROM ' + #tblName + #crlf
SET #sql = #sql + 'WHERE PATINDEX(''%'' + #toLookFor + ''%'', ' + #colName + ') > 0' + #crlf
END
ELSE
BEGIN
SET #sql = ''
SET #sql = #sql + 'INSERT INTO #result(tblName, colName, qtRows)' + #crlf
SET #sql = #sql + 'SELECT #tblName, #colName, COUNT(*)' + #crlf
SET #sql = #sql + 'FROM ' + #tblName + #crlf
SET #sql = #sql + 'WHERE ' + #colName + ' LIKE ''%'' + #toLookFor + ''%''' + #crlf
END
EXECUTE sp_executesql
#sql,
N'#tblName varchar(255), #colName varchar(255), #toLookFor varchar(255)',
#tblName, #colName, #toLookFor
FETCH cCursor
INTO #tblName, #colName, #typName
END
SELECT *
FROM #result
WHERE qtRows > 0
ORDER BY id
GO
DROP TABLE #result
go
If you are "getting data" from an application, the sensible thing would be to use the profiler and profile the database while running the application. Trace it, then search the results for that string.
The SSMS Tools PACK Add-In (Add-On) for Microsoft SQL Server Management Studio and Microsoft SQL Server Management Studio Express will do exactly what you need. On larger database it takes some time to search, but that is to be expected. It also includes a ton of cool features that should have be included with SQL Server Management Studio in the first place. Give it a try www.ssmstoolspack.com/
You do need to have SP2 for SQL Server Management Studio installed to run the tools.
I adapted a script originally written by Narayana Vyas Kondreddi in 2002. I changed the where clause to check text/ntext fields as well, by using patindex rather than like. I also changed the results table slightly. Unreasonably, I changed variable names, and aligned as I prefer (no disrespect to Mr. Kondretti). The user may want to change the data types searched. I used a global table to allow querying mid-processing, but a permanent table might be a smarter way to go.
/* original script by Narayana Vyas Kondreddi, 2002 */
/* adapted by Oliver Holloway, 2009 */
/* these lines can be replaced by use of input parameter for a proc */
declare #search_string varchar(1000);
set #search_string = 'what.you.are.searching.for';
/* create results table */
create table ##string_locations (
table_name varchar(1000),
field_name varchar(1000),
field_value varchar(8000)
)
;
/* special settings */
set nocount on
;
/* declare variables */
declare
#table_name varchar(1000),
#field_name varchar(1000)
;
/* variable settings */
set #table_name = ''
;
set #search_string = QUOTENAME('%' + #search_string + '%','''')
;
/* for each table */
while #table_name is not null
begin
set #field_name = ''
set #table_name = (
select MIN(QUOTENAME(table_schema) + '.' + QUOTENAME(table_name))
from INFORMATION_SCHEMA.TABLES
where
table_type = 'BASE TABLE' and
QUOTENAME(table_schema) + '.' + QUOTENAME(table_name) > #table_name and
OBJECTPROPERTY(OBJECT_ID(QUOTENAME(table_schema) + '.' + QUOTENAME(table_name)), 'IsMSShipped') = 0
)
/* for each string-ish field */
while (#table_name is not null) and (#field_name is not null)
begin
set #field_name = (
select MIN(QUOTENAME(column_name))
from INFORMATION_SCHEMA.COLUMNS
where
table_schema = PARSENAME(#table_name, 2) and
table_name = PARSENAME(#table_name, 1) and
data_type in ('char', 'varchar', 'nchar', 'nvarchar', 'text', 'ntext') and
QUOTENAME(column_name) > #field_name
)
/* search that field for the string supplied */
if #field_name is not null
begin
insert into ##string_locations
exec(
'select ''' + #table_name + ''',''' + #field_name + ''',' + #field_name +
'from ' + #table_name + ' (nolock) ' +
'where patindex(' + #search_string + ',' + #field_name + ') > 0' /* patindex works with char & text */
)
end
;
end
;
end
;
/* return results */
select table_name, field_name, field_value from ##string_locations (nolock)
;
/* drop temp table */
--drop table ##string_locations
;
Other answers posted already may work equally well or better, but I haven't used them. However, the following SQL I have used, and it really helped me out when I was trying to reverse-engineer a big system with a huge (and very unorganzied) SQL Server database.
This isn't my code. I wish I could credit the original author, but I can't find the link to the article anymore :(
Use
go
declare #SearchChar varchar(8000)
Set #SearchChar = -- Like 'A%', '11/11/2006'
declare #CMDMain varchar(8000), #CMDMainCount varchar(8000),#CMDJoin varchar(8000)
declare #ColumnName varchar(100),#TableName varchar(100)
declare dbTable cursor for
SELECT
Distinct b.Name as TableName
FROM
sysobjects b
WHERE
b.type='u' and b.Name 'dtproperties'
order by b.name
open dbTable
fetch next from dbTable into #TableName
WHILE ##FETCH_STATUS = 0
BEGIN
declare db cursor for
SELECT
c.Name as ColumnName
FROM
sysobjects b,
syscolumns c
WHERE
C.id = b.id and
b.type='u' and b.Name = #TableName
order by b.name
open db
fetch next from db into #ColumnName
set #CMDMain = 'SELECT ' + char(39) + #TableName + char(39) + ' as TableName,'+
' ['+ #TableName + '].* FROM [' + #TableName + ']'+
' WHERE '
set #CMDMainCount = 'SELECT Count(*) FROM [' + #TableName + '] Where '
Set #CMDJoin = ''
WHILE ##FETCH_STATUS = 0
BEGIN
set #CMDJoin = #CMDJoin + 'Convert(varchar(5000),[' +#ColumnName + ']) like ' + char(39) + #SearchChar + char(39) + ' OR '
fetch next from db into #ColumnName
end
close db
deallocate db
Set #CMDMainCount = 'If ('+ #CMDMainCount + Left(#CMDJoin, len(#CMDJoin) - 3)+ ') > 0 Begin '
Set #CMDMain = #CMDMainCount + #CMDMain + Left(#CMDJoin, len(#CMDJoin) - 3)
Set #CMDMain = #CMDMain + ' End '
Print #CMDMain
exec (#CMDMain)
fetch next from dbTable into #TableName
end
close dbTable
deallocate dbTable
Actually Im agree with MikeW (+1) it's better to use profiler for this case.
Anyway, if you really need to grab all (n)varchar columns in db and make a search. See below.
I suppose to use INFORMATION_SCHEMA.Tables + dynamic SQL.
The plain search:
DECLARE #SearchText VARCHAR(100)
SET #SearchText = '12'
DECLARE #Tables TABLE(N INT, TableName VARCHAR(100), ColumnNamesCSV VARCHAR(2000), SQL VARCHAR(4000))
INSERT INTO #Tables (TableName, ColumnNamesCSV)
SELECT T.TABLE_NAME AS TableName,
( SELECT C.Column_Name + ','
FROM INFORMATION_SCHEMA.Columns C
WHERE T.TABLE_NAME = C.TABLE_NAME
AND C.DATA_TYPE IN ('nvarchar','varchar')
FOR XML PATH('')
)
FROM INFORMATION_SCHEMA.Tables T
DELETE FROM #Tables WHERE ColumnNamesCSV IS NULL
INSERT INTO #Tables (N, TableName, ColumnNamesCSV)
SELECT ROW_NUMBER() OVER(ORDER BY TableName), TableName, ColumnNamesCSV
FROM #Tables
DELETE FROM #Tables WHERE N IS NULL
UPDATE #Tables
SET ColumnNamesCSV = SUBSTRING(ColumnNamesCSV, 0, LEN(ColumnNamesCSV))
UPDATE #Tables
SET SQL = 'SELECT * FROM ['+TableName+'] WHERE '''+#SearchText+''' IN ('+ColumnNamesCSV+')'
DECLARE #C INT,
#I INT,
#SQL VARCHAR(4000)
SELECT #I = 1,
#C = COUNT(1)
FROM #Tables
WHILE #I <= #C BEGIN
SELECT #SQL = SQL FROM #Tables WHERE N = #I
SET #I = #I+1
EXEC(#SQL)
END
and one with LIKE clause:
DECLARE #SearchText VARCHAR(100)
SET #SearchText = '12'
DECLARE #Tables TABLE(N INT, TableName VARCHAR(100), ColumnNamesCSVLike VARCHAR(2000), LIKESQL VARCHAR(4000))
INSERT INTO #Tables (TableName, ColumnNamesCSVLike)
SELECT T.TABLE_NAME AS TableName,
( SELECT C.Column_Name + ' LIKE ''%'+#SearchText+'%'' OR '
FROM INFORMATION_SCHEMA.Columns C
WHERE T.TABLE_NAME = C.TABLE_NAME
AND C.DATA_TYPE IN ('nvarchar','varchar')
FOR XML PATH(''))
FROM INFORMATION_SCHEMA.Tables T
DELETE FROM #Tables WHERE ColumnNamesCSVLike IS NULL
INSERT INTO #Tables (N, TableName, ColumnNamesCSVLike)
SELECT ROW_NUMBER() OVER(ORDER BY TableName), TableName, ColumnNamesCSVLike
FROM #Tables
DELETE FROM #Tables WHERE N IS NULL
UPDATE #Tables
SET ColumnNamesCSVLike = SUBSTRING(ColumnNamesCSVLike, 0, LEN(ColumnNamesCSVLike)-2)
UPDATE #Tables SET LIKESQL = 'SELECT * FROM ['+TableName+'] WHERE '+ColumnNamesCSVLike
DECLARE #C INT,
#I INT,
#LIKESQL VARCHAR(4000)
SELECT #I = 1,
#C = COUNT(1)
FROM #Tables
WHILE #I <= #C BEGIN
SELECT #LIKESQL = LIKESQL FROM #Tables WHERE N = #I
SET #I = #I +1
EXEC(#LIKESQL)
END
#NLwino, yery good query with a few errors for keyword usage. I had to modify it a little to wrap the keywords with [ ] and also look char and ntext columns.
DECLARE #searchstring NVARCHAR(255)
SET #searchstring = '%WDB1014%'
DECLARE #sql NVARCHAR(max)
SELECT #sql = STUFF((
SELECT ' UNION ALL SELECT ''' + TABLE_NAME + ''' AS tbl, ''' + COLUMN_NAME + ''' AS col, [' + COLUMN_NAME + '] AS val' +
' FROM ' + TABLE_SCHEMA + '.[' + TABLE_NAME +
'] WHERE [' + COLUMN_NAME + '] LIKE ''' + #searchstring + ''''
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE in ('nvarchar', 'varchar', 'char', 'ntext')
FOR XML PATH('')
) ,1, 11, '')
Exec (#sql)
I ran it on 2.5 GB database and it came back in 51 seconds
I think this can be an easiest way to find a string in all rows of your database -without using cursors and FOR XML-.
CREATE PROCEDURE SPFindAll (#find VARCHAR(max) = '')
AS
BEGIN
SET NOCOUNT ON;
--
DECLARE #query VARCHAR(max) = ''
SELECT #query = #query +
CASE
WHEN #query = '' THEN ''
ELSE ' UNION ALL '
END +
'SELECT ''' + s.name + ''' As schemaName, ''' + t.name + ''' As tableName, ''' + c.name + ''' As ColumnName, [' + c.name + '] COLLATE DATABASE_DEFAULT As [Data] FROM [' + s.name + '].[' + t.name + '] WHERE [' + c.name + '] Like ''%' + #find + '%'''
FROM
sys.schemas s
INNER JOIN
sys.tables t ON s.[schema_id] = t.[schema_id]
INNER JOIN
sys.columns c ON t.[object_id] = c.[object_id]
INNER JOIN
sys.types ty ON c.user_type_id = ty.user_type_id
WHERE
ty.name LIKE '%char'
EXEC(#query)
END
By creating this stored procedure you can run it for any string you want to find like this:
EXEC SPFindAll 'Hello World'
The result will be like this:
schemaName | tableName | columnName | Data
-----------+-----------+------------+-----------------------
schema1 | Table1 | Column1 | Hello World
schema1 | Table1 | Column1 | Hello World!
schema1 | Table2 | Column1 | I say "Hello World".
schema1 | Table2 | Column2 | Hello World
This uses no cursors or anything like that, just one dynamic query.
Also note that this uses LIKE. Since that happened to be what I needed. It works for all schemas, all tables and only query's those columns that are NVARCHAR or VARCHAR even if they have UDDT.
DECLARE #searchstring NVARCHAR(255)
SET #searchstring = '%searchstring%'
DECLARE #sql NVARCHAR(max)
SELECT #sql = STUFF((
SELECT ' UNION ALL SELECT ''' + TABLE_NAME + ''' AS tablename, ''' + COLUMN_NAME + ''' AS columnname, ' + COLUMN_NAME + ' AS valuename' +
' FROM ' + TABLE_SCHEMA + '.' + TABLE_NAME +
' WHERE ' + COLUMN_NAME + ' LIKE ''' + #searchstring + ''''
FROM INFORMATION_SCHEMA.COLUMNS
WHERE DATA_TYPE in ('nvarchar', 'varchar')
FOR XML PATH('')
) ,1, 11, '')
EXEC(#sql)
The output gives you the table, column and value. Time to execute on a small database was ~3 seconds, had about 3000 results.
/*
This procedure is for finding any string or date in all tables
if search string is date, its format should be yyyy-MM-dd
eg. 2011-07-05
*/
-- ================================================
-- Exec SearchInTables 'f6f56934-a5d4-4967-80a1-1a2223b9c7b1'
-- ================================================
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
-- =============================================
-- Author: <Joshy,,Name>
-- Create date: <Create Date,,>
-- Description: <Description,,>
-- =============================================
ALTER PROCEDURE SearchInTables
#myValue nvarchar(1000)
AS
BEGIN
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
SET NOCOUNT ON;
-- Insert statements for procedure here
DECLARE #searchsql nvarchar(max)
DECLARE #table_name nvarchar(1000)
DECLARE #Schema_name nvarchar(1000)
DECLARE #ParmDefinition nvarchar(500)
DECLARE #XMLIn nvarchar(max)
SET #ParmDefinition = N'#XMLOut varchar(max) OUTPUT'
SELECT A.name,b.name
FROM sys.tables A
INNER JOIN sys.schemas B ON A.schema_id=B.schema_id
WHERE A.name like 'tbl_Tax_Sections'
DECLARE tables_cur CURSOR FOR
SELECT A.name,b.name FOM sys.tables A
INNER JOIN sys.schemas B ON A.schema_id=B.schema_id
WHERE A.type = 'U'
OPEN tables_cur
FETCH NEXT FROM tables_cur INTO #table_name , #Schema_name
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #searchsql ='SELECT #XMLOut=(SELECT PATINDEX(''%'+ #myValue+ '%'''
SET #searchsql =#searchsql + ', (SELECT * FROM '+#Schema_name+'.'+#table_name+' FOR XML AUTO) ))'
--print #searchsql
EXEC sp_executesql #searchsql, #ParmDefinition, #XMLOut=#XMLIn OUTPUT
--print #XMLIn
IF #XMLIn <> 0 PRINT #Schema_name+'.'+#table_name
FETCH NEXT FROM tables_cur INTO #table_name , #Schema_name
END
CLOSE tables_cur
DEALLOCATE tables_cur
RETURN
END
GO
To "find where the data I get comes from", you can start SQL Profiler, start your report or application, and you will see all the queries issued against your database.
Or, you can use my query here, should be simpler then having to create sProcs for each DB you want to search: FullParam SQL Blog
/* Reto Egeter, fullparam.wordpress.com */
DECLARE #SearchStrTableName nvarchar(255), #SearchStrColumnName nvarchar(255), #SearchStrColumnValue nvarchar(255), #SearchStrInXML bit, #FullRowResult bit, #FullRowResultRows int
SET #SearchStrColumnValue = '%searchthis%' /* use LIKE syntax */
SET #FullRowResult = 1
SET #FullRowResultRows = 3
SET #SearchStrTableName = NULL /* NULL for all tables, uses LIKE syntax */
SET #SearchStrColumnName = NULL /* NULL for all columns, uses LIKE syntax */
SET #SearchStrInXML = 0 /* Searching XML data may be slow */
IF OBJECT_ID('tempdb..#Results') IS NOT NULL DROP TABLE #Results
CREATE TABLE #Results (TableName nvarchar(128), ColumnName nvarchar(128), ColumnValue nvarchar(max),ColumnType nvarchar(20))
SET NOCOUNT ON
DECLARE #TableName nvarchar(256) = '',#ColumnName nvarchar(128),#ColumnType nvarchar(20), #QuotedSearchStrColumnValue nvarchar(110), #QuotedSearchStrColumnName nvarchar(110)
SET #QuotedSearchStrColumnValue = QUOTENAME(#SearchStrColumnValue,'''')
DECLARE #ColumnNameTable TABLE (COLUMN_NAME nvarchar(128),DATA_TYPE nvarchar(20))
WHILE #TableName IS NOT NULL
BEGIN
SET #TableName =
(
SELECT MIN(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME))
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_TYPE = 'BASE TABLE'
AND TABLE_NAME LIKE COALESCE(#SearchStrTableName,TABLE_NAME)
AND QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME) > #TableName
AND OBJECTPROPERTY(OBJECT_ID(QUOTENAME(TABLE_SCHEMA) + '.' + QUOTENAME(TABLE_NAME)), 'IsMSShipped') = 0
)
IF #TableName IS NOT NULL
BEGIN
DECLARE #sql VARCHAR(MAX)
SET #sql = 'SELECT QUOTENAME(COLUMN_NAME),DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = PARSENAME(''' + #TableName + ''', 2)
AND TABLE_NAME = PARSENAME(''' + #TableName + ''', 1)
AND DATA_TYPE IN (' + CASE WHEN ISNUMERIC(REPLACE(REPLACE(REPLACE(REPLACE(REPLACE(#SearchStrColumnValue,'%',''),'_',''),'[',''),']',''),'-','')) = 1 THEN '''tinyint'',''int'',''smallint'',''bigint'',''numeric'',''decimal'',''smallmoney'',''money'',' ELSE '' END + '''char'',''varchar'',''nchar'',''nvarchar'',''timestamp'',''uniqueidentifier''' + CASE #SearchStrInXML WHEN 1 THEN ',''xml''' ELSE '' END + ')
AND COLUMN_NAME LIKE COALESCE(' + CASE WHEN #SearchStrColumnName IS NULL THEN 'NULL' ELSE '''' + #SearchStrColumnName + '''' END + ',COLUMN_NAME)'
INSERT INTO #ColumnNameTable
EXEC (#sql)
WHILE EXISTS (SELECT TOP 1 COLUMN_NAME FROM #ColumnNameTable)
BEGIN
PRINT #ColumnName
SELECT TOP 1 #ColumnName = COLUMN_NAME,#ColumnType = DATA_TYPE FROM #ColumnNameTable
SET #sql = 'SELECT ''' + #TableName + ''',''' + #ColumnName + ''',' + CASE #ColumnType WHEN 'xml' THEN 'LEFT(CAST(' + #ColumnName + ' AS nvarchar(MAX)), 4096),'''
WHEN 'timestamp' THEN 'master.dbo.fn_varbintohexstr('+ #ColumnName + '),'''
ELSE 'LEFT(' + #ColumnName + ', 4096),''' END + #ColumnType + '''
FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + CASE #ColumnType WHEN 'xml' THEN 'CAST(' + #ColumnName + ' AS nvarchar(MAX))'
WHEN 'timestamp' THEN 'master.dbo.fn_varbintohexstr('+ #ColumnName + ')'
ELSE #ColumnName END + ' LIKE ' + #QuotedSearchStrColumnValue
INSERT INTO #Results
EXEC(#sql)
IF ##ROWCOUNT > 0 IF #FullRowResult = 1
BEGIN
SET #sql = 'SELECT TOP ' + CAST(#FullRowResultRows AS VARCHAR(3)) + ' ''' + #TableName + ''' AS [TableFound],''' + #ColumnName + ''' AS [ColumnFound],''FullRow>'' AS [FullRow>],*' +
' FROM ' + #TableName + ' (NOLOCK) ' +
' WHERE ' + CASE #ColumnType WHEN 'xml' THEN 'CAST(' + #ColumnName + ' AS nvarchar(MAX))'
WHEN 'timestamp' THEN 'master.dbo.fn_varbintohexstr('+ #ColumnName + ')'
ELSE #ColumnName END + ' LIKE ' + #QuotedSearchStrColumnValue
EXEC(#sql)
END
DELETE FROM #ColumnNameTable WHERE COLUMN_NAME = #ColumnName
END
END
END
SET NOCOUNT OFF
SELECT TableName, ColumnName, ColumnValue, ColumnType, COUNT(*) AS Count FROM #Results
GROUP BY TableName, ColumnName, ColumnValue, ColumnType
This query can do the thing for you.
DECLARE
#search_string VARCHAR(100),
#table_name SYSNAME,
#table_id INT,
#column_name SYSNAME,
#sql_string VARCHAR(2000)
SET #search_string = 'StringtoSearch'
DECLARE tables_cur CURSOR FOR SELECT ss.name +'.'+ so.name [name], object_id FROM sys.objects so INNER JOIN sys.schemas ss ON so.schema_id = ss.schema_id WHERE type = 'U'
OPEN tables_cur
FETCH NEXT FROM tables_cur INTO #table_name, #table_id
WHILE (##FETCH_STATUS = 0)
BEGIN
DECLARE columns_cur CURSOR FOR SELECT name FROM sys.columns WHERE object_id = #table_id
AND system_type_id IN (167, 175, 231, 239, 99)
OPEN columns_cur
FETCH NEXT FROM columns_cur INTO #column_name
WHILE (##FETCH_STATUS = 0)
BEGIN
SET #sql_string = 'IF EXISTS (SELECT * FROM ' + #table_name + ' WHERE [' + #column_name + ']
LIKE ''%' + #search_string + '%'') PRINT ''' + #table_name + ', ' + #column_name + ''''
EXECUTE(#sql_string)
FETCH NEXT FROM columns_cur INTO #column_name
END
CLOSE columns_cur
DEALLOCATE columns_cur
FETCH NEXT FROM tables_cur INTO #table_name, #table_id
END
CLOSE tables_cur
DEALLOCATE tables_cur