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.