I am trying to convert rows to columns in postgres using crosstab or any other ways
Table 1:
Order_Id | Order_line_id |
---|---|
1 | 1001 |
1 | 1 |
1 | 2 |
Table 2:
Order_Id | Order_line_id | Type | Amount |
---|---|---|---|
1 | 1001 | APPLE | 60 |
1 | 1001 | APPLE | 90 |
1 | 1 | APPLE | 0 |
1 | 1 | ORANGE | 32 |
1 | 1 | KIWI | 45 |
1 | 2 | APPLE | 12 |
1 | 2 | ORANGE | 76 |
1 | 2 | ORANGE | 98 |
Result:
Order_Id | Order_line_id | APPLE1 | APPLE2 | ORANGE1 | ORANGE2 | KIWI1 | KIWI2 |
---|---|---|---|---|---|---|---|
1 | 1001 | 60 | 90 | null | null | null | null |
1 | 1 | 0 | null | 32 | null | null | 45 |
1 | 2 | 12 | null | 76 | 98 | null | null |
Column names are known already but the column values might be duplicate and they should be go next to each other.
i tried hard with cross tab and json (atleast tried to bring in json) couldnt progress. any help pls?
I tried to transpose rows to columns but the columns values may be duplicate. duplicate values must still be in separate column. I tried to achieve in crosstab but it didnt work
CodePudding user response:
Your problem cannot be resolved with crosstab
because the list of the columns in the result must be dynamically calculated against the rows of the Table 2
which may be updated at any time.
A solution exists to solve your problem. It consists of :
- creating a
composite type
dynamically with the list of expected column labels according to theTable 2
status within aplpgsql
procedure - calling the procedure before executing the query
- building the query by grouping the Table 2 rows by
Order_id
andOrder_line_id
so that to aggregate these rows into the taget row structure - converting the target rows into
json
objects and displaying thesejson
objects in the final status by using thejson_populate_record
function and thecomposite type
Step 1 :
CREATE OR REPLACE PROCEDURE composite_type() LANGUAGE plpgsql AS $$
DECLARE
column_list text ;
BEGIN
SELECT string_agg(m.name || ' text', ', ')
INTO column_list
FROM
( SELECT Type || generate_series(1, max(count)) AS name
FROM
( SELECT lower(Type) AS type, count(*)
FROM table_2
GROUP BY Order_Id, Order_line_id, Type
) AS s
GROUP BY Type
) AS m ;
DROP type IF EXISTS composite_type ;
EXECUTE 'CREATE type composite_type AS (' || column_list || ')';
END ; $$
Step 2 :
CALL composite_type() ;
Step 3,4 :
SELECT t.Order_Id, t.Order_line_id
, (json_populate_record(null :: composite_type, json_object_agg(t.label, t.Amount))).*
FROM
( SELECT Order_Id, Order_line_id
, lower(Type) || (row_number() OVER (PARTITION BY Order_Id, Order_line_id, Type)) :: text AS label
, Amount
FROM table_2
) AS t
GROUP BY t.Order_Id, t.Order_line_id ;
The final result is :
order_id | order_line_id | kiwi1 | orange1 | orange2 | apple1 | apple2 |
---|---|---|---|---|---|---|
1 | 1 | 45 | 32 | null | 0 | null |
1 | 2 | null | 98 | 76 | 12 | null |
1 | 1001 | null | null | null | 60 | 90 |
see the full test result in dbfiddle