Выберите строки с тем же идентификатором, но нулевым, и некоторые другие значения в другом столбце для этого идентификатора

9

Я хочу получить только строки, имеющие значение NULLи некоторые другие значения, чем NULLдля определенного столбца имени пользователя.

Если обе строки имеют значение NULL для данного имени пользователя или обе имеют значения, отличные от NULL, то они не должны появляться в выходных данных. Если существует более двух строк для одного имени пользователя с нулевым и другим значением, они должны появиться.

Ниже приведен пример выборки и вывода. Как это можно сделать с помощью SQL-запроса?

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

вывод

+----------+------+
| username | col2 |
+----------+------+
| c        | der  |
| c        | null |
+----------+------+
IT исследователь
источник
1
Что делать, если есть 2 строки с d, derи 2 с d, null?
ypercubeᵀᴹ
1
@ypercube Тогда должны появиться все 4 строки d
IT-исследователь
1
Если есть строки с e, one, e, twoи 2 или более с e, null?
ypercubeᵀᴹ
1
@ypercube тогда должны появиться все строки.
ИТ-исследователь

Ответы:

12

Вы должны быть в состоянии использовать условную агрегацию, чтобы получить имя пользователя как со значением, col2так и с null.

Я бы предложил использовать предложение HAVING с условиями. Запрос будет похож на:

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

Смотрите SQL Fiddle с демонстрацией . Этот запрос группирует ваши данные по каждому имени пользователя, а затем использует условную логику, чтобы проверить, col2удовлетворяет ли оба условия, которые вы хотите - где col2не ноль и col2 ноль.

Вы можете использовать это в подзапросе, и т.д. , чтобы получить usernameи 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

Смотрите SQL Fiddle с демонстрацией .

Если у вас более одной col2строки с обоими nullи другим значением, то вам просто нужно HAVINGнемного изменить предложение:

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;

Смотрите SQL Fiddle с демо

Тарын
источник
В вашем запросе пропущено одно очко (на самом деле я тоже четко не упомянул вопрос). Если существует более двух строк для одного имени пользователя с нулевым и другим значением, они должны появиться. в вашем запросе они не появятся (например, в этой скрипке, если есть другая строка с именем пользователя 'c' и нулевым или некоторым значением.
Исследователь ИТ
1
@ITresearcher Это простое исправление - вам нужно изменить это HAVINGпредложение на >=1- sqlfiddle.com/#!3/8af72/2
Taryn
Хорошо, это правильно. Ответ от JGA тоже работает.
ИТ-исследователь
8

Другое решение:

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

План выполнения

В том же логическом ключе:

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

План выполнения

Еще один:

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;

План выполнения

Пол Уайт 9
источник
Хороший ответ. Даже это имеет лучшую производительность, потому что мой стол был огромен.
ИТ-исследователь
5

Просто еще один способ сделать это:

; 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ᵀᴹ
источник
4

Этот тоже работает. SQL Fiddle demo

Я получаю C1 как общее количество строк для каждого имени пользователя, C2 как общее количество пустых строк для каждого имени пользователя, и я сравниваю эти значения позже.

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
источник
3

Я бы использовал подзапрос, чтобы выбрать такие имена пользователей, как:

select username
from   dbo.yourtable
group by username
having sum(distinct case when col2 is not null then 1 else 2 end) = 3;
Боско
источник
-1

Я пытался с этим ...

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;
Амму
источник
2
Это вызовет перекрестное соединение. Если для имени пользователя есть 3 строки с нулевым col2 и 2 строки с ненулевым col2, конечный результат будет иметь 6 строк, а не 5. И col2не будет в выходных данных.
ypercubeᵀᴹ