UNPIVOT converte colunas em linhas. No processo, elimina valores NULL ( referência ).
Dada a entrada
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);
a consulta 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;
produzirá a saída
| ID | ColName | ColValue |
|----|---------|----------|
| 1 | c1 | 12 |
| 1 | c2 | 13 |
| 2 | c2 | 14 |
| 3 | c1 | 15 |
Infelizmente a linha 4 foi totalmente eliminada, pois possui apenas NULLs! Ele pode ser convenientemente reintroduzido injetando um valor fictício na consulta de origem:
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;
Ao agregar as linhas no ID, podemos contar os valores não nulos. Uma comparação com o número total de colunas na tabela de origem identificará as linhas que contêm um ou mais 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;
Calculo 3 como
número de colunas na tabela de origem #t
+ 1 para a coluna fictícia injetada
- 1 para ID, que não é PERMANENTE
Este valor pode ser obtido em tempo de execução examinando as tabelas de catálogos.
As linhas originais podem ser recuperadas juntando-se aos resultados.
Se valores diferentes de NULL devem ser investigados, eles podem ser incluídos na cláusula where:
...
) as unpvt
where ColValue <> '' -- will eliminate empty strings
Discussão
Isso requer um identificador que é realizado através do UNPIVOT. Uma chave seria melhor. Se não existir, pode ser injetado pelo ROW_NUMBER () função da janela , embora isso possa ser caro de executar.
Todas as colunas devem ser listadas explicitamente dentro da cláusula UNPIVOT. Eles podem ser arrastados usando o SSMS, como @ db2 sugerido. Não será dinâmico quando a definição da tabela mudar, como seria a sugestão de Aaron Bertrand. Este é o caso de quase todo o SQL, no entanto.
Para meu conjunto de dados bastante limitado, o plano de execução é uma verificação de índice em cluster e um agregado de fluxo. Isso custará mais memória do que uma verificação direta da tabela e muitas cláusulas OR.