It's the recurring problem of "SELECT-or-INSERT", closely related to the popular UPSERT
problem. The upcoming Postgres 9.5 supplies the new INSERT .. ON CONFLICT DO NOTHING | UPDATE
to provide clean solutions for each.
Implementation for Postgres 9.4
For now, I suggest this bullet-proof implementation using two server-side plpgsql functions. Only the helper-function for the INSERT
implements the more expensive error-trapping, and that's only called if the SELECT
does not succeed.
This never raises an exception due to a unique violation and always returns a row.
Assumptions:
Assuming a table named tbl
with a column x
of data type text
. Adapt to your case accordingly.
x
is defined UNIQUE
or PRIMARY KEY
.
You want to return the whole row from the underlying table (return a record (found or created)
).
In many cases the row is already there. (Does not have to be the majority of cases, SELECT
is a lot cheaper than INSERT
.) Else it may be more efficient to try the INSERT
first.
Helper function:
CREATE OR REPLACE FUNCTION f_insert_x(_x text)
RETURNS SETOF tbl AS
$func$
BEGIN
RETURN QUERY
INSERT INTO tbl(x) VALUES (_x) RETURNING *;
EXCEPTION WHEN UNIQUE_VIOLATION THEN -- catch exception, no row is returned
-- do nothing
END
$func$ LANGUAGE plpgsql;
Main function:
CREATE OR REPLACE FUNCTION f_x(_x text)
RETURNS SETOF tbl AS
$func$
BEGIN
LOOP
RETURN QUERY
SELECT * FROM tbl WHERE x = _x
UNION ALL
SELECT * FROM f_insert_x(_x) -- only executed if x not found
LIMIT 1;
EXIT WHEN FOUND; -- else keep looping
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT * FROM f_x('foo');
SQL Fiddle demo.
The function is based on what I have worked out in this related answer:
Detailed explanation and links there.
We could also create a generic function with polymorphic return type and dynamic SQL to work for any given column and table (but that's beyond the scope of this question):
Basics for UPSERT
in this related answer by Craig Ringer: