This is a recurring SELECT-or-INSERT issue, closely related to the popular UPSERT issue. Upcoming Postgres 9.5 Delivers New INSERT .. ON CONFLICT DO NOTHING | UPDATE INSERT .. ON CONFLICT DO NOTHING | UPDATE to provide clean solutions for everyone.
Implementation for Postgres 9.4
I am currently proposing this bulletproof implementation using the two server side plpgsql functions. Only the helper function for INSERT implements the more expensive error trapping and is called only if SELECT not performed.
This one never throws an exception due to a unique violation, but always returns a string.
Assumptions:
Suppose a table named tbl has a column x of the text data type. Adapt accordingly to your business.
x defined by UNIQUE or PRIMARY KEY .
You want to return the entire row from the base table ( return a record (found or created) ).
In many cases, the string already exists. (There should not be most cases, SELECT much cheaper than INSERT .) Otherwise, it might be more efficient to try INSERT .
Auxiliary 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
The 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)
Call:
SELECT * FROM f_x('foo');
SQL Fiddle Script
The function is based on what I developed 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 in any columns and tables (but beyond the scope of this question):
The basics for UPSERT in this related Craig Ringer answer:
Erwin brandstetter
source share