How to find a string inside a entire database? - sql

I have one specific string, such as "123abcd" for example but I don't know the name of the table or even the name of the column inside the table on my SQL Server Database. I want to find it with a select and show all columns of the related string, so I was wondering something like:
select * from Database.dbo.* where * like '%123abcd%'
For obvious reasons it doens't work, but there is a simple way to create a select statement to do something like this?

This will work:
DECLARE #MyValue NVarChar(4000) = 'something';
SELECT S.name SchemaName, T.name TableName
INTO #T
FROM sys.schemas S INNER JOIN
sys.tables T ON S.schema_id = T.schema_id;
WHILE (EXISTS (SELECT * FROM #T)) BEGIN
DECLARE #SQL NVarChar(4000) = 'SELECT * FROM $$TableName WHERE (0 = 1) ';
DECLARE #TableName NVarChar(1000) = (
SELECT TOP 1 SchemaName + '.' + TableName FROM #T
);
SELECT #SQL = REPLACE(#SQL, '$$TableName', #TableName);
DECLARE #Cols NVarChar(4000) = '';
SELECT
#Cols = COALESCE(#Cols + 'OR CONVERT(NVarChar(4000), ', '') + C.name + ') = CONVERT(NVarChar(4000), ''$$MyValue'') '
FROM sys.columns C
WHERE C.object_id = OBJECT_ID(#TableName);
SELECT #Cols = REPLACE(#Cols, '$$MyValue', #MyValue);
SELECT #SQL = #SQL + #Cols;
EXECUTE(#SQL);
DELETE FROM #T
WHERE SchemaName + '.' + TableName = #TableName;
END;
DROP TABLE #T;
A couple caveats, though. First, this is outrageously slow and non-optimized. All values are being converted to nvarchar simply so that they can be compared without error. You may run into problems with values like datetime not converting as expected and therefore not being matched when they should be (false negatives).
The WHERE (0 = 1) is there to make building the OR clause easier. If there are not matches you won't get any rows back.

Here are couple more free tools that can be used for this. Both work as SSMS addins.
ApexSQL Search – 100% free - searches both schema and data in tables. Has couple more useful options such as dependency tracking…
SSMS Tools pack – free for all versions except SQL 2012 – doesn’t look as advanced as previous one but has a lot of other cool features.

I think you have to options:
Build a dynamic SQL using sys.tables and sys.columns to perform the search (example here).
Use any program that have this function. An example of this is SQL Workbench (free).

create procedure usp_find_string(#string as varchar(1000))
as
begin
declare #mincounter as int
declare #maxcounter as int
declare #stmtquery as varchar(1000)
set #stmtquery=''
create table #tmp(tablename varchar(128),columnname varchar(128),rowid int identity)
create table #tablelist(tablename varchar(128),columnname varchar(128))
declare #tmp table(name varchar(128))
declare #tablename as varchar(128)
declare #columnname as varchar(128)
insert into #tmp(tablename,columnname)
select a.name,b.name as columnname from sysobjects a
inner join syscolumns b on a.name=object_name(b.id)
where a.type='u'
and b.xtype in(select xtype from systypes
where name='text' or name='ntext' or name='varchar' or name='nvarchar' or name='char' or name='nchar')
order by a.name
select #maxcounter=max(rowid),#mincounter=min(rowid) from #tmp
while(#mincounter <= #maxcounter )
begin
select #tablename=tablename, #columnname=columnname from #tmp where rowid=#mincounter
set #stmtquery ='select top 1 ' + '[' +#columnname+']' + ' from ' + '['+#tablename+']' + ' where ' + '['+#columnname+']' + ' like ' + '''%' + #string + '%'''
insert into #tmp(name) exec(#stmtquery)
if ##rowcount >0
insert into #tablelist values(#tablename,#columnname)
set #mincounter=#mincounter +1
end
select * from #tablelist
end

In oracle you can use the following sql command to generate the sql commands you need:
select
"select * "
" from "||table_name||
" where "||column_name||" like '%123abcd%' ;" as sql_command
from user_tab_columns
where data_type='VARCHAR2';

Common Resource Grep (crgrep) will search for string matches in tables/columns by name or content and supports a number of DBs, including SQLServer, Oracle and others. Full wild-carding and other useful options.
It's opensource (I'm the author).
http://sourceforge.net/projects/crgrep/

I usually use information_Schema.columns and information_schema.tables, although like #yuck said, sys.tables and sys.columns are shorter to type.
In a loop, concatenate these
#sql = #sql + 'select' + column_name +
' from ' + table_name +
' where ' + column_name ' like ''%''+value+''%' UNION
Then execute the resulting sql.

Here is an easy and convenient cursor based solution
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 name, object_id FROM sys.objects 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)
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

Related

How to get metadata from columns that have specific number of distinct values?

I need to find all columns that have 5 or more distinct values. Now my query is like:
SELECT TABLE_NAME,COLUMN_NAME, DATA_TYPE
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_SCHEMA = 'MY_SCHEMA'
AND TABLE_NAME IN ('TABLE_1', 'TABLE_2', 'TABLE_3')
I thought it could be done like simple subquery. Something like:
*code above*
AND (select count(distinct COLUMN_NAME) FROM TABLE_SCHEMA + TABLE_NAME) > 5
I just recently started to learn SQL and thought this kind of thing is easy, but still I can't figure out right query.
With help of Stu's answer and this answer I was able to make workable solution.
declare #RowsToProcess int
declare #CurrentRow int
declare #SelectCol nvarchar(max)
declare #SelectTable nvarchar(max)
declare #tablesAndColumns table(RowID int not null primary key identity(1,1), table_name nvarchar(max), column_name nvarchar(max)
insert into #tablesAndColumns
select TABLE_NAME,COLUMN_NAME,DATA_TYPE
from INFORMATION_SCHEMA.COLUMNS
where TABLE_SCHEMA = 'my schema'
and TABLE_NAME in ('myTable', 'myTable2' ,'myTable3')
set #RowsToProcess=##ROWCOUNT
set #CurrentRow=0
while #CurrentRow<#RowsToProcess
begin
set #CurrentRow=#CurrentRow+1
select
#SelectCol=column_name,
#SelectTable=table_name
from #tablesAndColumns
where RowID=#CurrentRow
declare #QRY NVARCHAR(MAX)
set #QRY = ' insert into [my_schema].[result_table] (table_name,column_name,distinct_values)
SELECT ' + '''' +#SelectTable+ '''' + ', ' + '''' +#SelectCol+ '''' + ', count(*) as cnt
FROM (SELECT DISTINCT ' +#SelectCol+ ' FROM my_schema.'+ #SelectTable+') as a'
exec SP_EXECUTESQL #QRY
end
I'd like to propose another way. You can run through all the column and table names by using a CURSOR. That way you don't need to store them beforehand and can directly access them in your loop while also having a while condition.
Also I went with sys.tables and sys.columns since I noticed that INFORMATION_SCHEMA also contains views and sys.tables can be filtered for the table's type.
I added a "HAVING COUNT(*) >= 5" into the dynamic SQL so I don't save those informations in the first place rather than filtering them later.
Finally I went with "(NOLOCK)" because you only try to acces the tables for reading and that way you don't lock them for other users / interactions.
(The #i and #max are just for tracking the progress since I ran the query on ~10k columns and just wanted to see how far it is.)
Hopefully might be helpful aswell although you seem to have solved your problem.
DECLARE #columnName nvarchar(100),
#tableName nvarchar(100),
#sql nvarchar(MAX),
#i int = 0,
#max int = (SELECT COUNT(*)
FROM sys.tables T
INNER JOIN sys.columns C ON T.object_id = C.object_id
WHERE T.[type] = 'U')
DROP TABLE IF EXISTS #resultTable
CREATE TABLE #resultTable (ColumnName nvarchar(100), TableName nvarchar(100), ResultCount int)
DECLARE db_cursor CURSOR FOR
SELECT C.[name], T.[name]
FROM sys.tables T
INNER JOIN sys.columns C ON T.object_id = C.object_id
WHERE T.[type] = 'U'
OPEN db_cursor
FETCH NEXT FROM db_cursor INTO #columnName, #tableName
WHILE ##FETCH_STATUS = 0
BEGIN
SET #sql = CONCAT(' INSERT INTO #resultTable (ColumnName, TableName, ResultCount)
SELECT ''', #columnName, ''', ''', #tableName, ''', COUNT(*)
FROM (
SELECT DISTINCT [', #columnName, ']
FROM [', #tableName, '] (NOLOCK)
WHERE [', #columnName, '] IS NOT NULL
) t
HAVING COUNT(*) >= 5')
EXEC sp_executesql #sql
SET #i = #i + 1
PRINT CONCAT(#i, ' / ', #max)
FETCH NEXT FROM db_cursor INTO #columnName, #tableName
END
CLOSE db_cursor
DEALLOCATE db_cursor
SELECT *
FROM #resultTable

ERROR IN DYNAMIC SQL: Conversion failed when converting the varchar value ', ' to data type int

My purpose is to produce a table containing the table_name, column_name, number of row each column and number of null value in each column. But I get an error:
Conversion failed when converting the varchar value ', ' to data type int
These are my queries:
DECLARE #BANG TABLE
(
TABLE_NAME NVARCHAR(MAX),
COLUMN_NAME NVARCHAR(MAX),
ID INT IDENTITY(1, 1)
)
INSERT INTO #BANG (TABLE_NAME, COLUMN_NAME)
SELECT A.NAME AS TABLE_NAME, B.NAME AS COLUMN_NAME
FROM SYS.TABLES AS A
LEFT JOIN SYS.COLUMNS AS B ON A.OBJECT_ID = B.OBJECT_ID
WHERE 1=1
AND A.NAME IN ('CTHD', 'HOADON', 'SANPHAM', 'KHACHHANG', 'NHANVIEN')
DECLARE #RESULT TABLE
(
TABLE_NAME NVARCHAR(MAX),
COLUMN_NAME NVARCHAR(MAX),
TOTAL_ROW INT,
TOTAL_NULL INT
)
DECLARE #ID INT = 0
WHILE #ID <= (SELECT COUNT(*) FROM #BANG)
BEGIN
DECLARE #TABLE_NAME NVARCHAR(MAX)
SET #TABLE_NAME = (SELECT TABLE_NAME FROM #BANG WHERE #ID = ID)
DECLARE #COLUMN_NAME NVARCHAR(MAX)
SET #COLUMN_NAME = (SELECT COLUMN_NAME FROM #BANG WHERE ID = #ID)
DECLARE #TOTAL_ROW INT
DECLARE #TOTAL_NULL INT
DECLARE #SQL NVARCHAR(MAX)
SET #SQL = 'SET #TOTAL_ROW = (SELECT COUNT(*) FROM '+#TABLE_NAME+')
SET #TOTAL_NULL = (SELECT COUNT(*) FROM '+#TABLE_NAME+' WHERE '+#COLUMN_NAME+' IS NULL)
INSERT INTO #RESULT
VALUES ('+#TABLE_NAME+', '+#COLUMN_NAME+', '+#TOTAL_ROW+', '+#TOTAL_NULL+')
'
SET #ID += 1
EXEC (#SQL)
END
I need your help. Thanks in advance
You should be using parameterized SQL. But honestly, the code is such a mess that I'm not going to attempt that fix.
The problem is that parameters such as #TOTAL_ROW are integers, not strings. So, the + is treated as addition rather than string concatenation.
The simplest immediate fix is to use CONCAT():
SET #SQL = CONCAT('
INSERT INTO #RESULT
VALUES (''', #TABLE_NAME, ''', ''', #COLUMN_NAME, ''', ''', #TOTAL_ROW, ', ', #TOTAL_NULL, ')';
You may have the same error elsewhere in the code. You need to fix all places where you have a number and string combined with + and you intend string concatenation rather than addition.
However, the real fix is to not munge query strings with such values. Instead use sp_executesql passing the values in as parameters.
The conversion error is during the generation of the dynamic SQL query, not during execution of the statement.
There are a number of issues with the script in your question. Below is a script that uses QUOTENAME to more security build the SQL statement and uses a parameterized query to execute it. The WHILE pseudo cursor doesn't provide any value in this case so this version uses a real cursor.
DECLARE #RESULT TABLE (SCHEMA_NAME sysname, TABLE_NAME sysname, COLUMN_NAME sysname, TOTAL_ROW int, TOTAL_NULL int);
DECLARE #SQL nvarchar(MAX), #SchemaName sysname, #TableName sysname, #ColumnName sysname;
DECLARE BANG CURSOR LOCAL FAST_FORWARD FOR
SELECT s.name AS SCHEMA_NAME, t.name AS TABLE_NAME, c.name AS COLUMN_NAME
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
JOIN sys.columns AS c ON c.object_id = t.object_id
WHERE t.name IN (N'CTHD', N'HOADON', N'SANPHAM', N'KHACHHANG', N'NHANVIEN');
OPEN BANG;
WHILE 1 = 1
BEGIN
FETCH NEXT FROM BANG INTO #SchemaName, #TableName, #ColumnName;
IF ##FETCH_STATUS = -1 BREAK;
SET #SQL = N'SELECT #SchemaName, #TableName, #ColumnName, COUNT(*), COALESCE(SUM(CASE WHEN ' + QUOTENAME(#ColumnName) + N' IS NULL THEN 1 ELSE 0 END),0)
FROM ' + QUOTENAME(#SchemaName) + N'.' + QUOTENAME(#TableName) + N';'
PRINT #SQL
INSERT INTO #RESULT(SCHEMA_NAME, TABLE_NAME, COLUMN_NAME, TOTAL_ROW, TOTAL_NULL)
EXEC sp_executesql #SQL
, N'#SchemaName sysname, #TableName sysname, #ColumnName sysname'
, #SchemaName = #SchemaName
, #TableName = #TableName
, #ColumnName = #ColumnName;
END;
CLOSE BANG;
DEALLOCATE BANG;
SELECT SCHEMA_NAME, TABLE_NAME, COLUMN_NAME, TOTAL_ROW, TOTAL_NULL
FROM #RESULT
ORDER BY SCHEMA_NAME, TABLE_NAME, COLUMN_NAME;
GO
If you don't have many tables/columns, you could use a single UNION ALL query and ditch the (pseudo)cursor entirely:
DECLARE #SQL nvarchar(MAX) = (SELECT STRING_AGG(
N'SELECT ' + QUOTENAME(s.name,'''') + N' AS SCHEMA_NAME,'
+ QUOTENAME(t.name, '''') + N' AS TABLE_NAME,'
+ QUOTENAME(c.name,'''') + N' AS COLUMN_NAME,'
+ 'COUNT(*) AS TOTAL_ROW,'
+ 'COALESCE(SUM(CASE WHEN ' + QUOTENAME(c.name) + ' IS NULL THEN 1 ELSE 0 END),0) AS TOTAL_NULL '
+ 'FROM ' + QUOTENAME(s.name) + N'.' + QUOTENAME(t.name)
, ' UNION ALL ') + N';'
FROM sys.tables AS t
JOIN sys.schemas AS s ON s.schema_id = t.schema_id
JOIN sys.columns AS c ON c.object_id = t.object_id
WHERE t.name IN (N'CTHD', N'HOADON', N'SANPHAM', N'KHACHHANG', N'NHANVIEN');
EXEC sp_executesql #SQL;
As Gordon said, use sp_execute properly to execute dynamics sql. And there's some others issue with this code, not related to the question.
"the more important" when investigating is to use the print statement before the exec statement, to know what's being execute. Then, you'll realize where the errors is and why it won't work....
the first execution of the loop is useless (or maybe this is the one that produce the error....). You initialize #Id with the value of 0 and then compare it with the value of the identity in the table #BANG, starting at 1. This result to #TABLE_NAME and #COLUMN_NAME set to NULL, thus, concatening string without using CONCAT will end up in a NULL value. Nothing is execute on the first loop.
However, using concat with null value will not result in a null value, but an incorrect value (query in your case). As an exemple, this code
SET #SQL = CONCAT('
INSERT INTO #RESULT
VALUES (''', #TABLE_NAME, ''', ''', #COLUMN_NAME, ''', ''', #TOTAL_ROW, ', ', #TOTAL_NULL, ')';
will result in something like "INSERT INTO #RESULT VALUES (''CTHD'',''COL1'',,)" since both #TOTAL_ROW AND #TOTAL_NULL are null values. you need to use parametrized query using sp_executesql.
There's no need to execute two count on the same table, one for total rows, the second for null values. select count(1) return the total number of rows, and select count(#column_name) return the number of non-null value. So, Count(1) - count(#column_name) will gives you the number of null value. Then, use something like this to insert into #result :
INSERT INTO #RESULT (...) SELECT TABLE_NAME, COLUMN_NAME, COUNT(1), COUNT(1) - COUNT(#COLUMN_NAME) FROM ...
when dealing with SQL Server object, you the quotename function. You'll never know when someone will put a space, quote, bracket or whatever in a schema/table/column name that might break you query.
Do not use "WHILE #ID <= (SELECT COUNT(*) FROM #BANG)" to test if there are more rows to process. Use a "WHILE EXISTS (SELECT 1 FROM #BANG)"

SQL query to dynamically COUNT(FIELD) for all fields of table X

This should be such an easy thing, but it has me totally stumped.
You can easily return the count of each field of a table manually, with oneliners such as:
select count(FIELD1) from TABLE1 --42,706
select count(FIELD5) from TABLE1 --42,686
select count(FIELD9) from TABLE1 --2,918
This is slow and painful if you want to review several dozen tables the same way, and requires you to know the names of the fields in advance.
How handy would it be to have a script you can connect to any database, simply feed it a table name, and it will automatically return the counts for each field of that table?
Seems you can get half the work done with:
select COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'TABLE1'
Something is flawed even with my barebones approach (explicitly hitting one field instead of them all):
declare #TABLENAME varchar(30), #FIELDNAME varchar(30)
set #TABLENAME = 'TABLE1'
set #FIELDNAME = (select top 1 COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = #TABLENAME
and COLUMN_NAME = 'FIELD9')
select #FIELDNAME, count(#FIELDNAME) from TABLE1
The result is 42,706. Recall from my example above that FIELD9 only contains 2,918 values.
Even if that wasn't a problem, the more dynamic query would replace the last line with:
select #FIELDNAME, count(#FIELDNAME) from #TABLENAME
But SQL Server returns:
Must declare the table variable "#TABLENAME".
So I can avoid that by restructuring the query with a temp table:
declare #FIELDNAME varchar(30)
set #FIELDNAME = (select top 1 COLUMN_NAME
from INFORMATION_SCHEMA.COLUMNS
where TABLE_NAME = 'TABLE1'
and COLUMN_NAME = 'FIELD9')
if OBJECT_ID('TEMPDB..#TEMP1') is not null
drop table #TEMP1
select *
into #TEMP1
from TABLE1 --still not exactly dynamic!
select #FIELDNAME, count(#FIELDNAME) from #TEMP1
But that still brings us back to the original problem of returning 42,706 instead of 2,918.
I am running SQL Server 2008 R2, if it makes any difference.
Your query:
SELECT #FIELDNAME, COUNT(#FIELDNAME) FROM TABLE1
does not count FIELD9, #FIELDNAME is treated as a constant. It's like doing a COUNT(*).
You should use dynamic sql:
DECLARE #sql VARCHAR(MAX)
SET #sql = 'SELECT ''' + #fieldName + ''', COUNT([' + #fieldName + ']) FROM [' + #tableName + ']'
EXEC(#sql)
To get all columns and return it in a single result set without using a Temporary Table and CURSOR:
DECLARE #sql NVARCHAR(MAX) = ''
SELECT #sql = #sql +
'SELECT ''' + COLUMN_NAME + ''' AS ColName, COUNT([' + COLUMN_NAME + ']) FROM [' + #tableName + ']' + CHAR(10) +
'UNION ALL' + CHAR(10)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = #tableName
SELECT #sql = LEFT(#sql, LEN(#sql) - 10)
EXEC(#sql)
Just set the #TargetTableName will do the job
DECLARE #TargetTableName sysname = '*'
SET NOCOUNT ON
DECLARE #TableName sysname, #ColumnName sysname, #Sql nvarchar(max)
DECLARE #TableAndColumn table
(
TableName sysname,
ColumnName sysname
)
DECLARE #Result table
(
TableName sysname,
ColumnName sysname,
NonNullRecords int
)
INSERT #TableAndColumn
SELECT o.name, c.name FROM sys.objects o INNER JOIN sys.columns c ON o.object_id = c.object_id
WHERE (o.name = #TargetTableName OR #TargetTableName = '*') AND o.type = 'U' AND c.system_type_id NOT IN (34, 35, 99) -- 34:image 35:text 99:ntext
ORDER BY c.column_id
DECLARE column_cursor CURSOR FOR SELECT TableName, ColumnName FROM #TableAndColumn
OPEN column_cursor
FETCH NEXT FROM column_cursor
INTO #TableName, #ColumnName
WHILE ##FETCH_STATUS = 0
BEGIN
SELECT #Sql = 'SELECT ''' + #TableName + ''' AS TableName, ''' + #ColumnName + ''' AS ColumnName, COUNT([' + #ColumnName + ']) AS NonNullRecords FROM [' + #TableName + ']'
print #Sql
INSERT #Result
EXEC (#Sql)
FETCH NEXT FROM column_cursor
INTO #TableName, #ColumnName
END
CLOSE column_cursor;
DEALLOCATE column_cursor;
SET NOCOUNT OFF
SELECT * FROM #Result

List all columns referenced in all procedures of all databases

Is there a way that I can get all the columns and tables referenced in all the stored procedures in all the databases in an instance? The output should be:
Database Procedure Table Column
-------- --------- ----- ------
This will get the list you're after, however it won't help you if you have such column references embedded in dynamic SQL (and may not find references that rely on deferred name resolution). SQL Server doesn't parse the text of the stored procedure to come up with the DMV output.
Try now with COLLATE clauses to deal with cases where you have databases on the same server with different collations.
DECLARE #sql NVARCHAR(MAX) = N'';
SELECT #sql += N'UNION ALL
SELECT
[database] = ''' + REPLACE(name, '''', '''''') + ''',
[procedure] = QUOTENAME(s.name) + ''.'' + QUOTENAME(p.name)
COLLATE Latin1_General_CI_AI,
[table] = QUOTENAME(referenced_schema_name) + ''.''
+ QUOTENAME(referenced_entity_name)
COLLATE Latin1_General_CI_AI,
[column] = QUOTENAME(referenced_minor_name)
COLLATE Latin1_General_CI_AI
FROM ' + QUOTENAME(name) + '.sys.schemas AS s
INNER JOIN ' + QUOTENAME(name) + '.sys.procedures AS p
ON s.[schema_id] = p.[schema_id]
CROSS APPLY ' + QUOTENAME(name)
+ '.sys.dm_sql_referenced_entities'
+ '(QUOTENAME(s.name) + ''.'' + QUOTENAME(p.name), N''OBJECT'') AS d
WHERE d.referenced_minor_id > 0'
FROM sys.databases
WHERE database_id > 4
AND [state] = 0;
SET #sql = STUFF(#sql,1,11,'');
EXEC sp_executesql #sql;
Also the CROSS APPLY syntax won't work if you have databases that are in 80 compatibility mode. Just make sure you don't execute the code in such a database and it should work fine (even if some of the target databases are in 80).
To list all SP Name have contain the specified column name:
SELECT OBJECT_NAME(M.object_id), M.*
FROM sys.sql_modules M
JOIN sys.procedures P
ON M.object_id = P.object_id
WHERE M.definition LIKE '%ColumnName%'
Here is yet another way to do this. This is very DIRTY but I like it. Why? Because I came up with it. Anyway it is using Dynamic SQL inside Dynamic SQL to insert dependency information into temp table that can be queried.
This can be modified into a SP that you can run from time to time to update dependencies information, also temp table can be changes to real table if you want to store it.
IF OBJECT_ID('tempdb.dbo.#SPDependencyDetails') IS NOT NULL
DROP TABLE #SPDependencyDetails
CREATE TABLE #SPDependencyDetails
(
Or_Object_Database NVARCHAR(128)
,Or_Object_Name NVARCHAR(128)
,Ref_Database_Name NVARCHAR(128)
,Ref_Schema_Name NVARCHAR(128)
,Ref_Object_Name NVARCHAR(128)
,Ref_Column_Name NVARCHAR(128)
,Is_Selected BIT
,Is_Updated BIT
,Is_Select_All BIT
,Is_All_Columns_Found BIT
)
DECLARE #database_name VARCHAR(100)
DECLARE database_cursor CURSOR
FOR
SELECT name
FROM sys.databases
WHERE database_id > 4
OPEN database_cursor
FETCH NEXT FROM database_cursor
INTO #database_name
WHILE ##FETCH_STATUS = 0 --Outer Loop begin
BEGIN
DECLARE #WholeLotofSQL NVARCHAR(MAX) = '
DECLARE #object_name VARCHAR(150)
,#sqlstatement NVARCHAR(2500)
DECLARE object_cursor CURSOR --Inner cursor, iterates list of objects that match type
FOR
SELECT name
FROM '+#database_name+'.sys.objects AS o
WHERE o.type = ''P'' --Change Object type to find dependencies of Functions, Views and etc.
ORDER BY 1
OPEN object_cursor
FETCH NEXT FROM object_cursor INTO #object_name
WHILE ##FETCH_STATUS = 0 --Inner Loop Begin
BEGIN
SET #sqlstatement = ''USE '+#database_name+';
INSERT INTO #SPDependencyDetails
SELECT DB_NAME() AS Or_Object_Database
,'''''' + #object_name + '''''' AS Or_Object_Name
,CASE WHEN referenced_database_name IS NULL THEN DB_NAME()
ELSE referenced_database_name
END AS Ref_Database_Name
,referenced_schema_name AS Ref_Schema_Name
,referenced_entity_name AS Ref_Object_Name
,referenced_minor_name AS Ref_Column_Name
,is_selected
,is_updated
,is_select_all
,is_all_columns_found
FROM sys.dm_sql_referenced_entities(''''dbo.'' + #object_name + '''''', ''''OBJECT'''');''
EXEC sys.sp_executesql #sqlstatement
FETCH NEXT FROM object_cursor INTO #object_name
END
CLOSE object_cursor
DEALLOCATE object_cursor'
EXEC sys.sp_executesql #WholeLotofSQL
FETCH NEXT FROM database_cursor INTO #database_name
END
CLOSE database_cursor;
DEALLOCATE database_cursor;
SELECT Or_Object_Database as 'Database'
,Or_Object_Name as 'Procedure'
,Ref_Object_Name as 'Table'
,Ref_Column_Name as 'Column
FROM #SPDependencyDetails

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