Это беспорядок, но он работает:
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 лет).