SQL Server: поиск всех таблиц для определенного GUID
я столкнулся с необходимостью очистки некоторых данных, и мне нужно найти некоторые конкретные указатели (т.е. uniqueidentifiers) в SQL Server °.
Я придумал хранимую процедуру, которая выполняет SELECT из каждого столбца uniqueidentifier в каждой таблице в текущей базе данных и возвращает набор результатов, если найдено guid.
Он использует представления INFORMATION_SCHEMA для поиска всех столбцов uniqueidentifier во всех базовых таблицах (в отличие от представлений). Для каждого столбца выдается выбор, возвращающий имя таблицы и столбец, где он был найден.
CREATE PROCEDURE dbo.FindGUID @searchValue uniqueidentifier AS
/*
Search all tables in the database for a guid
6/9/2009: Removed the IF EXISTS to double hit the database
*/
--DECLARE @searchValue uniqueidentifier
--SET @searchValue = '{2A6814B9-8261-452D-A144-13264433864E}'
DECLARE abc CURSOR FOR
SELECT
c.TABLE_NAME, c.COLUMN_NAME
FROM INFORMATION_SCHEMA.Columns c
INNER JOIN INFORMATION_SCHEMA.Tables t
ON c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_TYPE = 'BASE TABLE'
WHERE DATA_TYPE = 'uniqueidentifier'
DECLARE @tableName varchar(200)
DECLARE @columnName varchar(200)
DECLARE @szQuery varchar(8000)
OPEN ABC
FETCH NEXT FROM abc INTO @tableName, @columnName
WHILE (@@FETCH_STATUS = 0)
BEGIN
SET @szQuery =
'SELECT '''[email protected]+''' AS TheTable, '''[email protected]+''' AS TheColumn '+
'FROM '[email protected]+' '+
'WHERE '[email protected]+' = '''+CAST(@searchValue AS varchar(50))+''''
PRINT 'Searching '[email protected]+'.'[email protected]+'..'
PRINT @szQuery
EXEC (@szQuery)
FETCH NEXT FROM abc INTO @tableName, @columnName
END
CLOSE abc
DEALLOCATE abc
Мой вопрос:
Вопрос 1
Может ли кто-нибудь выяснить способ его изменения, чтобы выполнить поиск нескольких столбцов uniqueidentifier в той же таблице, что и OR, а не отдельные запросы
то есть.
SELECT ... FROM Prices WHERE BookGUID = '{...}'
SELECT ... FROM Prices WHERE AuthorGUID = '{...}'
SELECT ... FROM Prices WHERE PublisherGUID = '{...}'
SELECT ... FROM Prices WHERE StoreGUID = '{...}'
станет:
SELECT ...
FROM Prices
WHERE BookGUID = '{...}'
OR AuthorGUID = '{...}'
OR PublisherGUID = '{...}'
OR StoreGUID = '{...}'
Я попытался использовать курсор внутри курсора, но конфликт FETCH_STATUS.
Вопрос 2
Может ли кто-нибудь подумать о каком-либо лучшем способе сделать это? ‡
Сноска:
° SQL Server 2000
‡ При условии ограничения использования уникальных идентификаторов в реляционной базе данных.
Ответы
Ответ 1
Вы можете отложить EXEC до завершения цикла курсора. Затем просто отследите имя таблицы внутри вашего цикла, и если оно то же самое, добавьте OR, иначе закончите SELECT и запустите новый.
DECLARE @lasttable varchar(255);
SET @lasttable='';
FETCH NEXT FROM abc INTO @tableName, @columnName;
WHILE (@@FETCH_STATUS = 0)
BEGIN
IF(@[email protected]) BEGIN
SET @szQuery = @szQuery + ' OR [' + @columnName + ']=''' + CAST(@searchValue AS varchar(50)) + '''';
END ELSE BEGIN
SET @lasttable = @tablename;
SET @szQuery = @szQuery +
'SELECT '''[email protected]+''' AS TheTable, '''[email protected]+''' AS TheColumn '+
'FROM '[email protected]+' '+
'WHERE '[email protected]+' = '''+CAST(@searchValue AS varchar(50))+''''
END
FETCH NEXT FROM abc INTO @tableName, @columnName;
END
PRINT @szQuery;
EXEC (@szQuery);
Вы также можете создать хранимую процедуру для создания VIEW, который выполняет UNION ALL для всех таблиц и полей uniqueidentifier. Что-то со схемой вроде этого:
CREATE VIEW all_uuids AS (
SELECT 'prices' AS tablename, 'BookGUID' as fieldname, ID as primarykey, BookGUID AS guid FROM prices
UNION ALL SELECT 'prices', 'AuthorGUID', ID, AuthorGUID FROM prices
UNION ALL SELECT 'othertable', 'otherfield', ID, otherfield FROM othertable
)
Затем вам просто нужно выполнить один оператор SELECT в этом многократном VIEW, чтобы получить все соответствующие идентификаторы GUID. Чтобы выполнить поиск внутри одной таблицы, используйте коррелированный подзапрос, например:
SELECT * FROM prices WHERE EXISTS (SELECT null FROM all_uuids u WHERE u.primarykey=prices.id AND [email protected] AND u.tablename='prices')
Это будет поиск по всем полям GUID в таблице цен. SQL Server достаточно умен, чтобы не просматривать другие таблицы, и использует индексы существующих таблиц.
Повторно используя один вид, вам нужно только перебирать информацию через информационную_схему, когда вы меняете схему, а не каждый запрос, а результаты представления могут быть объединены более легко, чем результаты хранимой процедуры.
Ответ
Окончательное решение для оригинальных плакатов на основе этого ответа:
CREATE PROCEDURE dbo.FindGUID @searchValue uniqueidentifier AS
/*
Search all tables in the database for a guid
Revision History
6/9/2009: Initally created
6/10/2009: Build or clause of multiple columns on one table
*/
--DECLARE @searchValue uniqueidentifier
--SET @searchValue = '{2A6814B9-8261-452D-A144-13264433864E}'
DECLARE abc CURSOR FOR
SELECT
c.TABLE_SCHEMA, c.TABLE_NAME, c.COLUMN_NAME
FROM INFORMATION_SCHEMA.Columns c
INNER JOIN INFORMATION_SCHEMA.Tables t
ON c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_TYPE = 'BASE TABLE'
WHERE DATA_TYPE = 'uniqueidentifier'
DECLARE @tableSchema varchar(200)
DECLARE @tableName varchar(200)
DECLARE @columnName varchar(200)
DECLARE @szQuery varchar(8000)
SET @szQuery = ''
DECLARE @lasttable varchar(255);
SET @lasttable='';
OPEN ABC
FETCH NEXT FROM abc INTO @tableSchema, @tableName, @columnName;
WHILE (@@FETCH_STATUS = 0)
BEGIN
IF(@[email protected])
BEGIN
SET @szQuery = @szQuery + ' OR [' + @columnName + ']=''' + CAST(@searchValue AS varchar(50)) + '''';
END
ELSE
BEGIN
SET @lasttable = @tablename;
IF @szQuery <> ''
BEGIN
PRINT @szQuery
EXEC (@szQuery);
END
SET @szQuery =
'SELECT '''[email protected]+'.'[email protected]+''' AS TheTable, '''[email protected]+''' AS TheColumn '+
'FROM '[email protected]+' '+
'WHERE '[email protected]+' = '''+CAST(@searchValue AS varchar(50))+''''
END
FETCH NEXT FROM abc INTO @tableSchema, @tableName, @columnName;
END
CLOSE abc
DEALLOCATE abc
IF @szQuery <> ''
BEGIN
PRINT @szQuery
EXEC (@szQuery);
END
GO
Ответ 2
Вы можете обернуть все в один SELECT и сразу же найти все таблицы:
ALTER PROCEDURE dbo.FindGUID @searchValue uniqueidentifier AS
BEGIN
SET NOCOUNT ON;
DECLARE @sql NVARCHAR(MAX);
WITH cte_all_tables(SQL) AS (
SELECT N'SELECT ''' + QUOTENAME(t.TABLE_SCHEMA) + '.' +QUOTENAME(t.TABLE_NAME) +
+ N''' FROM ' + QUOTENAME(t.TABLE_SCHEMA) + '.' +QUOTENAME(t.TABLE_NAME)
+ N' WHERE ' +
(
SELECT QUOTENAME(c.COLUMN_NAME) + N'= @searchValue OR '
FROM INFORMATION_SCHEMA.Columns c
WHERE c.TABLE_NAME = t.TABLE_NAME
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.DATA_TYPE = 'uniqueidentifier'
FOR XML PATH('')
) + N' 0=1 '
FROM INFORMATION_SCHEMA.Columns c
INNER JOIN INFORMATION_SCHEMA.Tables t
ON c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
AND t.TABLE_TYPE = 'BASE TABLE'
WHERE DATA_TYPE = 'uniqueidentifier')
SELECT @sql = (SELECT [SQL] + N' UNION ALL ' FROM cte_all_tables
FOR XML PATH('')) + N' SELECT NULL WHERE 0=1';
PRINT @SQL;
exec sp_executesql @sql, N'@searchValue uniqueidentifier', @searchValue;
END
Я использовал такие терминаторы, как "OR 0 = 1" и даже весь UNION, но это только потому, что я слишком ленив, чтобы обрезать конец из встроенных конкатенированных строк.
Ответ 3
Здесь решение для SQL 2000 с безвозмездным использованием курсоров:
declare @searchvalue uniqueidentifier
set @searchValue = '{2A6814B9-8261-452D-A144-13264433864E}'
if object_id('tempdb..#results') is not null drop table #results
create table #results (TableSchema sysname, TableName sysname)
declare @sql nvarchar(4000)
declare @cursor1 cursor
declare @tablename sysname
declare @tableschema sysname
declare @cursor2 cursor
declare @columnname sysname
declare @searchFields nvarchar(4000)
set @cursor1 = cursor for
select t.TABLE_SCHEMA, t.TABLE_NAME
from INFORMATION_SCHEMA.Tables t
where t.TABLE_TYPE = 'BASE TABLE'
and exists (
select * from INFORMATION_SCHEMA.Columns c
where c.TABLE_NAME = t.TABLE_NAME
and c.TABLE_SCHEMA = t.TABLE_SCHEMA
and c.DATA_TYPE = 'uniqueidentifier'
)
open @cursor1
while 1=1 begin
fetch next from @cursor1 into @tableschema, @tablename
if @@fetch_status <> 0 break
set @searchFields = ''
set @cursor2 = cursor for
select c.COLUMN_NAME
from INFORMATION_SCHEMA.Columns c
where c.TABLE_NAME = @tablename
and c.TABLE_SCHEMA = @tableschema
and c.DATA_TYPE = 'uniqueidentifier'
open @cursor2
while 1=1 begin
fetch next from @cursor2 into @columnname
if @@fetch_status <> 0 break
set @searchFields = @searchFields + ', ' + quotename(@columnname)
end
set @searchFields = substring(@searchFields,3,len(@searchFields))
set @sql = ' insert #results'
+ ' select '''[email protected]+''','''[email protected]+''''
+ ' from '+quotename(@tableschema)+'.'+quotename(@tablename)
+ ' where @searchValue in ('[email protected]+')'
print @sql
exec sp_executesql @sql, N'@searchValue uniqueidentifier', @searchValue
end
select * from #results
Здесь решение для SQL 2005, основанное на решении Remus, с временными таблицами для лучшего масштабирования:
DECLARE @searchValue uniqueidentifier
SET @searchValue = '{2A6814B9-8261-452D-A144-13264433864E}'
IF OBJECT_ID('tempdb..#results') IS NOT NULL DROP TABLE #results
CREATE TABLE #results (TableSchema SYSNAME, TableName SYSNAME);
DECLARE @sql NVARCHAR(MAX);
WITH cte_all_tables(SQL) AS (
SELECT
N' INSERT #results (TableSchema, TableName)'
+ N' SELECT ''' + t.TABLE_SCHEMA + ''', ''' + t.TABLE_NAME + N''''
+ N' FROM ' + QUOTENAME(t.TABLE_SCHEMA) + '.' +QUOTENAME(t.TABLE_NAME)
+ N' WHERE ' +
(
SELECT QUOTENAME(c.COLUMN_NAME) + N' = @searchValue OR '
FROM INFORMATION_SCHEMA.Columns c
WHERE c.TABLE_NAME = t.TABLE_NAME
AND c.TABLE_SCHEMA = t.TABLE_SCHEMA
AND c.DATA_TYPE = 'uniqueidentifier'
FOR XML PATH('')
) + N'0=1'
FROM INFORMATION_SCHEMA.Columns c
INNER JOIN INFORMATION_SCHEMA.Tables t
ON c.TABLE_NAME = t.TABLE_NAME
AND t.TABLE_SCHEMA = c.TABLE_SCHEMA
AND t.TABLE_TYPE = 'BASE TABLE'
WHERE DATA_TYPE = 'uniqueidentifier')
SELECT @sql = (SELECT [SQL]+nchar(10) FROM cte_all_tables FOR XML PATH(''));
PRINT @SQL;
exec sp_executesql @sql, N'@searchValue uniqueidentifier', @searchValue;
SELECT * FROM #results
Ответ 4
Похоже, вы в основном хотите объединить список столбцов в свой динамический sql. В mssql нет функции concat первого класса, вы можете написать свой собственный CLR udf, но я не люблю это решение. Проверьте этот вопрос для некоторых решений mssql concat.
Ответ 5
Похоже, что здесь немного развивается инженер... Вы сказали, что вам просто нужно "найти какие-то конкретные указания". Возможно, было бы проще экспортировать всю базу данных, а затем открыть ее в блокноте ++ и найти нужные вам адреса. Тогда вы увидите всю строку данных в то время и т.д.
Вы можете прочитать о Мастере публикации SQL Server, который экспортирует базу данных в текстовый файл
здесь.