【问题标题】:Filter SQL query by a unique set of column values, regardless of their order通过一组唯一的列值过滤 SQL 查询,而不考虑它们的顺序
【发布时间】:2009-06-10 19:24:45
【问题描述】:

我在 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 = John 和 PERSON_2 = Jill,我不想看到另一个记录 PERSON_1 = Jill 和 PERSON_2 = John。

有没有简单的方法可以做到这一点?

【问题讨论】:

  • 您是否特别希望在表中允许此类“重复”并在查询时将其过滤掉?如果没有,那么您可能会考虑使用表检查约束或插入/更新触发器,检查表中是否不存在反转的主键。
  • 是的,我想在表中允许重复组合,但还需要找到一种最佳方式来检索唯一集合。

标签: sql oracle


【解决方案1】:

这种关系总是双向的吗?即如果 John 和 Jill 是相关的,那么是否存在 always 一个 {John,Jill} 和 {Jill,John} ?如果是这样,只限于那些 Person_1

【讨论】:

  • 这种关系并不总是双向存在的。事实上,它很少同时存在于两个方向。但是,这是可能的,所以我需要能够过滤掉重复项。不幸的是,数据库已经投入生产,因此使用这种方法将不起作用,因为存在一些记录,例如 Person_2
  • @tekBlues:没有所谓的“愚蠢”解决方案。欢迎所有想法。事实上,其中许多想法都非常有帮助,即使它们并不能全部解决我的具体问题。
【解决方案2】:
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;

【讨论】:

    【解决方案3】:

    未经测试:

    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
    

    问候, 抢。

    【讨论】:

      【解决方案4】:

      您应该在 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:我看到 Marc Gravell 的回答比我快,采用了类似的解决方案。

      【讨论】:

      • 我考虑过 - 但问题是它使“关系”值复杂化 - 即取决于 数字 你必须有“父亲”或“儿子”——很难管理。在预期的方向上进行查询也很困难,因为您需要尝试两者......(具有倒置关系)。在两个方向上保持关系似乎更简单;磁盘空间很便宜,对更多数据的简单查询通常会胜过对更少数据的复杂查询。
      • 是的,正如您所说,如果您需要搜索给定类型的关系,或者如果关系并不总是互惠的,那么我上面的解决方案并不能解决问题。
      【解决方案5】:

      您是否要防止重复项被插入到数据库中存在一些不确定性。您可能只想获取唯一对,同时保留重复项。

      所以这是后一种情况的替代解决方案,即使存在重复也查询唯一对:

      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;
      

      因此,如果有一个匹配行的 id 颠倒了,则有一个规则,查询应该更喜欢哪一个(id 以数字顺序排列的那个)。

      如果没有匹配的行,则 r2 将为 NULL(这是外连接的工作方式),因此在这种情况下,只需使用 r1 中找到的任何内容。

      无需使用GROUP BYDISTINCT,因为只能有0 或1 个匹配行。

      在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 | 
      +----+-------------+-------+--------+---------------+---------+---------+-----------------------------------+------+--------------------------+
      

      这似乎是对索引的合理使用。

      【讨论】:

      • 你是对的,我不想防止表中的重复,而是想获取唯一的对。谢谢你的建议。不幸的是,这种语法似乎不适用于 Oracle。 :(
      • 我在查询示例中编辑了 ON 子句。这样效果更好吗?
      【解决方案6】:

      我认为这样的事情应该可以解决问题:

      select * from RELATIONSHIPS group by PERSON_1, PERSON_2
      

      【讨论】:

        【解决方案7】:

        我认为 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
        

        【讨论】:

          【解决方案8】:

          它很笨拙,但它至少会告诉你你有哪些独特的组合,只是不是以一种真正方便的方式......

          select distinct(case when person_1 <= person_2 then person_1||'|'||person_2 else person_2||'|'||person_1 end)
          from relationships;
          

          【讨论】:

          【解决方案9】:

          可能最简单的解决方案(不需要更改数据结构或创建触发器)是创建一组没有重复条目的结果,并将其中一个重复条目添加到该集合中。

          看起来像:

           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)
          

          但通常我从不创建没有单列主键的表。

          【讨论】:

            【解决方案10】:

            你可以,

            与 rel 为 ( 选择 *, row_number() over (partition by least(person_1,person_2), 最大(person_1,person_2))作为 rn 从关系 ) 选择 * 从相对 其中 rn = 1;

            【讨论】:

              猜你喜欢
              • 2015-04-30
              • 2015-04-18
              • 1970-01-01
              • 2020-11-13
              • 2022-01-27
              • 1970-01-01
              • 1970-01-01
              • 2021-07-21
              • 2020-11-11
              相关资源
              最近更新 更多