Right, let’s talk about the four functions that let you peer under the hood of your sequences and, more importantly, let you get yourself into a world of trouble if you’re not careful. You’ve created a sequence, you’re using nextval() in your INSERTs, and everything seems like magic. But sometimes you need to break the illusion, to ask the sequence, “Hey, what number are you actually on?” or, in a moment of hubris, tell it what number to be on. That’s what these functions are for.

nextval() is the workhorse. You call it, it does three things atomically: increments the sequence, returns the new value, and makes that value the “current” value. This atomicity is why multiple concurrent transactions all get unique values—it’s the core of the whole operation.

currval() is the gossip. It tells you the last value nextval() returned in your current session. This is a crucial distinction. If you just connected and haven’t called nextval() on that sequence yet, currval is undefined and will loudly throw an error. It also doesn’t care what other sessions are doing. Their nextval calls are their business; currval only reports on what you’ve done.

lastval() is a convenient shortcut. It’s exactly like currval, but it automatically references the most recent nextval() call from any sequence in your current session. It saves you the trouble of remembering which sequence you just used, which is handier than you might think.

setval() is the bossy one. It lets you set the sequence’s current value. This is your escape hatch for when you’ve dumped data from another system and need to sync the sequence, or for when you’ve royally messed things up and need to reset. Use this power wisely.

The Critical Session-Local Nature of currval

Let’s demonstrate why currval can’t be used across sessions. Open two separate psql connections (Session A and Session B).

-- Session A: First, let's get a value.
SELECT nextval('my_sequence_seq');
-- nextval
-----------
--      101

-- Now, ask for the current value in this session.
SELECT currval('my_sequence_seq');
-- currval
-----------
--     101

-- Session B: Now, let's see what *we* get.
SELECT nextval('my_sequence_seq');
-- nextval
-----------
--      102

-- And ask for our current value.
SELECT currval('my_sequence_seq');
-- currval
-----------
--     102

-- Session A: Now, let's check again. It hasn't changed.
-- It's still concerned with what *we* did.
SELECT currval('my_sequence_seq');
-- currval
-----------
--     101

See? No cross-session chatter. Each session’s currval is a personal memory of its own actions. This is by design and prevents absolute chaos.

Why lastval() is Your Friend for Single-Row INSERTs

Imagine a common scenario: you insert a new row and immediately need its generated ID, perhaps for a foreign key in a related table. Using RETURNING is the modern, clean way. But lastval() is the classic tool that still works perfectly.

-- The clean, modern way (use this)
INSERT INTO books (title, author) VALUES ('Postgres for Humans', 'A. Person')
RETURNING id;

-- The classic way that still works
INSERT INTO books (title, author) VALUES ('Postgres for Fun', 'S. Someone');
SELECT lastval();
-- lastval
-----------
--     55

The advantage of lastval() is that it works even if your INSERT statement is complex and selects from other tables, where the RETURNING clause might get a bit messy. It just reliably gives you the last sequence value your session touched.

The Power and Peril of setval()

Here’s where you can shoot yourself in the foot with a cannon. setval() is not a suggestion; it’s a command. The sequence will obey, and the next nextval() will be one more than whatever you set it to.

-- Let's say we just imported a bunch of books with IDs up to 200.
-- We need to set the sequence to start after that.
SELECT setval('books_id_seq', 200);
-- The next nextval() will return 201. Perfect.

-- Now, let's do something profoundly stupid.
SELECT setval('books_id_seq', 10);
-- The next nextval() will return 11.
-- You've just set yourself up for a primary key duplicate violation.

The setval() function has a second, optional boolean parameter called is_called. If you set it to false, you’re telling the sequence that the value you’re setting hasn’t been used yet. The next nextval() will return that exact value.

-- This is how you truly "reset" a sequence to start from 1.
SELECT setval('books_id_seq', 1, false);
-- The very next nextval() will return 1.

My strong advice? Never run setval() without first running a SELECT currval('your_seq') to see where you are. And always, always double-check the value you’re about to set. This isn’t a suggestion; it’s a plea from someone who has had to untangle the mess it creates.

A Final, Crucial Gotcha

Remember that nextval() advances the sequence whether you use the value or not. This is a common source of confusion. A failed transaction that calls nextval() still consumes that sequence value. The value is gone, and a gap in your primary keys is created. This is not a bug; it’s a necessary trade-off for performance and correctness in a multi-user environment. Trying to avoid these gaps leads to far worse problems (like blocking every other transaction waiting to insert). Embrace the gap. It’s a sign Postgres is working correctly.