Reputation: 5962
We have a monitor on our databases to check for ids approaching max-int or max-bigint. We just moved from MySQL, and I'm struggling to get a similar check working on PostgreSQL. I'm hoping someone can help.
Here's the query in MySQL
SELECT table_name, auto_increment FROM information_schema.tables WHERE table_schema = DATABASE();
I'm trying to get the same results from PostgreSQL. We found a way to do this with a bunch of calls to the database, checking each table individually.
I'd like to make just 1 call to the database. Here's what I have so far:
CREATE OR REPLACE FUNCTION getAllSeqId() RETURNS SETOF record AS
$body$
DECLARE
sequence_name varchar(255);
BEGIN
FOR sequence_name in SELECT relname FROM pg_class WHERE (relkind = 'S')
LOOP
RETURN QUERY EXECUTE 'SELECT last_value FROM ' || sequence_name;
END LOOP;
RETURN;
END
$body$
LANGUAGE 'plpgsql';
SELECT last_value from getAllSeqId() as(last_value bigint);
However, I need to somehow add the sequence_name to each record so that I get output in records of [table_name, last_value] or [sequence_name, last_value].
So I'd like to call my function something like this:
SELECT sequence_name, last_value from getAllSeqId() as(sequence_name varchar(255), last_value bigint);
How can I do this?
EDIT: In ruby, this creates the output we're looking for. As you can see, we're doing 1 call to get all the indexes, then 1 call per index to get the last value. Gotta be a better way.
def perform
find_auto_inc_tables.each do |auto_inc_table|
check_limit(auto_inc_table, find_curr_auto_inc_id(auto_inc_table))
end
end
def find_curr_auto_inc_id(table_name)
ActiveRecord::Base.connection.execute("SELECT last_value FROM #{table_name}").first["last_value"].to_i
end
def find_auto_inc_tables
ActiveRecord::Base.connection.execute(
"SELECT c.relname " +
"FROM pg_class c " +
"WHERE c.relkind = 'S'").map { |i| i["relname"] }
end
Upvotes: 1
Views: 5914
Reputation: 1160
I stumbled on this question trying to answer the question I think OP is asking: "what tables are in danger of primary key rollover?"
This query examines columns with a default provided by a sequence (the way folks commonly configure "id" primary key columns) and compares the current value of the sequence to the capacity of the primary key, reporting the top ten and how close they are:
WITH column_info AS (
SELECT t.relname AS table_name, c.attname AS column_name, c.atttypid, c.attlen AS length, y.typname AS type
, pg_get_expr(adbin, adrelid) AS default
, (regexp_matches(pg_get_expr(adbin, adrelid), 'nextval..(.*).::regclass'))[1] AS sequencename
FROM pg_class t
LEFT OUTER JOIN pg_attribute c ON c.attrelid = t.oid
LEFT OUTER JOIN pg_type y ON y.oid = c.atttypid
LEFT OUTER JOIN pg_attrdef d ON t.oid = d.adrelid AND d.adnum = c.attnum
WHERE pg_get_expr(adbin, adrelid) LIKE '%nextval%'
),
max_values(length, max_value) AS (VALUES (4, 2147483647), (8, 9223372036854775807)),
sequence_info AS (
SELECT table_name, column_name
, (s.last_value * 100 / m.max_value) percent_of_limit
FROM column_info ci
JOIN pg_sequences s ON s.sequencename = ci.sequencename
JOIN max_values m ON m.length = ci.length
WHERE s.last_value IS NOT NULL
)
SELECT (table_name || '.' || column_name) AS name,
percent_of_limit || '%' AS percent
FROM sequence_info
ORDER BY percent_of_limit DESC
LIMIT 10;
Upvotes: 1
Reputation: 61526
Your function seems quite close already. You'd want to modify it a bit to:
TABLE(...)
with typed columns instead of SET OF RECORD
because it's easier for the callerHere's a revised version:
CREATE OR REPLACE FUNCTION getAllSeqId() RETURNS TABLE(seqname text,val bigint) AS
$body$
DECLARE
sequence_name varchar(255);
BEGIN
FOR sequence_name in SELECT relname FROM pg_class WHERE (relkind = 'S')
LOOP
RETURN QUERY EXECUTE 'SELECT ' || quote_literal(sequence_name) || '::text,last_value FROM ' || quote_ident(sequence_name);
END LOOP;
RETURN;
END
$body$
LANGUAGE 'plpgsql';
Note that currval()
is not an option since it errors out when the sequence has not been set in the same session (by calling nextval()
, not sure if there's any other way).
Upvotes: 8
Reputation: 19320
Would something as simple as this work?
SELECT currval(sequence_name) from information_schema.sequences;
If you have sequences that aren't keys, I guess you could use PG's sequence name generation pattern to try to restrict it.
SELECT currval(sequence_name) from information_schema.sequences
WHERE sequence_name LIKE '%_seq';
If that is still too many false positives, you can get table names from the information_schema (or the pg_* schemata that I don't know very well) and refine the LIKE parameter.
Upvotes: 0