1

Using Postgres.

I have a pricelists

CREATE TABLE pricelists(
  id SERIAL PRIMARY KEY,
  name TEXT,
  parent_id INTEGER REFERENCES pricelists
);

and another table, prices, referencing it

CREATE TABLE prices(
  pricelist_id INTEGER REFERENCES pricelists,
  name TEXT,
  value INTEGER NOT NULL,
  PRIMARY KEY (pricelist_id, name)
);
  • Parent pricelist id=1 may have 10 prices.
  • Pricelist id=2 as a child of parent 1 may have 5 prices which override parent 1 prices of the same price name.
  • Child Pricelist id=3 as as a child of pricelist 2 may have 2 price which override child 2 prices of the same price name.

Thus when I ask for child 3 prices, I want to get

  • all prices of child 3 and
  • those prices of his parent (child 2) that do not exists in child 3 and
  • all parent 1 prices that do not exists until now.

The schema can be changed in order to be efficient.

Example:

If

SELECT pl.id AS id, pl.parent_id AS parent, p.name AS price_name, value
FROM pricelists pl
JOIN prices p ON pl.id = p.pricelist_id;

gives

| id       |      parent   |  price_name |     value   |  
|----------|:-------------:|------------:|------------:|  
| 1        |  1            | bb          |     10      |  
| 1        |  1            | cc          |     10      |  
| 2        |  1            | aa          |     20      |  
| 2        |  1            | bb          |     20      |  
| 3        |  2            | aa          |     30      |

then I'm looking for a way of fetching pricelist_id = 3 prices that'd give me

| id       |      parent   |  price_name |     value   |  
|----------|:-------------:|------------:|------------:|  
| 1        |  1            | cc          |     10      |  
| 2        |  1            | bb          |     20      |  
| 3        |  2            | aa          |     30      |
das-g
  • 9,718
  • 4
  • 38
  • 80
ZAky
  • 1,209
  • 8
  • 22

1 Answers1

1
WITH RECURSIVE cte AS (
   SELECT id, name, parent_id, 1 AS lvl
   FROM   pricelists
   WHERE  id = 3  -- provide your id here

   UNION ALL
   SELECT pl.id, pl.name, pl.parent_id, c.lvl + 1
   FROM   cte  c
   JOIN   pricelists pl ON pl.id = c.parent_id
   )
SELECT DISTINCT ON (p.price_name)
       c.id, c.parent_id, p.price_name, p.value
FROM   cte c
JOIN   prices p ON p.pricelist_id = c.id
ORDER  BY p.price_name, c.lvl;  -- lower lvl beats higher level
Community
  • 1
  • 1
Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
  • This query will not terminate if there are pricelists that have themselves as parents, as in the example given in the question. Though I [guess](http://stackoverflow.com/questions/29627270/tree-with-recursive-and-default/29628116#comment47403258_29627270) this might either be a mistake in the example data or be covered by the "The schema can be changed in order to be efficient." phrase. – das-g Apr 14 '15 at 15:07
  • 1
    @das-g. That's correct. The base table should have a constraint `CHECK (id <> parent_id)`. and the root should have `parent_id` is `NULL`. – Erwin Brandstetter Apr 14 '15 at 15:22
  • Ah, right, that should be part of the consistency requirements. I've proposed an edit to the question with that `CHECK`, even though it doesn't suffice to avoid loops in the pricelist directed graph in general. – das-g Apr 14 '15 at 15:46
  • @das-g: The `CHECK` constraint only rules out simple shortcut loops. all kinds of loops would violate a sane data model. – Erwin Brandstetter Apr 14 '15 at 15:59
  • That's what I wanted to point out in my previous comment. I just said it too convoluted, I guess. :-P – das-g Apr 14 '15 at 23:13
  • CHECK (id <> parent_id) prevent the situation of id==parent in a single record. But can I also prevent a parent loop, Where accidentally a child is set as his parent's parent? – ZAky Apr 16 '15 at 07:27
  • @ZAky: Not with basic tools. You would have to write a trigger function, that iterates the lineage and collects IDs along the way to detects an endless loop. Here is an implementation: http://stackoverflow.com/questions/26671612/prevent-and-or-detect-cycles-in-postgres/26672812#26672812 – Erwin Brandstetter Apr 16 '15 at 12:50