Выбор уникальных строк в наборе из двух возможностей
-
02-07-2019 - |
Вопрос
Сама проблема проста, но я не могу найти решение, которое делает это в одном запросе, и вот моя "абстракция" проблемы, позволяющая получить более простое объяснение:
Я оставлю свое первоначальное объяснение в силе, но вот набор выборочных данных и результат, который я ожидаю:
Итак, вот несколько примеров данных, я разделил пары пустой строкой
-------------
| Key | Col | (Together they from a Unique Pair)
--------------
| 1 Foo |
| 1 Bar |
| |
| 2 Foo |
| |
| 3 Bar |
| |
| 4 Foo |
| 4 Bar |
--------------
И результат, которого я ожидал бы, после выполнения запроса один раз, он должен иметь возможность выбрать этот результирующий набор в одном запросе:
1 - Foo
2 - Foo
3 - Bar
4 - Foo
Оригинальное объяснение:
У меня есть столик, назовите его TABLE
где у меня есть две колонки, говорящие ID
и NAME
которые вместе образуют первичный ключ таблицы.Теперь я хочу выбрать что-то, где ID=1
и затем сначала проверяет, может ли он найти строку, в которой NAME
имеет значение "John", если "John" не существует, то следует искать строку, где NAME
является "Bruce" - но возвращает "John" только в том случае, если оба "Bruce" и "John" существуют или, конечно, существует только "John".
Также обратите внимание, что он должен иметь возможность возвращать несколько строк для каждого запроса, которые соответствуют вышеуказанным критериям, но, конечно, с разными комбинациями ID / Name, и что приведенное выше объяснение является всего лишь упрощением реальной проблемы.
Я мог бы быть полностью ослеплен своим собственным кодом и ходом мыслей, но я просто не могу в этом разобраться.
Решение
Это довольно похоже на то, что вы написали, но должно быть довольно быстрым, поскольку в данном случае NOT EXISTS более эффективен, чем NOT IN...
mysql> select * from foo;
+----+-----+
| id | col |
+----+-----+
| 1 | Bar |
| 1 | Foo |
| 2 | Foo |
| 3 | Bar |
| 4 | Bar |
| 4 | Foo |
+----+-----+
SELECT id
, col
FROM foo f1
WHERE col = 'Foo'
OR ( col = 'Bar' AND NOT EXISTS( SELECT *
FROM foo f2
WHERE f1.id = f2.id
AND f2.col = 'Foo'
)
);
+----+-----+
| id | col |
+----+-----+
| 1 | Foo |
| 2 | Foo |
| 3 | Bar |
| 4 | Foo |
+----+-----+
Другие советы
Вы можете присоединить исходную таблицу к самой себе с помощью ВНЕШНЕГО СОЕДИНЕНИЯ, подобного этому:
create table #mytest
(
id int,
Name varchar(20)
);
go
insert into #mytest values (1,'Foo');
insert into #mytest values (1,'Bar');
insert into #mytest values (2,'Foo');
insert into #mytest values (3,'Bar');
insert into #mytest values (4,'Foo');
insert into #mytest values (4,'Bar');
go
select distinct
sc.id,
isnull(fc.Name, sc.Name) sel_name
from
#mytest sc
LEFT OUTER JOIN #mytest fc
on (fc.id = sc.id
and fc.Name = 'Foo')
вот так.
Не нужно делать это чрезмерно сложным, вы можете просто использовать MAX()
и group by ...
select id, max(col) from foo group by id
попробуй это:
select top 1 * from (
SELECT 1 as num, * FROM TABLE WHERE ID = 1 AND NAME = 'John'
union
SELECT 2 as num, * FROM TABLE WHERE ID = 1 AND NAME = 'Bruce'
) t
order by num
Я сам придумал решение, но оно довольно сложное и медленное - и к тому же плохо распространяется на более сложные запросы:
SELECT *
FROM users
WHERE name = "bruce"
OR (
name = "john"
AND NOT id
IN (
SELECT id
FROM posts
WHERE name = "bruce"
)
)
Нет альтернатив без тяжелых соединений и т.д.?
Итак, вот несколько примеров данных, я разделил пары пустой строкой
-------------
| Key | Col | (Together they from a Unique Pair)
--------------
| 1 Foo |
| 1 Bar |
| |
| 2 Foo |
| |
| 3 Bar |
| |
| 4 Foo |
| 4 Bar |
--------------
И результат, которого я ожидал бы:
1 - Foo
2 - Foo
3 - Bar
4 - Foo
Я решил это выше, но этот запрос ужасно неэффективен для столов с лагером, есть какой-нибудь другой способ?
Вот пример, который работает в SQL Server 2005 и более поздних версиях.Это полезный шаблон, в котором вы хотите выбрать верхнюю строку (или верхние n строк) на основе пользовательского порядка.Это позволит вам выбирать не просто одно из двух значений с пользовательскими приоритетами, но любое их количество.Вы можете использовать функцию ROW_NUMBER() и регистровое выражение:
CREATE TABLE T (id int, col varchar(10));
INSERT T VALUES (1, 'Foo')
INSERT T VALUES (1, 'Bar')
INSERT T VALUES (2, 'Foo')
INSERT T VALUES (3, 'Bar')
INSERT T VALUES (4, 'Foo')
INSERT T VALUES (4, 'Bar')
SELECT id,col
FROM
(SELECT id, col,
ROW_NUMBER() OVER (
PARTITION BY id
ORDER BY
CASE col
WHEN 'Foo' THEN 1
WHEN 'Bar' THEN 2
ELSE 3 END
) AS RowNum
FROM T
) AS X
WHERE RowNum = 1
ORDER BY id
В PostgreSQL, я полагаю, это было бы так:
SELECT DISTINCT ON (id) id, name
FROM mytable
ORDER BY id, name = 'John' DESC;
Update - false сортируется перед true - изначально у меня было наоборот.Обратите внимание, что DISTINCT ON является функцией PostgreSQL, а не частью стандартного SQL.Что здесь происходит, так это то, что он показывает вам только первую строку для любого заданного идентификатора, с которым он сталкивается.Поскольку мы упорядочиваем по погоде имя John, строки с именем John будут выбраны поверх всех других имен.
В вашем втором примере это было бы:
SELECT DISTINCT ON (key) key, col
FROM mytable
ORDER BY key, col = 'Foo' DESC;
Это даст вам:
1 - Foo
2 - Foo
3 - Bar
4 - Foo
Вы можете использовать joins вместо exists, и это может улучшить план запроса в случаях, когда оптимизатор недостаточно умен:
SELECT f1.id
,f1.col
FROM foo f1
LEFT JOIN foo f2
ON f1.id = f2.id
AND f2.col = 'Foo'
WHERE f1.col = 'Foo'
OR ( f1.col = 'Bar' AND f2.id IS NULL )