2013-05-31 83 views
6

我有一個500列和100M行的大桌子。基於一個小樣本,我相信只有大約50個列包含任何值,另外450個只包含NULL值。我想列出不包含數據的列。如何廉價確定列是否僅包含NULL記錄?

在我當前的硬件,這將需要大約24小時查詢每列(select count(1) from tab where col_n is not null

有沒有更便宜的方法來確定一個列是完全空/空?

+0

將列以某種方式進行更新?是否允許更改? –

+0

列不需要更新。它們不能作爲解決方案的一部分進行修改。 –

+0

主要關注的是性能,不寫動態sql來生成查詢。 –

回答

13

這個怎麼樣:

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表名

如果結果中包含值,列填充。它應該需要一個表掃描。

+0

我最關心的是性能,而不是生成查詢。 –

+0

+1。完全同意這個答案。 – Devart

+1

@ user2161466請注意,這個答案不僅僅是查詢創建,它允許您一次處理所有列,而不是逐個處理它們。這可能意味着巨大的業績。 –

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 
+0

在單列上,這給出了相同的性能。你期望在總體上有更好的表現嗎? select dbo.tab其中col不爲空 - 1:07 SELECT col = COUNT(CASE WHEN ISNULL(CAST(col AS NVARCHAR(MAX)),'')=''THEN 1 END) FROM dbo。標籤 - 1:08 –

+0

我(海報)沒有投票。 –

+1

@Devart:我不是downvoter,但爲什麼這更好的一些解釋會很有趣。總是最好不要只給出答案,而是幫助人們學習。 :) – Chris

0

你能檢查是否colums idexing意志幫助你達到一定的性能提升

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

我考慮過這個問題,但是需要儘可能在列上創建索引,因爲它會查詢它爲空值。 –

+0

但還是有好處的。你只索引一次,但你可以查詢多次,只要你願意 –

+0

我將不得不在每一列上創建索引,對吧?我在我的問題中忽略了一些背景,那就是我需要這個過程在表格的未索引版本上重複。 –

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 
+1

如果它有100M記錄,這聽起來像它已經在生產中使用。在這種情況下重構並不總是那麼容易。 – Mr47

+0

有時您必須作出重大更改以保持系統可用。 – Serge

+0

這是一個由數據提供者定期提供的表格。我正在寫的代碼是清理它。 –

0

你不會需要「數'所有的100M記錄。當您用一個非空值打開一列時,只要簡單地退出查詢,就可以在提供相同信息的同時節省大量時間。

0

如果大多數記錄不是空也許你可以把一些該方法的建議(例如檢查只可空字段)與此:

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

因爲存在這應該加快搜索就停止搜索當條件滿足時,在這個例子中,一個非空的記錄足以決定該字段的狀態。 如果該字段有索引,這應該幾乎是即時的。

通常將top 1添加到此查詢不是必需的,因爲查詢優化器知道您不需要檢索所有匹配的記錄。

0

您可以使用此存儲過程的伎倆,您需要提供您想查詢請注意,如果你傳遞給程序@exec參數= 1,將執行SELECT查詢

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) = '['[email protected]+']' 
      CREATE TABLE #table (ColumnName VARCHAR (max)) 
      SELECT @i+= 
      ' IF EXISTS (SELECT TOP 1 '+column_name+' FROM ' [email protected]+' WHERE ' +column_name+ 
       ' '+'IS NOT NULL) INSERT INTO #table VALUES ('''+column_name+''');' 
       FROM INFORMATION_SCHEMA.COLUMNS WHERE [email protected] 
       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 ' [email protected] 
       END 
       ELSE 
         BEGIN 
          SELECT @SELECT= 'SELECT '+ LTRIM (left (@sentence,NULLIF(LEN (@sentence)-1,-1)))+ 
               +' FROM '[email protected] 
        EXEC (@SELECT) 
       END 
END 
END 
表名

使用方法如下:

EXEC [dbo].[SP_SELECT_NON_NULL_COLUMNS] 'YourTableName' , 1 
相關問題