为每个数据库中每个表中的每个列选择一个非空值

时间:2018-08-09 11:01:30

标签: sql sql-server tsql sql-server-2016 privacy

也许有更好的方法可以做到这一点。但是我正在尝试查找可能包含个人信息的列。

问题在于表的命名不正确(非英语,缩写)。因此,我正在运行此动态脚本,它将返回所有数据库及其列中的所有表。

USE master;
DECLARE @SQL varchar(max)
SET @SQL=';WITH cteCols (dbName, colName) AS (SELECT NULL, NULL '
SELECT @SQL=@SQL+'UNION
SELECT
'''+d.name COLLATE Czech_CI_AS +'.''+sh.name COLLATE Czech_CI_AS +''.''+o.name COLLATE Czech_CI_AS  ''dbSchTab''
, c.name COLLATE Czech_CI_AS ''colName''
FROM ['+d.name+'].sys.columns   c 
    JOIN ['+d.name+'].sys.objects o ON c.object_id=o.object_id 
    JOIN ['+d.name+'].sys.schemas sh ON o.schema_id=sh.schema_id
WHERE o.[type] = ''U'' COLLATE Czech_CI_AS'
FROM sys.databases d

SET @SQL = @SQL + ')
SELECT
    *
FROM cteCols cs
ORDER BY 1;'

EXEC (@SQL);

结果:

+---------------------+------------+
| DatabaseSchemaTable | ColumnName |
+---------------------+------------+
| dev1.dbo.Users      | Col1       |
| dev1.dbo.Users      | Col2       |
| dev1.dbo.Users      | Col3       |
| dev1.dbo.Users      | Col4       |
+---------------------+------------+

但是由于列命名不佳,所以我无法确定这些列中包含什么数据。我想从每一列中选择一个TOP(1)非NULL值,但是我很挣扎。

必填结果:

+---------------------+------------+--------------+
| DatabaseSchemaTable | ColumnName | ColumnValue  |
+---------------------+------------+--------------+
| dev1.dbo.Users      | Col1       | 20           |
| dev1.dbo.Users      | Col2       | 2018-02-06   |
| dev1.dbo.Users      | Col3       | 202-555-0133 |
| dev1.dbo.Users      | Col4       | John Doe     |
+---------------------+------------+--------------+

我的想法:

  • 我需要对每个表进行转置(可能不是 PIVOT的工作)
  • 我可以动态地与表连接,仅显示当前列。但是我不能在相关子查询中使用动态列。

有什么想法吗?

2 个答案:

答案 0 :(得分:1)

您可以尝试以下方法:

--In this table we write our findings
CREATE TABLE ##TargetTable(ID INT IDENTITY, TableName VARCHAR(500), FirstRowXML XML);

--the undocumented sp "MSforeachtable" allows to create a statement where the 
--question mark is a place holder for the actual table
--(SELECT TOP 1 * FROM ? FOR XML PATH('row')) will create one single XML with all first row's values
EXEC sp_MSforeachtable 'INSERT INTO ##TargetTable(TableName,FirstRowXML) SELECT ''?'', (SELECT TOP 1 * FROM ? FOR XML PATH(''row''))';

--Now it is easy to get what you want
SELECT ID 
      ,TableName
      ,col.value('local-name(.)','nvarchar(max)') AS colname
      ,col.value('text()[1]','nvarchar(max)') AS colval
FROM ##TargetTable
CROSS APPLY FirstRowXML.nodes('/row/*') A(col);

GO
DROP TABLE ##TargetTable

只需使用SELECT TOP X即可获得不止一行...

更新

以下内容将创建一个表,其中包含所有数据库的所有表的所有列,并每行获取一个值。

CREATE TABLE ##TargetTable(ID INT IDENTITY
                           ,TABLE_CATALOG VARCHAR(300),TABLE_SCHEMA VARCHAR(300),TABLE_NAME VARCHAR(300),COLUMN_NAME VARCHAR(300)
                           ,DATA_TYPE VARCHAR(300),CHARACTER_MAXIMUM_LENGTH INT, IS_NULLABLE VARCHAR(10),Command VARCHAR(MAX),OneValue NVARCHAR(MAX));
EXEC sp_MSforeachdb
'USE ?; 
INSERT INTO ##TargetTable(TABLE_CATALOG,TABLE_SCHEMA,TABLE_NAME,COLUMN_NAME,DATA_TYPE,CHARACTER_MAXIMUM_LENGTH,IS_NULLABLE,Command)
SELECT ''?''
      ,c.TABLE_SCHEMA
      ,c.TABLE_NAME
      ,c.COLUMN_NAME
      ,c.DATA_TYPE
      ,c.CHARACTER_MAXIMUM_LENGTH
      ,c.IS_NULLABLE
      , CASE WHEN c.IS_NULLABLE=''YES''
             THEN ''SELECT CAST(MAX('' + QUOTENAME(c.COLUMN_NAME) + '') AS NVARCHAR(MAX))'' 
             ELSE ''SELECT TOP 1 CAST('' + QUOTENAME(c.COLUMN_NAME) + '' AS NVARCHAR(MAX))''
        END
      + '' FROM '' + QUOTENAME(''?'') + ''.'' + QUOTENAME(c.TABLE_SCHEMA) + ''.'' + QUOTENAME(c.TABLE_NAME) 
FROM INFORMATION_SCHEMA.COLUMNS c 
INNER JOIN INFORMATION_SCHEMA.TABLES t ON c.TABLE_CATALOG=t.TABLE_CATALOG AND c.TABLE_SCHEMA=t.TABLE_SCHEMA AND c.TABLE_NAME=T.TABLE_NAME AND t.TABLE_TYPE=''BASE TABLE''
WHERE c.DATA_TYPE NOT IN(''BINARY'',''VARBINARY'',''IMAGE'',''NTEXT'')';

DECLARE @ID INT,@Command VARCHAR(MAX);
DECLARE cur CURSOR FOR SELECT ID,Command FROM ##TargetTable
OPEN cur;
FETCH NEXT FROM cur INTO @ID,@Command;
WHILE @@FETCH_STATUS=0
BEGIN
    SET @Command = 'UPDATE ##TargetTable SET OneValue=(' + @Command + ') WHERE ID=' + CAST(@ID AS VARCHAR(100))
    PRINT @command;
    EXEC(@Command);
    FETCH NEXT FROM cur INTO @ID,@Command;
END
CLOSE cur;
DEALLOCATE cur;
GO
SELECT * FROM ##TargetTable;
GO
DROP TABLE ##TargetTable;

答案 1 :(得分:1)

我将创建一个临时表(例如## cols),然后使用该临时表遍历该表,并在表本身上运行更新查询。请注意,我们的字段名称中有很多空格和其他可能麻烦的字符。因此,我用字段/表/模式/数据库名称周围的一些QUOTENAME更新了您的cte。

order_value

虽然不漂亮,但可以满足您的需求。