web-dev-qa-db-ja.com

結合後に配列要素の順序を保持

次のようなCTEを返すクエリがあります

+-----------+-------------+
|   node_id | ancestors   |
|-----------+-------------|
|         1 | []          |
|         2 | []          |
|         3 | [1]         |
|         4 | [2]         |
|         5 | [4, 2]      |
+-----------+-------------+

私がやりたいのは、nodesテーブルと結合し、ancestors列にあるIDをnodesテーブルの別の列に置き換えることです。これまでの私のクエリは次のとおりです。

WITH RECURSIVE tree AS (
  -- snip --
)
SELECT node.entity_id AS id,
       array_remove(array_agg(parent_nodes.entity_id), NULL) AS ancestors
FROM tree
JOIN entity.nodes AS node ON node.id = tree.node_id
LEFT OUTER JOIN entity.nodes AS parent_nodes ON parent_nodes.id = ANY(tree.ancestors)
GROUP BY node.id;

このクエリの問題は、元のancestors配列の順序が失われることです。 array_agg関数で元の順序を維持しながら結合を実行する方法はありますか?

3
Eric Koslow

クエリの問題は、結合条件id = ANY(ancestors)です。元の順序が保持されないだけでなく、配列内の重複する要素が排除されます。 (idancestorsの10個の要素と一致する可能性がありますが、1回だけ選択されます。)クエリのロジックで重複する要素を許可するかどうかはわかりませんが、そうであればかなりわかりますすべてのインスタンスを保持したい-結局のところ「元の順序」を維持したい。

現在のPostgres9.4 +が情報不足であると仮定して、私は完全に異なるアプローチを提案します:

SELECT n.entity_id, p.ancestors
FROM   tree t
JOIN   nodes n ON n.id = t.node_id
LEFT   JOIN LATERAL (
   SELECT ARRAY (
      SELECT p.entity_id
      FROM   unnest(t.ancestors) WITH ORDINALITY a(id, ord)
      JOIN   entity.nodes p USING (id)
      ORDER  BY ord
      ) AS ancestors
   ) p ON true;

クエリは、nodes.idが主キーとして定義され、nodes.entity_idも一意である場合にのみ、意図したとおりに機能します。質問に情報がありません。

通常、明示的なORDER BYなしのこの単純なクエリも機能しますが、保証はありません(Postgres9.3 +)...

SELECT n.entity_id, p.ancestors
FROM   tree t
JOIN   nodes n ON n.id = t.node_id
LEFT   JOIN LATERAL (
   SELECT ARRAY (
      SELECT p.entity_id
      FROM   unnest(t.ancestors) id
      JOIN   entity.nodes p USING (id)
      ) AS ancestors
   ) p ON true;

これを安全にすることもできます。詳細な説明:

SQL Fiddle demo for Postgres 9.3。

Opionalの最適化

entity.nodesに2回参加します-node_idancestorsを同様に置き換えます。別の方法は、両方を1つの配列または1つのセットに折りたたみ、1回だけ結合することです。高速かもしれませんが、テストする必要があります。
これらの代替案では、どのような場合でもORDER BY必要です

ネストを解除する前に、node_idancestors配列に追加します...

SELECT p.arr[1] AS entity_id, p.arr[2:2147483647] AS ancestors
FROM   tree t
LEFT   JOIN LATERAL (
   SELECT ARRAY (
      SELECT p.entity_id
      FROM   unnest(t.node_id || t.ancestors) WITH ORDINALITY a(id, ord)
      JOIN   entity.nodes p USING (id)
      ORDER  BY ord
      ) AS arr
   ) p ON true;

または、結合する前にancestorsのネストされていない要素にnode_idを追加します...

SELECT p.arr[1] AS entity_id, p.arr[2:2147483647] AS ancestors
FROM   tree t
LEFT   JOIN LATERAL (
   SELECT ARRAY (
      SELECT p.entity_id
      FROM  (
         SELECT t.node_id AS id, 0 AS ord
         UNION ALL
         SELECT * FROM unnest(t.ancestors) WITH ORDINALITY
         ) x
      JOIN   entity.nodes p USING (id)
      ORDER  BY ord
      ) AS arr
   ) p ON true;

あなたは私たちのCTEを示さなかった、これはさらに最適化されるかもしれない...

4