2

I have a table in PostgreSQL that contains:

id   name   arrayofparents
1     First
2     Second      {1}
3     Second_Sec  {1,2}
4     Third       {1,2,3}
5     Second_A    {1}
6     Other
7     Trash       {6}

arrayofparents is of type integer[] it contains a list of the parents records for that row with the correct order.

id=4 parents are: First then Second then Second_sec

How do I write a query that for any given id it will generate a string of it's parents name?

for example:

id=3: First->Second.

id=4: First->Second->Second_sec.

id=7: Other.

Edit: if possible I prefer the requested id name will always appear. id=3: First->Second->Second_sec.

id=4: First->Second->Second_sec->Third.

id=7: Other->Trash.

id=6: Other.

avi
  • 1,626
  • 3
  • 27
  • 45
  • 4
    This is a bad DB design as (to my knowledge) you cannot enforce referential integrity with an array type (see http://dba.stackexchange.com/questions/60132/foreign-key-constraint-on-array-member ). I recommend changing your design to use a "join table", then you can use a recursive CTE to solve your problem. – Dai Dec 06 '15 at 07:50
  • The field arrayofparents is used for other porpuses.. mostly to with Any query.... I just wanted to take advantage of it. I can not change the table. – avi Dec 06 '15 at 08:28

3 Answers3

2

you can combine muliple operations like generate_subscripts and array to get the result:

with mtab as (
      SELECT id, name, array_append(arrayofparents,id) as arrayofparents,
      generate_subscripts(array_append(arrayofparents, id), 1) AS p_id FROM tab where id=2
)
select distinct array_to_string(
  array(
    select tab.name from tab join mtab t on tab.id=t.arrayofparents[t.p_id]
  ), '->'
) ;

live example Sqlfiddle

or use outer join combined with any:

SELECT coalesce(string_agg(p.name, '->') || '->' || t.name, t.name) AS parentnames
FROM tab AS t
  LEFT JOIN tab AS p ON p.id = ANY(t.arrayofparents)
 where t.id =7 
GROUP BY t.id, t.name

live example Sqlfiddle

Mauri
  • 1,185
  • 1
  • 13
  • 21
  • it doesn't work... check it on `id=7` it gives `First`.... it should give `Other` – avi Dec 06 '15 at 09:19
  • Question.. if I also want to add the current id name ? for example `id=7` : `Other -> Trash`. instad of `Other`. meaning add the current id name at the end every time. is it doable? – avi Dec 06 '15 at 09:46
  • it's working but it doesn't make sure that Trash is at the end. the union cause the order to be massed up. it can generate Trash-> Other. – avi Dec 06 '15 at 11:39
  • 1
    can you try the second option? – Mauri Dec 06 '15 at 13:01
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/97113/discussion-between-mohamed-el-housseine-and-avi). – Mauri Dec 06 '15 at 13:19
  • your second example does the order correctly but it doesn't work on the the "head" id's... in ur sql fiddle id=6. it returns no rows. it should include at least the id name – avi Dec 06 '15 at 14:52
  • 1
    i modified the second version (the new first version) and it can now return all items – Mauri Dec 06 '15 at 16:04
  • Both queries are not guaranteed to retain the original order of elements in their current state. – Erwin Brandstetter Dec 07 '15 at 20:13
1

Each of these queries work for a single id as well as for the whole table.
And you can return just the path / the full path or all other columns as well.

SELECT t.*, concat_ws('->', t1.path, t.name) AS full_path
FROM   tbl t
LEFT   JOIN LATERAL (
   SELECT string_agg(t1.name, '->' ORDER  BY i) AS path
   FROM   generate_subscripts(t.arrayofparents, 1) i
   JOIN   tbl t1 ON t1.id = t.arrayofparents[i]   
   ) t1 ON true
WHERE  t.id = 4;  -- optional

Alternatively, you could move the ORDER BY to a subquery - may be a bit faster:

SELECT concat_ws('->', t1.path, t.name) AS full_path
FROM   tbl t, LATERAL (
   SELECT string_agg(t1.name, '->') AS path
   FROM  (
      SELECT t1.name
      FROM   generate_subscripts(t.arrayofparents, 1) i
      JOIN   tbl t1 ON t1.id = t.arrayofparents[i]
      ORDER  BY i
      ) t1
   ) t1
WHERE  t.id = 4;  -- optional

Since the aggregation happens in the LATERAL subquery we don't need a GROUP BY step in the outer query.

We also don't need LEFT JOIN LATERAL ... ON true to retain all rows where arrayofparents is NULL or empty, because the LATERAL subquery always returns a row due to the aggregate function.
LATERAL requires Postgres 9.3.

Use concat_ws() to ignore possible NULL values in the concatenation.

SQL Fiddle.

WITH OTDINALITY makes it a bit simpler and faster in Postgres 9.4:

SELECT t.*, concat_ws('->', t1.path, t.name) AS full_path
FROM   tbl t, LATERAL (
   SELECT string_agg(t1.name, '->' ORDER BY ord) AS path
   FROM   unnest(t.arrayofparents) WITH ORDINALITY a(id,ord)
   JOIN   tbl t1 USING (id)  
   ) t1
WHERE  t.id = 4;

Detailed explanation:


Variant with UNION ALL for pg 9.3

SELECT t1.full_path
FROM   tbl t, LATERAL (
   SELECT string_agg(name, '->') AS full_path
   FROM  (
      (
      SELECT name
      FROM   generate_subscripts(t.arrayofparents, 1) i
      JOIN   tbl ON id = t.arrayofparents[i]
      ORDER  BY i
      )
      UNION ALL SELECT t.name
      ) t1
   ) t1
WHERE  t.id = 4;
Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • Can you show how to make sure that the id will always appear? see my question edit. – avi Dec 06 '15 at 14:59
  • unnest doesn't keep the order... it messes up the records.. it can show Second->First->Second_sec instad of First->Second->Second_sec – avi Dec 06 '15 at 15:08
  • 1
    @avi: Right, well mostly. Just like I explained in detail in the linked answer. `unnest()` itself reains the original order, but the *join* messes with it. I fixed that. Plus, the join condition was also missing. And I added the name of the current row according to your Q update. Each of these queries should now be faster than the currently accepted answer (besides being correct). Test with `EXPLAIN ANALYZE`. – Erwin Brandstetter Dec 07 '15 at 19:17
0

If you only want direct parents (not grandparents) then something like this should work:

SELECT c.id, c.name, string_agg(p.name, '->') AS parentnames
FROM yourtable AS c
  LEFT JOIN yourtable AS p ON p.id = ANY c.parents
GROUP BY c.id, c.name
Eelke
  • 20,897
  • 4
  • 50
  • 76
  • it says: ERROR: syntax error at or near "c". I need all parents from what is written in arrayofparents – avi Dec 06 '15 at 08:55