Selecione linhas com o mesmo ID, mas nulo e algum outro valor em outra coluna para esse ID

9

Quero obter apenas linhas com um valor NULLe algum outro valor que não seja NULLuma coluna de nome de usuário específica.

Se ambas as linhas tiverem nulo para esse nome de usuário específico ou ambos tiverem alguns valores diferentes de nulo, ele não deverá aparecer na saída. Se houver mais de duas linhas para o mesmo nome de usuário com nulo e algum outro valor, elas deverão aparecer.

Abaixo está um exemplo de amostra e saída. Como isso pode ser feito usando a consulta sql?

+----------+-------+
| username | col2  |
+----------+-------+
| a        | abc   |
| a        | ef    |
| b        | null  |
| b        | null  |
| c        | der   |
| c        | null  |
+----------+-------+

resultado

+----------+------+
| username | col2 |
+----------+------+
| c        | der  |
| c        | null |
+----------+------+
Pesquisador de TI
fonte
11
E se houver 2 linhas com d, dere 2 com d, null?
usar o seguinte código
11
@ypercube Então todas as 4 linhas de d deve aparecer
TI pesquisador
11
Se houver linhas com e, one, e, twoe 2 ou mais com e, null?
usar o seguinte código
11
@ypercube, todas as linhas devem aparecer.
Pesquisador de TI

Respostas:

12

Você deve poder usar a agregação condicional para obter o nome de usuário com um valor col2e também null.

Eu sugiro usar uma cláusula HAVING com as condições. A consulta seria semelhante a:

select username
from yourtable
group by username
having sum(case when col2 is not null then 1 else 0 end) = 1
  and sum(case when col2 is null then 1 else 0 end) = 1

Consulte SQL Fiddle com demonstração . Essa consulta agrupa seus dados por cada nome de usuário e, em seguida, usa lógica condicional para verificar se col2atende às duas condições desejadas - onde col2não é nulo e col2 é nulo.

Você pode usar isso em uma subconsulta, etc, para obter os valores usernamee col2:

select 
  t.username, 
  t.col2
from yourtable t
inner join
(
  select username
  from yourtable
  group by username
  having sum(case when col2 is not null then 1 else 0 end) = 1
    and sum(case when col2 is null then 1 else 0 end) = 1
) d
  on t.username = d.username

Consulte SQL Fiddle com demonstração .

Se você tiver mais de uma col2linha com ambos nulle outro valor, precisará alterar HAVINGligeiramente a cláusula:

select 
  t.username, 
  t.col2
from yourtable t
inner join
(
  select username
  from yourtable
  group by username
  having sum(case when col2 is not null then 1 else 0 end) >= 1
    and sum(case when col2 is null then 1 else 0 end) >= 1
) d
  on t.username = d.username;

Veja SQL Fiddle com demonstração

Taryn
fonte
Sua consulta perdeu um ponto (na verdade, também não mencionei claramente a questão). Se houver mais de duas linhas para o mesmo nome de usuário com nulo e algum outro valor, elas deverão aparecer. na sua consulta, eles não aparecerão (por exemplo, nesse caso, se houver outra linha com o nome de usuário 'c' e um valor nulo ou algum valor.
Pesquisador de TI
11
@ITresearcher Essa é uma solução simples - você precisa mudar a HAVINGcláusula para ser >=1- sqlfiddle.com/#!3/8af72/2
Taryn
Ok. Está correto. Responder por JGA também funciona.
Pesquisador de TI
8

Outra solução:

SELECT Y1.*
FROM dbo.yourtable AS Y1
WHERE Y1.username = ANY
(
    SELECT Y2.username 
    FROM dbo.yourtable AS Y2
    WHERE Y2.col2 IS NULL
    INTERSECT
    SELECT Y3.username 
    FROM dbo.yourtable AS Y3
    WHERE Y3.col2 IS NOT NULL
);

Plano de execução

Na mesma linha lógica:

SELECT Y.* 
FROM dbo.yourtable AS Y
WHERE EXISTS
    (
    SELECT * 
    FROM dbo.yourtable AS Y2 
    WHERE Y2.username = Y.username 
    AND Y2.col2 IS NULL
    )
AND EXISTS
    (
    SELECT * 
    FROM dbo.yourtable AS Y3 
    WHERE Y3.username = Y.username 
    AND Y3.col2 IS NOT NULL
    );

Plano de execução

Ainda outra:

SELECT
    SQ1.username,
    SQ1.col2
FROM 
(
    SELECT
        Y.username, 
        Y.col2,
        MinCol2 = 
            MIN(CASE WHEN Y.col2 IS NULL THEN -1 ELSE 1 END) 
            OVER (PARTITION BY Y.username), 
        MaxCol2 = 
            MAX(CASE WHEN Y.col2 IS NULL THEN -1 ELSE 1 END) 
            OVER (PARTITION BY Y.username)
    FROM dbo.yourtable AS Y
) AS SQ1
WHERE 
    SQ1.MinCol2 = -SQ1.MaxCol2;

Plano de execução

Paul White 9
fonte
Boa resposta. Mesmo com melhor desempenho, minha mesa era enorme.
TI pesquisador
5

Apenas outra maneira de fazer isso:

; WITH cte AS
  ( SELECT username, col2,
           cnt_all  = COUNT(*) OVER (PARTITION BY username),
           not_null = COUNT(col2) OVER (PARTITION BY username)
    FROM yourtable AS a
  )
SELECT username, col2
FROM cte
WHERE cnt_all > not_null 
  AND not_null > 0 ;
ypercubeᵀᴹ
fonte
4

Este também funciona. Demonstração do SQL Fiddle

Eu obtenho C1 como o total de linhas para cada nome de usuário, C2 como o total de linhas nulas para cada nome de usuário e comparo esses valores posteriormente.

SELECT username, col2 FROM
(
SELECT *,
(SELECT Count(*) FROM T Where username = T1.username) C1,
(SELECT Count(*) FROM T Where username = T1.username and col2 is null) C2
FROM T T1
) T2
WHERE C2 > 0 And C1 <> C2
JGA
fonte
3

Eu usaria a subconsulta para selecionar nomes de usuário como:

select username
from   dbo.yourtable
group by username
having sum(distinct case when col2 is not null then 1 else 2 end) = 3;
Bosko
fonte
-1

Eu tentei com este ...

select a.username from  
(select username ,col2 
   from yourtable
where col2 is null) a,(select username ,col2 
                       from yourtable
                        where col2 is not null) b
where a.username=b.username;
ammu
fonte
2
Isso induzirá uma junção cruzada. Se, para um nome de usuário, houver 3 linhas com col2 nulo e 2 linhas com col2 não nulo, o resultado final terá 6 linhas, não 5. E col2não estará na saída.
ypercubeᵀᴹ