Tester si des colonnes sont NULL

16

J'essaie de comprendre une requête simple que je peux faire pour tester si une grande table a une liste d'entrées qui a au moins UNE valeur vide (NULL / vide) dans N'IMPORTE QUELLE colonne.

J'ai besoin de quelque chose comme

SELECT * FROM table AS t WHERE ANY(t.* IS NULL)

Je ne veux pas avoir à faire

SELECT * FROM table AS t WHERE t.c1 = NULL OR t.c2 = NULL OR t.c3 = NULL

Ce serait une ÉNORME requête.

Dexter
la source

Réponses:

16

Une extension de la réponse de @ db2 avec moins (lire: zéro) de disputes à la main:

DECLARE @tb nvarchar(512) = N'dbo.[table]';

DECLARE @sql nvarchar(max) = N'SELECT * FROM ' + @tb
    + ' WHERE 1 = 0';

SELECT @sql += N' OR ' + QUOTENAME(name) + ' IS NULL'
    FROM sys.columns 
    WHERE [object_id] = OBJECT_ID(@tb);

EXEC sys.sp_executesql @sql;
Aaron Bertrand
la source
8

Vous devez lister toutes les colonnes selon le commentaire de JNK.

WHERE c1 IS NULL OR c2 IS NULL OR c3 IS NULL

Une approche un peu moins efficace qui évite cela est cependant ci-dessous.

;WITH xmlnamespaces('http://www.w3.org/2001/XMLSchema-instance' AS ns) 
SELECT * 
FROM   YourTable AS T1 
WHERE (
    SELECT T1.* 
    FOR XML PATH('row'), ELEMENTS XSINIL, TYPE
  ).exist('//*/@ns:nil') = 1 

(Basé sur cette réponse SO)

Martin Smith
la source
5

Il n'y a pas de belle syntaxe intégrée, mais Management Studio dispose de quelques fonctionnalités pratiques pour générer rapidement la requête.

Dans l'Explorateur d'objets, accédez à la table souhaitée, développez-la, puis faites glisser le dossier "Colonnes" dans un éditeur de requête vide. Cela ajoutera une liste de colonnes séparées par des virgules à la requête.

Ensuite, ouvrez Rechercher et remplacer. Définissez "Rechercher quoi" sur ,et définissez "Remplacer par" sur IS NULL OR(avec un espace de début), puis appuyez sur Remplacer tout. Vous devrez nettoyer le dernier de la séquence à la main.

C'est toujours moche, mais c'est moins moche.

db2
la source
4

Solutions multiples pour: certaines valeurs nulles, toutes les valeurs nulles, colonnes simples et multiples, plus une utilisation rapide en utilisant le Top 1

Si vous devez tester plusieurs colonnes, vous pouvez utiliser les éléments suivants:

Column_1 Column_2 Column_3
-------- -------- --------
1        2        NULL
1        NULL     NULL
5        6        NULL

Tout d'abord , testez les valeurs NULL et comptez-les:

select 
    sum(case when Column_1 is null then 1 else 0 end) as Column_1, 
    sum(case when Column_2 is null then 1 else 0 end) as Column_2, 
    sum(case when Column_3 is null then 1 else 0 end) as Column_3,
from TestTable 

Donne un nombre de NULLs:

Column_1  Column_2  Column_3
0         1         3

Lorsque le résultat est 0, il n'y a pas de NULL.

Deuxièmement , comptons les non NULL:

select 
    sum(case when Column_1 is null then 0 else 1 end) as Column_1, 
    sum(case when Column_2 is null then 0 else 1 end) as Column_2, 
    sum(case when Column_3 is null then 0 else 1 end) as Column_3,
from TestTable

... Mais parce que nous comptons ici les valeurs non NULL, cela peut être simplifié pour:

select 
    count(Column_1) as Column_1, 
    count(Column_2) as Column_2, 
    count(Column_3) as Column_3,
from TestTable

Soit on donne:

Column_1  Column_2  Column_3
3         2         0

Lorsque le résultat est 0, la colonne est entièrement composée de valeurs NULL.

Enfin , si vous avez seulement besoin de vérifier une colonne spécifique, alors TOP 1 est plus rapide car il devrait s'arrêter au premier hit. Vous pouvez ensuite éventuellement utiliser count (*) pour donner un résultat de style booléen:

select top 1 'There is at least one NULL' from TestTable where Column_3 is NULL

select count(*) from (select top 1 'There is at least one NULL' AS note from TestTable where Column_3 is NULL) a

