I am trying to write a postgreSQL function that duplicates all rows of a table by for the specified condition and return the mapping of existing row ids and duplicated row ids.
Here is the example data that I want to work with.
CREATE TABLE foo (
id integer PRIMARY KEY GENERATED ALWAYS AS IDENTITY (START WITH 6),
foo_content text NOT NULL,
condition_col integer NOT NULL
);
INSERT INTO foo OVERRIDING SYSTEM VALUE VALUES
( 1 , 'Sing, O goddess', 99),
( 2 , 'the anger of Achilles', 99),
( 3 , 'son of Peleus', 99),
( 4 , 'that brought countless ills', 2),
( 5 , 'upon the Achaeans', 3);
SELECT * FROM foo;
This is my attempt of writing the SQL function that is able to duplicate the entries:
CREATE OR REPLACE FUNCTION duplicate_entries(condition INT, new_condition INT)
RETURNS TABLE (
old_id INT,
new_id INT) AS
$$
BEGIN
RETURN QUERY (
WITH select_cte AS (
SELECT id
FROM foo f
WHERE f.condition_col = condition
), insertion_cte AS (
INSERT INTO foo (foo_content, condition_col)
SELECT f.foo_content, new_condition
FROM select_cte s
JOIN foo f ON s.id = f.id
RETURNING id
)
--- how to I return a mapping of ids from here?
);
END;
$$ LANGUAGE plpgsql;
-- To call the function and get the result
SELECT * FROM duplicate_entries(99, 100);
-- To see the updated 'foo' table
SELECT * FROM foo;
Here although I have achieved the desired function, I am unable to return the mapping of old_ids and new_ids from this function.
The returning table should look like this. I have tried to use a UNION ALL query but does not seem to do the trick. I think maybe a temp table would help in this case but I don’t want to use a temp table as that may be a bad practice.
This is is what my desired output should look like:
old_ids | new_ids |
---|---|
1 | 6 |
2 | 7 |
3 | 8 |
2
Answers
You can only use literals or stuff from the inserted data in your
returning
clause. You can iterate over the records you’re duplicating and inject them as literals: demoYou could, but probably shouldn’t, rely on the order of inserted rows to match between the selection and the
RETURNING
clause. A better approach is to generate the new ids ahead of the insert, using the sequence that is underlying the identity column:(online demo)
This does however require
USAGE
permission on the underlying sequence, which may not be granted by default even if a role can normally use the identity column. You may need to useSECURITY DEFINER
on the function.