sql - 如何用SQL标记 "transitive groups"?

标签 sql algorithm postgresql

我有一个表,其中的 ID 对位于 transitive relationt,也就是说,如果“A t B”和“B t C”则“A t C ”。示例:

  table T1
  ID1 | ID2 
  1   | 2
  1   | 5
  4   | 7
  7   | 8
  9   | 1

所以有两组,

  • g1:{1,2,5,9} 因为“1 t 2”、“1 t 5”和“9 t 1"
  • g2:{4,7,8} 因为“4 t 7”和“7 t 8”

我需要通过“纯标准 SQL”生成一个新表或 View :

  table T2
  ID1 | ID2 | LABEL 
  1   | 2   | 1
  1   | 5   | 1
  4   | 7   | 2
  7   | 8   | 2
  9   | 1   | 1

PS-1:我们可以通过

列出“传递组”
  SELECT DISTINCT label, id   
  FROM (SELECT id1 as id, * FROM T2) UNION (SELECT id2 as id, * FROM T2)
  ORDER BY 1,2;

PS-2:我正在使用 PostgreSQL 9.1,但如果有“标准 SQL”的解决方案,我更喜欢。

最佳答案

你可以在 Postgres 中做到这一点;您不能在所有数据库中执行此操作。这是查询:

with 
    recursive cte(id1, id2) as (
     select id1, id2, 1 as level
     from t
     union all
     select t.id1, cte.id2, cte.level + 1
     from t join
          cte
          on t.id2 = cte.id1
  )
select id1, id2,
       dense_rank() over (order by grp) as label
from (select id1, id2,
             least(min(id2) over (partition by id1), min(id1) over (partition by id2)) as grp,
             level
      from cte
     ) t
where level = 1;

使用 SQL Fiddle here .

您正在遍历树结构以分配标签(顺便说一句,循环可能会给这个特定版本带来问题)。在 Postgres 中,您可以使用显式 recursive CTE 来执行此操作。在 SQL Server 中,您可以使用隐式“递归”(不使用关键字)的 CTE 来执行此操作。在 Oracle 中,您可以使用 connect by 执行此操作。

递归 CTE 获取所有相互连接的对。主查询然后将 id1 和 id2 的最小值分配给该对,以识别所有相互连接的对。只需为 grp 分配一个顺序值即可生成最终标签。

编辑:

Egor 提出了一个很好的观点。以上假设 ids“下降”到较小的值。以下版本改为对分组的每个 id 使用最高级别(这确实是预期的):

with 
    recursive cte(id1, id2) as (
     select id1, id2, 1 as level
     from t
     union all
     select t.id1, cte.id2, cte.level + 1
     from t join
          cte
          on t.id2 = cte.id1
    --  where not exists (select 1 from cte cte2 where cte2.id1 = t.id1 and cte2.id2 = t.id2) 
  ) 
select id1, id2,
       dense_rank() over (order by topvalue) as label
from (select id1, id2,
             first_value(id2) over (partition by id1 order by level desc) as topvalue,
             level
      from cte
     ) t
where level = 1;

编辑二:

回应 Egor 的第二条评论。这个数据相对于原来的问题有点问题。以下将其分为两部分:

with 
    recursive cte as (
     select id1, id2, id2 as last, id1||','||id2 as grp, 1 as level
     from t
     where id2 not in (select id1 from t)
     union all
     select t.id1, t.id2, cte.last, cte.grp, cte.level + 1
     from t join
          cte
          on t.id2 = cte.id1
    --  where not exists (select 1 from cte cte2 where cte2.id1 = t.id1 and cte2.id2 = t.id2) 
  ) 
select *
from cte;

但是,不清楚这是否是原始人想要的。它会把原来的分成三个重叠的组,因为第二列中有三个 id 从不在第一列中。这里的问题是关于交换性。

关于sql - 如何用SQL标记 "transitive groups"?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/18033115/

相关文章:

c++ - 什么是从 visual c++ 到 sql 的好 api

algorithm - 二叉树的实现

sql - 有一个带有 pcode 的表,在不同的行中有许多日期。如何只选择密码的最新日期

c - 构建有向无环词图 (DAWG) 的最佳方法

php - 在redbean php中获取 'owner' bean

postgresql - 需要在事务中插入的当前行的 ID

sql - PG::InvalidColumnReference: 错误:对于 Ruby on Rails 应用程序中的 SELECT DISTINCT

sql - PL/SQL 正则表达式检查

mysql - select 语句中的子查询找不到派生表?

javascript - 理解餐 table 最佳座位算法的问题