按重叠数组分组,可传递,无重复

Wil*_*ard 5 postgresql aggregate array recursive

我发现:

但是,我无法将其用于我的案例。

我有一个像这样的表(实际myid值是散列,但为了说明在这里简化了):

create temp table a (myid text, ip inet);
insert into a (myid, ip)
values
  ('0a', '10.10.1.1'),
  ('0a', '10.10.1.2'),
  ('0a', '10.10.1.3'),
  ('0b', '10.10.1.2'),
  ('0b', '10.10.1.4'),
  ('0c', '10.10.1.5'),
  ('0d', '10.10.1.3'),
  ('0e', '10.10.1.6'),
  ('0e', '10.10.1.7'),
  ('0f', '10.10.1.8'),
  ('0f', '10.10.1.9'),
  ('10', '10.10.1.9'),
  ('11', '10.10.1.10'),
  ('12', '10.10.1.11'),
  ('12', '10.10.1.4'),
  ('1a', '10.10.1.2'),
  ('1a', '10.10.1.4'),
  ('1e', '10.10.1.11'),
  ('1f', '10.10.1.12'),
  ('23', '10.10.1.12');
Run Code Online (Sandbox Code Playgroud)

我无法弄清楚如何产生的结果是:

         ids         |                         ips
---------------------+------------------------------------------------------
 {0a,0b,0d,12,1a,1e} | {10.10.1.1,10.10.1.2,10.10.1.3,10.10.1.4,10.10.1.11}
 {0c}                | {10.10.1.5}
 {0e}                | {10.10.1.6,10.10.1.7}
 {0f,10}             | {10.10.1.8,10.10.1.9}
 {11}                | {10.10.1.10}
 {1f,23}             | {10.10.1.12}
Run Code Online (Sandbox Code Playgroud)

这里的逻辑是,任何具有共同 ips 的 id 都可以传递地组合在一起。例如,0a0b;有一个共同的 ip 。0b有一个共同点1212有一个共同点1e,等等。

有数以万计的行,对于任何给定 id 的 ip 数量没有具体限制,对于可以显示任何给定 ip 的 id 数量没有具体限制。

我知道如何按 ip 聚合或按 id 聚合,但是两者都传递给我带来麻烦。我尝试了递归 CTE,但似乎无法正确使用,而且我不确定这是否是正确的方法。(如果我可以先按 id 分组,然后按重叠的 ips 数组分组,并避免聚合中的重复,我就准备好了,但可能有更好的方法。)

有没有办法用标准 SQL 产生上述结果?或者至少使用标准的 Postgres?(我使用的是 9.6.6。)


这是一次失败的尝试。(这是一个确实返回结果的合法查询,但不是所需的结果。)它失败的原因是:

  1. 它包括中间结果,而不是用后来的结果替换它们,并且
  2. 它不对数组串联进行排序,因此它多次包含每个结果。对于我正在使用的实际数据集,这也是一个非常缓慢的查询,因为它返回n!每个结果的次数。

这是查询:

with recursive b as (
  select
    array[myid] as ids,
    array_agg(ip) as ips 
  from a
  group by myid
), c as (
  select
    ids,
    ips
  from b
  union
  select
    b.ids || c.ids,
    b.ips || c.ips
  from
    b
    join c on
      (not b.ids && c.ids)
      and (b.ips && c.ips)
)
select * from c
;
Run Code Online (Sandbox Code Playgroud)

And*_*y M 7

Jack Douglas 在Group by 数组重叠中的解决方案的关键部分之一|是在递归tCTE的递归部分中的数组上使用的(管道)运算符,如下所示:

...
select t.id, a.id, t.clst | a.clst
...
Run Code Online (Sandbox Code Playgroud)

此运算符连接两个数组以抑制重复项。答案不能直接应用于您的设置的原因是因为显然该|运算符int仅针对数组定义,而您需要一种对inet数组执行相同操作的方法。

您可以通过将数组视为行集来做到这一点。如果您注意到,|运算符产生的实际上是两个集合的并集。因此,如果您将unnest两个数组,union它们并将组合集合聚合为一个数组,您将获得相同的结果。所以这个表情,

t.clst | a.clst
Run Code Online (Sandbox Code Playgroud)

可以用相关子查询替换:

(
  select
    array_agg(sub.n)
  from
    (
      select unnest(t.clst)
      union
      select unnest(a.clst)
    ) as sub (n)
)
Run Code Online (Sandbox Code Playgroud)

是的,相比之下,替代是相当笨拙的,但它可以完成工作,这是开始的事情。

根据您的示例调整解决方案(并在原始代码中添加一些空格),完整的查询将如下所示:

with recursive
  cte_a as
  (
    select
      myid,
      array_agg(distinct ip) as ip
    from
      a
    group by
      myid
  )
, cte_t (myid, pmyid, ip) as
  (
    select
      myid,
      myid,
      ip
    from
      cte_a

    union all

    select
      t.myid,
      a.myid,

      (  /* this is the replacement expression */
        select
          array_agg(sub.n)
        from
          (
            select unnest(t.ip)

            union

            select unnest(a.ip)
          ) as sub (n)
      )

    from
      cte_t as t
      join cte_a as a
        on a.myid <> t.pmyid and t.ip && a.ip and not t.ip @> a.ip
  )
, cte_d as
  (
    select distinct on (myid)
      myid,
      ip
    from
      cte_t
    order by
      myid,
      cardinality(ip) desc
  )
select
  array_agg(myid),
  ip
from
  cte_d
group by
  ip
;
Run Code Online (Sandbox Code Playgroud)

您可以在此演示中测试查询dbfiddle 徽标db<>fiddle.uk。

另请注意,Jack 的警告可能也适用于您的情况:

请记住,这不太可能在数百万行上表现良好。