Seleccione filas con la misma identificación pero nula y algún otro valor en otra columna para esa identificación

9

Quiero obtener solo filas que tengan un valor NULLy algún otro valor que no sea NULLpara una columna de nombre de usuario en particular.

Si ambas filas tienen un valor nulo para ese nombre de usuario en particular o ambos tienen algunos valores distintos de nulo, entonces no debería aparecer en la salida. Si hay más de dos filas para el mismo nombre de usuario con nulo y algún otro valor, entonces deberían aparecer.

A continuación se muestra un ejemplo de muestra y salida. ¿Cómo se puede hacer usando la consulta SQL?

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

salida

+----------+------+
| username | col2 |
+----------+------+
| c        | der  |
| c        | null |
+----------+------+
Investigador de TI
fuente
1
¿Qué pasa si hay 2 filas con d, dery 2 con d, null?
ypercubeᵀᴹ
1
@ypercube Entonces deberían aparecer las 4 filas de d
investigador de TI
1
Si hay filas con e, one, e, twoy 2 o más con e, null?
ypercubeᵀᴹ
1
@ypercube, entonces deberían aparecer todas las filas.
Investigador de TI

Respuestas:

12

Debería poder usar la agregación condicional para obtener el nombre de usuario con un valor col2y también con null.

Sugeriría usar una cláusula HAVING con las condiciones. La consulta sería similar 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

Ver SQL Fiddle con Demo . Esta consulta agrupa sus datos por cada nombre de usuario y luego utiliza la lógica condicional para verificar si col2cumple con las dos condiciones que desea, donde col2no es nulo y col2 es nulo.

Luego puede usar esto en una subconsulta, etc. para obtener los valores usernamey 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

Ver SQL Fiddle con Demo .

Si tiene más de una col2fila con ambos nully otro valor, solo necesita modificar la HAVINGcláusula ligeramente:

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;

Ver SQL Fiddle con Demo

Taryn
fuente
Su consulta perdió un punto (en realidad no he mencionado claramente en la pregunta también). Si hay más de dos filas para el mismo nombre de usuario con nulo y algún otro valor, entonces deberían aparecer. en su consulta no aparecerán (por ejemplo, en ese violín si hay otra fila con el nombre de usuario 'c' y un valor nulo o algo.
Investigador de TI
1
@ITresearcher Esa es una solución simple: debe cambiar la HAVINGcláusula para que sea >=1- sqlfiddle.com/#!3/8af72/2
Taryn
Ok, eso es correcto. La respuesta de JGA también funciona.
Investigador de TI
8

Otra solución:

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
);

Plan de ejecución

En una vena lógica similar:

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
    );

Plan de ejecución

Aún otra:

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;

Plan de ejecución

Paul White 9
fuente
Buena respuesta. Incluso tiene un mejor rendimiento porque mi mesa era enorme.
Investigador de TI
5

Solo otra forma de hacerlo:

; 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ᵀᴹ
fuente
4

Este también funciona. Demostración de SQL Fiddle

Obtengo C1 como las filas totales para cada nombre de usuario, C2 como las filas nulas totales para cada nombre de usuario y comparo estos valores más adelante.

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
fuente
3

Usaría la subconsulta para seleccionar esos nombres de usuario 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
fuente
-1

Lo intenté con 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
fuente
2
Esto inducirá una unión cruzada. Si para un nombre de usuario, hay 3 filas con col2 nulo y 2 filas con col2 no nulo, el resultado final tendrá 6 filas, no 5. Y col2no estará en la salida.
ypercubeᵀᴹ