Unpivoting data using JSONB

One of my favorite uses of JSONB functionality is to unpivot data.

Let's say you were handed a dataset with this structure where each type of bed column holds the count of units of that kind.


CREATE TABLE housing_data (
    neighborhood VARCHAR(50),
    studio INTEGER,
    br1 INTEGER,
    br2 INTEGER,
    br3 INTEGER,
    br4 INTEGER
);

INSERT INTO housing_data (neighborhood, studio, br1, br2, br3, br4)
VALUES ('Downtown', 500, 800, 1000, 1200, 1500),
       ('Midtown', 700, 950, 1150, 1400, 1700),
       ('Uptown', 600, 800, 1000, 1200, 1500),
       ('Suburbs', 300, 450, 600, 750, 900);

For your reporting needs, you would like each bed type count to be a separate row. Here is one of my favorite tricks for accomplishing this:

SELECT neighborhood, replace(je.key,'br','') AS bed_type, je.value::integer AS num_units
FROM housing_data AS d 
	CROSS JOIN jsonb_each_text( to_jsonb(d) - 'neighborhood') AS je;

The assumption here is that every column except for neighborhood is a bedroom unit count. The output is:

 neighborhood | bed_type | num_units
--------------+----------+-----------
 Downtown     | 1        |       800
 Downtown     | 2        |      1000
 Downtown     | 3        |      1200
 Downtown     | 4        |      1500
 Downtown     | studio   |       500
 Midtown      | 1        |       950
 Midtown      | 2        |      1150
 Midtown      | 3        |      1400
 Midtown      | 4        |      1700
 Midtown      | studio   |       700
 Uptown       | 1        |       800
 Uptown       | 2        |      1000
 Uptown       | 3        |      1200
 Uptown       | 4        |      1500
 Uptown       | studio   |       600
 Suburbs      | 1        |       450
 Suburbs      | 2        |       600
 Suburbs      | 3        |       750
 Suburbs      | 4        |       900
 Suburbs      | studio   |       300
(20 rows)
(20 rows)

If you had more than one column that wasn't a bedroom count you could revise your subtraction, to exclude that too using revised: to_jsonb(d) - 'neighborhood' - 'other_column', alternatively you could use array construct to_jsonb(d) - ARRAY['neighborhood','other_column']. This trick can also be accomplished using the hstore extension and converting a row to an hstore and using the each function of hstore.