Question

je suis tombé sur la nécessité de nettoyer des données, et je dois trouver des GUIDs particulières (par exemple uniqueidentifiers ) en ° SQL Server.

Je suis venu avec une procédure stockée qui fait un SELECT de chaque colonne uniqueidentifier dans toutes les tables dans la base de données actuelle et renvoie un résultat si l'guid se trouve.

Il utilise les vues INFORMATION_SCHEMA pour trouver tous les uniqueidentifier colonnes dans toutes les tables de base (par opposition à vue). Pour chaque colonne, il émet une sélection et renvoie le nom de la table et la colonne où il a été trouvé.

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 '''+@tableName+''' AS TheTable, '''+@columnName+''' AS TheColumn '+
        'FROM '+@tableName+' '+
        'WHERE '+@columnName+' = '''+CAST(@searchValue AS varchar(50))+''''

    PRINT 'Searching '+@tableName+'.'+@columnName+'..'
    PRINT @szQuery
    EXEC (@szQuery)

    FETCH NEXT FROM abc INTO @tableName, @columnName
END

CLOSE abc
DEALLOCATE abc  

Ma question est:

Question 1 Quelqu'un pourrait-il trouver un moyen de le changer pour effectuer une recherche de plusieurs colonnes de UNIQUEIDENTIFIER dans la même table que, plutôt que ORs requêtes distinctes

i.e..

SELECT ... FROM Prices WHERE BookGUID = '{...}'
SELECT ... FROM Prices WHERE AuthorGUID = '{...}'
SELECT ... FROM Prices WHERE PublisherGUID = '{...}'
SELECT ... FROM Prices WHERE StoreGUID = '{...}'

deviendrait:

SELECT ... 
FROM Prices 
WHERE BookGUID = '{...}'
OR AuthorGUID = '{...}'
OR PublisherGUID = '{...}'
OR StoreGUID = '{...}'

i essayé d'utiliser un curseur à l'intérieur d'un curseur, mais le FETCH_STATUS conflit d '.

Question 2 Quelqu'un peut-il penser à une meilleure façon de le faire? ‡


Notes:

° SQL Server 2000

‡ Sous réserve de la contrainte d'utiliser uniqueidentifiers dans une base de données relationnelle.

Était-ce utile?

La solution

Vous pouvez reporter l'EXEC jusqu'à ce que votre boucle de curseur est fait. Ensuite, suivre simplement le nom de table dans votre boucle et si elle est la même chose, ajouter un OU, sinon votre fin SELECT et commencer une nouvelle.

DECLARE @lasttable varchar(255);
SET @lasttable='';
FETCH NEXT FROM abc INTO @tableName, @columnName;
WHILE (@@FETCH_STATUS = 0)
BEGIN
   IF(@lasttable=@tablename) BEGIN
       SET @szQuery = @szQuery + ' OR [' + @columnName + ']=''' + CAST(@searchValue AS varchar(50)) + '''';
   END ELSE BEGIN
       SET @lasttable = @tablename;
       SET @szQuery = @szQuery + 
         'SELECT '''+@tableName+''' AS TheTable, '''+@columnName+''' AS TheColumn '+
         'FROM '+@tableName+' '+
         'WHERE '+@columnName+' = '''+CAST(@searchValue AS varchar(50))+''''
   END
   FETCH NEXT FROM abc INTO @tableName, @columnName;
END
PRINT @szQuery;
EXEC (@szQuery);

Vous pouvez également créer la procédure stockée pour construire une vision qui fait un UNION ALL de toutes les tables et les champs uniqueidentifier. Quelque chose avec un schéma comme celui-ci:

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
    )

, il vous suffit ensuite d'effectuer une seule instruction SELECT sur ce point de vue réutilisable pour obtenir tous les GUIDs correspondants. Pour effectuer une recherche dans une seule table, utilisez un sous-requête corrélée, par exemple:.

SELECT * FROM prices WHERE EXISTS (SELECT null FROM all_uuids u WHERE u.primarykey=prices.id AND u.guid=@searchfor AND u.tablename='prices')

Cela recherche dans tous les champs GUID dans le tableau des prix. SQL Server est assez intelligent pour ne pas aller regarder à travers d'autres tables, et il utilise les index de vos tables existantes.

En réutilisant une seule vue, il suffit d'aller en boucle par information_schema lorsque vous changez votre schéma, pas avec toutes les requêtes et les résultats d'une vue peut être rejoint plus facilement que les résultats d'une procédure stockée.


Réponse

Affiches originales de solution finale, basée sur cette réponse:

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(@lasttable=@tablename) 
   BEGIN
      SET @szQuery = @szQuery + ' OR [' + @columnName + ']=''' + CAST(@searchValue AS varchar(50)) + '''';
   END 
   ELSE 
   BEGIN
       SET @lasttable = @tablename;

       IF @szQuery <> '' 
       BEGIN
          PRINT @szQuery
          EXEC ('IF EXISTS (' + @szQuery + ') BEGIN ' + @szQuery + ' END');
       END

       SET @szQuery = 
         'SELECT '''+@tableSchema+'.'+@tableName+''' AS TheTable, '''+@columnName+''' AS TheColumn '+
         'FROM '+@tableName+' '+
         'WHERE '+@columnName+' = '''+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 ('IF EXISTS (' + @szQuery + ') BEGIN ' + @szQuery + ' END');
END
GO

Autres conseils

Vous pouvez envelopper tout en un seul SELECT et rechercher toutes les tables à la fois:

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

je terminateurs pierre tombale comme « OR 0 = 1 » et même une union entière, mais qui est juste parce que je suis trop paresseux pour couper la terminaison des chaînes concaténées construites.

Voici une solution pour SQL 2000, avec l'utilisation gratuite des curseurs:

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 '''+@tableschema+''','''+@tablename+''''
           + ' from '+quotename(@tableschema)+'.'+quotename(@tablename)
           + ' where @searchValue in ('+@searchFields+')'

  print @sql
  exec sp_executesql @sql, N'@searchValue uniqueidentifier', @searchValue
end

select * from #results

Voici une solution pour SQL 2005, basée sur la solution de Remus, avec des tables temporaires pour une meilleure mise à l'échelle:

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

On dirait que vous voulez essentiellement à concaténer la liste des colonnes dans votre sql dynamique. Il n'y a pas une première fonction classe concat dans MSSQL, vous pouvez écrire votre propre CLR udf pour le faire, mais je n'aime pas cette solution. Consultez cette question pour certaines solutions concat MSSQL.

On dirait un peu plus d'ingénierie qui se passe ici .... Vous avez dit que vous aviez juste besoin de « trouver des particuliers GUIDs ». Il pourrait être plus facile d'exporter la base de données entière, puis l'ouvrir dans le bloc-notes ++ et recherchez le GUID que vous vouliez. Ensuite, vous allez voir toute la ligne de données à ce moment-là, etc.

Vous pouvez lire sur l'Assistant de publication SQL Server qui exporte la base de données vers un fichier texte ici.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top