1

I have two tables in PostgreSQL, class and inheritance.
Each row in inheritance has 2 class IDs source_id and target_id:

CREATE TABLE public.class (
    id bigint NOT NULL DEFAULT nextval('class_id_seq'::regclass),
    name character varying(500) COLLATE pg_catalog."default" NOT NULL,
    CONSTRAINT class_pkey PRIMARY KEY (id)
)

CREATE TABLE public.inheritance (
    id bigint NOT NULL DEFAULT nextval('inherited_id_seq'::regclass),
    source_id bigint NOT NULL,
    target_id bigint NOT NULL,
    CONSTRAINT inherited_pkey PRIMARY KEY (id),
    CONSTRAINT inherited_source_id_fkey FOREIGN KEY (source_id)
        REFERENCES public.class (id),
    CONSTRAINT inherited_target_id_fkey FOREIGN KEY (target_id)
        REFERENCES public.class (id)
)

I want to create Access Matrix between all classes based in inheritance relationship in inheritance table. I try this code:

select * , case when id in (select target_id from inheritance where source_id=1) then 1 else 0 end as "1"   
         , case when id in (select target_id from inheritance where source_id=2) then 1 else 0 end as "2"
         , case when id in (select target_id from inheritance where source_id=3) then 1 else 0 end as "3"
         , case when id in (select target_id from inheritance where source_id=4) then 1 else 0 end as "4"
         , case when id in (select target_id from inheritance where source_id=5) then 1 else 0 end as "5"
         , case when id in (select target_id from inheritance where source_id=6) then 1 else 0 end as "6"
         , case when id in (select target_id from inheritance where source_id=7) then 1 else 0 end as "7"
         , case when id in (select target_id from inheritance where source_id=8) then 1 else 0 end as "8"
         , case when id in (select target_id from inheritance where source_id=9) then 1 else 0 end as "9"
from class

and get the right answer, but it's just for 9 static rows in class.

enter image description here

How can I get all number of rows in class using a dynamic SQL command?

If we can't do it with SQL, how can we do it with PL/pgSQL?

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228
Gholamali Irani
  • 4,391
  • 6
  • 28
  • 59
  • AFAIK you simply cannot achieve this. You need to know the number of columns beforehand. You cannot create a query with an arbitrary number of columns. – S-Man Jun 30 '22 at 14:40
  • What do you mean with "*If we can't do it with SQL*"? Do you have flexibility on the DMBS to be used? – lemon Jul 03 '22 at 16:21
  • I need dynamic SQL command in PostgreSQL to do that, if it is impossible, I need PL/pgSQL commands, just in PostgreSQL. – Gholamali Irani Jul 03 '22 at 20:03
  • 1
    Please post sample input data as formatted text - **no images** - and the expected results of that data. See [here](https://senseful.github.io/text-table/) for good method to create formatted text. Even better provide a [fiddle](https://dbfiddle.uk/?rdbms=postgres_14). – Belayer Jul 04 '22 at 19:03

1 Answers1

2

Static solution

SQL demands to know name and type of each result column (and consequently their number) at call time. You cannot derive result columns from data dynamically with plain SQL. You can use an array or a document type instead of separate columns:

SELECT *
FROM   class c
LEFT   JOIN (
   SELECT target_id AS id, array_agg(source_id) AS sources
   FROM  (SELECT source_id, target_id FROM inheritance i ORDER BY 1,2) sub
   GROUP  BY 1
   ) i USING (id);
id name sources
1 c1 {2,3,4}
2 c2 {5}
3 c3 {5,6,7}
4 c4 {7}
5 c5 {8}
6 c6 {9}
7 c7 {9}
8 c8 null
9 c9 null

Dynamic solution

If that's not good enough you need dynamic SQL with two round-trips to the DB server: 1. Generate SQL. 2. Execute SQL. Using the crosstab() function from the additional module tablefunc. If you are unfamiliar, read this first:

  1. Generate SQL:
SELECT format(
$q$SELECT *
FROM   class c
LEFT   JOIN crosstab(
   'SELECT target_id, source_id, 1 FROM inheritance ORDER BY 1,2'
 , 'VALUES (%s)'
   ) AS ct (id int, %s int)
     USING (id)
ORDER  BY id;
$q$
        , string_agg(c.id::text, '), (')
        , string_agg('"' || c.id || '"', ' int, ')
      )
FROM  (SELECT id FROM class ORDER BY 1) c;

Returns a query of this form, which we ...
2. Execute:

SELECT *
FROM   class c
LEFT   JOIN crosstab(
   'SELECT target_id, source_id, 1 FROM inheritance ORDER BY 1,2'
 , 'VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9)'
   ) AS ct (id int, "1" int, "2" int, "3" int, "4" int, "5" int, "6" int, "7" int, "8" int, "9" int)
     USING (id)
ORDER  BY id;

... to get:

 id | name | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9
----+------+---+---+---+---+---+---+---+---+---
  1 | c1   |   | 1 | 1 | 1 |   |   |   |   |  
  2 | c2   |   |   |   |   | 1 |   |   |   |  
  3 | c3   |   |   |   |   | 1 | 1 | 1 |   |  
  4 | c4   |   |   |   |   |   |   | 1 |   |  
  5 | c5   |   |   |   |   |   |   |   | 1 |  
  6 | c6   |   |   |   |   |   |   |   |   | 1
  7 | c7   |   |   |   |   |   |   |   |   | 1
  8 | c8   |   |   |   |   |   |   |   |   |  
  9 | c9   |   |   |   |   |   |   |   |   |  

db<>fiddle here

See:

Dynamic execution with psql

Still two round-trips to the server, but only a single command.
Both of the following solutions use psql meta-commands and only work from within psql!

With \gexec

Using the standard interactive terminal, you can feed the generated SQL back to the Postgres server for execution directly with \gexec:

test=> SELECT format(
$q$SELECT *
FROM   class c
LEFT   JOIN crosstab(
'SELECT target_id, source_id, 1 FROM inheritance ORDER BY 1,2'
, 'VALUES (%s)'
) AS ct (id int, %s int)
  USING (id)
ORDER  BY id;
$q$
     , string_agg(c.id::text, '), (')
     , string_agg('c' || c.id, ' int, ')
   )
FROM  (SELECT id FROM class ORDER BY 1) c\gexec

Same result.

With \crosstabview

test=> SELECT *
test-> FROM   class c
test-> LEFT   JOIN (
test(>    SELECT target_id AS id, source_id, 1 AS val
test(>    FROM   inheritance
test(>    ) i USING (id)
test-> \crosstabview id source_id val
 id | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |  
----+---+---+---+---+---+---+---+---+--
  1 | 1 | 1 | 1 |   |   |   |   |   | 
  2 |   |   |   | 1 |   |   |   |   | 
  3 |   |   |   | 1 | 1 | 1 |   |   | 
  4 |   |   |   |   |   | 1 |   |   | 
  5 |   |   |   |   |   |   | 1 |   | 
  6 |   |   |   |   |   |   |   | 1 | 
  7 |   |   |   |   |   |   |   | 1 | 
  8 |   |   |   |   |   |   |   |   | 
  9 |   |   |   |   |   |   |   |   | 
(9 rows)

See (with related answers for both):

There are lots of subtleties in these solutions ...

Aside

Assuming there are some mechanisms in place to disallow duplicates and directly contradicting relationships. Like:

CREATE UNIQUE INDEX inheritance_uni_idx
ON inheritance (GREATEST(source_id, target_id), LEAST(source_id, target_id));

See:

Erwin Brandstetter
  • 605,456
  • 145
  • 1,078
  • 1,228