How do I search an SQL Server database for a string?

后端 未结 15 1687
执念已碎
执念已碎 2020-11-28 18:40

I know it\'s possible, but I don\'t know how.

I need to search an SQL Server database for all mentions of a specific string.

For example: I would like t

15条回答
  •  执笔经年
    2020-11-28 19:12

    My version...

    I named it "Needle in the haystack" for obvious reasons.

    It searches for a specific value in each row and each column, not for column names, etc.

    Execute search (replace values for the first two variables of course):

    DECLARE @SEARCH_DB VARCHAR(100)='REPLACE_WITH_YOUR_DB_NAME'
    DECLARE @SEARCH_VALUE_LIKE NVARCHAR(100)=N'%REPLACE_WITH_SEARCH_STRING%'
    
    SET NOCOUNT ON;
    DECLARE col_cur CURSOR FOR
    SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE
    FROM information_schema.columns WHERE TABLE_CATALOG=@SEARCH_DB AND DATA_TYPE NOT IN ('timestamp', 'datetime');
    
    DECLARE @TOTAL int = (SELECT COUNT(*)
    FROM information_schema.columns WHERE TABLE_CATALOG=@SEARCH_DB AND DATA_TYPE NOT IN ('timestamp', 'datetime'));
    
    
    DECLARE @TABLE_CATALOG nvarchar(500), @TABLE_SCHEMA nvarchar(500), @TABLE_NAME nvarchar(500), @COLUMN_NAME nvarchar(500), @DATA_TYPE nvarchar(500);
    DECLARE @SQL nvarchar(4000)='';
    
    PRINT '-------- BEGIN SEARCH --------';
    OPEN col_cur;
    
    FETCH NEXT FROM col_cur INTO @TABLE_CATALOG, @TABLE_SCHEMA, @TABLE_NAME, @COLUMN_NAME, @DATA_TYPE;
    
    BEGIN TRY DROP TABLE ##RESULTS; END TRY BEGIN CATCH END CATCH
    CREATE TABLE ##RESULTS( TABLE_CATALOG nvarchar(500), TABLE_SCHEMA nvarchar(500), TABLE_NAME nvarchar(500), COLUMN_NAME nvarchar(500), DATA_TYPE nvarchar(500), RECORDS int)
    DECLARE @SHOULD_CAST bit=0
    DECLARE @i int =0
    DECLARE @progress_sum bigint=0
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        -- PRINT '' + CAST(@i as varchar(100)) +' of ' + CAST(@TOTAL as varchar(100)) + '  ' + @TABLE_CATALOG+'.'+@TABLE_SCHEMA+'.'+@TABLE_NAME+': '+@COLUMN_NAME+' ('+@DATA_TYPE+')';
    
        SET @SHOULD_CAST = (SELECT CASE @DATA_TYPE
                                    WHEN 'varchar' THEN 0
                                    WHEN 'nvarchar' THEN 0
                                    WHEN 'char' THEN 0
                                    ELSE 1 END)
    
        SET @SQL='SELECT '''+@TABLE_CATALOG+''' catalog_name, '''+@TABLE_SCHEMA+''' schema_name, '''+@TABLE_NAME+''' table_name, '''+@COLUMN_NAME+''' column_name, '''+@DATA_TYPE+''' data_type, ' +
                +' COUNT(['+@COLUMN_NAME+']) records '+
                +' FROM '+@TABLE_CATALOG+'.'+@TABLE_SCHEMA+'.'+@TABLE_NAME +
                +' WHERE ' + CASE WHEN @SHOULD_CAST=1 THEN 'CAST(['+@COLUMN_NAME + '] as NVARCHAR(max)) ' ELSE ' ['+@COLUMN_NAME + '] ' END
                +' LIKE '''+ @SEARCH_VALUE_LIKE + ''' '
    
        -- PRINT @SQL;
    
        IF @i % 100 = 0
            BEGIN
                SET @progress_sum = (SELECT SUM(RECORDS) FROM ##RESULTS)
                PRINT CAST (@i as varchar(100)) +' of ' + CAST(@TOTAL as varchar(100)) +': '+ CAST (@progress_sum as varchar(100))
            END
    
        INSERT INTO ##RESULTS (TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, RECORDS)
        EXEC(@SQL)
    
        FETCH NEXT FROM col_cur INTO @TABLE_CATALOG, @TABLE_SCHEMA, @TABLE_NAME, @COLUMN_NAME, @DATA_TYPE;
        SET @i=@i+1
        -- IF @i > 1000
        --     BREAK
    END
    CLOSE col_cur;
    DEALLOCATE col_cur;
    
    SELECT * FROM ##RESULTS WHERE RECORDS>0;
    

    Then to view results, even while executing, from another window, execute:

    DECLARE @SEARCH_VALUE_LIKE NVARCHAR(100)=N'%@FLEX@%'
    SELECT * FROM ##RESULTS WHERE RECORDS>0;
    
    SET NOCOUNT ON;
    DECLARE col_cur CURSOR FOR
    SELECT TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE
    FROM ##RESULTS WHERE RECORDS>0;
    
    DECLARE @TABLE_CATALOG nvarchar(500), @TABLE_SCHEMA nvarchar(500), @TABLE_NAME nvarchar(500), @COLUMN_NAME nvarchar(500), @DATA_TYPE nvarchar(500);
    DECLARE @SQL nvarchar(4000)='';
    
    OPEN col_cur;
    
    FETCH NEXT FROM col_cur INTO @TABLE_CATALOG, @TABLE_SCHEMA, @TABLE_NAME, @COLUMN_NAME, @DATA_TYPE;
    DECLARE @i int =0
    DECLARE @SHOULD_CAST bit=0
    
    WHILE @@FETCH_STATUS = 0
    BEGIN
        SET @SHOULD_CAST = (SELECT CASE @DATA_TYPE
                                    WHEN 'varchar' THEN 0
                                    WHEN 'nvarchar' THEN 0
                                    WHEN 'char' THEN 0
                                    ELSE 1 END)
    
        SET @SQL='SELECT '''+@TABLE_CATALOG+''' catalog_name, '''+@TABLE_SCHEMA+''' schema_name, '''+@TABLE_NAME+''' table_name, '''+@COLUMN_NAME+''' column_name, '''+@DATA_TYPE+''' data_type, ' +
                +' ['+@COLUMN_NAME+']'+
                +', * '
                +' FROM '+@TABLE_CATALOG+'.'+@TABLE_SCHEMA+'.'+@TABLE_NAME +
                +' WHERE ' + CASE WHEN @SHOULD_CAST=1 THEN 'CAST(['+@COLUMN_NAME + '] as NVARCHAR(max)) ' ELSE ' ['+@COLUMN_NAME + '] ' END
                +' LIKE '''+ @SEARCH_VALUE_LIKE + ''' '
    
        PRINT @SQL;
    
        EXEC(@SQL)
    
        FETCH NEXT FROM col_cur INTO @TABLE_CATALOG, @TABLE_SCHEMA, @TABLE_NAME, @COLUMN_NAME, @DATA_TYPE;
        SET @i=@i+1
        -- IF @i > 10
        --    BREAK
    END
    CLOSE col_cur;
    DEALLOCATE col_cur;
    

    Few mentions about it:

    • it uses cursors instead of a blocking while loop
    • it can print progress (uncomment if needed)
    • it can exit after a few attempts (uncomment the IF at the end)
    • it displays all records
    • you can fine tune it as needed

    DISCLAIMERS:

    • DO NOT run it in production environments!
    • It is slow. If the DB is accessed by other services/users, please add " WITH (NOLOCK) " after every table name in all the selects, especially the dynamic select ones.
    • It does not validate/protect against all sorts of SQL injection options.
    • If your DB is huge, prepare yourself for some sleep, make sure the query will not be killed after a few minutes.
    • It casts some values to string, including ints/bigints/smallints/tinyints. If you don't need those, put them at the same exclusion lists with the timestamps at the top of the script.

提交回复
热议问题