I have a plpgsql
function in which I want to add one row into a Data
table
where many columns are updated from subselects from tables TableA
, TableB
and Session
:
CREATE TABLE TableA (
a_id SERIAL PRIMARY KEY,
a_name TEXT UNIQUE NOT NULL
);
CREATE TABLE TableB (
b_id SERIAL PRIMARY KEY,
b_name TEXT UNIQUE NOT NULL
);
CREATE TABLE Session (
session_id SERIAL PRIMARY KEY
);
CREATE TABLE Data (
session_id INTEGER REFERENCES Session(session_id) NOT NULL,
a_id INTEGER REFERENCES TableA(a_id) NULL,
b_id INTEGER REFERENCES TableB(b_id) NULL
);
This is quite easy, but the function must be as fast as possible and also I need specific error messages to discriminate between subselect fails. Specifically:
- invalid (or
NULL
)session id
- invalid
a
name (if it was notNULL
) - invalid
b
name (if it was notNULL
)
First I tried most straightforward approach - just selected all values I needed, checked it for errors and then inserted the values:
CREATE OR REPLACE FUNCTION store_data(ssid INTEGER, a TEXT, b TEXT) RETURNS VOID
AS $$
DECLARE
_a_id INTEGER = NULL;
_b_id INTEGER = NULL;
BEGIN
PERFORM 1 FROM Session WHERE session_id = ssid;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID SESSION: %', ssid;
END IF;
IF a_name IS NOT NULL THEN
SELECT INTO _a_id a_id
FROM TableA WHERE a_name = a;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID A NAME: %', a;
END IF;
END IF;
IF b_name IS NOT NULL THEN
SELECT INTO _b_id b_id
FROM TableA WHERE b_name = b;
IF NOT FOUND THEN
RAISE EXCEPTION 'INVALID B NAME: %', b;
END IF;
END IF;
INSERT INTO Data (session_id, a_id, b_id) VALUES (ssid, _a_id, _b_id);
END;
$$ LANGUAGE plpgsql SECURITY DEFINER;
This works just fine, but it's not very fast. I need to make it faster, so my other approach was with subselects:
...
BEGIN
INSERT INTO Data (session_id, a_id, b_id)
VALUES (
(SELECT session_id FROM Session WHERE session_id = ssid),
CASE WHEN a IS NULL THEN
NULL
ELSE
(SELECT a_id FROM TableA WHERE a_name = a)
END,
CASE WHEN b IS NULL THEN
NULL
ELSE
(SELECT b_id FROM TableB WHERE b_name = b)
END
);
-- but no error handling :(
END;
...
This is a bit faster, but I can't figure out how to figure out which subselect failed and what error to report.
My question: is there a way to make it faster while keeping specific error messages?
The solution must work with postgres 8.4.