This blog post is an exercise in identifying all the sequences in a PostgreSQL database that is associated with a table column via an OWNED BY relationship. Figuring out how to do this was harder than it should have been and this journals my understanding of it.
When I first started looking at this, I ended up at this Stack Overflow answer from 2012. While that seemed to work, it is fair to say that I had no idea what it did. So, I set out to understand it and hopefully improve it.
We will start by creating a table that we will later associate a sequence with.
CREATE TABLE users
(
id BIGINT NOT NULL,
NAME VARCHAR(40) NOT NULL
);Next, we will create a sequence that is not going to be owned by any columns - a freehanging sequence.
CREATE SEQUENCE freehanging;Postgres stores sequences across two different catalogs - pg_sequence and pg_class. Catalog pg_sequence contains sequence parameters like seqstart, seqincrement etc. The rest of the information gets stored in pg_class catalog with the seqlrelid column in pg_sequence pointing to the corresponding pg_class entry.
=> \x
Expanded display is on.
=> SELECT *
FROM pg_sequence;
-[ RECORD 1 ]+--------------------
seqrelid | 41000
seqtypid | 20
seqstart | 1
seqincrement | 1
seqmax | 9223372036854775807
seqmin | 1
seqcache | 1
seqcycle | fThat has the information that Postgres can use, but it does not look particularly useful to a human being. Luckily, Postgres provides a view named pg_sequences that shows us more information.
=> SELECT *
FROM pg_sequences;
-[ RECORD 1 ]-+--------------------
schemaname | public
sequencename | freehanging
sequenceowner | todo
data_type | bigint
start_value | 1
min_value | 1
max_value | 9223372036854775807
increment_by | 1
cycle | f
cache_size | 1
last_value |We can query pg_class with our seqrelid:
=> SELECT *
FROM pg_class
WHERE relfilenode = 41000;
-[ RECORD 1 ]-------+------------
relname | freehanging
relnamespace | 2200
reltype | 41001
reloftype | 0
relowner | 24576
relam | 0
relfilenode | 41000
reltablespace | 0
relpages | 1
reltuples | 1
relallvisible | 0
reltoastrelid | 0
relhasindex | f
relisshared | f
relpersistence | p
relkind | S
relnatts | 3
relchecks | 0
relhasoids | f
relhasrules | f
relhastriggers | f
relhassubclass | f
relrowsecurity | f
relforcerowsecurity | f
relispopulated | t
relreplident | n
relispartition | f
relrewrite | 0
relfrozenxid | 0
relminmxid | 0
relacl |
reloptions |
relpartbound |We can create a second sequence we want to associate with the users table.
=> CREATE SEQUENCE users_id_seq;
CREATE SEQUENCEThis will now show up in our queries.
=> SELECT *
FROM pg_sequence;
-[ RECORD 1 ]+--------------------
seqrelid | 41000
seqtypid | 20
seqstart | 1
seqincrement | 1
seqmax | 9223372036854775807
seqmin | 1
seqcache | 1
seqcycle | f
-[ RECORD 2 ]+--------------------
seqrelid | 41002
seqtypid | 20
seqstart | 1
seqincrement | 1
seqmax | 9223372036854775807
seqmin | 1
seqcache | 1
seqcycle | f
=> SELECT *
FROM pg_sequences;
-[ RECORD 1 ]-+--------------------
schemaname | public
sequencename | freehanging
sequenceowner | todo
data_type | bigint
start_value | 1
min_value | 1
max_value | 9223372036854775807
increment_by | 1
cycle | f
cache_size | 1
last_value |
-[ RECORD 2 ]-+--------------------
schemaname | public
sequencename | users_id_seq
sequenceowner | todo
data_type | bigint
start_value | 1
min_value | 1
max_value | 9223372036854775807
increment_by | 1
cycle | f
cache_size | 1
last_value |We can perform a join on these catalogs.
=> SELECT seqclass.relname,
seqclass.relfilenode
FROM pg_class AS seqclass
JOIN pg_sequence AS seq
ON ( seq.seqrelid = seqclass.relfilenode );
-[ RECORD 1 ]-------------
relname | freehanging
relfilenode | 41000
-[ RECORD 2 ]-------------
relname | users_id_seq
relfilenode | 41002We can go ahead and associate the new sequence with the users table by specifying OWNED BY. By setting OWNED BY, we are specifying that if the column is dropped, we want the sequence to be dropped as well.
=> ALTER SEQUENCE users_id_seq OWNED BY users.id;
ALTER SEQUENCE
This association is recorded by Postgres in the pg_depend catalog, using an a dependency type.
DEPENDENCY_AUTO (a)
The dependent object can be dropped separately from the referenced object, and should be automatically dropped (regardless of RESTRICT or CASCADE mode) if the referenced object is dropped. Example: a named constraint on a table is made autodependent on the table, so that it will go away if the table is dropped.
=> SELECT *
FROM pg_depend
WHERE objid = 41002
AND deptype = 'a';
-[ RECORD 1 ]------
classid | 1259
objid | 41002
objsubid | 0
refclassid | 1259
refobjid | 40997
refobjsubid | 1
deptype | aWe can also verify that the freehanging sequence has no dependencies of type a.
=> SELECT *
FROM pg_depend
WHERE objid = 41000
AND deptype = 'a';
(0 rows)Now that we have a dependency identified for this relationship, we can verify that the dependency is indeed with the users table. For this, we will query using the dependency’s refobjid.
=> SELECT *
FROM pg_class
WHERE relfilenode = 40997;
-[ RECORD 1 ]-------+------
relname | users
relnamespace | 2200
reltype | 40999
reloftype | 0
relowner | 24576
relam | 0
relfilenode | 40997
reltablespace | 0
relpages | 0
reltuples | 0
relallvisible | 0
reltoastrelid | 0
relhasindex | f
relisshared | f
relpersistence | p
relkind | r
relnatts | 2
relchecks | 0
relhasoids | f
relhasrules | f
relhastriggers | f
relhassubclass | f
relrowsecurity | f
relforcerowsecurity | f
relispopulated | t
relreplident | d
relispartition | f
relrewrite | 0
relfrozenxid | 5021
relminmxid | 1
relacl |
reloptions |
relpartbound |We can indeed see that the dependency is on the users table.
What about the column associated with this dependency? Postgres stores information about table columns in pg_attribute catalog. We can verify that the dependency is on the id column, by querying pg_attribute.
=> SELECT *
FROM pg_attribute
WHERE attrelid = 40997
AND attnum = 1;
-[ RECORD 1 ]-+------
attrelid | 40997
attname | id
atttypid | 20
attstattarget | -1
attlen | 8
attnum | 1
attndims | 0
attcacheoff | -1
atttypmod | -1
attbyval | t
attstorage | p
attalign | d
attnotnull | t
atthasdef | f
atthasmissing | f
attidentity |
attisdropped | f
attislocal | t
attinhcount | 0
attcollation | 0
attacl |
attoptions |
attfdwoptions |
attmissingval |We can see that the column corresponding to the dependency is indeed id.
Our join can now be improved to use this information. First, we will add the table name:
=> SELECT seqclass.relname AS sequence_name,
seqclass.relfilenode AS sequenceref,
dep.refobjid AS depobjref,
depclass.relname AS table_name
FROM pg_class AS seqclass
JOIN pg_sequence AS seq
ON ( seq.seqrelid = seqclass.relfilenode )
JOIN pg_depend AS dep
ON ( seq.seqrelid = dep.objid )
JOIN pg_class AS depclass
ON ( dep.refobjid = depclass.relfilenode );
-[ RECORD 1 ]+-------------
sequence_name | users_id_seq
sequenceref | 41002
depobjref | 40997
table_name | usersWe have to join twice on the pg_class catalog - once to get the sequence’s columns and once to get the dependency’s columns. This leaves us with the name of the table and the sequence name.
Finally, we can perform a join on pg_attribute to get column information.
=> SELECT seqclass.relname AS sequence_name,
seqclass.relfilenode AS sequenceref,
dep.refobjid AS depobjref,
depclass.relname AS tabl_ename,
attrib.attname AS column_name
FROM pg_class AS seqclass
JOIN pg_sequence AS seq
ON ( seq.seqrelid = seqclass.relfilenode )
JOIN pg_depend AS dep
ON ( seq.seqrelid = dep.objid )
JOIN pg_class AS depclass
ON ( dep.refobjid = depclass.relfilenode )
JOIN pg_attribute AS attrib
ON ( attrib.attnum = dep.refobjsubid
AND attrib.attrelid = dep.refobjid );
-[ RECORD 1 ]+-------------
sequence_name | users_id_seq
sequenceref | 41002
depobjref | 40997
table_name | users
column_name | idWe can drop sequenceref and depobjref from the result as it is not of particular interest to us when reporting this.
=> SELECT seqclass.relname AS sequence_name,
depclass.relname AS table_name,
attrib.attname AS column_name
FROM pg_class AS seqclass
JOIN pg_sequence AS seq
ON ( seq.seqrelid = seqclass.relfilenode )
JOIN pg_depend AS dep
ON ( seq.seqrelid = dep.objid )
JOIN pg_class AS depclass
ON ( dep.refobjid = depclass.relfilenode )
JOIN pg_attribute AS attrib
ON ( attrib.attnum = dep.refobjsubid
AND attrib.attrelid = dep.refobjid );
-[ RECORD 1 ]+-------------
sequenc_ename | users_id_seq
table_name | users
column_name | idPostgres versions before 10
The pg_sequence catalog was introduced in Postgres 10. For versions before 10, we need another way to get hold of the sequence’s representation in pg_class so that we can look up the dependencies. Luckily, pg_class has a column relkind that holds this informations. For a sequence, this column will be S. We can use this in the join instead of pg_sequence.
=> SELECT seqclass.relname AS sequence_name,
depclass.relname AS table_name,
attrib.attname as column_name
FROM pg_class AS seqclass
JOIN pg_depend AS dep
ON ( seqclass.relfilenode = dep.objid )
JOIN pg_class AS depclass
ON ( dep.refobjid = depclass.relfilenode )
JOIN pg_attribute AS attrib
ON ( attrib.attnum = dep.refobjsubid
AND attrib.attrelid = dep.refobjid )
WHERE seqclass.relkind = 'S';
-[ RECORD 1 ]-+-------------
sequence_name | users_id_seq
table_name | users
column_name | idThis seems like information that should be surfaced by Postgres in an easier to access way. In fact there is a patch from 2008 that would have introduced this capability. When Postgres 10 introduced the pg_sequences catalog, it stopped at surfacing the id of the user who owns the sequence.