Один SQL запрос для поиска значений NULL во всех столбцах базы данных - PullRequest
0 голосов
/ 12 февраля 2020

Я хотел бы определить количество пустых значений в каждом столбце во всех таблицах. У меня есть база данных, которая состоит из около 250 таблиц. Большинство из них используются. Проблема состоит в том, что почти все таблицы содержат ненужные столбцы, которые создали для некоторого кратковременного использования. Теперь мы хотим идентифицировать столбцы с нулевыми значениями для всех таблиц. Так как количество таблиц велико, а время меньше. Я хотел бы знать самый простой способ определить количество пустых записей для каждой таблицы в столбцах. .

Я пробовал этот запрос, который я получил от inte rnet. Но в этом я должен дать каждое имя таблицы вручную.

DECLARE @cols1 NVARCHAR(MAX);
DECLARE @sql NVARCHAR(MAX);

SELECT @cols1 = STUFF((
    SELECT ', COUNT(CASE WHEN ISNULL(CONVERT(NVARCHAR(MAX), [' + t1.NAME + ']),'''' ) = '''' THEN 1 END) AS ' + t1.name
    FROM sys.columns AS t1
    WHERE t1.object_id = OBJECT_ID('Area')
    -- ORDER BY ', COUNT([' + t1.name + ']) AS ' + t1.name
    FOR XML PATH('')
), 1, 2, '');

SET @sql = '
SELECT ' + @cols1 + '
FROM Area
'
EXEC(@sql)

Пожалуйста, помогите мне получить улучшенный запрос получить Результат .

Спасибо

Ответы [ 2 ]

3 голосов
/ 12 февраля 2020

Это беспорядок, но он работает:

DECLARE @SQL nvarchar(MAX),
        @CRLF nchar(2) = NCHAR(13) + NCHAR(10);

CREATE TABLE #NullCounts (SchemaName sysname,
                          TableName sysname,
                          ColumnName sysname,
                          NULLCount bigint);

DECLARE @Delimiter nchar(3) = ',' +@CRLF;

