One of the neat little features that arrived at PostgreSQL 9.4 is the WITH ORDINALITY
ANSI-SQL construct. What this construct does is to tack an additional column called ordinality
as an additional column when you use a set returning function in the FROM
part of an SQL Statement.
Here is the simple example of its use:
SELECT *
FROM unnest('{my,dog, eats, dog food}'::text[] )
WITH ordinality;
which outputs:
unnest | ordinality ----------+------------ my | 1 dog | 2 eats | 3 dog food | 4
If you aren't happy with the default column names, you can change them by revising your query like this
SELECT f.*
FROM unnest('{my,dog, eats, dog food}'::text[] )
WITH ordinality As f(phrase, sort_order);
Which would output this:
phrase | sort_order ----------+------------ my | 1 dog | 2 eats | 3 dog food | 4
The greatest value of WITH ORDINALITY
comes when you apply it to rows of data. How do you do that when you need to use WITH ORDINALITY
. This is where one of our favorite constructs, the LATERAL
construct comes to the rescue.
First let's construct our table with a text array column for demonstration. Note that the fish has no tags.
CREATE TABLE pets(pet varchar(100) PRIMARY KEY, tags text[]);
INSERT INTO pets(pet, tags)
VALUES ('dog', '{big, furry, friendly, eats steak}'::text[]),
('cat', '{small, snob, eats greenbeans, plays with mouse}'::text[]),
('mouse', '{very small, fits in pocket, eat peanuts, watches cat}'::text[]),
('fish', NULL);
If you do a cross join, you'll leave out fish because he's got no tags
SELECT pet, sort_order, tag
FROM pets, unnest(tags)
WITH ORDINALITY As f(tag, sort_order) ;
pet | sort_order | tag ------+------------+------------------ dog | 1 | big dog | 2 | furry dog | 3 | friendly dog | 4 | eats steak cat | 1 | small cat | 2 | snob cat | 3 | eats greenbeans cat | 4 | plays with mouse mouse | 1 | very small mouse | 2 | fits in pocket mouse | 3 | eat peanuts mouse | 4 | watches cat
In order to include pets that have no tags, you need to do a LEFT JOIN like so
SELECT pet, sort_order, tag
FROM pets LEFT JOIN
LATERAL unnest(tags)
WITH ORDINALITY As f(tag, sort_order) ON true;
And voila, all our pets are accounted for:
pet | sort_order | tag ------+------------+------------------ dog | 1 | big dog | 2 | furry dog | 3 | friendly dog | 4 | eats steak cat | 1 | small cat | 2 | snob cat | 3 | eats greenbeans cat | 4 | plays with mouse mouse | 1 | very small mouse | 2 | fits in pocket mouse | 3 | eat peanuts mouse | 4 | watches cat fish | |