web-dev-qa-db-ja.com

重複することなく、重複して配列をグループ化する

私が見つけた:

しかし、私の場合に使用するのに苦労しています。

私はそのようなテーブルを持っています(実際の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');

私がどのように生成するかを理解できない結果は次のとおりです:

         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}

ここでのロジックは、共通のIPを持つすべてのIDがグループ化されることですtransitively。たとえば、0aには0bと共通のIPがあります。 0bには12との共通点があります。 121eと共通のものがあります。

数万行あり、特定のIDのIPの数に特定の制限はなく、特定のIPを表示できるIDの数に特定の制限はありません。

IPで集計する方法、またはIDで集計する方法を知っていますが、bothを推移的に実行すると問題が発生します。再帰的なCTEを試しましたが、正しく理解できなかったため、そもそもそれが正しいアプローチであったかどうかはわかりません。 (最初にIDでグループ化し、次に重複するIPの配列でグループ化し、集約の重複を回避できれば、すべて設定できますが、より良いアプローチがあるかもしれません。)

標準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
;
5
Wildcard

Group by array overlay のJack Douglasのソリューションの重要な部分の1つは、再帰的なt CTEのような再帰的な部分の配列で使用される|(パイプ)演算子ですこの:

...
select t.id, a.id, t.clst | a.clst
...

この演算子は、重複するアイテムを抑制する2つの配列を連結します。設定に直接回答を適用できないのは、int配列に対してのみ|演算子が定義されているようですが、inetに対して同じ操作を実行する方法が必要だからです。配列。

これを行うには、配列を行セットとして扱います。気づいた場合、|演算子が生成するのは、事実上2つのセットの和集合です。したがって、両方の配列をunnest、それらをunion結合し、組み合わせたセットを配列として集計すると、同じ結果が得られます。だから、この表現、

t.clst | a.clst

相関サブクエリで置き換えることができます:

(
  select
    array_agg(sub.n)
  from
    (
      select unnest(t.clst)
      union
      select unnest(a.clst)
    ) as sub (n)
)

はい、置換は比較してかなり扱いにくいですが、それは仕事をします、そしてそれは最初に何かです。

ソリューションをあなたの例に適合させて(そして元のコードに少し空白を追加して)、完全なクエリは次のようになります:

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
;

このデモ でクエリをテストできます dbfiddle logodb <> fiddle.uk。

ジャックの注意事項は、おそらくあなたの状況にも当てはまることに注意してください。

これが数百万行でうまく機能する可能性は低いことを覚えておいてください。

7
Andriy M