[{"data":1,"prerenderedAt":3753},["ShallowReactive",2],{"page-\u002Fsql\u002F23-sequences-and-identifiers":3},{"id":4,"title":5,"body":6,"description":3746,"extension":3747,"meta":3748,"navigation":105,"path":3749,"seo":3750,"stem":3751,"__hash__":3752},"content\u002Fsql\u002F23-sequences-and-identifiers.md","23 — Sequences & Identifiers",{"type":7,"value":8,"toc":3721},"minimark",[9,13,30,35,55,230,235,329,333,346,514,518,590,594,645,649,658,779,783,920,924,1056,1060,1277,1281,1367,1371,1487,1491,1751,1755,1758,2375,2379,2507,2511,2613,2617,2927,2931,3057,3061,3183,3187,3432,3436,3443,3566,3717],[10,11,5],"h1",{"id":12},"_23-sequences-identifiers",[14,15,16,17,21,22,25,26,29],"p",{},"Auto-incrementing IDs have evolved through three mechanisms: ",[18,19,20],"code",{},"SEQUENCE"," (the primitive), ",[18,23,24],{},"SERIAL"," (the legacy shortcut), and ",[18,27,28],{},"IDENTITY"," (the SQL standard). Understanding all three — and why sequence gaps are normal — prevents duplicate-key errors, replication failures, and migration headaches.",[31,32,34],"h2",{"id":33},"sequence-the-primitive","SEQUENCE — the primitive",[14,36,37,38,41,42,45,46,49,50,54],{},"A sequence is a server-side counter, independent of any table. It's a first-class object in ",[18,39,40],{},"pg_class"," (relkind ",[18,43,44],{},"S","), stored on disk, updated via ",[18,47,48],{},"nextval"," which is ",[51,52,53],"strong",{},"non-transactional by design",".",[56,57,59],"code-wrapper",{"language":58},"sql",[60,61,65],"pre",{"className":62,"code":63,"language":58,"meta":64,"style":64},"language-sql shiki shiki-themes github-light github-dark","CREATE SEQUENCE my_seq START 1 INCREMENT 1;\n\nSELECT nextval('my_seq');   -- 1 (advances the counter, returns the new value)\nSELECT nextval('my_seq');   -- 2\nSELECT currval('my_seq');   -- 2 (THIS SESSION's last nextval — not the global value)\nSELECT lastval();           -- 2 (last sequence used by nextval in this session)\nSELECT setval('my_seq', 100);   -- manually set the value (for migrations)\n-- nextval is NEVER rolled back. If a transaction calls nextval, gets 5,\n-- then rolls back — the sequence is STILL at 5. The gap (5) is permanent.\n-- This is by design: if nextval were transactional, two concurrent\n-- transactions would block on the sequence, serializing all inserts.\n-- Non-transactional nextval = lock-free concurrency = high throughput.\n-- The cost: gaps are normal and expected. A sequence guarantees\n-- UNIQUENESS, not CONSECUTIVENESS.\n","",[18,66,67,100,107,127,141,156,167,188,194,200,206,212,218,224],{"__ignoreMap":64},[68,69,72,76,79,83,86,90,94,97],"span",{"class":70,"line":71},"line",1,[68,73,75],{"class":74},"svdQ7","CREATE",[68,77,78],{"class":74}," SEQUENCE",[68,80,82],{"class":81},"sIsaT"," my_seq",[68,84,85],{"class":74}," START",[68,87,89],{"class":88},"snvgF"," 1",[68,91,93],{"class":92},"ssxIu"," INCREMENT ",[68,95,96],{"class":88},"1",[68,98,99],{"class":92},";\n",[68,101,103],{"class":70,"line":102},2,[68,104,106],{"emptyLinePlaceholder":105},true,"\n",[68,108,110,113,116,120,123],{"class":70,"line":109},3,[68,111,112],{"class":74},"SELECT",[68,114,115],{"class":92}," nextval(",[68,117,119],{"class":118},"sJ6F3","'my_seq'",[68,121,122],{"class":92},");   ",[68,124,126],{"class":125},"sdCPZ","-- 1 (advances the counter, returns the new value)\n",[68,128,130,132,134,136,138],{"class":70,"line":129},4,[68,131,112],{"class":74},[68,133,115],{"class":92},[68,135,119],{"class":118},[68,137,122],{"class":92},[68,139,140],{"class":125},"-- 2\n",[68,142,144,146,149,151,153],{"class":70,"line":143},5,[68,145,112],{"class":74},[68,147,148],{"class":92}," currval(",[68,150,119],{"class":118},[68,152,122],{"class":92},[68,154,155],{"class":125},"-- 2 (THIS SESSION's last nextval — not the global value)\n",[68,157,159,161,164],{"class":70,"line":158},6,[68,160,112],{"class":74},[68,162,163],{"class":92}," lastval();           ",[68,165,166],{"class":125},"-- 2 (last sequence used by nextval in this session)\n",[68,168,170,172,175,177,180,183,185],{"class":70,"line":169},7,[68,171,112],{"class":74},[68,173,174],{"class":92}," setval(",[68,176,119],{"class":118},[68,178,179],{"class":92},", ",[68,181,182],{"class":88},"100",[68,184,122],{"class":92},[68,186,187],{"class":125},"-- manually set the value (for migrations)\n",[68,189,191],{"class":70,"line":190},8,[68,192,193],{"class":125},"-- nextval is NEVER rolled back. If a transaction calls nextval, gets 5,\n",[68,195,197],{"class":70,"line":196},9,[68,198,199],{"class":125},"-- then rolls back — the sequence is STILL at 5. The gap (5) is permanent.\n",[68,201,203],{"class":70,"line":202},10,[68,204,205],{"class":125},"-- This is by design: if nextval were transactional, two concurrent\n",[68,207,209],{"class":70,"line":208},11,[68,210,211],{"class":125},"-- transactions would block on the sequence, serializing all inserts.\n",[68,213,215],{"class":70,"line":214},12,[68,216,217],{"class":125},"-- Non-transactional nextval = lock-free concurrency = high throughput.\n",[68,219,221],{"class":70,"line":220},13,[68,222,223],{"class":125},"-- The cost: gaps are normal and expected. A sequence guarantees\n",[68,225,227],{"class":70,"line":226},14,[68,228,229],{"class":125},"-- UNIQUENESS, not CONSECUTIVENESS.\n",[231,232,234],"h3",{"id":233},"currval-vs-lastval-session-scoped","currval vs lastval — session-scoped",[56,236,237],{"language":58},[60,238,240],{"className":62,"code":239,"language":58,"meta":64,"style":64},"-- currval('seq') returns the value that THIS SESSION last obtained from\n-- nextval('seq'). It does NOT return the sequence's global current value\n-- (another session might have advanced it further). Call nextval first\n-- in the session, or currval errors:\n-- ERROR: session does not have a current value for sequence \"my_seq\"\n\nSELECT currval('my_seq');   -- ERROR if no nextval in this session yet\nSELECT nextval('my_seq');   -- 3\nSELECT currval('my_seq');   -- 3 (this session's last nextval)\n\n-- lastval() returns the last value obtained by nextval for ANY sequence\n-- in this session — no argument. Convenient but fragile (depends on\n-- call order). Prefer explicit currval('seq_name') in production code.\n",[18,241,242,247,252,257,262,267,271,284,297,310,314,319,324],{"__ignoreMap":64},[68,243,244],{"class":70,"line":71},[68,245,246],{"class":125},"-- currval('seq') returns the value that THIS SESSION last obtained from\n",[68,248,249],{"class":70,"line":102},[68,250,251],{"class":125},"-- nextval('seq'). It does NOT return the sequence's global current value\n",[68,253,254],{"class":70,"line":109},[68,255,256],{"class":125},"-- (another session might have advanced it further). Call nextval first\n",[68,258,259],{"class":70,"line":129},[68,260,261],{"class":125},"-- in the session, or currval errors:\n",[68,263,264],{"class":70,"line":143},[68,265,266],{"class":125},"-- ERROR: session does not have a current value for sequence \"my_seq\"\n",[68,268,269],{"class":70,"line":158},[68,270,106],{"emptyLinePlaceholder":105},[68,272,273,275,277,279,281],{"class":70,"line":169},[68,274,112],{"class":74},[68,276,148],{"class":92},[68,278,119],{"class":118},[68,280,122],{"class":92},[68,282,283],{"class":125},"-- ERROR if no nextval in this session yet\n",[68,285,286,288,290,292,294],{"class":70,"line":190},[68,287,112],{"class":74},[68,289,115],{"class":92},[68,291,119],{"class":118},[68,293,122],{"class":92},[68,295,296],{"class":125},"-- 3\n",[68,298,299,301,303,305,307],{"class":70,"line":196},[68,300,112],{"class":74},[68,302,148],{"class":92},[68,304,119],{"class":118},[68,306,122],{"class":92},[68,308,309],{"class":125},"-- 3 (this session's last nextval)\n",[68,311,312],{"class":70,"line":202},[68,313,106],{"emptyLinePlaceholder":105},[68,315,316],{"class":70,"line":208},[68,317,318],{"class":125},"-- lastval() returns the last value obtained by nextval for ANY sequence\n",[68,320,321],{"class":70,"line":214},[68,322,323],{"class":125},"-- in this session — no argument. Convenient but fragile (depends on\n",[68,325,326],{"class":70,"line":220},[68,327,328],{"class":125},"-- call order). Prefer explicit currval('seq_name') in production code.\n",[31,330,332],{"id":331},"serial-the-legacy-shortcut-what-it-actually-creates","SERIAL — the legacy shortcut (what it actually creates)",[14,334,335,337,338,341,342,345],{},[18,336,24],{}," is not a type — it's a declaration-time macro that creates three things: a sequence, a ",[18,339,340],{},"DEFAULT nextval(...)",", and a ",[18,343,344],{},"NOT NULL"," constraint.",[56,347,348],{"language":58},[60,349,351],{"className":62,"code":350,"language":58,"meta":64,"style":64},"-- This:\nCREATE TABLE users (\n  id SERIAL PRIMARY KEY,\n  name TEXT\n);\n\n-- Is sugar for ALL of this:\nCREATE SEQUENCE users_id_seq;                          -- 1. a sequence named \u003Ctable>_\u003Ccol>_seq\nCREATE TABLE users (\n  id INTEGER NOT NULL PRIMARY KEY DEFAULT nextval('users_id_seq')  -- 2. DEFAULT + 3. NOT NULL\n);\nALTER SEQUENCE users_id_seq OWNED BY users.id;         -- 4. sequence \"owned by\" the column\n-- OWNED BY means: DROP COLUMN id → sequence is dropped too.\n\n-- The column type is INTEGER, not \"serial\". SERIAL is not a type.\n-- pg_typeof: SELECT pg_typeof(id) FROM users → \"integer\"\n-- You CANNOT do: ALTER COLUMN id TYPE serial (not valid).\n",[18,352,353,358,371,384,392,397,401,406,421,431,457,461,487,492,496,502,508],{"__ignoreMap":64},[68,354,355],{"class":70,"line":71},[68,356,357],{"class":125},"-- This:\n",[68,359,360,362,365,368],{"class":70,"line":102},[68,361,75],{"class":74},[68,363,364],{"class":74}," TABLE",[68,366,367],{"class":81}," users",[68,369,370],{"class":92}," (\n",[68,372,373,376,378,381],{"class":70,"line":109},[68,374,375],{"class":92},"  id ",[68,377,24],{"class":74},[68,379,380],{"class":74}," PRIMARY KEY",[68,382,383],{"class":92},",\n",[68,385,386,389],{"class":70,"line":129},[68,387,388],{"class":74},"  name",[68,390,391],{"class":74}," TEXT\n",[68,393,394],{"class":70,"line":143},[68,395,396],{"class":92},");\n",[68,398,399],{"class":70,"line":158},[68,400,106],{"emptyLinePlaceholder":105},[68,402,403],{"class":70,"line":169},[68,404,405],{"class":125},"-- Is sugar for ALL of this:\n",[68,407,408,410,412,415,418],{"class":70,"line":190},[68,409,75],{"class":74},[68,411,78],{"class":74},[68,413,414],{"class":81}," users_id_seq",[68,416,417],{"class":92},";                          ",[68,419,420],{"class":125},"-- 1. a sequence named \u003Ctable>_\u003Ccol>_seq\n",[68,422,423,425,427,429],{"class":70,"line":196},[68,424,75],{"class":74},[68,426,364],{"class":74},[68,428,367],{"class":81},[68,430,370],{"class":92},[68,432,433,435,438,441,443,446,448,451,454],{"class":70,"line":202},[68,434,375],{"class":92},[68,436,437],{"class":74},"INTEGER",[68,439,440],{"class":74}," NOT NULL",[68,442,380],{"class":74},[68,444,445],{"class":74}," DEFAULT",[68,447,115],{"class":92},[68,449,450],{"class":118},"'users_id_seq'",[68,452,453],{"class":92},")  ",[68,455,456],{"class":125},"-- 2. DEFAULT + 3. NOT NULL\n",[68,458,459],{"class":70,"line":208},[68,460,396],{"class":92},[68,462,463,466,468,471,474,476,478,481,484],{"class":70,"line":214},[68,464,465],{"class":74},"ALTER",[68,467,78],{"class":74},[68,469,470],{"class":92}," users_id_seq OWNED ",[68,472,473],{"class":74},"BY",[68,475,367],{"class":88},[68,477,54],{"class":92},[68,479,480],{"class":88},"id",[68,482,483],{"class":92},";         ",[68,485,486],{"class":125},"-- 4. sequence \"owned by\" the column\n",[68,488,489],{"class":70,"line":220},[68,490,491],{"class":125},"-- OWNED BY means: DROP COLUMN id → sequence is dropped too.\n",[68,493,494],{"class":70,"line":226},[68,495,106],{"emptyLinePlaceholder":105},[68,497,499],{"class":70,"line":498},15,[68,500,501],{"class":125},"-- The column type is INTEGER, not \"serial\". SERIAL is not a type.\n",[68,503,505],{"class":70,"line":504},16,[68,506,507],{"class":125},"-- pg_typeof: SELECT pg_typeof(id) FROM users → \"integer\"\n",[68,509,511],{"class":70,"line":510},17,[68,512,513],{"class":125},"-- You CANNOT do: ALTER COLUMN id TYPE serial (not valid).\n",[231,515,517],{"id":516},"bigserial-use-this-instead-of-serial","BIGSERIAL — use this instead of SERIAL",[56,519,520],{"language":58},[60,521,523],{"className":62,"code":522,"language":58,"meta":64,"style":64},"-- SERIAL is INTEGER → max 2,147,483,647 (~2.1B). High-write tables\n-- hit this in months. BIGSERIAL is BIGINT → max 9.2 × 10^18.\n-- Always use BIGSERIAL (or BIGINT IDENTITY) for new tables.\nCREATE TABLE events (\n  id BIGSERIAL PRIMARY KEY,    -- bigint + sequence + default + not null\n  event_data JSONB\n);\n-- The cost of bigint vs int: 8 bytes vs 4 bytes per column. For a PK\n-- that appears in every index and every FK, the 4-byte difference\n-- adds up — but overflow is worse. Use bigint.\n",[18,524,525,530,535,540,551,566,571,575,580,585],{"__ignoreMap":64},[68,526,527],{"class":70,"line":71},[68,528,529],{"class":125},"-- SERIAL is INTEGER → max 2,147,483,647 (~2.1B). High-write tables\n",[68,531,532],{"class":70,"line":102},[68,533,534],{"class":125},"-- hit this in months. BIGSERIAL is BIGINT → max 9.2 × 10^18.\n",[68,536,537],{"class":70,"line":109},[68,538,539],{"class":125},"-- Always use BIGSERIAL (or BIGINT IDENTITY) for new tables.\n",[68,541,542,544,546,549],{"class":70,"line":129},[68,543,75],{"class":74},[68,545,364],{"class":74},[68,547,548],{"class":81}," events",[68,550,370],{"class":92},[68,552,553,555,558,560,563],{"class":70,"line":143},[68,554,375],{"class":92},[68,556,557],{"class":74},"BIGSERIAL",[68,559,380],{"class":74},[68,561,562],{"class":92},",    ",[68,564,565],{"class":125},"-- bigint + sequence + default + not null\n",[68,567,568],{"class":70,"line":158},[68,569,570],{"class":92},"  event_data JSONB\n",[68,572,573],{"class":70,"line":169},[68,574,396],{"class":92},[68,576,577],{"class":70,"line":190},[68,578,579],{"class":125},"-- The cost of bigint vs int: 8 bytes vs 4 bytes per column. For a PK\n",[68,581,582],{"class":70,"line":196},[68,583,584],{"class":125},"-- that appears in every index and every FK, the 4-byte difference\n",[68,586,587],{"class":70,"line":202},[68,588,589],{"class":125},"-- adds up — but overflow is worse. Use bigint.\n",[231,591,593],{"id":592},"serial-gotchas","SERIAL gotchas",[595,596,597,612,623,634],"ul",{},[598,599,600,601,604,605,608,609,611],"li",{},"The column type is ",[18,602,603],{},"integer","\u002F",[18,606,607],{},"bigint",", not \"serial\" — ",[18,610,24],{}," is a macro, not a type.",[598,613,614,615,618,619,622],{},"The sequence is named ",[18,616,617],{},"\u003Ctable>_\u003Ccolumn>_seq"," by convention (not guaranteed — use ",[18,620,621],{},"pg_get_serial_sequence",").",[598,624,625,627,628,631,632,622],{},[18,626,24],{}," is PostgreSQL-specific — not portable (MySQL: ",[18,629,630],{},"AUTO_INCREMENT",", SQL Server: ",[18,633,28],{},[598,635,636,637,640,641,644],{},"Dropping the default (",[18,638,639],{},"ALTER TABLE ... ALTER COLUMN id DROP DEFAULT",") leaves the sequence orphaned (it's still ",[18,642,643],{},"OWNED BY"," the column, dropped only when the column is dropped).",[31,646,648],{"id":647},"identity-the-sql-standard-modern-approach","IDENTITY — the SQL-standard modern approach",[14,650,651,654,655,657],{},[18,652,653],{},"GENERATED ... AS IDENTITY"," (PostgreSQL 10+) is the SQL-standard equivalent of ",[18,656,24],{},", with cleaner semantics, proper dependency tracking, and easier alteration.",[56,659,660],{"language":58},[60,661,663],{"className":62,"code":662,"language":58,"meta":64,"style":64},"-- GENERATED ALWAYS: the DB always generates the value. Manual inserts\n-- specifying id are REJECTED (prevents accidental collisions).\nCREATE TABLE users (\n  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  name TEXT\n);\n\n-- GENERATED BY DEFAULT: the DB generates a value if id is omitted,\n-- but ALLOWS manual inserts (with collision risk if the manual value\n-- is below the sequence's current position).\nCREATE TABLE users_mutable (\n  id BIGINT GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,\n  name TEXT\n);\n",[18,664,665,670,675,685,708,714,718,722,727,732,737,748,769,775],{"__ignoreMap":64},[68,666,667],{"class":70,"line":71},[68,668,669],{"class":125},"-- GENERATED ALWAYS: the DB always generates the value. Manual inserts\n",[68,671,672],{"class":70,"line":102},[68,673,674],{"class":125},"-- specifying id are REJECTED (prevents accidental collisions).\n",[68,676,677,679,681,683],{"class":70,"line":109},[68,678,75],{"class":74},[68,680,364],{"class":74},[68,682,367],{"class":81},[68,684,370],{"class":92},[68,686,687,689,692,695,698,701,704,706],{"class":70,"line":129},[68,688,375],{"class":92},[68,690,691],{"class":74},"BIGINT",[68,693,694],{"class":74}," GENERATED",[68,696,697],{"class":74}," ALWAYS",[68,699,700],{"class":74}," AS",[68,702,703],{"class":74}," IDENTITY",[68,705,380],{"class":74},[68,707,383],{"class":92},[68,709,710,712],{"class":70,"line":143},[68,711,388],{"class":74},[68,713,391],{"class":74},[68,715,716],{"class":70,"line":158},[68,717,396],{"class":92},[68,719,720],{"class":70,"line":169},[68,721,106],{"emptyLinePlaceholder":105},[68,723,724],{"class":70,"line":190},[68,725,726],{"class":125},"-- GENERATED BY DEFAULT: the DB generates a value if id is omitted,\n",[68,728,729],{"class":70,"line":196},[68,730,731],{"class":125},"-- but ALLOWS manual inserts (with collision risk if the manual value\n",[68,733,734],{"class":70,"line":202},[68,735,736],{"class":125},"-- is below the sequence's current position).\n",[68,738,739,741,743,746],{"class":70,"line":208},[68,740,75],{"class":74},[68,742,364],{"class":74},[68,744,745],{"class":81}," users_mutable",[68,747,370],{"class":92},[68,749,750,752,754,756,759,761,763,765,767],{"class":70,"line":214},[68,751,375],{"class":92},[68,753,691],{"class":74},[68,755,694],{"class":74},[68,757,758],{"class":74}," BY",[68,760,445],{"class":74},[68,762,700],{"class":74},[68,764,703],{"class":74},[68,766,380],{"class":74},[68,768,383],{"class":92},[68,770,771,773],{"class":70,"line":220},[68,772,388],{"class":74},[68,774,391],{"class":74},[68,776,777],{"class":70,"line":226},[68,778,396],{"class":92},[231,780,782],{"id":781},"always-vs-by-default-the-safety-tradeoff","ALWAYS vs BY DEFAULT — the safety tradeoff",[56,784,785],{"language":58},[60,786,788],{"className":62,"code":787,"language":58,"meta":64,"style":64},"-- GENERATED ALWAYS: manual id insert is rejected\nINSERT INTO users (id, name) VALUES (100, 'Alice');\n-- ERROR: cannot insert a non-DEFAULT value into identity column \"id\"\n-- Override (for migrations\u002Fdata loads):\nINSERT INTO users (id, name) OVERRIDING SYSTEM VALUE VALUES (100, 'Alice');\n-- OVERRIDING SYSTEM VALUE is an explicit \"I know what I'm doing\" escape hatch.\n\n-- GENERATED BY DEFAULT: manual id insert is allowed\nINSERT INTO users_mutable (id, name) VALUES (100, 'Alice');  -- works\n-- But: if 100 is below the sequence's current position, a later\n-- auto-generated insert might collide (duplicate key). BY DEFAULT\n-- is more flexible for migrations but riskier for production.\n",[18,789,790,795,824,829,834,864,869,873,878,905,910,915],{"__ignoreMap":64},[68,791,792],{"class":70,"line":71},[68,793,794],{"class":125},"-- GENERATED ALWAYS: manual id insert is rejected\n",[68,796,797,800,803,806,809,812,815,817,819,822],{"class":70,"line":102},[68,798,799],{"class":74},"INSERT INTO",[68,801,802],{"class":92}," users (id, ",[68,804,805],{"class":74},"name",[68,807,808],{"class":92},") ",[68,810,811],{"class":74},"VALUES",[68,813,814],{"class":92}," (",[68,816,182],{"class":88},[68,818,179],{"class":92},[68,820,821],{"class":118},"'Alice'",[68,823,396],{"class":92},[68,825,826],{"class":70,"line":109},[68,827,828],{"class":125},"-- ERROR: cannot insert a non-DEFAULT value into identity column \"id\"\n",[68,830,831],{"class":70,"line":129},[68,832,833],{"class":125},"-- Override (for migrations\u002Fdata loads):\n",[68,835,836,838,840,842,845,848,851,854,856,858,860,862],{"class":70,"line":143},[68,837,799],{"class":74},[68,839,802],{"class":92},[68,841,805],{"class":74},[68,843,844],{"class":92},") OVERRIDING ",[68,846,847],{"class":74},"SYSTEM",[68,849,850],{"class":74}," VALUE",[68,852,853],{"class":74}," VALUES",[68,855,814],{"class":92},[68,857,182],{"class":88},[68,859,179],{"class":92},[68,861,821],{"class":118},[68,863,396],{"class":92},[68,865,866],{"class":70,"line":158},[68,867,868],{"class":125},"-- OVERRIDING SYSTEM VALUE is an explicit \"I know what I'm doing\" escape hatch.\n",[68,870,871],{"class":70,"line":169},[68,872,106],{"emptyLinePlaceholder":105},[68,874,875],{"class":70,"line":190},[68,876,877],{"class":125},"-- GENERATED BY DEFAULT: manual id insert is allowed\n",[68,879,880,882,885,887,889,891,893,895,897,899,902],{"class":70,"line":196},[68,881,799],{"class":74},[68,883,884],{"class":92}," users_mutable (id, ",[68,886,805],{"class":74},[68,888,808],{"class":92},[68,890,811],{"class":74},[68,892,814],{"class":92},[68,894,182],{"class":88},[68,896,179],{"class":92},[68,898,821],{"class":118},[68,900,901],{"class":92},");  ",[68,903,904],{"class":125},"-- works\n",[68,906,907],{"class":70,"line":202},[68,908,909],{"class":125},"-- But: if 100 is below the sequence's current position, a later\n",[68,911,912],{"class":70,"line":208},[68,913,914],{"class":125},"-- auto-generated insert might collide (duplicate key). BY DEFAULT\n",[68,916,917],{"class":70,"line":214},[68,918,919],{"class":125},"-- is more flexible for migrations but riskier for production.\n",[231,921,923],{"id":922},"why-prefer-identity-over-serial","Why prefer IDENTITY over SERIAL",[56,925,926],{"language":58},[60,927,929],{"className":62,"code":928,"language":58,"meta":64,"style":64},"-- 1. Standard SQL: portable to Oracle, DB2, SQL Server (with syntax tweaks)\n-- 2. Proper dependency tracking: identity is tied to the column\n--    DROP COLUMN id → identity is cleaned up automatically\n--    SERIAL: the sequence can be orphaned if you DROP DEFAULT first\n-- 3. Easier alteration: no manual sequence juggling\nALTER TABLE users ALTER COLUMN id RESTART WITH 1000;          -- reset the identity\nALTER TABLE users ALTER COLUMN id SET GENERATED BY DEFAULT;   -- change generation mode\nALTER TABLE users ALTER COLUMN id INCREMENT BY 10;            -- change increment\n-- With SERIAL, you'd ALTER SEQUENCE users_id_seq RESTART WITH 1000 —\n-- and you have to know the sequence name (use pg_get_serial_sequence).\n-- 4. Cleaner pg_dump\u002Frestore: identities restore correctly; SERIAL\n--    sequences can get out of sync if the restore order is wrong.\n",[18,930,931,936,941,946,951,956,985,1012,1036,1041,1046,1051],{"__ignoreMap":64},[68,932,933],{"class":70,"line":71},[68,934,935],{"class":125},"-- 1. Standard SQL: portable to Oracle, DB2, SQL Server (with syntax tweaks)\n",[68,937,938],{"class":70,"line":102},[68,939,940],{"class":125},"-- 2. Proper dependency tracking: identity is tied to the column\n",[68,942,943],{"class":70,"line":109},[68,944,945],{"class":125},"--    DROP COLUMN id → identity is cleaned up automatically\n",[68,947,948],{"class":70,"line":129},[68,949,950],{"class":125},"--    SERIAL: the sequence can be orphaned if you DROP DEFAULT first\n",[68,952,953],{"class":70,"line":143},[68,954,955],{"class":125},"-- 3. Easier alteration: no manual sequence juggling\n",[68,957,958,960,962,965,967,970,973,976,979,982],{"class":70,"line":158},[68,959,465],{"class":74},[68,961,364],{"class":74},[68,963,964],{"class":92}," users ",[68,966,465],{"class":74},[68,968,969],{"class":92}," COLUMN id ",[68,971,972],{"class":74},"RESTART",[68,974,975],{"class":74}," WITH",[68,977,978],{"class":88}," 1000",[68,980,981],{"class":92},";          ",[68,983,984],{"class":125},"-- reset the identity\n",[68,986,987,989,991,993,995,997,1000,1002,1004,1006,1009],{"class":70,"line":169},[68,988,465],{"class":74},[68,990,364],{"class":74},[68,992,964],{"class":92},[68,994,465],{"class":74},[68,996,969],{"class":92},[68,998,999],{"class":74},"SET",[68,1001,694],{"class":74},[68,1003,758],{"class":74},[68,1005,445],{"class":74},[68,1007,1008],{"class":92},";   ",[68,1010,1011],{"class":125},"-- change generation mode\n",[68,1013,1014,1016,1018,1020,1022,1025,1027,1030,1033],{"class":70,"line":190},[68,1015,465],{"class":74},[68,1017,364],{"class":74},[68,1019,964],{"class":92},[68,1021,465],{"class":74},[68,1023,1024],{"class":92}," COLUMN id INCREMENT ",[68,1026,473],{"class":74},[68,1028,1029],{"class":88}," 10",[68,1031,1032],{"class":92},";            ",[68,1034,1035],{"class":125},"-- change increment\n",[68,1037,1038],{"class":70,"line":196},[68,1039,1040],{"class":125},"-- With SERIAL, you'd ALTER SEQUENCE users_id_seq RESTART WITH 1000 —\n",[68,1042,1043],{"class":70,"line":202},[68,1044,1045],{"class":125},"-- and you have to know the sequence name (use pg_get_serial_sequence).\n",[68,1047,1048],{"class":70,"line":208},[68,1049,1050],{"class":125},"-- 4. Cleaner pg_dump\u002Frestore: identities restore correctly; SERIAL\n",[68,1052,1053],{"class":70,"line":214},[68,1054,1055],{"class":125},"--    sequences can get out of sync if the restore order is wrong.\n",[31,1057,1059],{"id":1058},"sequence-gaps-why-theyre-normal-not-bugs","Sequence Gaps — Why They're Normal (Not Bugs)",[56,1061,1062],{"language":58},[60,1063,1065],{"className":62,"code":1064,"language":58,"meta":64,"style":64},"-- Gaps come from:\n-- 1. Rolled-back inserts: nextval advances, the insert rolls back → gap\n-- 2. Failed inserts (constraint violation): nextval advanced, insert failed → gap\n-- 3. Session crashes: CACHE values reserved but unused → gap\n-- 4. Failover: replica's sequence is behind the primary's → gap on failover\n\n-- This is BY DESIGN. nextval is non-transactional to avoid lock contention.\n-- If nextval were transactional, two concurrent transactions would need\n-- to lock the sequence, serializing all inserts → throughput killed.\n\n-- ❌ WRONG: trying to \"fill gaps\" by finding missing IDs and reusing them\n-- This requires serialization (find the gap, claim it, insert) → kills\n-- concurrency. And it breaks FK references (a \"reused\" ID might point\n-- to a deleted row's old data in a foreign table).\n\n-- ✅ RIGHT: accept gaps. Sequences guarantee uniqueness, not consecutiveness.\n-- If you need consecutive numbers (invoice numbers, check numbers),\n-- use a separate counter table with row-level locking:\nCREATE TABLE invoice_counters (\n  tenant_id BIGINT PRIMARY KEY,\n  next_number BIGINT NOT NULL DEFAULT 1\n);\n-- Atomically get and increment:\nUPDATE invoice_counters\nSET next_number = next_number + 1\nWHERE tenant_id = $1\nRETURNING next_number - 1 AS invoice_number;\n-- This is serialized (one transaction at a time per tenant) but guarantees\n-- consecutive numbers. Use only when consecutiveness is a business requirement.\n",[18,1066,1067,1072,1077,1082,1087,1092,1096,1101,1106,1111,1115,1120,1125,1130,1135,1139,1144,1149,1155,1167,1179,1194,1199,1205,1214,1232,1249,1265,1271],{"__ignoreMap":64},[68,1068,1069],{"class":70,"line":71},[68,1070,1071],{"class":125},"-- Gaps come from:\n",[68,1073,1074],{"class":70,"line":102},[68,1075,1076],{"class":125},"-- 1. Rolled-back inserts: nextval advances, the insert rolls back → gap\n",[68,1078,1079],{"class":70,"line":109},[68,1080,1081],{"class":125},"-- 2. Failed inserts (constraint violation): nextval advanced, insert failed → gap\n",[68,1083,1084],{"class":70,"line":129},[68,1085,1086],{"class":125},"-- 3. Session crashes: CACHE values reserved but unused → gap\n",[68,1088,1089],{"class":70,"line":143},[68,1090,1091],{"class":125},"-- 4. Failover: replica's sequence is behind the primary's → gap on failover\n",[68,1093,1094],{"class":70,"line":158},[68,1095,106],{"emptyLinePlaceholder":105},[68,1097,1098],{"class":70,"line":169},[68,1099,1100],{"class":125},"-- This is BY DESIGN. nextval is non-transactional to avoid lock contention.\n",[68,1102,1103],{"class":70,"line":190},[68,1104,1105],{"class":125},"-- If nextval were transactional, two concurrent transactions would need\n",[68,1107,1108],{"class":70,"line":196},[68,1109,1110],{"class":125},"-- to lock the sequence, serializing all inserts → throughput killed.\n",[68,1112,1113],{"class":70,"line":202},[68,1114,106],{"emptyLinePlaceholder":105},[68,1116,1117],{"class":70,"line":208},[68,1118,1119],{"class":125},"-- ❌ WRONG: trying to \"fill gaps\" by finding missing IDs and reusing them\n",[68,1121,1122],{"class":70,"line":214},[68,1123,1124],{"class":125},"-- This requires serialization (find the gap, claim it, insert) → kills\n",[68,1126,1127],{"class":70,"line":220},[68,1128,1129],{"class":125},"-- concurrency. And it breaks FK references (a \"reused\" ID might point\n",[68,1131,1132],{"class":70,"line":226},[68,1133,1134],{"class":125},"-- to a deleted row's old data in a foreign table).\n",[68,1136,1137],{"class":70,"line":498},[68,1138,106],{"emptyLinePlaceholder":105},[68,1140,1141],{"class":70,"line":504},[68,1142,1143],{"class":125},"-- ✅ RIGHT: accept gaps. Sequences guarantee uniqueness, not consecutiveness.\n",[68,1145,1146],{"class":70,"line":510},[68,1147,1148],{"class":125},"-- If you need consecutive numbers (invoice numbers, check numbers),\n",[68,1150,1152],{"class":70,"line":1151},18,[68,1153,1154],{"class":125},"-- use a separate counter table with row-level locking:\n",[68,1156,1158,1160,1162,1165],{"class":70,"line":1157},19,[68,1159,75],{"class":74},[68,1161,364],{"class":74},[68,1163,1164],{"class":81}," invoice_counters",[68,1166,370],{"class":92},[68,1168,1170,1173,1175,1177],{"class":70,"line":1169},20,[68,1171,1172],{"class":92},"  tenant_id ",[68,1174,691],{"class":74},[68,1176,380],{"class":74},[68,1178,383],{"class":92},[68,1180,1182,1185,1187,1189,1191],{"class":70,"line":1181},21,[68,1183,1184],{"class":92},"  next_number ",[68,1186,691],{"class":74},[68,1188,440],{"class":74},[68,1190,445],{"class":74},[68,1192,1193],{"class":88}," 1\n",[68,1195,1197],{"class":70,"line":1196},22,[68,1198,396],{"class":92},[68,1200,1202],{"class":70,"line":1201},23,[68,1203,1204],{"class":125},"-- Atomically get and increment:\n",[68,1206,1208,1211],{"class":70,"line":1207},24,[68,1209,1210],{"class":74},"UPDATE",[68,1212,1213],{"class":92}," invoice_counters\n",[68,1215,1217,1219,1222,1225,1227,1230],{"class":70,"line":1216},25,[68,1218,999],{"class":74},[68,1220,1221],{"class":92}," next_number ",[68,1223,1224],{"class":74},"=",[68,1226,1221],{"class":92},[68,1228,1229],{"class":74},"+",[68,1231,1193],{"class":88},[68,1233,1235,1238,1241,1243,1246],{"class":70,"line":1234},26,[68,1236,1237],{"class":74},"WHERE",[68,1239,1240],{"class":92}," tenant_id ",[68,1242,1224],{"class":74},[68,1244,1245],{"class":92}," $",[68,1247,1248],{"class":88},"1\n",[68,1250,1252,1255,1258,1260,1262],{"class":70,"line":1251},27,[68,1253,1254],{"class":92},"RETURNING next_number ",[68,1256,1257],{"class":74},"-",[68,1259,89],{"class":88},[68,1261,700],{"class":74},[68,1263,1264],{"class":92}," invoice_number;\n",[68,1266,1268],{"class":70,"line":1267},28,[68,1269,1270],{"class":125},"-- This is serialized (one transaction at a time per tenant) but guarantees\n",[68,1272,1274],{"class":70,"line":1273},29,[68,1275,1276],{"class":125},"-- consecutive numbers. Use only when consecutiveness is a business requirement.\n",[31,1278,1280],{"id":1279},"sequence-caching-throughput-tuning","Sequence Caching — throughput tuning",[56,1282,1283],{"language":58},[60,1284,1286],{"className":62,"code":1285,"language":58,"meta":64,"style":64},"-- CACHE N: each session reserves N values at once. Reduces the\n-- shared-counter lock contention (the sequence's internal lock is\n-- acquired once per N values, not once per value).\nCREATE SEQUENCE high_throughput_seq CACHE 1000;\n\n-- Default CACHE is 1 (no caching — every nextval hits the shared counter).\n-- For high-throughput insert tables:\nALTER SEQUENCE users_id_seq CACHE 100;\n-- Now each session grabs 100 values at once → 100x fewer lock acquisitions.\n-- Cost: on session crash, the unused cached values are LOST (gaps).\n-- For a table with 10K inserts\u002Fsec, CACHE 1000 is reasonable.\n-- For a table with 10 inserts\u002Fday, CACHE 1 is fine (no gap waste).\n",[18,1287,1288,1293,1298,1303,1320,1324,1329,1334,1347,1352,1357,1362],{"__ignoreMap":64},[68,1289,1290],{"class":70,"line":71},[68,1291,1292],{"class":125},"-- CACHE N: each session reserves N values at once. Reduces the\n",[68,1294,1295],{"class":70,"line":102},[68,1296,1297],{"class":125},"-- shared-counter lock contention (the sequence's internal lock is\n",[68,1299,1300],{"class":70,"line":109},[68,1301,1302],{"class":125},"-- acquired once per N values, not once per value).\n",[68,1304,1305,1307,1309,1312,1315,1318],{"class":70,"line":129},[68,1306,75],{"class":74},[68,1308,78],{"class":74},[68,1310,1311],{"class":81}," high_throughput_seq",[68,1313,1314],{"class":92}," CACHE ",[68,1316,1317],{"class":88},"1000",[68,1319,99],{"class":92},[68,1321,1322],{"class":70,"line":143},[68,1323,106],{"emptyLinePlaceholder":105},[68,1325,1326],{"class":70,"line":158},[68,1327,1328],{"class":125},"-- Default CACHE is 1 (no caching — every nextval hits the shared counter).\n",[68,1330,1331],{"class":70,"line":169},[68,1332,1333],{"class":125},"-- For high-throughput insert tables:\n",[68,1335,1336,1338,1340,1343,1345],{"class":70,"line":190},[68,1337,465],{"class":74},[68,1339,78],{"class":74},[68,1341,1342],{"class":92}," users_id_seq CACHE ",[68,1344,182],{"class":88},[68,1346,99],{"class":92},[68,1348,1349],{"class":70,"line":196},[68,1350,1351],{"class":125},"-- Now each session grabs 100 values at once → 100x fewer lock acquisitions.\n",[68,1353,1354],{"class":70,"line":202},[68,1355,1356],{"class":125},"-- Cost: on session crash, the unused cached values are LOST (gaps).\n",[68,1358,1359],{"class":70,"line":208},[68,1360,1361],{"class":125},"-- For a table with 10K inserts\u002Fsec, CACHE 1000 is reasonable.\n",[68,1363,1364],{"class":70,"line":214},[68,1365,1366],{"class":125},"-- For a table with 10 inserts\u002Fday, CACHE 1 is fine (no gap waste).\n",[31,1368,1370],{"id":1369},"sequence-ownership-and-pg_get_serial_sequence","Sequence Ownership and pg_get_serial_sequence",[56,1372,1373],{"language":58},[60,1374,1376],{"className":62,"code":1375,"language":58,"meta":64,"style":64},"-- OWNED BY ties a sequence to a column → DROP COLUMN drops the sequence\nALTER SEQUENCE my_seq OWNED BY users.id;\n\n-- pg_get_serial_sequence: the RELIABLE way to find a column's sequence\n-- Don't assume the name is users_id_seq (it is for SERIAL, but IDENTITY\n-- and manually-created sequences differ).\nSELECT pg_get_serial_sequence('users', 'id');\n-- → 'public.users_id_seq' (or the identity's internal sequence)\n\n-- Use it in setval calls (don't hardcode the sequence name):\nSELECT setval(pg_get_serial_sequence('users', 'id'),\n              (SELECT max(id) FROM users));\n",[18,1377,1378,1383,1402,1406,1411,1416,1421,1438,1443,1447,1452,1468],{"__ignoreMap":64},[68,1379,1380],{"class":70,"line":71},[68,1381,1382],{"class":125},"-- OWNED BY ties a sequence to a column → DROP COLUMN drops the sequence\n",[68,1384,1385,1387,1389,1392,1394,1396,1398,1400],{"class":70,"line":102},[68,1386,465],{"class":74},[68,1388,78],{"class":74},[68,1390,1391],{"class":92}," my_seq OWNED ",[68,1393,473],{"class":74},[68,1395,367],{"class":88},[68,1397,54],{"class":92},[68,1399,480],{"class":88},[68,1401,99],{"class":92},[68,1403,1404],{"class":70,"line":109},[68,1405,106],{"emptyLinePlaceholder":105},[68,1407,1408],{"class":70,"line":129},[68,1409,1410],{"class":125},"-- pg_get_serial_sequence: the RELIABLE way to find a column's sequence\n",[68,1412,1413],{"class":70,"line":143},[68,1414,1415],{"class":125},"-- Don't assume the name is users_id_seq (it is for SERIAL, but IDENTITY\n",[68,1417,1418],{"class":70,"line":158},[68,1419,1420],{"class":125},"-- and manually-created sequences differ).\n",[68,1422,1423,1425,1428,1431,1433,1436],{"class":70,"line":169},[68,1424,112],{"class":74},[68,1426,1427],{"class":92}," pg_get_serial_sequence(",[68,1429,1430],{"class":118},"'users'",[68,1432,179],{"class":92},[68,1434,1435],{"class":118},"'id'",[68,1437,396],{"class":92},[68,1439,1440],{"class":70,"line":190},[68,1441,1442],{"class":125},"-- → 'public.users_id_seq' (or the identity's internal sequence)\n",[68,1444,1445],{"class":70,"line":196},[68,1446,106],{"emptyLinePlaceholder":105},[68,1448,1449],{"class":70,"line":202},[68,1450,1451],{"class":125},"-- Use it in setval calls (don't hardcode the sequence name):\n",[68,1453,1454,1456,1459,1461,1463,1465],{"class":70,"line":208},[68,1455,112],{"class":74},[68,1457,1458],{"class":92}," setval(pg_get_serial_sequence(",[68,1460,1430],{"class":118},[68,1462,179],{"class":92},[68,1464,1435],{"class":118},[68,1466,1467],{"class":92},"),\n",[68,1469,1470,1473,1475,1478,1481,1484],{"class":70,"line":214},[68,1471,1472],{"class":92},"              (",[68,1474,112],{"class":74},[68,1476,1477],{"class":88}," max",[68,1479,1480],{"class":92},"(id) ",[68,1482,1483],{"class":74},"FROM",[68,1485,1486],{"class":92}," users));\n",[31,1488,1490],{"id":1489},"resetting-sequences-after-data-migration","Resetting Sequences After Data Migration",[56,1492,1493],{"language":58},[60,1494,1496],{"className":62,"code":1495,"language":58,"meta":64,"style":64},"-- After a COPY or bulk INSERT with explicit IDs, the sequence is\n-- still at its old position. The next auto-insert calls nextval,\n-- gets a value that already exists → duplicate key error.\n-- Fix: advance the sequence past max(id).\n\n-- For SERIAL \u002F manual sequences:\nSELECT setval(pg_get_serial_sequence('users', 'id'),\n              (SELECT max(id) FROM users));\n-- setval(seq, N) → next nextval returns N+1\n-- setval(seq, N, false) → next nextval returns N (the is_called flag)\n\n-- For IDENTITY columns:\nALTER TABLE users ALTER COLUMN id RESTART WITH 10001;\n-- Or compute max first, then RESTART:\nSELECT max(id) + 1 FROM users;  -- get the value\n-- ALTER TABLE users ALTER COLUMN id RESTART WITH \u003Cvalue>;  -- use the literal\n-- Note: RESTART doesn't accept a subquery — compute the value first.\n\n-- Bulk reset for ALL tables after a full data load:\n-- (Generate and execute the setval statements)\nSELECT format(\n  'SELECT setval(%L, (SELECT max(%I) FROM %I.%I));',\n  pg_get_serial_sequence(schemaname || '.' || tablename, column_name),\n  column_name, schemaname, tablename\n)\nFROM information_schema.columns\nWHERE column_default LIKE 'nextval%'\n  AND table_schema NOT IN ('pg_catalog', 'information_schema');\n-- Copy the output and run it. Or use psql's \\gexec:\n-- SELECT ... \\gexec\n",[18,1497,1498,1503,1508,1513,1518,1522,1527,1541,1555,1560,1565,1569,1574,1595,1600,1621,1626,1631,1635,1640,1645,1655,1662,1679,1684,1689,1701,1714,1740,1745],{"__ignoreMap":64},[68,1499,1500],{"class":70,"line":71},[68,1501,1502],{"class":125},"-- After a COPY or bulk INSERT with explicit IDs, the sequence is\n",[68,1504,1505],{"class":70,"line":102},[68,1506,1507],{"class":125},"-- still at its old position. The next auto-insert calls nextval,\n",[68,1509,1510],{"class":70,"line":109},[68,1511,1512],{"class":125},"-- gets a value that already exists → duplicate key error.\n",[68,1514,1515],{"class":70,"line":129},[68,1516,1517],{"class":125},"-- Fix: advance the sequence past max(id).\n",[68,1519,1520],{"class":70,"line":143},[68,1521,106],{"emptyLinePlaceholder":105},[68,1523,1524],{"class":70,"line":158},[68,1525,1526],{"class":125},"-- For SERIAL \u002F manual sequences:\n",[68,1528,1529,1531,1533,1535,1537,1539],{"class":70,"line":169},[68,1530,112],{"class":74},[68,1532,1458],{"class":92},[68,1534,1430],{"class":118},[68,1536,179],{"class":92},[68,1538,1435],{"class":118},[68,1540,1467],{"class":92},[68,1542,1543,1545,1547,1549,1551,1553],{"class":70,"line":190},[68,1544,1472],{"class":92},[68,1546,112],{"class":74},[68,1548,1477],{"class":88},[68,1550,1480],{"class":92},[68,1552,1483],{"class":74},[68,1554,1486],{"class":92},[68,1556,1557],{"class":70,"line":196},[68,1558,1559],{"class":125},"-- setval(seq, N) → next nextval returns N+1\n",[68,1561,1562],{"class":70,"line":202},[68,1563,1564],{"class":125},"-- setval(seq, N, false) → next nextval returns N (the is_called flag)\n",[68,1566,1567],{"class":70,"line":208},[68,1568,106],{"emptyLinePlaceholder":105},[68,1570,1571],{"class":70,"line":214},[68,1572,1573],{"class":125},"-- For IDENTITY columns:\n",[68,1575,1576,1578,1580,1582,1584,1586,1588,1590,1593],{"class":70,"line":220},[68,1577,465],{"class":74},[68,1579,364],{"class":74},[68,1581,964],{"class":92},[68,1583,465],{"class":74},[68,1585,969],{"class":92},[68,1587,972],{"class":74},[68,1589,975],{"class":74},[68,1591,1592],{"class":88}," 10001",[68,1594,99],{"class":92},[68,1596,1597],{"class":70,"line":226},[68,1598,1599],{"class":125},"-- Or compute max first, then RESTART:\n",[68,1601,1602,1604,1606,1608,1610,1612,1615,1618],{"class":70,"line":498},[68,1603,112],{"class":74},[68,1605,1477],{"class":88},[68,1607,1480],{"class":92},[68,1609,1229],{"class":74},[68,1611,89],{"class":88},[68,1613,1614],{"class":74}," FROM",[68,1616,1617],{"class":92}," users;  ",[68,1619,1620],{"class":125},"-- get the value\n",[68,1622,1623],{"class":70,"line":504},[68,1624,1625],{"class":125},"-- ALTER TABLE users ALTER COLUMN id RESTART WITH \u003Cvalue>;  -- use the literal\n",[68,1627,1628],{"class":70,"line":510},[68,1629,1630],{"class":125},"-- Note: RESTART doesn't accept a subquery — compute the value first.\n",[68,1632,1633],{"class":70,"line":1151},[68,1634,106],{"emptyLinePlaceholder":105},[68,1636,1637],{"class":70,"line":1157},[68,1638,1639],{"class":125},"-- Bulk reset for ALL tables after a full data load:\n",[68,1641,1642],{"class":70,"line":1169},[68,1643,1644],{"class":125},"-- (Generate and execute the setval statements)\n",[68,1646,1647,1649,1652],{"class":70,"line":1181},[68,1648,112],{"class":74},[68,1650,1651],{"class":88}," format",[68,1653,1654],{"class":92},"(\n",[68,1656,1657,1660],{"class":70,"line":1196},[68,1658,1659],{"class":118},"  'SELECT setval(%L, (SELECT max(%I) FROM %I.%I));'",[68,1661,383],{"class":92},[68,1663,1664,1667,1670,1673,1676],{"class":70,"line":1201},[68,1665,1666],{"class":92},"  pg_get_serial_sequence(schemaname ",[68,1668,1669],{"class":74},"||",[68,1671,1672],{"class":118}," '.'",[68,1674,1675],{"class":74}," ||",[68,1677,1678],{"class":92}," tablename, column_name),\n",[68,1680,1681],{"class":70,"line":1207},[68,1682,1683],{"class":92},"  column_name, schemaname, tablename\n",[68,1685,1686],{"class":70,"line":1216},[68,1687,1688],{"class":92},")\n",[68,1690,1691,1693,1696,1698],{"class":70,"line":1234},[68,1692,1483],{"class":74},[68,1694,1695],{"class":88}," information_schema",[68,1697,54],{"class":92},[68,1699,1700],{"class":88},"columns\n",[68,1702,1703,1705,1708,1711],{"class":70,"line":1251},[68,1704,1237],{"class":74},[68,1706,1707],{"class":92}," column_default ",[68,1709,1710],{"class":74},"LIKE",[68,1712,1713],{"class":118}," 'nextval%'\n",[68,1715,1716,1719,1722,1725,1728,1730,1733,1735,1738],{"class":70,"line":1267},[68,1717,1718],{"class":74},"  AND",[68,1720,1721],{"class":92}," table_schema ",[68,1723,1724],{"class":74},"NOT",[68,1726,1727],{"class":74}," IN",[68,1729,814],{"class":92},[68,1731,1732],{"class":118},"'pg_catalog'",[68,1734,179],{"class":92},[68,1736,1737],{"class":118},"'information_schema'",[68,1739,396],{"class":92},[68,1741,1742],{"class":70,"line":1273},[68,1743,1744],{"class":125},"-- Copy the output and run it. Or use psql's \\gexec:\n",[68,1746,1748],{"class":70,"line":1747},30,[68,1749,1750],{"class":125},"-- SELECT ... \\gexec\n",[31,1752,1754],{"id":1753},"complex-implementation-production-id-strategy","Complex Implementation — Production ID Strategy",[14,1756,1757],{},"A complete ID strategy showing IDENTITY columns with caching, a sequence reset after a data load, and a comparison with UUID-based IDs.",[56,1759,1760],{"language":58},[60,1761,1763],{"className":62,"code":1762,"language":58,"meta":64,"style":64},"-- ── Strategy 1: BIGINT IDENTITY (the default for most tables) ───\nCREATE TABLE orders (\n  id BIGINT GENERATED ALWAYS AS IDENTITY\n    (START WITH 1 INCREMENT BY 1 CACHE 1000),  -- cache for throughput\n  customer_id BIGINT NOT NULL,\n  amount NUMERIC NOT NULL,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT now(),\n  PRIMARY KEY (id)\n);\n-- BIGINT: no overflow risk (9.2 × 10^18 max)\n-- GENERATED ALWAYS: prevents manual id injection (collision-safe)\n-- CACHE 1000: each session reserves 1000 IDs → low lock contention\n\n-- ── Strategy 2: UUID for distributed\u002Fclient-side generation ──────\n-- Use when: client generates IDs before insert (idempotent retries),\n-- multi-master writes (no shared counter), or IDs span databases.\nCREATE EXTENSION IF NOT EXISTS pgcrypto;  -- for gen_random_uuid() in PG \u003C 13\n-- PG 13+: gen_random_uuid() is built-in (no extension needed)\n\nCREATE TABLE events (\n  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),  -- 128-bit, globally unique\n  event_type TEXT NOT NULL,\n  payload JSONB,\n  created_at TIMESTAMPTZ NOT NULL DEFAULT now()\n);\n-- UUID v4 (gen_random_uuid): random → B-tree index fragmentation\n-- (random insert positions cause page splits). Larger indexes, worse\n-- cache locality. For large-scale UUID tables, consider UUIDv7\n-- (time-ordered, monotonic → append-only index behavior).\n\n-- ── Post-migration reset ────────────────────────────────────────\n-- After loading a dump with explicit IDs into the orders table:\n-- The identity sequence is still at 1. Next auto-insert collides.\n-- Reset it:\nSELECT setval(pg_get_serial_sequence('orders', 'id'),\n              (SELECT max(id) FROM users));\n-- Next INSERT INTO orders (customer_id, amount) VALUES (...) →\n-- nextval returns max(id)+1 → no collision.\n\n-- ── Size comparison: bigint vs UUID ─────────────────────────────\n-- BIGINT: 8 bytes. UUID: 16 bytes (2x).\n-- In a B-tree index with 10M rows:\n--   bigint PK index: ~80MB (8 bytes × 10M + overhead)\n--   UUID PK index:   ~160MB (16 bytes × 10M + overhead)\n-- Every FK that references the PK also doubles in size.\n-- Every composite index that includes the PK column grows.\n-- For large-scale systems, the 2x size difference is significant.\n\n-- ── UUIDv7: time-ordered UUID for indexable distributed IDs ─────\n-- UUIDv7 combines a Unix timestamp (first 48 bits) with random bits.\n-- Monotonic-ish → append-only index behavior (no fragmentation).\n-- PostgreSQL doesn't have built-in UUIDv7 (as of PG 16), but you can\n-- generate it in the application or via a function:\n-- (simplified — use a library in production)\nCREATE OR REPLACE FUNCTION uuid_v7() RETURNS UUID AS $$\nDECLARE\n  unixts_ms BIGINT;\n  uuid_bytes BYTEA;\nBEGIN\n  unixts_ms := (extract(epoch from now()) * 1000)::bigint;\n  -- 48-bit timestamp + 12 random bits + version + 62 random bits + variant\n  uuid_bytes := substring(int8send(unixts_ms) FROM 3 FOR 6)  -- 6 bytes timestamp\n                || gen_random_bytes(10);                      -- 10 random bytes\n  -- Set version (7) and variant (10xx) bits (omitted for brevity)\n  RETURN encode(uuid_bytes, 'hex')::uuid;\nEND;\n$$ LANGUAGE plpgsql VOLATILE;\n",[18,1764,1765,1770,1781,1796,1824,1835,1847,1865,1873,1877,1882,1887,1892,1896,1901,1906,1911,1933,1938,1942,1952,1968,1980,1985,2000,2004,2009,2014,2019,2024,2028,2034,2040,2046,2052,2068,2083,2089,2095,2100,2106,2112,2118,2124,2130,2136,2142,2148,2153,2159,2165,2171,2177,2183,2189,2216,2222,2232,2243,2249,2280,2286,2316,2334,2340,2355,2363],{"__ignoreMap":64},[68,1766,1767],{"class":70,"line":71},[68,1768,1769],{"class":125},"-- ── Strategy 1: BIGINT IDENTITY (the default for most tables) ───\n",[68,1771,1772,1774,1776,1779],{"class":70,"line":102},[68,1773,75],{"class":74},[68,1775,364],{"class":74},[68,1777,1778],{"class":81}," orders",[68,1780,370],{"class":92},[68,1782,1783,1785,1787,1789,1791,1793],{"class":70,"line":109},[68,1784,375],{"class":92},[68,1786,691],{"class":74},[68,1788,694],{"class":74},[68,1790,697],{"class":74},[68,1792,700],{"class":74},[68,1794,1795],{"class":74}," IDENTITY\n",[68,1797,1798,1801,1804,1806,1808,1810,1812,1814,1816,1818,1821],{"class":70,"line":129},[68,1799,1800],{"class":92},"    (",[68,1802,1803],{"class":74},"START",[68,1805,975],{"class":74},[68,1807,89],{"class":88},[68,1809,93],{"class":92},[68,1811,473],{"class":74},[68,1813,89],{"class":88},[68,1815,1314],{"class":92},[68,1817,1317],{"class":88},[68,1819,1820],{"class":92},"),  ",[68,1822,1823],{"class":125},"-- cache for throughput\n",[68,1825,1826,1829,1831,1833],{"class":70,"line":143},[68,1827,1828],{"class":92},"  customer_id ",[68,1830,691],{"class":74},[68,1832,440],{"class":74},[68,1834,383],{"class":92},[68,1836,1837,1840,1843,1845],{"class":70,"line":158},[68,1838,1839],{"class":92},"  amount ",[68,1841,1842],{"class":74},"NUMERIC",[68,1844,440],{"class":74},[68,1846,383],{"class":92},[68,1848,1849,1852,1855,1857,1859,1862],{"class":70,"line":169},[68,1850,1851],{"class":92},"  created_at ",[68,1853,1854],{"class":74},"TIMESTAMPTZ",[68,1856,440],{"class":74},[68,1858,445],{"class":74},[68,1860,1861],{"class":74}," now",[68,1863,1864],{"class":92},"(),\n",[68,1866,1867,1870],{"class":70,"line":190},[68,1868,1869],{"class":74},"  PRIMARY KEY",[68,1871,1872],{"class":92}," (id)\n",[68,1874,1875],{"class":70,"line":196},[68,1876,396],{"class":92},[68,1878,1879],{"class":70,"line":202},[68,1880,1881],{"class":125},"-- BIGINT: no overflow risk (9.2 × 10^18 max)\n",[68,1883,1884],{"class":70,"line":208},[68,1885,1886],{"class":125},"-- GENERATED ALWAYS: prevents manual id injection (collision-safe)\n",[68,1888,1889],{"class":70,"line":214},[68,1890,1891],{"class":125},"-- CACHE 1000: each session reserves 1000 IDs → low lock contention\n",[68,1893,1894],{"class":70,"line":220},[68,1895,106],{"emptyLinePlaceholder":105},[68,1897,1898],{"class":70,"line":226},[68,1899,1900],{"class":125},"-- ── Strategy 2: UUID for distributed\u002Fclient-side generation ──────\n",[68,1902,1903],{"class":70,"line":498},[68,1904,1905],{"class":125},"-- Use when: client generates IDs before insert (idempotent retries),\n",[68,1907,1908],{"class":70,"line":504},[68,1909,1910],{"class":125},"-- multi-master writes (no shared counter), or IDs span databases.\n",[68,1912,1913,1915,1918,1921,1924,1927,1930],{"class":70,"line":510},[68,1914,75],{"class":74},[68,1916,1917],{"class":92}," EXTENSION ",[68,1919,1920],{"class":74},"IF",[68,1922,1923],{"class":74}," NOT",[68,1925,1926],{"class":74}," EXISTS",[68,1928,1929],{"class":92}," pgcrypto;  ",[68,1931,1932],{"class":125},"-- for gen_random_uuid() in PG \u003C 13\n",[68,1934,1935],{"class":70,"line":1151},[68,1936,1937],{"class":125},"-- PG 13+: gen_random_uuid() is built-in (no extension needed)\n",[68,1939,1940],{"class":70,"line":1157},[68,1941,106],{"emptyLinePlaceholder":105},[68,1943,1944,1946,1948,1950],{"class":70,"line":1169},[68,1945,75],{"class":74},[68,1947,364],{"class":74},[68,1949,548],{"class":81},[68,1951,370],{"class":92},[68,1953,1954,1957,1960,1962,1965],{"class":70,"line":1181},[68,1955,1956],{"class":92},"  id UUID ",[68,1958,1959],{"class":74},"PRIMARY KEY",[68,1961,445],{"class":74},[68,1963,1964],{"class":92}," gen_random_uuid(),  ",[68,1966,1967],{"class":125},"-- 128-bit, globally unique\n",[68,1969,1970,1973,1976,1978],{"class":70,"line":1196},[68,1971,1972],{"class":92},"  event_type ",[68,1974,1975],{"class":74},"TEXT",[68,1977,440],{"class":74},[68,1979,383],{"class":92},[68,1981,1982],{"class":70,"line":1201},[68,1983,1984],{"class":92},"  payload JSONB,\n",[68,1986,1987,1989,1991,1993,1995,1997],{"class":70,"line":1207},[68,1988,1851],{"class":92},[68,1990,1854],{"class":74},[68,1992,440],{"class":74},[68,1994,445],{"class":74},[68,1996,1861],{"class":74},[68,1998,1999],{"class":92},"()\n",[68,2001,2002],{"class":70,"line":1216},[68,2003,396],{"class":92},[68,2005,2006],{"class":70,"line":1234},[68,2007,2008],{"class":125},"-- UUID v4 (gen_random_uuid): random → B-tree index fragmentation\n",[68,2010,2011],{"class":70,"line":1251},[68,2012,2013],{"class":125},"-- (random insert positions cause page splits). Larger indexes, worse\n",[68,2015,2016],{"class":70,"line":1267},[68,2017,2018],{"class":125},"-- cache locality. For large-scale UUID tables, consider UUIDv7\n",[68,2020,2021],{"class":70,"line":1273},[68,2022,2023],{"class":125},"-- (time-ordered, monotonic → append-only index behavior).\n",[68,2025,2026],{"class":70,"line":1747},[68,2027,106],{"emptyLinePlaceholder":105},[68,2029,2031],{"class":70,"line":2030},31,[68,2032,2033],{"class":125},"-- ── Post-migration reset ────────────────────────────────────────\n",[68,2035,2037],{"class":70,"line":2036},32,[68,2038,2039],{"class":125},"-- After loading a dump with explicit IDs into the orders table:\n",[68,2041,2043],{"class":70,"line":2042},33,[68,2044,2045],{"class":125},"-- The identity sequence is still at 1. Next auto-insert collides.\n",[68,2047,2049],{"class":70,"line":2048},34,[68,2050,2051],{"class":125},"-- Reset it:\n",[68,2053,2055,2057,2059,2062,2064,2066],{"class":70,"line":2054},35,[68,2056,112],{"class":74},[68,2058,1458],{"class":92},[68,2060,2061],{"class":118},"'orders'",[68,2063,179],{"class":92},[68,2065,1435],{"class":118},[68,2067,1467],{"class":92},[68,2069,2071,2073,2075,2077,2079,2081],{"class":70,"line":2070},36,[68,2072,1472],{"class":92},[68,2074,112],{"class":74},[68,2076,1477],{"class":88},[68,2078,1480],{"class":92},[68,2080,1483],{"class":74},[68,2082,1486],{"class":92},[68,2084,2086],{"class":70,"line":2085},37,[68,2087,2088],{"class":125},"-- Next INSERT INTO orders (customer_id, amount) VALUES (...) →\n",[68,2090,2092],{"class":70,"line":2091},38,[68,2093,2094],{"class":125},"-- nextval returns max(id)+1 → no collision.\n",[68,2096,2098],{"class":70,"line":2097},39,[68,2099,106],{"emptyLinePlaceholder":105},[68,2101,2103],{"class":70,"line":2102},40,[68,2104,2105],{"class":125},"-- ── Size comparison: bigint vs UUID ─────────────────────────────\n",[68,2107,2109],{"class":70,"line":2108},41,[68,2110,2111],{"class":125},"-- BIGINT: 8 bytes. UUID: 16 bytes (2x).\n",[68,2113,2115],{"class":70,"line":2114},42,[68,2116,2117],{"class":125},"-- In a B-tree index with 10M rows:\n",[68,2119,2121],{"class":70,"line":2120},43,[68,2122,2123],{"class":125},"--   bigint PK index: ~80MB (8 bytes × 10M + overhead)\n",[68,2125,2127],{"class":70,"line":2126},44,[68,2128,2129],{"class":125},"--   UUID PK index:   ~160MB (16 bytes × 10M + overhead)\n",[68,2131,2133],{"class":70,"line":2132},45,[68,2134,2135],{"class":125},"-- Every FK that references the PK also doubles in size.\n",[68,2137,2139],{"class":70,"line":2138},46,[68,2140,2141],{"class":125},"-- Every composite index that includes the PK column grows.\n",[68,2143,2145],{"class":70,"line":2144},47,[68,2146,2147],{"class":125},"-- For large-scale systems, the 2x size difference is significant.\n",[68,2149,2151],{"class":70,"line":2150},48,[68,2152,106],{"emptyLinePlaceholder":105},[68,2154,2156],{"class":70,"line":2155},49,[68,2157,2158],{"class":125},"-- ── UUIDv7: time-ordered UUID for indexable distributed IDs ─────\n",[68,2160,2162],{"class":70,"line":2161},50,[68,2163,2164],{"class":125},"-- UUIDv7 combines a Unix timestamp (first 48 bits) with random bits.\n",[68,2166,2168],{"class":70,"line":2167},51,[68,2169,2170],{"class":125},"-- Monotonic-ish → append-only index behavior (no fragmentation).\n",[68,2172,2174],{"class":70,"line":2173},52,[68,2175,2176],{"class":125},"-- PostgreSQL doesn't have built-in UUIDv7 (as of PG 16), but you can\n",[68,2178,2180],{"class":70,"line":2179},53,[68,2181,2182],{"class":125},"-- generate it in the application or via a function:\n",[68,2184,2186],{"class":70,"line":2185},54,[68,2187,2188],{"class":125},"-- (simplified — use a library in production)\n",[68,2190,2192,2195,2198,2201,2204,2207,2210,2213],{"class":70,"line":2191},55,[68,2193,2194],{"class":74},"CREATE OR REPLACE",[68,2196,2197],{"class":74}," FUNCTION",[68,2199,2200],{"class":81}," uuid_v7",[68,2202,2203],{"class":92},"() ",[68,2205,2206],{"class":74},"RETURNS",[68,2208,2209],{"class":92}," UUID ",[68,2211,2212],{"class":74},"AS",[68,2214,2215],{"class":92}," $$\n",[68,2217,2219],{"class":70,"line":2218},56,[68,2220,2221],{"class":74},"DECLARE\n",[68,2223,2225,2228,2230],{"class":70,"line":2224},57,[68,2226,2227],{"class":92},"  unixts_ms ",[68,2229,691],{"class":74},[68,2231,99],{"class":92},[68,2233,2235,2238,2241],{"class":70,"line":2234},58,[68,2236,2237],{"class":92},"  uuid_bytes ",[68,2239,2240],{"class":74},"BYTEA",[68,2242,99],{"class":92},[68,2244,2246],{"class":70,"line":2245},59,[68,2247,2248],{"class":74},"BEGIN\n",[68,2250,2252,2255,2257,2260,2263,2265,2268,2271,2273,2276,2278],{"class":70,"line":2251},60,[68,2253,2254],{"class":92},"  unixts_ms :",[68,2256,1224],{"class":74},[68,2258,2259],{"class":92}," (extract(epoch ",[68,2261,2262],{"class":74},"from",[68,2264,1861],{"class":74},[68,2266,2267],{"class":92},"()) ",[68,2269,2270],{"class":74},"*",[68,2272,978],{"class":88},[68,2274,2275],{"class":92},")::",[68,2277,607],{"class":74},[68,2279,99],{"class":92},[68,2281,2283],{"class":70,"line":2282},61,[68,2284,2285],{"class":125},"  -- 48-bit timestamp + 12 random bits + version + 62 random bits + variant\n",[68,2287,2289,2292,2294,2297,2300,2302,2305,2308,2311,2313],{"class":70,"line":2288},62,[68,2290,2291],{"class":92},"  uuid_bytes :",[68,2293,1224],{"class":74},[68,2295,2296],{"class":88}," substring",[68,2298,2299],{"class":92},"(int8send(unixts_ms) ",[68,2301,1483],{"class":74},[68,2303,2304],{"class":88}," 3",[68,2306,2307],{"class":74}," FOR",[68,2309,2310],{"class":88}," 6",[68,2312,453],{"class":92},[68,2314,2315],{"class":125},"-- 6 bytes timestamp\n",[68,2317,2319,2322,2325,2328,2331],{"class":70,"line":2318},63,[68,2320,2321],{"class":74},"                ||",[68,2323,2324],{"class":92}," gen_random_bytes(",[68,2326,2327],{"class":88},"10",[68,2329,2330],{"class":92},");                      ",[68,2332,2333],{"class":125},"-- 10 random bytes\n",[68,2335,2337],{"class":70,"line":2336},64,[68,2338,2339],{"class":125},"  -- Set version (7) and variant (10xx) bits (omitted for brevity)\n",[68,2341,2343,2346,2349,2352],{"class":70,"line":2342},65,[68,2344,2345],{"class":74},"  RETURN",[68,2347,2348],{"class":92}," encode(uuid_bytes, ",[68,2350,2351],{"class":118},"'hex'",[68,2353,2354],{"class":92},")::uuid;\n",[68,2356,2358,2361],{"class":70,"line":2357},66,[68,2359,2360],{"class":74},"END",[68,2362,99],{"class":92},[68,2364,2366,2369,2372],{"class":70,"line":2365},67,[68,2367,2368],{"class":92},"$$ ",[68,2370,2371],{"class":74},"LANGUAGE",[68,2373,2374],{"class":92}," plpgsql VOLATILE;\n",[31,2376,2378],{"id":2377},"anti-pattern-serial-when-identity-is-available","Anti-Pattern: SERIAL When IDENTITY Is Available",[56,2380,2381],{"language":58},[60,2382,2384],{"className":62,"code":2383,"language":58,"meta":64,"style":64},"-- ❌ WRONG: using SERIAL for new tables (IDENTITY available since PG 10)\nCREATE TABLE users (\n  id SERIAL PRIMARY KEY,           -- non-standard, harder to migrate, orphanable sequence\n  name TEXT\n);\n-- Problems:\n-- 1. Not standard SQL → not portable to other databases\n-- 2. The sequence can be orphaned (DROP DEFAULT → sequence survives, junk)\n-- 3. Resetting requires knowing the sequence name (pg_get_serial_sequence)\n-- 4. pg_dump\u002Frestore can desync the sequence from the data\n\n-- ✅ RIGHT: use IDENTITY for all new tables\nCREATE TABLE users (\n  id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,\n  name TEXT\n);\n-- Standard SQL, proper dependency tracking, easy ALTER\u002FRESTART,\n-- clean dump\u002Frestore. Use SERIAL only for legacy compatibility.\n",[18,2385,2386,2391,2401,2415,2421,2425,2430,2435,2440,2445,2450,2454,2459,2469,2487,2493,2497,2502],{"__ignoreMap":64},[68,2387,2388],{"class":70,"line":71},[68,2389,2390],{"class":125},"-- ❌ WRONG: using SERIAL for new tables (IDENTITY available since PG 10)\n",[68,2392,2393,2395,2397,2399],{"class":70,"line":102},[68,2394,75],{"class":74},[68,2396,364],{"class":74},[68,2398,367],{"class":81},[68,2400,370],{"class":92},[68,2402,2403,2405,2407,2409,2412],{"class":70,"line":109},[68,2404,375],{"class":92},[68,2406,24],{"class":74},[68,2408,380],{"class":74},[68,2410,2411],{"class":92},",           ",[68,2413,2414],{"class":125},"-- non-standard, harder to migrate, orphanable sequence\n",[68,2416,2417,2419],{"class":70,"line":129},[68,2418,388],{"class":74},[68,2420,391],{"class":74},[68,2422,2423],{"class":70,"line":143},[68,2424,396],{"class":92},[68,2426,2427],{"class":70,"line":158},[68,2428,2429],{"class":125},"-- Problems:\n",[68,2431,2432],{"class":70,"line":169},[68,2433,2434],{"class":125},"-- 1. Not standard SQL → not portable to other databases\n",[68,2436,2437],{"class":70,"line":190},[68,2438,2439],{"class":125},"-- 2. The sequence can be orphaned (DROP DEFAULT → sequence survives, junk)\n",[68,2441,2442],{"class":70,"line":196},[68,2443,2444],{"class":125},"-- 3. Resetting requires knowing the sequence name (pg_get_serial_sequence)\n",[68,2446,2447],{"class":70,"line":202},[68,2448,2449],{"class":125},"-- 4. pg_dump\u002Frestore can desync the sequence from the data\n",[68,2451,2452],{"class":70,"line":208},[68,2453,106],{"emptyLinePlaceholder":105},[68,2455,2456],{"class":70,"line":214},[68,2457,2458],{"class":125},"-- ✅ RIGHT: use IDENTITY for all new tables\n",[68,2460,2461,2463,2465,2467],{"class":70,"line":220},[68,2462,75],{"class":74},[68,2464,364],{"class":74},[68,2466,367],{"class":81},[68,2468,370],{"class":92},[68,2470,2471,2473,2475,2477,2479,2481,2483,2485],{"class":70,"line":226},[68,2472,375],{"class":92},[68,2474,691],{"class":74},[68,2476,694],{"class":74},[68,2478,697],{"class":74},[68,2480,700],{"class":74},[68,2482,703],{"class":74},[68,2484,380],{"class":74},[68,2486,383],{"class":92},[68,2488,2489,2491],{"class":70,"line":498},[68,2490,388],{"class":74},[68,2492,391],{"class":74},[68,2494,2495],{"class":70,"line":504},[68,2496,396],{"class":92},[68,2498,2499],{"class":70,"line":510},[68,2500,2501],{"class":125},"-- Standard SQL, proper dependency tracking, easy ALTER\u002FRESTART,\n",[68,2503,2504],{"class":70,"line":1151},[68,2505,2506],{"class":125},"-- clean dump\u002Frestore. Use SERIAL only for legacy compatibility.\n",[31,2508,2510],{"id":2509},"anti-pattern-worrying-about-sequence-gaps","Anti-Pattern: Worrying About Sequence Gaps",[56,2512,2513],{"language":58},[60,2514,2516],{"className":62,"code":2515,"language":58,"meta":64,"style":64},"-- ❌ WRONG: \"fixing\" gaps by reusing skipped IDs\n-- A developer notices IDs go 1, 2, 3, 5, 6, 8 (gaps at 4, 7) and\n-- writes code to \"fill\" them:\nSELECT generate_series(1, max_id) EXCEPT SELECT id FROM users;  -- find gaps\n-- Then inserts into the gaps. This is WRONG:\n-- 1. Requires serialization (find gap → claim → insert) → kills throughput\n-- 2. Breaks FK references: if id=4 was referenced by an order, then deleted,\n--    reusing id=4 makes the order point to the NEW row (wrong data)\n-- 3. Sequences are designed to be gap-tolerant — gaps are not errors\n\n-- ✅ RIGHT: accept gaps. A sequence guarantees uniqueness, not consecutiveness.\n-- If consecutive numbers are a legal requirement (invoices, checks), use a\n-- counter table with explicit locking (see the \"Sequence Gaps\" section above).\n-- For entity IDs, gaps are meaningless — nobody cares that user 4 doesn't exist.\n",[18,2517,2518,2523,2528,2533,2564,2569,2574,2579,2584,2589,2593,2598,2603,2608],{"__ignoreMap":64},[68,2519,2520],{"class":70,"line":71},[68,2521,2522],{"class":125},"-- ❌ WRONG: \"fixing\" gaps by reusing skipped IDs\n",[68,2524,2525],{"class":70,"line":102},[68,2526,2527],{"class":125},"-- A developer notices IDs go 1, 2, 3, 5, 6, 8 (gaps at 4, 7) and\n",[68,2529,2530],{"class":70,"line":109},[68,2531,2532],{"class":125},"-- writes code to \"fill\" them:\n",[68,2534,2535,2537,2540,2543,2545,2548,2551,2554,2557,2559,2561],{"class":70,"line":129},[68,2536,112],{"class":74},[68,2538,2539],{"class":88}," generate_series",[68,2541,2542],{"class":92},"(",[68,2544,96],{"class":88},[68,2546,2547],{"class":92},", max_id) ",[68,2549,2550],{"class":74},"EXCEPT",[68,2552,2553],{"class":74}," SELECT",[68,2555,2556],{"class":92}," id ",[68,2558,1483],{"class":74},[68,2560,1617],{"class":92},[68,2562,2563],{"class":125},"-- find gaps\n",[68,2565,2566],{"class":70,"line":143},[68,2567,2568],{"class":125},"-- Then inserts into the gaps. This is WRONG:\n",[68,2570,2571],{"class":70,"line":158},[68,2572,2573],{"class":125},"-- 1. Requires serialization (find gap → claim → insert) → kills throughput\n",[68,2575,2576],{"class":70,"line":169},[68,2577,2578],{"class":125},"-- 2. Breaks FK references: if id=4 was referenced by an order, then deleted,\n",[68,2580,2581],{"class":70,"line":190},[68,2582,2583],{"class":125},"--    reusing id=4 makes the order point to the NEW row (wrong data)\n",[68,2585,2586],{"class":70,"line":196},[68,2587,2588],{"class":125},"-- 3. Sequences are designed to be gap-tolerant — gaps are not errors\n",[68,2590,2591],{"class":70,"line":202},[68,2592,106],{"emptyLinePlaceholder":105},[68,2594,2595],{"class":70,"line":208},[68,2596,2597],{"class":125},"-- ✅ RIGHT: accept gaps. A sequence guarantees uniqueness, not consecutiveness.\n",[68,2599,2600],{"class":70,"line":214},[68,2601,2602],{"class":125},"-- If consecutive numbers are a legal requirement (invoices, checks), use a\n",[68,2604,2605],{"class":70,"line":220},[68,2606,2607],{"class":125},"-- counter table with explicit locking (see the \"Sequence Gaps\" section above).\n",[68,2609,2610],{"class":70,"line":226},[68,2611,2612],{"class":125},"-- For entity IDs, gaps are meaningless — nobody cares that user 4 doesn't exist.\n",[31,2614,2616],{"id":2615},"replication-and-failover-sequences-dont-replicate","Replication and Failover — sequences don't replicate",[56,2618,2619],{"language":58},[60,2620,2622],{"className":62,"code":2621,"language":58,"meta":64,"style":64},"-- Sequences are per-server state. Logical replication does NOT replicate\n-- sequence values. In a primary + replica setup:\n-- - Replicas have their own sequence state (may lag the primary)\n-- - On failover, the new primary's sequence might be BEHIND the max(id)\n--   already in the table → immediate duplicate-key errors on insert\n\n-- Post-failover fix: advance ALL sequences past max(id)\n-- Run this on the new primary after failover:\nSELECT format(\n  'SELECT setval(%L, (SELECT max(%I) FROM %I.%I), true);',\n  pg_get_serial_sequence(n.nspname || '.' || c.relname, a.attname),\n  a.attname, n.nspname, c.relname\n)\nFROM pg_class c\nJOIN pg_namespace n ON n.oid = c.relnamespace\nJOIN pg_attribute a ON a.attrelid = c.oid\nJOIN pg_attrdef d ON d.adrelid = c.oid AND d.adnum = a.attnum\nWHERE d.adbin LIKE '%nextval%'                          -- columns with sequence defaults\n  AND n.nspname NOT IN ('pg_catalog', 'information_schema');\n-- Copy and run the output. Or use \\gexec in psql.\n\n-- Prevention: allocate IDs in blocks per node (no shared counter)\n-- Node 1: CREATE SEQUENCE ... START 1 INCREMENT 1\n-- Node 2: CREATE SEQUENCE ... START 1000000001 INCREMENT 1\n-- No replication contention, no failover gap. Or use UUIDs (no shared counter).\n",[18,2623,2624,2629,2634,2639,2644,2649,2653,2658,2663,2671,2678,2717,2744,2748,2755,2784,2810,2854,2874,2898,2903,2907,2912,2917,2922],{"__ignoreMap":64},[68,2625,2626],{"class":70,"line":71},[68,2627,2628],{"class":125},"-- Sequences are per-server state. Logical replication does NOT replicate\n",[68,2630,2631],{"class":70,"line":102},[68,2632,2633],{"class":125},"-- sequence values. In a primary + replica setup:\n",[68,2635,2636],{"class":70,"line":109},[68,2637,2638],{"class":125},"-- - Replicas have their own sequence state (may lag the primary)\n",[68,2640,2641],{"class":70,"line":129},[68,2642,2643],{"class":125},"-- - On failover, the new primary's sequence might be BEHIND the max(id)\n",[68,2645,2646],{"class":70,"line":143},[68,2647,2648],{"class":125},"--   already in the table → immediate duplicate-key errors on insert\n",[68,2650,2651],{"class":70,"line":158},[68,2652,106],{"emptyLinePlaceholder":105},[68,2654,2655],{"class":70,"line":169},[68,2656,2657],{"class":125},"-- Post-failover fix: advance ALL sequences past max(id)\n",[68,2659,2660],{"class":70,"line":190},[68,2661,2662],{"class":125},"-- Run this on the new primary after failover:\n",[68,2664,2665,2667,2669],{"class":70,"line":196},[68,2666,112],{"class":74},[68,2668,1651],{"class":88},[68,2670,1654],{"class":92},[68,2672,2673,2676],{"class":70,"line":202},[68,2674,2675],{"class":118},"  'SELECT setval(%L, (SELECT max(%I) FROM %I.%I), true);'",[68,2677,383],{"class":92},[68,2679,2680,2683,2686,2688,2691,2693,2695,2697,2700,2702,2705,2707,2710,2712,2715],{"class":70,"line":208},[68,2681,2682],{"class":92},"  pg_get_serial_sequence(",[68,2684,2685],{"class":88},"n",[68,2687,54],{"class":92},[68,2689,2690],{"class":88},"nspname",[68,2692,1675],{"class":74},[68,2694,1672],{"class":118},[68,2696,1675],{"class":74},[68,2698,2699],{"class":88}," c",[68,2701,54],{"class":92},[68,2703,2704],{"class":88},"relname",[68,2706,179],{"class":92},[68,2708,2709],{"class":88},"a",[68,2711,54],{"class":92},[68,2713,2714],{"class":88},"attname",[68,2716,1467],{"class":92},[68,2718,2719,2722,2724,2726,2728,2730,2732,2734,2736,2739,2741],{"class":70,"line":214},[68,2720,2721],{"class":88},"  a",[68,2723,54],{"class":92},[68,2725,2714],{"class":88},[68,2727,179],{"class":92},[68,2729,2685],{"class":88},[68,2731,54],{"class":92},[68,2733,2690],{"class":88},[68,2735,179],{"class":92},[68,2737,2738],{"class":88},"c",[68,2740,54],{"class":92},[68,2742,2743],{"class":88},"relname\n",[68,2745,2746],{"class":70,"line":220},[68,2747,1688],{"class":92},[68,2749,2750,2752],{"class":70,"line":226},[68,2751,1483],{"class":74},[68,2753,2754],{"class":92}," pg_class c\n",[68,2756,2757,2760,2763,2766,2769,2771,2774,2777,2779,2781],{"class":70,"line":498},[68,2758,2759],{"class":74},"JOIN",[68,2761,2762],{"class":92}," pg_namespace n ",[68,2764,2765],{"class":74},"ON",[68,2767,2768],{"class":88}," n",[68,2770,54],{"class":92},[68,2772,2773],{"class":88},"oid",[68,2775,2776],{"class":74}," =",[68,2778,2699],{"class":88},[68,2780,54],{"class":92},[68,2782,2783],{"class":88},"relnamespace\n",[68,2785,2786,2788,2791,2793,2796,2798,2801,2803,2805,2807],{"class":70,"line":504},[68,2787,2759],{"class":74},[68,2789,2790],{"class":92}," pg_attribute a ",[68,2792,2765],{"class":74},[68,2794,2795],{"class":88}," a",[68,2797,54],{"class":92},[68,2799,2800],{"class":88},"attrelid",[68,2802,2776],{"class":74},[68,2804,2699],{"class":88},[68,2806,54],{"class":92},[68,2808,2809],{"class":88},"oid\n",[68,2811,2812,2814,2817,2819,2822,2824,2827,2829,2831,2833,2835,2838,2840,2842,2845,2847,2849,2851],{"class":70,"line":510},[68,2813,2759],{"class":74},[68,2815,2816],{"class":92}," pg_attrdef d ",[68,2818,2765],{"class":74},[68,2820,2821],{"class":88}," d",[68,2823,54],{"class":92},[68,2825,2826],{"class":88},"adrelid",[68,2828,2776],{"class":74},[68,2830,2699],{"class":88},[68,2832,54],{"class":92},[68,2834,2773],{"class":88},[68,2836,2837],{"class":74}," AND",[68,2839,2821],{"class":88},[68,2841,54],{"class":92},[68,2843,2844],{"class":88},"adnum",[68,2846,2776],{"class":74},[68,2848,2795],{"class":88},[68,2850,54],{"class":92},[68,2852,2853],{"class":88},"attnum\n",[68,2855,2856,2858,2860,2862,2865,2868,2871],{"class":70,"line":1151},[68,2857,1237],{"class":74},[68,2859,2821],{"class":88},[68,2861,54],{"class":92},[68,2863,2864],{"class":88},"adbin",[68,2866,2867],{"class":74}," LIKE",[68,2869,2870],{"class":118}," '%nextval%'",[68,2872,2873],{"class":125},"                          -- columns with sequence defaults\n",[68,2875,2876,2878,2880,2882,2884,2886,2888,2890,2892,2894,2896],{"class":70,"line":1157},[68,2877,1718],{"class":74},[68,2879,2768],{"class":88},[68,2881,54],{"class":92},[68,2883,2690],{"class":88},[68,2885,1923],{"class":74},[68,2887,1727],{"class":74},[68,2889,814],{"class":92},[68,2891,1732],{"class":118},[68,2893,179],{"class":92},[68,2895,1737],{"class":118},[68,2897,396],{"class":92},[68,2899,2900],{"class":70,"line":1169},[68,2901,2902],{"class":125},"-- Copy and run the output. Or use \\gexec in psql.\n",[68,2904,2905],{"class":70,"line":1181},[68,2906,106],{"emptyLinePlaceholder":105},[68,2908,2909],{"class":70,"line":1196},[68,2910,2911],{"class":125},"-- Prevention: allocate IDs in blocks per node (no shared counter)\n",[68,2913,2914],{"class":70,"line":1201},[68,2915,2916],{"class":125},"-- Node 1: CREATE SEQUENCE ... START 1 INCREMENT 1\n",[68,2918,2919],{"class":70,"line":1207},[68,2920,2921],{"class":125},"-- Node 2: CREATE SEQUENCE ... START 1000000001 INCREMENT 1\n",[68,2923,2924],{"class":70,"line":1216},[68,2925,2926],{"class":125},"-- No replication contention, no failover gap. Or use UUIDs (no shared counter).\n",[31,2928,2930],{"id":2929},"alter-sequence-restart-cache-increment","ALTER SEQUENCE — restart, cache, increment",[56,2932,2933],{"language":58},[60,2934,2936],{"className":62,"code":2935,"language":58,"meta":64,"style":64},"ALTER SEQUENCE my_seq RESTART WITH 1000;      -- reset (next nextval → 1000)\nALTER SEQUENCE my_seq INCREMENT BY 10;        -- change the increment\nALTER SEQUENCE my_seq CACHE 1000;             -- change the cache size\nALTER SEQUENCE my_seq MINVALUE 1 MAXVALUE 1000000;  -- bounds\nALTER SEQUENCE my_seq CYCLE;                  -- wrap around at MAXVALUE (rare, dangerous)\nALTER SEQUENCE my_seq NO CYCLE;               -- error at MAXVALUE (default, safer)\n-- WARNING: ALTER SEQUENCE ... RESTART affects ALL sessions immediately.\n-- Concurrent sessions with cached values may still use their cache.\n",[18,2937,2938,2959,2978,2995,3018,3030,3047,3052],{"__ignoreMap":64},[68,2939,2940,2942,2944,2947,2949,2951,2953,2956],{"class":70,"line":71},[68,2941,465],{"class":74},[68,2943,78],{"class":74},[68,2945,2946],{"class":92}," my_seq ",[68,2948,972],{"class":74},[68,2950,975],{"class":74},[68,2952,978],{"class":88},[68,2954,2955],{"class":92},";      ",[68,2957,2958],{"class":125},"-- reset (next nextval → 1000)\n",[68,2960,2961,2963,2965,2968,2970,2972,2975],{"class":70,"line":102},[68,2962,465],{"class":74},[68,2964,78],{"class":74},[68,2966,2967],{"class":92}," my_seq INCREMENT ",[68,2969,473],{"class":74},[68,2971,1029],{"class":88},[68,2973,2974],{"class":92},";        ",[68,2976,2977],{"class":125},"-- change the increment\n",[68,2979,2980,2982,2984,2987,2989,2992],{"class":70,"line":109},[68,2981,465],{"class":74},[68,2983,78],{"class":74},[68,2985,2986],{"class":92}," my_seq CACHE ",[68,2988,1317],{"class":88},[68,2990,2991],{"class":92},";             ",[68,2993,2994],{"class":125},"-- change the cache size\n",[68,2996,2997,2999,3001,3004,3006,3009,3012,3015],{"class":70,"line":129},[68,2998,465],{"class":74},[68,3000,78],{"class":74},[68,3002,3003],{"class":92}," my_seq MINVALUE ",[68,3005,96],{"class":88},[68,3007,3008],{"class":92}," MAXVALUE ",[68,3010,3011],{"class":88},"1000000",[68,3013,3014],{"class":92},";  ",[68,3016,3017],{"class":125},"-- bounds\n",[68,3019,3020,3022,3024,3027],{"class":70,"line":143},[68,3021,465],{"class":74},[68,3023,78],{"class":74},[68,3025,3026],{"class":92}," my_seq CYCLE;                  ",[68,3028,3029],{"class":125},"-- wrap around at MAXVALUE (rare, dangerous)\n",[68,3031,3032,3034,3036,3038,3041,3044],{"class":70,"line":158},[68,3033,465],{"class":74},[68,3035,78],{"class":74},[68,3037,2946],{"class":92},[68,3039,3040],{"class":74},"NO",[68,3042,3043],{"class":92}," CYCLE;               ",[68,3045,3046],{"class":125},"-- error at MAXVALUE (default, safer)\n",[68,3048,3049],{"class":70,"line":169},[68,3050,3051],{"class":125},"-- WARNING: ALTER SEQUENCE ... RESTART affects ALL sessions immediately.\n",[68,3053,3054],{"class":70,"line":190},[68,3055,3056],{"class":125},"-- Concurrent sessions with cached values may still use their cache.\n",[31,3058,3060],{"id":3059},"tips-tricks","💡 Tips & Tricks",[595,3062,3063,3084,3096,3113,3133,3143,3164],{},[598,3064,3065,3068,3069,3072,3073,3075,3076,604,3078,3080,3081,3083],{},[51,3066,3067],{},"Idiom",": use ",[18,3070,3071],{},"BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY"," for all new tables — it's the SQL-standard, properly-tracked equivalent of ",[18,3074,557],{},", with easier ",[18,3077,465],{},[18,3079,972],{},", proper dependency tracking (DROP COLUMN cleans up), and cleaner dump\u002Frestore. Reserve ",[18,3082,24],{}," for legacy compatibility.",[598,3085,3086,3068,3088,3091,3092,3095],{},[51,3087,3067],{},[18,3089,3090],{},"UUID"," (v4 via ",[18,3093,3094],{},"gen_random_uuid()",", or v7 for time-ordered) when you need client-side ID generation (idempotent retries, distributed systems) or global uniqueness across databases — but accept the 16-byte size (2x bigint) and B-tree index fragmentation (random UUIDs cause page splits). For large-scale UUID tables, use UUIDv7 (monotonic, append-only index behavior).",[598,3097,3098,3100,3101,3104,3105,3108,3109,3112],{},[51,3099,3067],{},": after a bulk load or ",[18,3102,3103],{},"COPY"," that inserted explicit IDs, ",[51,3106,3107],{},"always reset the sequence"," with ",[18,3110,3111],{},"SELECT setval(pg_get_serial_sequence('t', 'id'), (SELECT max(id) FROM t))"," — otherwise the next auto-insert collides with a manually-inserted ID. This is the #1 cause of \"duplicate key value violates unique constraint\" after a data migration.",[598,3114,3115,3118,3119,3122,3123,3126,3127,3130,3131,54],{},[51,3116,3117],{},"Performance",": increase a sequence's ",[18,3120,3121],{},"CACHE"," for high-throughput insert tables — ",[18,3124,3125],{},"CACHE 1000"," means each backend grabs 1000 values at once, reducing the shared-counter lock contention that becomes a bottleneck on heavily concurrent inserts. Default ",[18,3128,3129],{},"CACHE 1"," acquires the lock on every ",[18,3132,48],{},[598,3134,3135,3138,3139,3142],{},[51,3136,3137],{},"Reliability",": after a replica failover, advance the new primary's sequences past ",[18,3140,3141],{},"max(id)"," for every table — sequences don't replicate with logical replication, and a behind-the-data sequence causes immediate duplicate-key errors on the new primary. Automate this in your failover script.",[598,3144,3145,3068,3147,3150,3151,3153,3154,3156,3157,3159,3160,3163],{},[51,3146,3067],{},[18,3148,3149],{},"pg_get_serial_sequence('table', 'column')"," to find a column's sequence name — don't assume it's ",[18,3152,617],{}," (true for ",[18,3155,24],{},", but ",[18,3158,28],{}," and manually-created sequences differ). Use it in ",[18,3161,3162],{},"setval"," calls for portability.",[598,3165,3166,3068,3168,3171,3172,3175,3176,3179,3180,3182],{},[51,3167,3067],{},[18,3169,3170],{},"GENERATED ALWAYS"," (not ",[18,3173,3174],{},"BY DEFAULT",") for production tables — it prevents manual ID injection (a common cause of collisions and data inconsistency). Use ",[18,3177,3178],{},"OVERRIDING SYSTEM VALUE"," only for explicit migrations. ",[18,3181,3174],{}," is for migration flexibility, not production.",[31,3184,3186],{"id":3185},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[595,3188,3189,3198,3223,3254,3266,3296,3322,3331,3355,3385,3412,3418],{},[598,3190,3191,3194,3195,3197],{},[51,3192,3193],{},"Sequence gaps after rollback (by design)",": ",[18,3196,48],{}," is never rolled back. A rolled-back insert, a failed insert, a crash, or cached-but-unused values all create gaps. Sequences guarantee uniqueness, not consecutiveness. Don't try to fill gaps — it breaks FK references and kills throughput.",[598,3199,3200,3194,3209,3212,3213,3216,3217,3219,3220,3222],{},[51,3201,3202,3205,3206,3208],{},[18,3203,3204],{},"currval"," without ",[18,3207,48],{}," in session",[18,3210,3211],{},"currval('seq')"," errors with \"session does not have a current value for sequence\" if ",[18,3214,3215],{},"nextval('seq')"," hasn't been called in the current session. ",[18,3218,3204],{}," returns THIS session's last ",[18,3221,48],{},", not the global sequence value.",[598,3224,3225,3228,3229,3232,3233,3236,3237,3240,3241,3244,3245,3247,3248,3250,3251,3253],{},[51,3226,3227],{},"Sequence wraparound",": with ",[18,3230,3231],{},"CYCLE",", the sequence wraps to ",[18,3234,3235],{},"MINVALUE"," after ",[18,3238,3239],{},"MAXVALUE",". This causes duplicate keys if old rows still exist. ",[18,3242,3243],{},"NO CYCLE"," (default) errors at ",[18,3246,3239],{}," — safer. For ",[18,3249,691],{},", wraparound is practically impossible (9.2 × 10^18), but for ",[18,3252,437],{}," sequences, it's a real risk.",[598,3255,3256,3194,3259,3262,3263,3265],{},[51,3257,3258],{},"Sequence caching and failover",[18,3260,3261],{},"CACHE N"," reserves N values per session. On crash\u002Ffailover, the unused cached values are lost (gaps). A ",[18,3264,3125],{}," sequence on a primary that fails over may have 1000 unused values per active session — the replica's sequence is behind by that much. Advance the sequence post-failover.",[598,3267,3268,3273,3274,604,3276,3278,3279,3281,3282,3285,3286,3288,3289,3292,3293,54],{},[51,3269,3270,3272],{},[18,3271,24],{}," type is not real",": the column is ",[18,3275,603],{},[18,3277,607],{},"; ",[18,3280,24],{}," is a declaration-time macro. ",[18,3283,3284],{},"pg_typeof(id)"," returns ",[18,3287,603],{},", not ",[18,3290,3291],{},"serial",". You can't ",[18,3294,3295],{},"ALTER COLUMN col TYPE serial",[598,3297,3298,3194,3306,3309,3310,3312,3313,3315,3316,3318,3319,3321],{},[51,3299,3300,3303,3304],{},[18,3301,3302],{},"IDENTITY ALWAYS"," vs ",[18,3305,3174],{},[18,3307,3308],{},"ALWAYS"," rejects manual inserts (use ",[18,3311,3178],{}," to override). ",[18,3314,3174],{}," allows manual inserts (collision risk if the manual value is below the sequence position). Use ",[18,3317,3308],{}," for production, ",[18,3320,3174],{}," for migration flexibility.",[598,3323,3324,3330],{},[51,3325,3326,3329],{},[18,3327,3328],{},"ALTER SEQUENCE RESTART"," affects all sessions",": the restart is immediate and global. Concurrent sessions with cached values may still use their cache (stale values). For a clean restart, ensure no concurrent inserts.",[598,3332,3333,3336,3337,3340,3341,3343,3344,3347,3348,3350,3351,3354],{},[51,3334,3335],{},"Sequence permissions",": a role needs ",[18,3338,3339],{},"USAGE"," on the sequence to call ",[18,3342,48],{},". ",[18,3345,3346],{},"GRANT INSERT ON table"," does NOT grant ",[18,3349,3339],{}," on the table's sequence — grant it explicitly: ",[18,3352,3353],{},"GRANT USAGE, SELECT ON SEQUENCE users_id_seq TO app_role;"," (SELECT for currval, USAGE for nextval).",[598,3356,3357,3194,3362,3365,3366,3285,3368,3371,3372,3375,3376,3365,3379,3285,3381,3384],{},[51,3358,3359,3361],{},[18,3360,3162],{}," semantics",[18,3363,3364],{},"setval(seq, N)"," → next ",[18,3367,48],{},[18,3369,3370],{},"N+1"," (the ",[18,3373,3374],{},"is_called"," flag defaults to true). ",[18,3377,3378],{},"setval(seq, N, false)",[18,3380,48],{},[18,3382,3383],{},"N"," (as if N hasn't been \"called\" yet). Know which you want — the difference is off-by-one.",[598,3386,3387,3194,3392,3395,3396,3399,3400,3402,3403,3405,3406,3408,3409,3411],{},[51,3388,3389,3391],{},[18,3390,643],{}," and column drop",[18,3393,3394],{},"ALTER SEQUENCE ... OWNED BY table.col"," ties the sequence to the column — ",[18,3397,3398],{},"DROP COLUMN col"," drops the sequence. Without ",[18,3401,643],{},", the sequence survives column drop (orphaned). ",[18,3404,28],{}," handles this automatically; ",[18,3407,24],{}," sets ",[18,3410,643],{}," in the macro.",[598,3413,3414,3417],{},[51,3415,3416],{},"UUIDv4 index fragmentation",": random UUIDs cause B-tree index fragmentation (random insert positions → page splits). Indexes grow larger than necessary and cache locality is poor. For large-scale UUID tables, use UUIDv7 (time-ordered, monotonic → append-only index behavior) or ULID.",[598,3419,3420,3425,3426,3428,3429,3431],{},[51,3421,3422,3424],{},[18,3423,621],{}," returns NULL for non-sequence columns",": if the column has no sequence default (e.g., a UUID column), ",[18,3427,621],{}," returns NULL. Don't use it blindly in ",[18,3430,3162],{}," — check for NULL first.",[31,3433,3435],{"id":3434},"spot-the-bug","🧠 Spot the Bug",[14,3437,3438,3439,3442],{},"A team migrates a ",[18,3440,3441],{},"users"," table by loading a dump with explicit IDs (1 through 10000). After the migration, new user registrations fail with \"duplicate key value violates unique constraint.\" The table:",[56,3444,3445],{"language":58},[60,3446,3448],{"className":62,"code":3447,"language":58,"meta":64,"style":64},"CREATE TABLE users (\n  id BIGSERIAL PRIMARY KEY,           -- sequence users_id_seq starts at 1\n  name TEXT\n);\n\n-- Migration: loaded dump with explicit IDs\nCOPY users (id, name) FROM '\u002Ftmp\u002Fusers.csv' WITH CSV HEADER;\n-- The dump has IDs 1..10000. COPY inserts them directly, bypassing\n-- the DEFAULT nextval('users_id_seq'). The sequence is still at 1.\n\n-- After migration, the app inserts a new user:\nINSERT INTO users (name) VALUES ('new_user');\n-- → nextval('users_id_seq') returns 1\n-- → id=1 already exists (from the dump)\n-- → ERROR: duplicate key value violates unique constraint \"users_pkey\"\n",[18,3449,3450,3460,3473,3479,3483,3487,3492,3512,3517,3522,3526,3531,3551,3556,3561],{"__ignoreMap":64},[68,3451,3452,3454,3456,3458],{"class":70,"line":71},[68,3453,75],{"class":74},[68,3455,364],{"class":74},[68,3457,367],{"class":81},[68,3459,370],{"class":92},[68,3461,3462,3464,3466,3468,3470],{"class":70,"line":102},[68,3463,375],{"class":92},[68,3465,557],{"class":74},[68,3467,380],{"class":74},[68,3469,2411],{"class":92},[68,3471,3472],{"class":125},"-- sequence users_id_seq starts at 1\n",[68,3474,3475,3477],{"class":70,"line":109},[68,3476,388],{"class":74},[68,3478,391],{"class":74},[68,3480,3481],{"class":70,"line":129},[68,3482,396],{"class":92},[68,3484,3485],{"class":70,"line":143},[68,3486,106],{"emptyLinePlaceholder":105},[68,3488,3489],{"class":70,"line":158},[68,3490,3491],{"class":125},"-- Migration: loaded dump with explicit IDs\n",[68,3493,3494,3496,3498,3500,3502,3504,3507,3509],{"class":70,"line":169},[68,3495,3103],{"class":74},[68,3497,802],{"class":92},[68,3499,805],{"class":74},[68,3501,808],{"class":92},[68,3503,1483],{"class":74},[68,3505,3506],{"class":118}," '\u002Ftmp\u002Fusers.csv'",[68,3508,975],{"class":74},[68,3510,3511],{"class":92}," CSV HEADER;\n",[68,3513,3514],{"class":70,"line":190},[68,3515,3516],{"class":125},"-- The dump has IDs 1..10000. COPY inserts them directly, bypassing\n",[68,3518,3519],{"class":70,"line":196},[68,3520,3521],{"class":125},"-- the DEFAULT nextval('users_id_seq'). The sequence is still at 1.\n",[68,3523,3524],{"class":70,"line":202},[68,3525,106],{"emptyLinePlaceholder":105},[68,3527,3528],{"class":70,"line":208},[68,3529,3530],{"class":125},"-- After migration, the app inserts a new user:\n",[68,3532,3533,3535,3538,3540,3542,3544,3546,3549],{"class":70,"line":214},[68,3534,799],{"class":74},[68,3536,3537],{"class":92}," users (",[68,3539,805],{"class":74},[68,3541,808],{"class":92},[68,3543,811],{"class":74},[68,3545,814],{"class":92},[68,3547,3548],{"class":118},"'new_user'",[68,3550,396],{"class":92},[68,3552,3553],{"class":70,"line":220},[68,3554,3555],{"class":125},"-- → nextval('users_id_seq') returns 1\n",[68,3557,3558],{"class":70,"line":226},[68,3559,3560],{"class":125},"-- → id=1 already exists (from the dump)\n",[68,3562,3563],{"class":70,"line":498},[68,3564,3565],{"class":125},"-- → ERROR: duplicate key value violates unique constraint \"users_pkey\"\n",[3567,3568,3569,3573,3590,3596,3641,3647,3698],"details",{},[3570,3571,3572],"summary",{},"Answer",[14,3574,3575,3576,3578,3579,3582,3583,3586,3587,3589],{},"The dump inserted rows with explicit IDs (1–10000), bypassing the ",[18,3577,480],{}," column's default (",[18,3580,3581],{},"nextval('users_id_seq')","). The sequence ",[18,3584,3585],{},"users_id_seq"," wasn't advanced — it's still at its pre-load position (1). The next auto-generated insert calls ",[18,3588,48],{},", gets a value that already exists in the table, and violates the primary key uniqueness constraint.",[14,3591,3592,3593,3595],{},"The fix — after any data load with explicit IDs, advance the sequence past ",[18,3594,3141],{},":",[56,3597,3598],{"language":58},[60,3599,3601],{"className":62,"code":3600,"language":58,"meta":64,"style":64},"SELECT setval(pg_get_serial_sequence('users', 'id'),\n              (SELECT max(id) FROM users));\n-- setval(seq, max_id) → next nextval returns max_id + 1 → no collision.\n-- Always run this after a COPY or bulk INSERT with explicit IDs.\n",[18,3602,3603,3617,3631,3636],{"__ignoreMap":64},[68,3604,3605,3607,3609,3611,3613,3615],{"class":70,"line":71},[68,3606,112],{"class":74},[68,3608,1458],{"class":92},[68,3610,1430],{"class":118},[68,3612,179],{"class":92},[68,3614,1435],{"class":118},[68,3616,1467],{"class":92},[68,3618,3619,3621,3623,3625,3627,3629],{"class":70,"line":102},[68,3620,1472],{"class":92},[68,3622,112],{"class":74},[68,3624,1477],{"class":88},[68,3626,1480],{"class":92},[68,3628,1483],{"class":74},[68,3630,1486],{"class":92},[68,3632,3633],{"class":70,"line":109},[68,3634,3635],{"class":125},"-- setval(seq, max_id) → next nextval returns max_id + 1 → no collision.\n",[68,3637,3638],{"class":70,"line":129},[68,3639,3640],{"class":125},"-- Always run this after a COPY or bulk INSERT with explicit IDs.\n",[14,3642,3643,3644,3646],{},"For an ",[18,3645,28],{}," column, the equivalent:",[56,3648,3649],{"language":58},[60,3650,3652],{"className":62,"code":3651,"language":58,"meta":64,"style":64},"-- Compute max first (RESTART doesn't accept subqueries):\nSELECT max(id) + 1 FROM users;  -- → 10001\nALTER TABLE users ALTER COLUMN id RESTART WITH 10001;\n",[18,3653,3654,3659,3678],{"__ignoreMap":64},[68,3655,3656],{"class":70,"line":71},[68,3657,3658],{"class":125},"-- Compute max first (RESTART doesn't accept subqueries):\n",[68,3660,3661,3663,3665,3667,3669,3671,3673,3675],{"class":70,"line":102},[68,3662,112],{"class":74},[68,3664,1477],{"class":88},[68,3666,1480],{"class":92},[68,3668,1229],{"class":74},[68,3670,89],{"class":88},[68,3672,1614],{"class":74},[68,3674,1617],{"class":92},[68,3676,3677],{"class":125},"-- → 10001\n",[68,3679,3680,3682,3684,3686,3688,3690,3692,3694,3696],{"class":70,"line":109},[68,3681,465],{"class":74},[68,3683,364],{"class":74},[68,3685,964],{"class":92},[68,3687,465],{"class":74},[68,3689,969],{"class":92},[68,3691,972],{"class":74},[68,3693,975],{"class":74},[68,3695,1592],{"class":88},[68,3697,99],{"class":92},[14,3699,3700,3703,3704,179,3706,3709,3710,3713,3714,3716],{},[51,3701,3702],{},"The lesson",": explicit-ID inserts (via ",[18,3705,3103],{},[18,3707,3708],{},"INSERT INTO ... VALUES (id, ...)",", or ",[18,3711,3712],{},"pg_restore",") don't advance the sequence. After a data load\u002Fmigration that supplies IDs, always reset the sequence to ",[18,3715,3141],{},", or the next auto-generated insert collides.",[3718,3719,3720],"style",{},"html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}",{"title":64,"searchDepth":102,"depth":102,"links":3722},[3723,3726,3730,3734,3735,3736,3737,3738,3739,3740,3741,3742,3743,3744,3745],{"id":33,"depth":102,"text":34,"children":3724},[3725],{"id":233,"depth":109,"text":234},{"id":331,"depth":102,"text":332,"children":3727},[3728,3729],{"id":516,"depth":109,"text":517},{"id":592,"depth":109,"text":593},{"id":647,"depth":102,"text":648,"children":3731},[3732,3733],{"id":781,"depth":109,"text":782},{"id":922,"depth":109,"text":923},{"id":1058,"depth":102,"text":1059},{"id":1279,"depth":102,"text":1280},{"id":1369,"depth":102,"text":1370},{"id":1489,"depth":102,"text":1490},{"id":1753,"depth":102,"text":1754},{"id":2377,"depth":102,"text":2378},{"id":2509,"depth":102,"text":2510},{"id":2615,"depth":102,"text":2616},{"id":2929,"depth":102,"text":2930},{"id":3059,"depth":102,"text":3060},{"id":3185,"depth":102,"text":3186},{"id":3434,"depth":102,"text":3435},"SEQUENCE primitive (nextval\u002Fcurrval\u002Fsetval), SERIAL legacy shortcut, IDENTITY columns (ALWAYS vs BY DEFAULT), why sequence gaps are normal (non-transactional by design), CACHE for throughput, UUID vs sequential tradeoffs, replication failover — code-first reference with a production ID strategy and anti-patterns for SERIAL and gap-chasing.","md",{},"\u002Fsql\u002F23-sequences-and-identifiers",{"title":5,"description":3746},"sql\u002F23-sequences-and-identifiers","ogdhZuBloybdVZiHfAen5Qab7yCP2JRGjwveEb2TH8E",1789924654905]