postgres

时间:2016-05-05 17:52:46

标签: database postgresql hierarchical-data

我正从 Oracle 转移到 Postgresql 。我试图将一些Oracle分层查询转换为Postgres。例如,在Oracle中返回逗号分隔的所有ID的有序列表(即子项)并包括id_to_start_with我将执行以下操作:

SELECT LISTAGG(id_something, ',') WITHIN GROUP (ORDER BY id_something) AS somethings FROM(
SELECT DISTINCT D.id_something
FROM something_table D
START WITH D.id_something = :id_to_start_with
CONNECT BY D.id_something_parent = PRIOR D.id_something
)

Postgres中的等价物似乎是:

WITH RECURSIVE the_somethings(id_something) AS (
SELECT id_something
FROM something_table
WHERE id_something = $id_to_start_with
UNION ALL
SELECT D.id_something
FROM the_somethings DR
JOIN something_table D ON DR.id_something = D.id_something_parent
)
SELECT string_agg(temp_somethings.id_something::TEXT, ',') AS somethings
FROM (
SELECT id_something
FROM the_somethings
ORDER BY id_something
) AS temp_somethings

同样,如果我想返回上面所有id的逗号分隔的有序列表(即父项)并包括id_to_start_with我将在Oracle中执行以下操作:

SELECT LISTAGG(id_something, ',') WITHIN GROUP (ORDER BY id_something) AS somethings FROM(
SELECT DISTINCT D.id_something
FROM something_table D
START WITH D.id_something = :id_to_start_with
CONNECT BY D.id_something = PRIOR D.id_something_parent
)

Postgres中的等价物似乎是:

WITH RECURSIVE the_somethings(id_something, path) AS (
SELECT id_something
, id_something::TEXT as path
FROM something_table
WHERE id_something_parent IS NULL
UNION ALL
SELECT D.id_something
, (DR.path || ',' || D.id_something::TEXT) as path
FROM something_table D
JOIN the_somethings DR ON DR.id_something = D.id_something_parent
)
SELECT path
FROM the_somethings
WHERE id_something = $id_to_start_with
ORDER BY id_something

我的问题与最后一个Postgres查询有关。这对我来说似乎非常低效,我想知道是否有更好的方法来编写它。也就是说,在Oracle中,查询将查找id_to_start_with的父级,然后查找父级的父级,依此类推。查看根目录。

另一方面,Postgres查询可以获得每个单一根到子路径组合,然后将所有内容抛出,除了我要查找的一个根到id_to_start_with。除了我正在寻找的那一行之外,这可能是为了将它全部抛弃而创建的大量数据。

有没有办法获得逗号分隔的特定id_to_start_with父母的所有父母的有序列表,这些父母在Postgres中的性能与在Oracle中一样高?

编辑:从Oracle和Postgres添加解释计划。

Oracle解释计划输出 Oracle Plan ScreenShot

Postgres解释分析输出

CTE Scan on the_somethings  (cost=62.27..74.66 rows=3 width=76) (actual time=0.361..0.572 rows=1 loops=1)
  Filter: (id_something = 1047)
  Rows Removed by Filter: 82
  CTE the_somethings
    ->  Recursive Union  (cost=0.00..62.27 rows=551 width=76) (actual time=0.026..0.433 rows=83 loops=1)
          ->  Seq Scan on something_table  (cost=0.00..2.83 rows=1 width=8) (actual time=0.023..0.034 rows=1 loops=1)
                Filter: (id_something_parent IS NULL)
                Rows Removed by Filter: 82
          ->  Hash Join  (cost=0.33..4.84 rows=55 width=76) (actual time=0.028..0.065 rows=16 loops=5)
                Hash Cond: (d.id_something_parent = dr.id_something)
                ->  Seq Scan on something_table d  (cost=0.00..2.83 rows=83 width=16) (actual time=0.002..0.012 rows=83 loops=5)
                ->  Hash  (cost=0.20..0.20 rows=10 width=76) (actual time=0.009..0.009 rows=17 loops=5)
                      Buckets: 1024  Batches: 1  Memory Usage: 10kB
                      ->  WorkTable Scan on the_somethings dr  (cost=0.00..0.20 rows=10 width=76) (actual time=0.001..0.004 rows=17 loops=5)
Planning time: 0.407 ms
Execution time: 0.652 ms

这是基于Jakub的答案的最终查询。

WITH RECURSIVE the_somethings(id_something, path, level, orig_id, id_something_parent) AS (

SELECT id_something

, id_something::TEXT as path

, 0 as level

, id_something AS orig_id

, id_something_parent

FROM something_table

WHERE id_something IN (1047, 448)

UNION ALL

SELECT D.id_something

, (D.id_something::TEXT || ',' || DR.path) as path

, DR.level + 1 as level

, DR.orig_id as orig_id

, D.id_something_parent

FROM something_table D

JOIN the_somethings DR ON D.id_something = DR.id_something_parent

)

SELECT DISTINCT ON(orig_id) orig_id, path

FROM the_somethings

ORDER BY orig_id, level DESC
;

1 个答案:

答案 0 :(得分:1)

PostgreSQL中的CTE fenced意味着它们将被实现,只有这样才能应用外部查询的过滤器。要使查询正确执行,请以相反的方式构建它,并将过滤器放在CTE中。

WITH RECURSIVE the_somethings(id_something, path) AS (
SELECT id_something
, id_something::TEXT as path, 0 as level, id_something AS orig_id
FROM something_table
WHERE id_something IN ($id_to_start_with,$id_to_start_with2)
UNION ALL
SELECT D.id_something
, (D.id_something::TEXT || ',' || DR.path) as path, DR.level + 1, DR.orig_id
FROM something_table D
JOIN the_somethings DR ON DR.id_something_parent = D.id_something
)
SELECT DISTINCT ON(orig_id) orig_id, path
FROM the_somethings
ORDER BY orig_id, DR.level DESC