Выбор уникальных строк в наборе из двух возможностей

StackOverflow https://stackoverflow.com/questions/150610

Вопрос

Сама проблема проста, но я не могу найти решение, которое делает это в одном запросе, и вот моя "абстракция" проблемы, позволяющая получить более простое объяснение:

Я оставлю свое первоначальное объяснение в силе, но вот набор выборочных данных и результат, который я ожидаю:

Итак, вот несколько примеров данных, я разделил пары пустой строкой

-------------
| 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 )
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top