0

I have a table similar to this: (Postgressql v.10.8)

id  ref     street      city    total   year
1   2077    Burban Str. London  10000   2010
2   2077    Burban Str. London  12000   2011
3   2077    Burban Str. London  14000   2012
4   2077    Burban Str. London  14000   2013
5   2077    Burban Str. London  10000   2014
6   2077    Burban Str. London  19000   2015
7   2077    Burban Str. London  20000   2016
8   2077    Burban Str. London  10000   2017
9   2077    Burban Str. London  20000   2018
10  2077    Burban Str. London  11000   2019
11  2077    Burban Str. London  13000   2020
12  1000    Ocean road  London  9000    2018
13  1000    Ocean road  London  10000   2019
14  1000    Ocean road  London  12000   2020
15  2000    City Str.   Manchester  500 2019
16  2000    City Str.   Manchester  800 2020

With this table i want to create a function with ref and year as parameters.

If i call the function like this select * from myfunction(2077,2020) i want to get the following output:

ref    street        city     2020    2019     difference
2077   Burban Str.   London   13000   11000    2000

As you can see i want to compare the year 2020 (from parameter) with the year before and also pivot the query so that the years become column with the total as values.

This is as far i come with my knowledge.. with this query i almost get the right output but i don't how to solve the problem with the years :( Do i need to go and hardcode like 50 years here to cover up all possible years or is there i way to use my parameter here since the year is unknown?

select * from crosstab('select ref,street,city,year,total from mytable order by 1,4',$$values ('2010'::text),('2011'::text),('2012'::text),('2013'::text),('2014'::text),('2015'::text),('2016'::text),('2017'::text),('2018'::text),('2019'::text),('2020'::text)$$)
as ct  (ref int,street text,city text,"2010" int,"2011" int,"2012" int,"2013" int,"2014" int,"2015" int,"2016" int,"2017" int,"2018" int,"2019" int,"2020" int);

Demo

So if send parameters ref 2077 and year 2020 the query has to be something like this:

select * from crosstab('select ref,street,city,year,total from mytable order by 1,4',$$values ('2019'::text),('2020'::text)$$)
    as ct  (ref int,street text,city text,"2019" int,"2020" int);
Edouard
  • 6,577
  • 1
  • 9
  • 20
user2210516
  • 613
  • 3
  • 15
  • 32

1 Answers1

0

Naming dynamically the columns of a query's result in postgres is not obvious. Here is a full-dynamic solution based on a user-defined composite type and the standard jsonb function jsonb_populate_record :

First we create dynamically the composite type which correspond to a specific year with a procedure :

CREATE OR REPLACE PROCEDURE composite_type(_year integer)
LANGUAGE plpgsql AS
$$
BEGIN
  EXECUTE 'DROP TYPE IF EXISTS ' || quote_ident(_year :: text) ; 
  EXECUTE 'CREATE TYPE ' || quote_ident(_year :: text) || ' AS (ref integer, street text, city text, ' || quote_ident(_year :: text) || ' integer, ' || quote_ident((_year - 1) :: text) || ' integer, difference integer)' ;
END ;
$$ ;

Then we create the generic function test and which implements a dynamic query :

CREATE OR REPLACE FUNCTION test(_ref integer, _year integer)
RETURNS record LANGUAGE plpgsql AS
$$
DECLARE
   row record ;
BEGIN
   EXECUTE 
   'SELECT ref, street, city
         , (array_agg(total ORDER BY year))[2] AS ' || quote_ident(_year :: text) || '
         , (array_agg(total ORDER BY year))[1] AS ' || quote_ident((_year - 1) :: text) || '
         , (array_agg(total ORDER BY year))[2] - (array_agg(total ORDER BY year))[1] AS difference
      FROM mytable
     WHERE ref = ' || quote_nullable(_ref :: text) || '
       AND year BETWEEN ' || quote_nullable((_year - 1) :: text) || ' AND ' || quote_nullable(_year :: text) || '
     GROUP BY ref, street, city' INTO row ;
   RETURN row ;
END ;
$$ ;

Finally, we can call the procedure composite_type and then the function test with the expected input parameters year and ref :

CALL composite_type(2020) ;
SELECT (jsonb_populate_record(NULL :: "2020", to_jsonb(test(2077, 2020)))).* ;

see the result in dbfiddle.

Edouard
  • 6,577
  • 1
  • 9
  • 20
  • Thanks for your reply, sadly im on postgres v 10 and not on 14. so i can't use Procedure and i guess jsonb_populate_record also :( – user2210516 Jan 28 '22 at 07:19
  • It is not a big issue, just create a function returning void instead of a procedure, see the result in [dbfiddle](https://dbfiddle.uk/?rdbms=postgres_10&fiddle=02e82b857db12f60b76423f89fdbdc56) – Edouard Jan 28 '22 at 13:48
  • Thanks a lot for your help. I still have some issues though :( To get to my mytable im using 3 complicated cte's and also mytable is a cte. I get this error: relation "mytable" does not exist. I dont wan't make this crosstab over all of my cte's.. – user2210516 Jan 31 '22 at 15:14
  • You can implement `mytable` as a `materialzed view`, see [this exemple](https://stackoverflow.com/a/70924949/8060017) and then implement my solution based on this materialized view. – Edouard Jan 31 '22 at 17:03
  • Hi again @edouard and thanks, i created a new question because a lot of changes has been made on my side since i posted this one. This is how my problem looks lite atm. https://stackoverflow.com/questions/70944098/do-checkup-on-columns-before-crosstab-in-postgres – user2210516 Feb 01 '22 at 16:33