CREATE OR REPLACE FUNCTION merge_privileges(key integer, data_controller text, data_permissions text)
RETURNS void AS
$BODY$
BEGIN
LOOP
-- first try to update the key
UPDATE privileges SET controller = data_controller, permissions = data_permissions WHERE user_id = key AND controller = data_controller;
IF found THEN
RETURN;
END IF;
-- not there, so try to insert the key
-- if someone else inserts the same key concurrently,
-- we could get a unique-key failure
BEGIN
INSERT INTO privileges(user_id, controller, permissions) VALUES (key, data_controller, data_permissions);
RETURN;
EXCEPTION WHEN unique_violation THEN
-- do nothing, and loop to try the UPDATE again
END;
END LOOP;
END;
$BODY$
LANGUAGE plpgsql VOLATILE
COST 100; WITH upsert AS (
UPDATE some_table
SET attrib = 'foo'
WHERE id = 123
RETURNING *
) INSERT INTO some_table ( id , attrib )
SELECT 123 as id, 'foo' as attrib
WHERE 123 NOT IN (SELECT id FROM upsert );If this is standard upsert query in your app, you should turn it into a procedure and parameterize, catch exceptions anyway.
Or you could mess around with transaction isolation.
At first I wanted to try and do it within PL/pgSQL (e.g., from your SO link), but I couldn't figure out how to generalize it like I could with my the UPDATE ... INSERT WHERE. (I haven't really studied PL/pgSQL too hard yet.)
[1] - https://github.com/BurntSushi/nfldb/blob/master/nfldb/db.py#...
CREATE TABLE namelog (
name TEXT
);
CREATE TABLE namesummary (
name TEXT,
seen int
);
Then each time you see a name, log it: INSERT INTO namelog VALUES (%s)
Then on a regular basis, run a housekeeping/summary process like this: CREATE TEMPORARY TABLE prevsummary AS
SELECT * FROM namesummary;
TRUNCATE namesummary;
INSERT INTO namesummary
SELECT coalesce(a.name, b.name) as name,
coalesce(a.seen,0) + coalesce(b.seen, 0) as seen
FROM
prevsummary a
FULL OUTER JOIN
(SELECT name, count(*) as seen
FROM namelog
GROUP BY name) b
ON a.name=b.name
;
DELETE FROM namelog;
This of course is grossly space-inefficient, but making the logging process INSERT-only should enable a great degree of concurrency - and you can tune the summary process to run as often as necessary to keep memory/disk usage within your available resources.If concurrency isn't an issue, then your initial solution is probably fine.
For more detail, here is my initial table: CREATE TABLE names(name_id serial, name text, count integer)
I was planning to access the name count a good numbers of times, so I went with a simple SELECT from names. I don't necessarily need "on demand" counts so having the summary process run 5 or 10 mins would be a better!
Thanks for your insight!