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 typedynamically with the list of expected column labels according to theTable 2status within aplpgsqlprocedure - calling the procedure before executing the query
- building the query by grouping the Table 2 rows by
Order_idandOrder_line_idso that to aggregate these rows into the taget row structure - converting the target rows into
jsonobjects and displaying thesejsonobjects in the final status by using thejson_populate_recordfunction 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
