2013-05-31 10 views
6

Mam dużą tabelę z 500 kolumnami i 100 milionami wierszy. Na podstawie niewielkiej próbki sądzę, że tylko około 50 kolumn zawiera dowolne wartości, a pozostałe 450 zawiera tylko wartości NULL. Chcę wyświetlić kolumny, które nie zawierają danych.Jak mogę niedrogo określić, czy kolumna zawiera tylko NULL rekordy?

Na moim obecnym sprzęcie, zajęłoby około 24 godzin do kwerendy każdą kolumnę (select count(1) from tab where col_n is not null)

Czy istnieje tańszy sposób określić, że kolumna jest całkowicie pusta/null?

+0

Will kolumny zostać zaktualizowane w jakiś sposób? Czy dozwolone są zmiany? –

+0

Kolumny nie muszą być aktualizowane. Nie można ich modyfikować w ramach rozwiązania. –

+0

Głównym problemem jest wydajność, a nie pisanie dynamicznego sql w celu wygenerowania zapytania. –

Odpowiedz

13

Co o tym:

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 

?

Możesz łatwo utworzyć to zapytanie, jeśli używasz tabeli INFORMATION_SCHEMA.COLUMNS.

EDIT:

Inny pomysł:

SELECT MAX (column_1), MAX (column_2) ..... Z table_name

Jeżeli wynik zawiera wartość kolumna jest wypełniana. To powinno wymagać jednego skanowania tabeli.

+0

Moim głównym zmartwieniem jest wydajność, a nie generowanie zapytania. –

+0

+1. W pełni zgadzam się z tą odpowiedzią. – Devart

+1

@ user2161466 Zauważ, że ta odpowiedź nie dotyczy tylko tworzenia zapytań, ale pozwala poradzić sobie ze wszystkimi kolumnami naraz, zamiast przechodzić kolejno jeden po drugim. To może oznaczać ogromną poprawę wydajności. –

1

Spróbuj jeden -

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) 

Query 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 

Wyjście 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 

Query 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 

Wyjście 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 

Wyniki:

ID   Name  IsCitizen Age 
----------- ----------- ----------- ----------- 
0   0   0   1 
+0

W jednej kolumnie daje to mniej więcej taką samą wydajność. Czy oczekujesz lepszej wydajności w agregacie? wybrać Ilość (1) od kol dbo.tab gdzie nie jest zerowy - 01:07 SELECT kolumna = COUNT (przypadek, gdy ISNULL (obsada (kolumna AS NVARCHAR (MAX)), '') = '', a następnie 1 END) OD dbo.tab - 1:08 –

+0

Ja (plakat) nie oddałem tego głosu. –

+1

@Devart: Nie jestem downvoter ale wyjaśnienie, dlaczego to jest lepsze, byłoby interesujące. Zawsze najlepiej nie dawać odpowiedzi, ale pomagać ludziom w nauce. :) – Chris

0

można sprawdzić czy colums idexing woli pomóc osiągnąć jakiś wydajność poprawić

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

Rozważałem to, ale tworzenie indeksu w kolumnie trwa tak długo, jak w przypadku zapytania o wartości zerowe. –

+0

Ale nadal istnieje korzyść. indeksujesz tylko raz, ale możesz zapytać tyle razy ile chcesz –

+0

Musiałbym utworzyć indeks na każdej kolumnie, prawda? W moim pytaniu pominięto kontekst, który wymaga, aby proces ten był powtarzalny w nieindeksowanej wersji tabeli. –

0

500 kolumn ?!
Dobra odpowiedź na twoje pytanie: znormalizuj swój stół.

Oto, co dzieje się na razie:

Nie masz indeksu na tej kolumny tak SQL Server ma zrobić pełne skanowanie naszego wspólnego stołu.
Serwer SQL na pewno przeczyta wszystkie wiersze (co oznacza każdą kolumnę, nawet jeśli interesuje Cię tylko jeden).
A ponieważ twój rząd najprawdopodobniej ponad 8 kb ... http://msdn.microsoft.com/en-us/library/ms186981%28v=sql.105%29.aspx

Poważnie, znormalizować swoją tabelę iw razie potrzeby podzielić ją poziomo (put „motyw zgrupowane” kolumny wewnątrz osobnej tabeli, tylko czytać je, gdy są potrzebne) .

EDIT: Można przepisać zapytanie jak to

select count(col_n) from tab 

a jeśli chcesz, aby wszystkie kolumny naraz (lepiej):

SELECT 
    COUNT(column_1) column_1_count, 
    COUNT(column_2) column_2_count, 
    ... 
FROM table_name 
+1

Jeśli ma 100M rekordów, to brzmi dla mnie tak, jakby był już w użytku produkcyjnym. Refaktoryzacja nie zawsze jest łatwa w tym przypadku. – Mr47

+0

Czasami trzeba wprowadzić drastyczne zmiany, aby system działał. – Serge

+0

Jest to tabela dostarczana przez dostawcę danych okresowo. Kod, który piszę, to oczyścić. –

0

Ty nie trzeba „count "wszystkie 100M rekordów. Po prostu wycofanie się z zapytania z TOP 1, gdy tylko klikniesz kolumnę z wartością inną niż null, zaoszczędziłoby dużo czasu, dostarczając te same informacje.

0

Jeśli większość zapisy nie są nieważne może można mieszać niektóre podejścia sugerował (na przykład sprawdzać tylko pola dopuszczające wartość null), z tego:

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

powinno to przyspieszyć wyszukiwanie, ponieważ istnieje zatrzymuje przeszukiwanie tak szybko ponieważ warunek jest spełniony, w tym przykładzie pojedynczy rekord niezerowy wystarcza do określenia statusu pola. Jeśli pole ma indeks, powinno to być niemal natychmiastowe.

Zwykle dodawanie pierwszego do tego zapytania nie jest potrzebne, ponieważ optymalizator zapytań wie, że nie trzeba pobierać wszystkich pasujących rekordów.

0

Można użyć tej procedury przechowywanej do trick Musisz podać nazwę tabeli chcesz zapytać pamiętać, że jeśli będziesz przechodzić do procedury @exec parametr = 1 będzie wykonywał kwerendę wybierającą

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 

Używaj go tak:

EXEC [dbo].[SP_SELECT_NON_NULL_COLUMNS] 'YourTableName' , 1 
Powiązane problemy