Фильтровать SQL-запрос по уникальному набору значений столбцов, независимо от их порядка - PullRequest
4 голосов
/ 10 июня 2009

У меня есть таблица в Oracle, содержащая два столбца, которые я хотел бы запросить для записей, содержащих уникальную комбинацию значений, независимо от порядка этих значений. Например, если у меня есть следующая таблица:

create table RELATIONSHIPS (
    PERSON_1 number not null,
    PERSON_2 number not null,
    RELATIONSHIP  number not null,
    constraint PK_RELATIONSHIPS
        primary key (PERSON_1, PERSON_2)
);

Я бы хотел запросить все уникальные отношения. Поэтому, если у меня есть запись PERSON_1 = Джон и PERSON_2 = Джилл, я не хочу видеть другую запись, где PERSON_1 = Джилл и PERSON_2 = Джон.

Есть ли простой способ сделать это?

Ответы [ 10 ]

4 голосов
/ 10 июня 2009

Всегда ли существуют отношения в обоих направлениях? то есть, если Джон и Джилл связаны, то есть ли всегда a {Джон, Джилл} и {Джилл, Джон}? Если это так, просто ограничитесь теми, где Person_1

3 голосов
/ 10 июня 2009

Непроверенные:

select least(person_1,person_2)
     , greatest(person_1,person_2)
  from relationships
 group by least(person_1,person_2)
     , greatest(person_1,person_2)

Чтобы предотвратить такие двойные записи, вы можете добавить уникальный индекс, используя ту же идею (проверено!):

SQL> create table relationships
  2  ( person_1 number not null
  3  , person_2 number not null
  4  , relationship number not null
  5  , constraint pk_relationships primary key (person_1, person_2)
  6  )
  7  /

Table created.

SQL> create unique index ui_relationships on relationships(least(person_1,person_2),greatest(person_1,person_2))
  2  /

Index created.

SQL> insert into relationships values (1,2,0)
  2  /

1 row created.

SQL> insert into relationships values (1,3,0)
  2  /

1 row created.

SQL> insert into relationships values (2,1,0)
  2  /
insert into relationships values (2,1,0)
*
ERROR at line 1:
ORA-00001: unique constraint (RWIJK.UI_RELATIONSHIPS) violated

С уважением, Роб.

3 голосов
/ 10 июня 2009
select distinct
case when PERSON_1>=PERSON_2 then PERSON_1 ELSE PERSON_2 END person_a,
case when PERSON_1>=PERSON_2 then PERSON_2 ELSE PERSON_1 END person_b
FROM RELATIONSHIPS;
2 голосов
/ 10 июня 2009

Вам следует создать ограничение для таблицы Relationships, чтобы числовое значение person_1 было меньше числового значения person_2.

create table RELATIONSHIPS (
    PERSON_1 number not null,
    PERSON_2 number not null,
    RELATIONSHIP  number not null,
    constraint PK_RELATIONSHIPS
        primary key (PERSON_1, PERSON_2),
    constraint UNIQ_RELATIONSHIPS
        CHECK (PERSON_1 < PERSON_2)
);

Таким образом, вы можете быть уверены, что (2,1) никогда не будет вставлено - это должно быть (1,2). Тогда ваше ограничение PRIMARY KEY предотвратит дублирование.

PS: Я вижу, Марк Гравелл ответил быстрее, чем я, с аналогичным решением.

1 голос
/ 11 июня 2009

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

Итак, вот альтернативное решение для последнего случая, запрашивающее уникальные пары, даже если существуют дубликаты:

SELECT r1.*
FROM Relationships r1
LEFT OUTER JOIN Relationships r2
  ON (r1.person_1 = r2.person_2 AND r1.person_2 = r2.person_1)
WHERE r1.person_1 < r1.person_2
  OR  r2.person_1 IS NULL;

Так что, если есть совпадающая строка с обратным идентификатором, есть правило, для которого предпочтение должно быть выполнено запросом (правило с идентификаторами в числовом порядке).

Если соответствующей строки нет, то r2 будет NULL (так работает внешнее объединение), поэтому в этом случае просто используйте все, что найдено в r1.

Нет необходимости использовать GROUP BY или DISTINCT, потому что может быть только ноль или одна совпадающая строка.

Пробуя это в MySQL, я получаю следующий план оптимизации:

+----+-------------+-------+--------+---------------+---------+---------+-----------------------------------+------+--------------------------+
| id | select_type | table | type   | possible_keys | key     | key_len | ref                               | rows | Extra                    |
+----+-------------+-------+--------+---------------+---------+---------+-----------------------------------+------+--------------------------+
|  1 | SIMPLE      | r1    | ALL    | NULL          | NULL    | NULL    | NULL                              |    2 |                          | 
|  1 | SIMPLE      | r2    | eq_ref | PRIMARY       | PRIMARY | 8       | test.r1.person_2,test.r1.person_1 |    1 | Using where; Using index | 
+----+-------------+-------+--------+---------------+---------+---------+-----------------------------------+------+--------------------------+

Кажется, это разумно хорошее использование индексов.

0 голосов
/ 12 июня 2009

Вы могли бы просто,

with rel as (
select *,
       row_number() over (partition by least(person_1,person_2), 
                                       greatest(person_1,person_2)) as rn
  from relationships
       )
select *
  from rel
 where rn = 1;
0 голосов
/ 11 июня 2009

Возможно, самое простое решение (которое не требует изменения структуры данных или создания триггеров) - это создать набор результатов без повторяющихся записей и добавить одну из дублирующих записей в этот набор.

будет выглядеть примерно так:

 select * from relationships where rowid not in 
    (select a.rowid from  relationships a,relationships b 
       where a.person_1=b.person_2 and a.person_2=b.person_1)
union all
 select * from relationships where rowid in 
    (select a.rowid from  relationships a,relationships b where 
       a.person_1=b.person_2 and a.person_2=b.person_1 and a.person_1>a.person_2)

Но обычно я никогда не создаю таблицу без первичного ключа из одного столбца.

0 голосов
/ 10 июня 2009

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

select distinct(case when person_1 <= person_2 then person_1||'|'||person_2 else person_2||'|'||person_1 end)
from relationships;
0 голосов
/ 10 июня 2009

Я думаю, что KM почти понял все правильно, я добавил concat.

SELECT DISTINCT *
    FROM (SELECT DISTINCT concat(Person_1,Person_2) FROM RELATIONSHIPS
          UNION 
          SELECT DISTINCT concat(Person_2, Person_1) FROM RELATIONSHIPS
         ) dt
0 голосов
/ 10 июня 2009

Я думаю, что-то вроде этого должно сработать:

select * from RELATIONSHIPS group by PERSON_1, PERSON_2
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...