SET @SQL = STUFF((SELECT @CRLF + @CRLF +
                         N'WITH Counts AS(' + @CRLF +
                         N'    SELECT N' + QUOTENAME(s.[name],'''') +N' AS SchemaName,' + @CRLF +
                         N'           N' + QUOTENAME(t.[name],'''') +N' AS TableName,' + @CRLF +
                         STRING_AGG(N'           COUNT_BIG(CASE WHEN N' + QUOTENAME(c.[name],'''') + N' IS NULL THEN 1 END) AS ' + QUOTENAME(c.[name]),@Delimiter) WITHIN GROUP(ORDER BY c.column_id) + @CRLF +
                         N'    FROM ' + QUOTENAME(s.[name]) + N'.' + QUOTENAME(t.[name]) + N' T)' + @CRLF +
                         N'INSERT INTO #NullCounts(SchemaName, TableName, ColumnName, NULLCount)' + @CRLF +
                         N'SELECT SchemaName,' + @CRLF +
                         N'       TableName,' + @CRLF +
                         N'       V.ColumnName,' + @CRLF +
                         N'       V.NULLCount' + @CRLF +
                         N'FROM Counts C' + @CRLF +
                         N'     CROSS APPLY (VALUES' +
                         STUFF(STRING_AGG(N'                        (N' + QUOTENAME(c.[name], '''') + N', C.' + QUOTENAME(c.[name]) + N')',@Delimiter) WITHIN GROUP (ORDER BY c.column_id),1,24,N'') + N')V(ColumnName,NULLCount);'
                  FROM sys.schemas s
                       JOIN sys.tables t ON s.schema_id = t.schema_id
                       JOIN sys.columns c ON t.object_id = c.object_id
                  GROUP BY s.[name], t.[name]
                  FOR XML PATH(N''),TYPE).value('.','nvarchar(MAX)'),1,4,N'');

--PRINT @SQL; --This is gunna be way longer than 4,000 characters, so you'll want SELECT

EXEC sys.sp_executesql @SQL;

GO

SELECT *
FROM #NullCounts
ORDER BY SchemaName,
         TableName,
         ColumnName;

GO

DROP TABLE #NullCounts;

Да, я смешиваю STRING_AGG и FOR XML PATH, да, это бельмо на глазу, но напечатанное (выбранное) SQL производит очень хорошие заявления. См. Ниже:

WITH Counts AS(
    SELECT N'dbo' AS SchemaName,
           N'PerformanceTest' AS TableName,
           COUNT_BIG(CASE WHEN N'TestID' IS NULL THEN 1 END) AS [TestID],
           COUNT_BIG(CASE WHEN N'TestTarget' IS NULL THEN 1 END) AS [TestTarget],
           COUNT_BIG(CASE WHEN N'TestName' IS NULL THEN 1 END) AS [TestName],
           COUNT_BIG(CASE WHEN N'TimeStart' IS NULL THEN 1 END) AS [TimeStart],
           COUNT_BIG(CASE WHEN N'TimeEnd' IS NULL THEN 1 END) AS [TimeEnd],
           COUNT_BIG(CASE WHEN N'TimeTaken_ms' IS NULL THEN 1 END) AS [TimeTaken_ms],
           COUNT_BIG(CASE WHEN N'TotalRows' IS NULL THEN 1 END) AS [TotalRows],
           COUNT_BIG(CASE WHEN N'RowSets' IS NULL THEN 1 END) AS [RowSets],
           COUNT_BIG(CASE WHEN N'AvgRowsPerSet' IS NULL THEN 1 END) AS [AvgRowsPerSet]
    FROM [dbo].[PerformanceTest] T)
INSERT INTO #NullCounts(SchemaName, TableName, ColumnName, NULLCount)
SELECT SchemaName,
       TableName,
       V.ColumnName,
       V.NULLCount
FROM Counts C
     CROSS APPLY (VALUES(N'TestID', C.[TestID]),
                        (N'TestTarget', C.[TestTarget]),
                        (N'TestName', C.[TestName]),
                        (N'TimeStart', C.[TimeStart]),
                        (N'TimeEnd', C.[TimeEnd]),
                        (N'TimeTaken_ms', C.[TimeTaken_ms]),
                        (N'TotalRows', C.[TotalRows]),
                        (N'RowSets', C.[RowSets]),
                        (N'AvgRowsPerSet', C.[AvgRowsPerSet]))V(ColumnName,NULLCount);

WITH Counts AS(
    SELECT N'dbo' AS SchemaName,
           N'someTable' AS TableName,
           COUNT_BIG(CASE WHEN N'id' IS NULL THEN 1 END) AS [id],
           COUNT_BIG(CASE WHEN N'SomeCol' IS NULL THEN 1 END) AS [SomeCol]
    FROM [dbo].[someTable] T)
INSERT INTO #NullCounts(SchemaName, TableName, ColumnName, NULLCount)
SELECT SchemaName,
       TableName,
       V.ColumnName,
       V.NULLCount
FROM Counts C
     CROSS APPLY (VALUES(N'id', C.[id]),
                        (N'SomeCol', C.[SomeCol]))V(ColumnName,NULLCount);

И да, я действительно потратил последние 45 минут на написание всего этого ...

Честно говоря, это не начальный уровень, и если вы его не понимаете вы не должны его использовать; но я также очень сомневаюсь, что вы найдете другое решение начального уровня, столь же производительное, как и это. A CURSOR, например, хотя, вероятно, легче понять, будет очень медленно делать это .

Предупреждение: Если в вашей базе данных есть какие-либо устаревшие типы данных (т.е. text) это не удастся. Если это так, вам нужно будет исключить их из запроса в WHERE. Однако я предлагаю вам исправить ваши типы данных (например, text устарел в течение 15 лет).

0 голосов
/ 12 февраля 2020

Для табличных результатов:

declare @sql  nvarchar(max) = 
(
select 'union all select (select object_schema_name('+ cast(tableobjectid as varchar(20))+') +''.''+ object_name('+ cast(tableobjectid as varchar(20))+') as "table/@name", count(*) as "table/@rowcount", ' + cols_concat + ' from ' + tablename + ' for xml path(''''), type)' as 'text()'
from
(
select 
    t.object_id as tableobjectid,
    quotename(schema_name(t.schema_id)) + '.' + quotename(t.name) as tablename,
    stuff( (select ', col_name(' + cast(c.object_id as varchar(20)) + ',' + cast(c.column_id as varchar(20)) + ') as "table/col/@name", count(*)-count('+case when type_name(c.system_type_id) in ('text', 'ntext', 'image') then ' case when ' + quotename(c.name) + ' is not null then 1 end' else quotename(c.name) end + ') as "table/col", null as "table"'
            from sys.columns as c where c.object_id = t.object_id for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '') as cols_concat
from sys.tables as t
where t.is_ms_shipped = 0
) as tbl
for xml path(''), type).value('.', 'nvarchar(max)')
;

select @sql = 'select @xml = (select * from (select cast(null as xml) as "*"  '+ @sql + ') as u for xml path('''') )';
declare @x xml;

set transaction isolation level read uncommitted;
exec sp_executesql @sql, N'@xml xml output', @xml = @x output;
set transaction isolation level read committed;

--shred 
select 
    t.col.value('@name[1]', 'varchar(200)') as tablename,
    t.col.value('@rowcount[1]', 'int') as tablerowcount,    
    r.col.value('@name[1]', 'varchar(200)') as columnname,  
    r.col.value('.[1]', 'int') as null_values
from @x.nodes('table') as t(col)
cross apply t.col.nodes('col') as r(col);

Для нескольких наборов результатов (по одному на таблицу):

  declare @sql  nvarchar(max) = 
    (
    select 'select object_schema_name('+ cast(tableobjectid as varchar(20))+') +''.''+ object_name('+ cast(tableobjectid as varchar(20))+') as tablename, ' + cols_concat + ' from ' + tablename + ';' as 'text()'
    from
    (
    select 
        t.object_id as tableobjectid,
        quotename(schema_name(t.schema_id)) + '.' + quotename(t.name) as tablename,
        stuff( (select ', count(*)-count('+case when type_name(c.system_type_id) in ('text', 'ntext', 'image') then ' case when ' + quotename(c.name) + ' is not null then 1 end' else quotename(c.name) end + ') as "NULL_' + replace(quotename(name), '"', '""')+'"'  from sys.columns as c where c.object_id = t.object_id for xml path(''), type).value('.', 'nvarchar(max)'), 1, 1, '') as cols_concat
    from sys.tables as t
    where t.is_ms_shipped = 0
    ) as tbl
    for xml path(''), type).value('.', 'nvarchar(max)');

    set transaction isolation level read uncommitted;
    exec(@sql);    
    set transaction isolation level read committed;
...