我有一个有 500 列和 100M 行的大表。基于一个小样本,我相信只有大约 50 列包含任何值,而其他 450 列仅包含 NULL 值。我想列出不包含数据的列。
在我目前的硬件上,查询每一列大约需要 24 小时 ( select count(1) from tab where col_n is not null
)
有没有更便宜的方法来确定一列完全为空/NULL?
我有一个有 500 列和 100M 行的大表。基于一个小样本,我相信只有大约 50 列包含任何值,而其他 450 列仅包含 NULL 值。我想列出不包含数据的列。
在我目前的硬件上,查询每一列大约需要 24 小时 ( select count(1) from tab where col_n is not null
)
有没有更便宜的方法来确定一列完全为空/NULL?
那这个呢:
SELECT
SUM(CASE WHEN column_1 IS NOT NULL THEN 1 ELSE 0) column_1_count,
SUM(CASE WHEN column_2 IS NOT NULL THEN 1 ELSE 0) column_2_count,
...
FROM table_name
?
如果您使用 INFORMATION_SCHEMA.COLUMNS 表,您可以轻松创建此查询。
编辑:
另一个想法:
SELECT MAX(column_1), MAX(column_2),..... FROM table_name
如果结果包含值,则填充列。它应该需要一次表扫描。
试试这个——
DDL:
IF OBJECT_ID ('dbo.test2') IS NOT NULL
DROP TABLE dbo.test2
CREATE TABLE dbo.test2
(
ID BIGINT IDENTITY(1,1) PRIMARY KEY
, Name VARCHAR(10) NOT NULL
, IsCitizen BIT NULL
, Age INT NULL
)
INSERT INTO dbo.test2 (Name, IsCitizen, Age)
VALUES
('1', 1, NULL),
('2', 0, NULL),
('3', NULL, NULL)
查询一:
DECLARE
@TableName SYSNAME
, @ObjectID INT
, @SQL NVARCHAR(MAX)
SELECT
@TableName = 'dbo.test2'
, @ObjectID = OBJECT_ID(@TableName)
SELECT @SQL = 'SELECT' + CHAR(13) + STUFF((
SELECT CHAR(13) + ', [' + c.name + '] = ' +
CASE WHEN c.is_nullable = 0
THEN '0'
ELSE 'CASE WHEN ' + totalrows +
' = SUM(CASE WHEN [' + c.name + '] IS NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END'
END
FROM sys.columns c WITH (NOWAIT)
CROSS JOIN (
SELECT totalrows = CAST(MIN(p.[rows]) AS VARCHAR(50))
FROM sys.partitions p
WHERE p.[object_id] = @ObjectID
AND p.index_id IN (0, 1)
) r
WHERE c.[object_id] = @ObjectID
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ') + CHAR(13) + 'FROM ' + @TableName
PRINT @SQL
EXEC sys.sp_executesql @SQL
输出 1:
SELECT
[ID] = 0
, [Name] = 0
, [IsCitizen] = CASE WHEN 3 = SUM(CASE WHEN [IsCitizen] IS NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END
, [Age] = CASE WHEN 3 = SUM(CASE WHEN [Age] IS NULL THEN 1 ELSE 0 END) THEN 1 ELSE 0 END
FROM dbo.test2
查询 2:
DECLARE
@TableName SYSNAME
, @SQL NVARCHAR(MAX)
SELECT @TableName = 'dbo.test2'
SELECT @SQL = 'SELECT' + CHAR(13) + STUFF((
SELECT CHAR(13) + ', [' + c.name + '] = ' +
CASE WHEN c.is_nullable = 0
THEN '0'
ELSE 'CASE WHEN '+
'MAX(CAST([' + c.name + '] AS CHAR(1))) IS NULL THEN 1 ELSE 0 END'
END
FROM sys.columns c WITH (NOWAIT)
WHERE c.[object_id] = OBJECT_ID(@TableName)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 2, ' ') + CHAR(13) + 'FROM ' + @TableName
PRINT @SQL
EXEC sys.sp_executesql @SQL
输出 2:
SELECT
[ID] = 0
, [Name] = 0
, [IsCitizen] = CASE WHEN MAX(CAST([IsCitizen] AS CHAR(1))) IS NULL THEN 1 ELSE 0 END
, [Age] = CASE WHEN MAX(CAST([Age] AS CHAR(1))) IS NULL THEN 1 ELSE 0 END
FROM dbo.test2
结果:
ID Name IsCitizen Age
----------- ----------- ----------- -----------
0 0 0 1
SQL 服务器查询以获取表中的列列表以及数据类型、NOT NULL 和 PRIMARY KEY 约束
在上述问题的最佳答案中运行 SQL 并生成如下所示的新查询。
Select ISNULL(column1,1), ISNULL(column2,1), ISNULL(column3,1) from table
500列?!
好的,您问题的正确答案是:规范化您的表格。
以下是暂时发生的事情:
您在该列上没有索引,因此 SQL Server 必须对您的庞大表进行全面扫描。
SQL Server 肯定会完全读取每一行(即使您只对其中一个感兴趣,这也意味着每一列)。
而且由于您的行很可能超过 8kb ... http://msdn.microsoft.com/en-us/library/ms186981%28v=sql.105%29.aspx
说真的,规范化你的表格,如果需要的话水平拆分它(将“主题分组”列放在单独的表格中,只在需要时阅读它们)。
编辑:您可以像这样重写您的查询
select count(col_n) from tab
如果您想一次获取所有列(更好):
SELECT
COUNT(column_1) column_1_count,
COUNT(column_2) column_2_count,
...
FROM table_name
你能检查一下colums idexing是否能帮助你提高性能吗
CREATE UNIQUE NONCLUSTERED INDEX IndexName ON dbo.TableName(ColumnName)
WHERE ColumnName IS NOT NULL;
GO
您不需要“计算”所有 100M 记录。当您在点击具有非空值的列后立即使用 TOP 1 退出查询时,将在提供相同信息的同时节省大量时间。
如果大多数记录不为空,则您可以将建议的一些方法(例如仅检查可空字段)与以下内容混合使用:
if exists (select * from table where field is not null)
这应该会加快搜索速度,因为一旦满足条件,exists 就会停止搜索,在此示例中,一条非空记录足以决定字段的状态。如果该字段有一个索引,这应该几乎是即时的。
通常不需要将 top 1 添加到此查询,因为查询优化器知道您不需要检索所有匹配的记录。
您可以使用此存储过程来解决问题 您需要提供要查询的表名 请注意,如果您将 @exec 参数 = 1 传递给过程,它将执行选择查询
SET ANSI_NULLS ON
GO
SET QUOTED_IDENTIFIER ON
GO
CREATE PROCEDURE [dbo].[SP_SELECT_NON_NULL_COLUMNS] ( @tablename varchar (100)=null, @exec int =0)
AS BEGIN
SET NOCOUNT ON
IF @tablename IS NULL
RAISERROR('CANT EXECUTE THE PROC, TABLE NAME IS MISSING',16 ,1)
ELSE
BEGIN
IF OBJECT_ID('tempdb..#table') IS NOT NULL DROP TABLE #table
DECLARE @i VARCHAR (max)=''
DECLARE @sentence VARCHAR (max)=''
DECLARE @SELECT VARCHAR (max)
DECLARE @LocalTableName VARCHAR(50) = '['+@tablename+']'
CREATE TABLE #table (ColumnName VARCHAR (max))
SELECT @i+=
' IF EXISTS ( SELECT TOP 1 '+column_name+' FROM ' +@LocalTableName+' WHERE ' +column_name+
' '+'IS NOT NULL) INSERT INTO #table VALUES ('''+column_name+''');'
FROM INFORMATION_SCHEMA.COLUMNS WHERE table_name=@tablename
INSERT INTO #table
EXEC (@i)
SELECT @sentence = @sentence+' '+columnname+' ,' FROM #table
DROP TABLE #table
IF @exec=0
BEGIN
SELECT 'SELECT '+ LTRIM (left (@sentence,NULLIF(LEN (@sentence)-1,-1)))+
+' FROM ' +@LocalTableName
END
ELSE
BEGIN
SELECT @SELECT= 'SELECT '+ LTRIM (left (@sentence,NULLIF(LEN (@sentence)-1,-1)))+
+' FROM '+@LocalTableName
EXEC (@SELECT)
END
END
END
像这样使用它:
EXEC [dbo].[SP_SELECT_NON_NULL_COLUMNS] 'YourTableName' , 1