如何便宜地确定列是否仅包含NULL记录?

时间:2013-05-31 07:29:45

标签: sql-server sql-server-2008

我有一个包含500列和100M行的大表。基于一个小样本,我相信只有大约50个列包含任何值,而另一个450只包含NULL值。我想列出不包含数据的列。

在我目前的硬件上,查询每列(select count(1) from tab where col_n is not null

大约需要24小时

是否有一种较便宜的方法来确定列是否完全为空/ NULL?

8 个答案:

答案 0 :(得分:14)

这个怎么样:

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

如果结果包含值,则填充列。它应该需要一次表扫描。

答案 1 :(得分:1)

试试这个 -

<强> 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)

查询1:

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

答案 2 :(得分:0)

您能否检查列索引是否有助于您达到某些性能提升

CREATE UNIQUE NONCLUSTERED INDEX IndexName ON dbo.TableName(ColumnName)
WHERE ColumnName IS NOT NULL;
GO

答案 3 :(得分:0)

SQL server query to get the list of columns in a table along with Data types, NOT NULL, and PRIMARY KEY constraints

在上述问题的最佳答案中运行SQL并生成如下所示的新查询。

Select ISNULL(column1,1), ISNULL(column2,1), ISNULL(column3,1) from table

答案 4 :(得分:0)

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

答案 5 :(得分:0)

您不需要“计算”所有100M记录。当您点击具有非空值的列时,只需使用TOP 1退出查询,在提供相同信息时将节省大量时间。

答案 6 :(得分:0)

如果大多数记录都不为null,那么你可以将一些建议的方法(例如只检查可空字段)混合使用:

if exists (select * from table where field is not null)

这应该加快搜索速度,因为只要满足条件,存在就会停止搜索,在这个例子中,单个非空记录足以决定字段的状态。 如果该字段有一个索引,那么这几乎应该是即时的。

通常不需要在此查询中添加前1,因为查询优化器知道您不需要检索所有匹配的记录。

答案 7 :(得分:0)

您可以使用此存储过程来获取技巧您需要提供要查询的表名,请注意如果您将传递给过程@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