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.
Basic WITH ORDINALITY
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
LATERAL WITH ORDINALITY
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 | |