9

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

在我目前的硬件上,查询每一列大约需要 24 小时 ( select count(1) from tab where col_n is not null)

有没有更便宜的方法来确定一列完全为空/NULL?

4

8 回答 8

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

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

于 2013-05-31T07:57:13.250 回答
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)

查询一:

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
于 2013-05-31T07:35:02.400 回答
0

SQL 服务器查询以获取表中的列列表以及数据类型、NOT NULL 和 PRIMARY KEY 约束

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

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

于 2013-05-31T07:44:07.950 回答
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
于 2013-05-31T08:30:50.157 回答
0

你能检查一下colums idexing是否能帮助你提高性能吗

CREATE UNIQUE NONCLUSTERED INDEX IndexName ON dbo.TableName(ColumnName)
WHERE ColumnName IS NOT NULL;
GO
于 2013-05-31T07:40:15.700 回答
0

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

于 2013-05-31T08:55:42.043 回答
0

如果大多数记录不为空,则您可以将建议的一些方法(例如仅检查可空字段)与以下内容混合使用:

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

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

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

于 2015-02-11T10:47:20.530 回答
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
于 2015-05-15T17:58:46.550 回答