patternsqlMinor
Reset the sequence of a serial PK with dynamic SQL
Viewed 0 times
thewithsqlsequenceserialdynamicreset
Problem
How can I dynamically reference a sequence name (as below) using a combination of strings and
My goal is to do this within a
DECLARE variables? Is the code below proper or is there another method of doing this?My goal is to do this within a
DO/BEGIN block for performance reasons plus it'll help me understand plpgsql by doing something useful so I'm not going to mix this in to PHP unless I really have to.DO $
DECLARE PKEY VARCHAR;
BEGIN
SELECT pg_attribute.attname INTO PKEY
FROM pg_index, pg_class, pg_attribute
WHERE pg_class.oid = 'parts1'::regclass
AND indrelid = pg_class.oid
AND pg_attribute.attrelid = pg_class.oid
AND pg_attribute.attnum = any(pg_index.indkey)
AND indisprimary;
--SELECT setval('parts1_id_seq', (SELECT MAX(pkey) + 1 FROM parts));
SELECT setval('parts1_' || PKEY || '_seq', (SELECT MAX(pkey) + 1 FROM parts));
END;
$ LANGUAGE plpgsql;Solution
General advice
You mentioned it yourself, you just started using Postgres. Yet, you're tackling extremely advanced tasks right away, juggling system catalogs and operating with advanced dynamic SQL to automate things.
While your objectives seem reasonable, you still need to start at the basics. There is just too much to explain here. Start with (relevant parts of) the excellent manual. I provided a couple of deep links further down.
Answer
To get name(s) (and data type(s)) of columns involved in the primary key, rather use this simpler query:
I updated the Postgres Wiki page where your original query seems to originate from.
However, this can return multiple rows, while you only assign a single value. Assuming you have established that we are dealing with a
Then use the dedicated system information function
Per documentation:
get name of the sequence that a
This builds and executes a query of the form:
Explain / Advise
-
This is advanced stuff and not really suitable for beginners. Messing with system catalogs can go south quickly if you don't know exactly what you are doing.
-
Stick to legal all-lower case identifiers in Postgres and plpgsql to make your life easier. But never rely on it in dynamic SQL, where you also need to defend against SQL injection at all times.
-
Making heavy use of
-
Inside a plpgsql function you cannot call
I removed that completely, since I reduced everything to a single
-
Since the whole operation only makes sense for a single-column primary key, I simplified the
Note that
-
Make it a habbit to use dollar-quotes with a token around plpgsql code (including
-
You only need a single
instead of:
SQL Fiddle demonstrating a few things.
You mentioned it yourself, you just started using Postgres. Yet, you're tackling extremely advanced tasks right away, juggling system catalogs and operating with advanced dynamic SQL to automate things.
While your objectives seem reasonable, you still need to start at the basics. There is just too much to explain here. Start with (relevant parts of) the excellent manual. I provided a couple of deep links further down.
Answer
To get name(s) (and data type(s)) of columns involved in the primary key, rather use this simpler query:
SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = 'tbl'::regclass
AND i.indisprimary;I updated the Postgres Wiki page where your original query seems to originate from.
However, this can return multiple rows, while you only assign a single value. Assuming you have established that we are dealing with a
serial type (single column) primary key. Else use similar techniques as laid out in my previous answer to make sure.Then use the dedicated system information function
pg_get_serial_sequence() to determine the name of the used sequence, like demonstrated in my previous answer.Per documentation:
get name of the sequence that a
serial, smallserial or bigserial column usesDO
$do$
BEGIN
EXECUTE (
SELECT format($SELECT setval('%s'::regclass, max(%I)) FROM %s$
, pg_get_serial_sequence(a.attrelid::regclass::text, a.attname)
, a.attname
, a.attrelid::regclass
)
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = i.indkey[0]
WHERE i.indrelid = 'tbl'::regclass
AND i.indisprimary
);
END
$do$ LANGUAGE plpgsql;This builds and executes a query of the form:
SELECT setval('tbl_tbl_id_seq'::regclass, max(tbl_id)) FROM tbl;Explain / Advise
-
This is advanced stuff and not really suitable for beginners. Messing with system catalogs can go south quickly if you don't know exactly what you are doing.
-
Stick to legal all-lower case identifiers in Postgres and plpgsql to make your life easier. But never rely on it in dynamic SQL, where you also need to defend against SQL injection at all times.
-
Making heavy use of
format() to build the query string conveniently & safely.-
Inside a plpgsql function you cannot call
SELECT without assigning the result. You would use PERFORM instead. Details in the manual.I removed that completely, since I reduced everything to a single
EXECUTE.-
Since the whole operation only makes sense for a single-column primary key, I simplified the
JOIN condition to a.attnum = i.indkey[0]Note that
pg_index.indkey has the special (internal) type int2vector. Unlike Postgres arrays its index starts with 0, not 1.-
Make it a habbit to use dollar-quotes with a token around plpgsql code (including
DO statements). This allows to nest simple dollar-quotes like I do in my example. Details:- Insert varchar with single quotes in PostgreSQL
-
You only need a single
SELECT here:SELECT setval('tbl_tbl_id_seq'::regclass, max(tbl_id)) FROM tbl;instead of:
SELECT setval('tbl_tbl_id_seq'::regclass, (SELECT max(tbl_id) FROM tbl));SQL Fiddle demonstrating a few things.
Code Snippets
SELECT a.attname, format_type(a.atttypid, a.atttypmod) AS data_type
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = ANY(i.indkey)
WHERE i.indrelid = 'tbl'::regclass
AND i.indisprimary;DO
$do$
BEGIN
EXECUTE (
SELECT format($$SELECT setval('%s'::regclass, max(%I)) FROM %s$$
, pg_get_serial_sequence(a.attrelid::regclass::text, a.attname)
, a.attname
, a.attrelid::regclass
)
FROM pg_index i
JOIN pg_attribute a ON a.attrelid = i.indrelid
AND a.attnum = i.indkey[0]
WHERE i.indrelid = 'tbl'::regclass
AND i.indisprimary
);
END
$do$ LANGUAGE plpgsql;SELECT setval('tbl_tbl_id_seq'::regclass, max(tbl_id)) FROM tbl;SELECT setval('tbl_tbl_id_seq'::regclass, max(tbl_id)) FROM tbl;SELECT setval('tbl_tbl_id_seq'::regclass, (SELECT max(tbl_id) FROM tbl));Context
StackExchange Database Administrators Q#91731, answer score: 5
Revisions (0)
No revisions yet.