0 = Il n'y a pas de NULL, 1 = Il y a au moins un NULL

ou

select top 1 'There is at least one non-NULL' AS note from TestTable where Column_3 is not NULL

select count(*) from (select top 1 'There is at least one non-NULL' AS note from TestTable where Column_3 is not NULL) a

0 = Ils sont tous NULL, 1 = Il y a au moins un non NULL

J'espère que ça aide.

jwolf
la source
Bien que cela semble assez utile, je me sens obligé de noter que ce n'est pas ce que l'OP demandait - ils voulaient le contenu de chaque ligne qui comprenait une valeur NULL, pas seulement une vérification pour voir s'il en existe.
RDFozz
C'est suffisant. Je pense que je le lisais différemment. J'étais concentré sur la partie "... tester si une grande table a ...", donc ... Boolean (dans mon cas Boolean-ish). Mais si, par "liste d'entrées", il entendait des lignes, alors vous avez absolument raison.
jwolf
Je viens de revoir cela. J'ai définitivement mal interprété la question - j'aurais dû en déduire qu'il cherchait les lignes comme résultat. Je pense que j'ai également mal compris ce qu'il entendait par ÉNORME. Au départ, je pensais qu'il voulait dire cher en calcul, mais maintenant je pense simplement qu'il voulait dire large avec des colonnes, donc Arron et DB2 ont bien compris, à la fois dans la lecture et dans les solutions (selon ce qui est le plus fatigué: votre cerveau ou vos doigts)
jwolf
2

UNPIVOT traduit les colonnes en lignes. Dans le processus, il élimine les valeurs NULL ( référence ).

Compte tenu de l'entrée

create table #t
(
    ID  int primary key,
    c1  int null,
    c2  int null
);

insert #t(id, c1, c2)
values
    (1, 12, 13),
    (2, null, 14),
    (3, 15, null),
    (4, null, null);

la requête UNPIVOT

select
    ID, ColName, ColValue
from
(
    select *
    from #t
) as p
unpivot
(
    ColValue for ColName in
    (c1, c2)                  -- explicit source column names required
) as unpvt;

produira la sortie

| ID | ColName | ColValue |
|----|---------|----------|
| 1  | c1      | 12       |
| 1  | c2      | 13       |
| 2  | c2      | 14       |
| 3  | c1      | 15       |

Malheureusement, la ligne 4 a été entièrement éliminée car elle ne contient que des valeurs NULL! Il peut être facilement réintroduit en injectant une valeur fictive dans la requête source:

select
    ID, ColName, ColValue
from
(
    select
        -5 as dummy,               -- injected here, -5 is arbitrary
        *
    from #t
) as p
unpivot
(
    ColValue for ColName in
    (dummy, c1, c2)                -- referenced here
) as unpvt;

En agrégeant les lignes sur l'ID, nous pouvons compter les valeurs non nulles. Une comparaison avec le nombre total de colonnes dans la table source identifiera les lignes contenant un ou plusieurs NULL.

select
    ID
from
(
    select -5 as dummy, *
    from #t
) as p
unpivot
(
    ColValue for ColName in
    (dummy, c1, c2)
) as unpvt
group by ID
having COUNT(*) <> 3;

Je calcule 3 comme
nombre de colonnes dans la table source #t
+ 1 pour la colonne factice injectée
- 1 pour l'ID, qui n'est pas UNPIVOTED

Cette valeur peut être obtenue au moment de l'exécution en examinant les tables du catalogue.

Les lignes d'origine peuvent être récupérées en se joignant aux résultats.

Si des valeurs autres que NULL doivent être recherchées, elles peuvent être incluses dans une clause where:

...
) as unpvt
where ColValue <> ''      -- will eliminate empty strings

Discussion

Cela nécessite un identifiant qui est acheminé via l'UNPIVOT. Une clé serait la meilleure. Si aucun n'existe, un peut être injecté par la fonction de fenêtre ROW_NUMBER () , bien que cela puisse être coûteux à exécuter.

Toutes les colonnes doivent être explicitement répertoriées dans la clause UNPIVOT. Ils peuvent être glissés dans SSMS, comme l'a suggéré @ db2. Il ne sera pas dynamique lorsque la définition de la table chagnera, comme le serait la suggestion d'Aaron Bertrand. C'est cependant le cas pour presque tous les SQL.

Pour mon ensemble de données plutôt limité, le plan d'exécution est une analyse d'index en cluster et un agrégat de flux. Cela coûtera plus de mémoire qu'un simple scan de la table et beaucoup de clauses OR.

Michael Green
la source