sql >> Databasteknik >  >> RDS >> PostgreSQL

Postgres rekursiv fråga med row_to_json

Ursäkta för det mycket sena svaret men jag tror att jag hittade en elegant lösning som skulle kunna bli ett accepterat svar på den här frågan.

Baserat på det fantastiska "lilla hacket" som @pozs hittade, kom jag på en lösning som:

  • löser situationen med "skurkiga lämnar" med väldigt lite kod (utnyttjar NOT EXISTS predikat)
  • undviker hela nivåberäkningen/villkoren
WITH RECURSIVE customer_area_tree("id", "customer_id", "parent_id", "name", "description", "children") AS (
  -- tree leaves (no matching children)
  SELECT c.*, json '[]'
  FROM customer_area_node c
  WHERE NOT EXISTS(SELECT * FROM customer_area_node AS hypothetic_child WHERE hypothetic_child.parent_id = c.id)

  UNION ALL

  -- pozs's awesome "little hack"
  SELECT (parent).*, json_agg(child) AS "children"
  FROM (
    SELECT parent, child
    FROM customer_area_tree AS child
    JOIN customer_area_node parent ON parent.id = child.parent_id
  ) branch
  GROUP BY branch.parent
)
SELECT json_agg(t)
FROM customer_area_tree t
LEFT JOIN customer_area_node AS hypothetic_parent ON(hypothetic_parent.id = t.parent_id)
WHERE hypothetic_parent.id IS NULL

Uppdatera :

Testat med mycket enkel data fungerar det, men som posz påpekade i en kommentar, med hans exempeldata, glöms några oseriösa lövnoder. Men jag fick reda på att med ännu mer komplexa data fungerar inte det tidigare svaret heller, eftersom endast oseriösa lövnoder som har en gemensam förfader med "maxnivå" lövnoder fångas (när "1.2.5.8" inte finns där, " 1.2.4" och "1.2.5" saknas eftersom de inte har någon gemensam förfader med någon "maxnivå" lövnod).

Så här är ett nytt förslag som blandar poszs arbete med mitt genom att extrahera NOT EXISTS subrequest och gör den till en intern UNION , utnyttjar UNION de-dupliceringsförmåga (som utnyttjar jsonbs jämförelseförmåga):

<!-- language: sql -->
WITH RECURSIVE
c_with_level AS (

    SELECT *, 0 as lvl
    FROM   customer_area_node
    WHERE  parent_id IS NULL

    UNION ALL

    SELECT child.*, parent.lvl + 1
    FROM   customer_area_node child
    JOIN   c_with_level parent ON parent.id = child.parent_id
),
maxlvl AS (
  SELECT max(lvl) maxlvl FROM c_with_level
),
c_tree AS (
    SELECT c_with_level.*, jsonb '[]' children
    FROM   c_with_level, maxlvl
    WHERE  lvl = maxlvl

    UNION 
    (
        SELECT (branch_parent).*, jsonb_agg(branch_child)
        FROM (
            SELECT branch_parent, branch_child
            FROM c_with_level branch_parent
            JOIN c_tree branch_child ON branch_child.parent_id = branch_parent.id
        ) branch
        GROUP BY branch.branch_parent

        UNION

        SELECT c.*, jsonb '[]' children
        FROM   c_with_level c
        WHERE  NOT EXISTS (SELECT 1 FROM c_with_level hypothetical_child WHERE hypothetical_child.parent_id = c.id)
    )
)
SELECT jsonb_pretty(row_to_json(c_tree)::jsonb)
FROM c_tree
WHERE lvl = 0;

Testad på http://rextester.com/SMM38494;)



  1. Hur man undslipper sträng medan man matchar mönster i PostgreSQL

  2. MIN() Funktion i PostgreSQL

  3. Insekt? #1146 - Tabell 'xxx.xxxxx' finns inte

  4. korrekt vilolägeskommentar för byte[]