Esse é um problema com o qual me deparo periodicamente e ainda não encontrei uma boa solução.
Supondo a seguinte estrutura de tabela
CREATE TABLE T
(
A INT PRIMARY KEY,
B CHAR(1000) NULL,
C CHAR(1000) NULL
)
e o requisito é determinar se uma das colunas anuláveis B
ou C
realmente contém algum NULL
valor (e se sim, qual (s)).
Suponha também que a tabela contenha milhões de linhas (e que nenhuma estatística de coluna esteja disponível que possa ser vista, pois estou interessado em uma solução mais genérica para essa classe de consultas).
Posso pensar em algumas maneiras de abordar isso, mas todas têm fraquezas.
Duas EXISTS
declarações separadas . Isso teria a vantagem de permitir que as consultas parassem de varrer mais cedo assim que uma NULL
delas fosse encontrada. Mas se as duas colunas de fato não contêm NULL
s, resultarão em duas varreduras completas.
Consulta agregada única
SELECT
MAX(CASE WHEN B IS NULL THEN 1 ELSE 0 END) AS B,
MAX(CASE WHEN C IS NULL THEN 1 ELSE 0 END) AS C
FROM T
Isso pode processar as duas colunas ao mesmo tempo, para ter o pior caso de uma verificação completa. A desvantagem é que, mesmo que encontre um NULL
em ambas as colunas muito cedo, a consulta ainda acabará examinando todo o restante da tabela.
Variáveis de usuário
Eu posso pensar em uma terceira maneira de fazer isso
BEGIN TRY
DECLARE @B INT, @C INT, @D INT
SELECT
@B = CASE WHEN B IS NULL THEN 1 ELSE @B END,
@C = CASE WHEN C IS NULL THEN 1 ELSE @C END,
/*Divide by zero error if both @B and @C are 1.
Might happen next row as no guarantee of order of
assignments*/
@D = 1 / (2 - (@B + @C))
FROM T
OPTION (MAXDOP 1)
END TRY
BEGIN CATCH
IF ERROR_NUMBER() = 8134 /*Divide by zero*/
BEGIN
SELECT 'B,C both contain NULLs'
RETURN;
END
ELSE
RETURN;
END CATCH
SELECT ISNULL(@B,0),
ISNULL(@C,0)
mas isso não é adequado para o código de produção, pois o comportamento correto para uma consulta de concatenação agregada é indefinido. e encerrar a verificação lançando um erro é uma solução bastante horrível de qualquer maneira.
Existe outra opção que combina os pontos fortes das abordagens acima?
Editar
Apenas para atualizar isso com os resultados que recebo em termos de leituras para as respostas enviadas até o momento (usando os dados de teste do @ ypercube)
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | 2 * EXISTS | CASE | Kejser | Kejser | Kejser | ypercube | 8kb |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
| | | | | MAXDOP 1 | HASH GROUP, MAXDOP 1 | | |
| No Nulls | 15208 | 7604 | 8343 | 7604 | 7604 | 15208 | 8346 (8343+3) |
| One Null | 7613 | 7604 | 8343 | 7604 | 7604 | 7620 | 7630 (25+7602+3) |
| Two Null | 23 | 7604 | 8343 | 7604 | 7604 | 30 | 30 (18+12) |
+----------+------------+------+---------+----------+----------------------+----------+------------------+
Por @ resposta de Thomas eu mudei TOP 3
para TOP 2
permitir potencialmente lo para sair mais cedo. Por padrão, eu tenho um plano paralelo para essa resposta, então tentei com uma MAXDOP 1
dica para tornar o número de leituras mais comparável aos outros planos. Fiquei um pouco surpreso com os resultados, pois em meu teste anterior eu havia visto a consulta em curto-circuito sem ler a tabela inteira.
O plano para os meus dados de teste em que curto-circuito está abaixo
O plano para os dados do ypercube é
Portanto, ele adiciona um operador de classificação de bloqueio ao plano. Eu também tentei com a HASH GROUP
dica, mas isso ainda acaba lendo todas as linhas
Portanto, a chave é conseguir que um hash match (flow distinct)
operador permita que esse plano entre em curto-circuito, pois as outras alternativas bloquearão e consumirão todas as linhas de qualquer maneira. Não acho que exista uma dica para forçar isso especificamente, mas aparentemente "em geral, o otimizador escolhe um Flow Distinct onde determina que menos linhas de saída são necessárias do que valores distintos no conjunto de entradas". .
Os dados do @ ypercube têm apenas 1 linha em cada coluna com NULL
valores (cardinalidade da tabela = 30300) e as linhas estimadas entrando e saindo do operador são ambas 1
. Ao tornar o predicado um pouco mais opaco para o otimizador, ele gerou um plano com o operador Flow Distinct.
SELECT TOP 2 *
FROM (SELECT DISTINCT
CASE WHEN b IS NULL THEN NULL ELSE 'foo' END AS b
, CASE WHEN c IS NULL THEN NULL ELSE 'bar' END AS c
FROM test T
WHERE LEFT(b,1) + LEFT(c,1) IS NULL
) AS DT
Editar 2
Um último ajuste que me ocorreu é que a consulta acima ainda pode acabar processando mais linhas do que o necessário, caso a primeira linha que encontrar com a NULL
tenha NULLs na coluna B
e C
. Ele continuará a digitalização em vez de sair imediatamente. Uma maneira de evitar isso seria desviar as linhas à medida que elas são verificadas. Portanto, minha alteração final à resposta de Thomas Kejser está abaixo
SELECT DISTINCT TOP 2 NullExists
FROM test T
CROSS APPLY (VALUES(CASE WHEN b IS NULL THEN 'b' END),
(CASE WHEN c IS NULL THEN 'c' END)) V(NullExists)
WHERE NullExists IS NOT NULL
Provavelmente seria melhor para o predicado, WHERE (b IS NULL OR c IS NULL) AND NullExists IS NOT NULL
mas contra os dados de teste anteriores, que não se fornece um plano com um Flow Distinct, enquanto o NullExists IS NOT NULL
faz (plano abaixo).
fonte
TOP 3
pode ser oTOP 2
que está sendo digitalizado até encontrar um dos seguintes(NOT_NULL,NULL)
itens(NULL,NOT_NULL)
,,(NULL,NULL)
. Quaisquer 2 desses 3 seriam suficientes - e se encontrar(NULL,NULL)
primeiro, o segundo também não seria necessário. Além disso, a fim de curto-circuito o plano seria necessário para implementar a nítida através de umhash match (flow distinct)
operador em vez dehash match (aggregate)
oudistinct sort
Pelo que entendi a pergunta, você deseja saber se existe um nulo em qualquer um dos valores das colunas, em vez de realmente retornar as linhas nas quais B ou C é nulo. Se for esse o caso, por que não:
Na minha plataforma de teste com o SQL 2008 R2 e um milhão de linhas, obtive os seguintes resultados em ms na guia Estatísticas do Cliente:
Se você adicionar a dica nolock, os resultados serão ainda mais rápidos:
Para referência, usei o SQL Generator da Red-gate para gerar os dados. Fora do meu milhão de linhas, 9.886 linhas tinham um valor B nulo e 10.019 tinham um valor C nulo.
Nesta série de testes, todas as linhas da coluna B têm um valor:
Antes de cada teste (ambos os conjuntos) eu corri
CHECKPOINT
eDBCC DROPCLEANBUFFERS
.Aqui estão os resultados quando não há nulos na tabela. Observe que as 2 soluções existentes fornecidas pelo ypercube são quase idênticas às minhas em termos de leituras e tempo de execução. Eu (nós) acredito que isso se deve às vantagens da edição Enterprise / Developer ao usar a Verificação avançada . Se você estava usando apenas a edição Standard ou inferior, a solução da Kejser pode muito bem ser a solução mais rápida.
fonte
As
IF
declarações são permitidas?Isso deve permitir que você confirme a existência de B ou C em uma passagem pela tabela:
fonte
Testado no SQL-Fiddle nas versões: 2008 R2 e 2012 com 30K linhas.
EXISTS
consulta mostra um enorme benefício em eficiência quando encontra Nulls mais cedo - o que é esperado.EXISTS
consulta - em todos os casos em 2012, o que não posso explicar.CASE
consulta de Martin .Consultas e horários. Os horários foram feitos:
B
tendo umNULL
em um pequenoid
.NULL
em ids pequenos.Aqui vamos nós (há um problema com os planos, tentarei novamente mais tarde. Siga os links por enquanto):
Consulta com 2 subconsultas EXISTS
Consulta agregada única de Martin Smith
Consulta de Thomas Kejser
Minha sugestão (1)
Ele precisa de um polimento na saída, mas a eficiência é semelhante à
EXISTS
consulta. Eu pensei que seria melhor quando não há nulos, mas o teste mostra que não é.Sugestão (2)
Tentando simplificar a lógica:
Parece ter um desempenho melhor em 2008R2 do que a sugestão anterior, mas pior em 2012 (talvez o segundo
INSERT
possa ser reescrito usandoIF
, como a resposta de @ 8kb):fonte
Quando você usa EXISTS, o SQL Server sabe que você está fazendo uma verificação de existência. Quando encontra o primeiro valor correspondente, ele retorna TRUE e para de procurar.
quando você concatinar 2 colunas e se alguma for nula, o resultado será nulo
por exemplo
então verifique este código
fonte
E se:
Se isso funcionar (eu não testei), produziria uma tabela de uma linha com 2 colunas, cada uma delas TRUE ou FALSE. Não testei a eficiência.
fonte
T.B is null
seja tratado como um resultado booleanoEXISTS(SELECT true)
eEXISTS(SELECT false)
que ambos retornem true. Este exemplo MySQL indica que ambas as colunas contêm NULL quando nenhum de fato fazer