[{"data":1,"prerenderedAt":4642},["ShallowReactive",2],{"page-\u002Fsql\u002F11-tables-and-ddl":3},{"id":4,"title":5,"body":6,"description":4635,"extension":4636,"meta":4637,"navigation":112,"path":4638,"seo":4639,"stem":4640,"__hash__":4641},"content\u002Fsql\u002F11-tables-and-ddl.md","11 — Tables, Schemas & DDL",{"type":7,"value":8,"toc":4610},"minimark",[9,13,30,35,542,547,550,713,717,728,798,802,970,994,998,1159,1169,1173,1344,1348,1420,1526,1530,1697,1701,1793,1797,2143,2147,2423,2427,2656,2660,2831,2835,2973,2977,3096,3100,3821,3825,4062,4066,4178,4182,4371,4375,4382,4410,4413,4606],[10,11,5],"h1",{"id":12},"_11-tables-schemas-ddl",[14,15,16,17,21,22,25,26,29],"p",{},"DDL defines the skeleton: tables, schemas, constraints, types. Get it wrong and every query downstream pays for it. This chapter is a code-first reference for production DDL — constraints, defaults, generated columns, temp tables, safe ",[18,19,20],"code",{},"ALTER"," patterns, and ",[18,23,24],{},"TRUNCATE"," vs ",[18,27,28],{},"DELETE",".",[31,32,34],"h2",{"id":33},"create-table-full-syntax","CREATE TABLE — Full Syntax",[36,37,39],"code-wrapper",{"language":38},"sql",[40,41,45],"pre",{"className":42,"code":43,"language":38,"meta":44,"style":44},"language-sql shiki shiki-themes github-light github-dark","-- Complete production-grade table definition.\n-- Every clause annotated with its runtime\u002Flifetime implication.\nCREATE TABLE billing.invoices (\n  -- BIGSERIAL: creates a SEQUENCE + integer column + NOT NULL default.\n  -- Sequence is a separate O(1) allocation object; gaps are possible on rollback.\n  id              BIGSERIAL   PRIMARY KEY,\n\n  -- TEXT vs VARCHAR(n): PG has no performance difference; VARCHAR(n) adds a CHECK.\n  -- Prefer TEXT unless you need a length cap for business rules.\n  invoice_number  TEXT        NOT NULL,\n\n  -- NUMERIC(p,s): exact decimal — no floating-point error. p=total digits, s=scale.\n  -- Storage: 2 bytes per 4 digits. Slower than float but safe for money.\n  amount          NUMERIC(12,2) NOT NULL,\n\n  -- TIMESTAMPTZ stores timestamp + timezone offset, normalized to UTC on disk.\n  -- 8 bytes. Use TIMESTAMPTZ over TIMESTAMP unless you have a specific reason.\n  issued_at       TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n  -- Dynamic default: now() is STABLE (same value within a statement), not VOLATILE.\n  -- The default is evaluated at INSERT time, not at table creation.\n  due_at          TIMESTAMPTZ NOT NULL DEFAULT (now() + interval '30 days'),\n\n  -- JSONB: binary JSON, GIN-indexable, reordered on storage. 1 byte overhead\u002Fjson.\n  -- Keys are stored sorted; duplicate keys keep last value.\n  metadata        JSONB       NOT NULL DEFAULT '{}'::jsonb,\n\n  -- Generated STORED column: computed on write, physically stored, read at no cost.\n  -- Cannot be written to (INSERT\u002FUPDATE on this column errors).\n  -- Cannot reference other tables, subqueries, or non-immutable functions.\n  amount_usd      NUMERIC(12,2) GENERATED ALWAYS AS (amount * 1.0) STORED,\n\n  -- Partial unique: only one non-draft invoice per number.\n  -- Implemented as a partial unique index under the hood.\n  CONSTRAINT invoices_unique_number UNIQUE (invoice_number)\n    WHERE metadata->>'status' \u003C> 'draft',\n\n  -- CHECK constraint: per-row boolean; NULL (UNKNOWN) passes.\n  -- CHECK can reference only the current row — no subqueries, no other tables.\n  CONSTRAINT invoices_valid_amount CHECK (amount >= 0),\n\n  -- Table-level FK with explicit name for readable error messages.\n  -- ON DELETE RESTRICT prevents deleting a customer with invoices.\n  CONSTRAINT invoices_customer_fk\n    FOREIGN KEY (metadata->>'customer_id')\n    REFERENCES billing.customers(id)\n    ON DELETE RESTRICT\n);\n","",[18,46,47,56,62,80,86,92,107,114,120,126,140,145,151,157,187,192,198,204,225,230,236,242,276,281,287,293,309,314,320,326,332,376,381,387,393,408,431,436,442,448,469,474,480,486,494,511,527,536],{"__ignoreMap":44},[48,49,52],"span",{"class":50,"line":51},"line",1,[48,53,55],{"class":54},"sdCPZ","-- Complete production-grade table definition.\n",[48,57,59],{"class":50,"line":58},2,[48,60,61],{"class":54},"-- Every clause annotated with its runtime\u002Flifetime implication.\n",[48,63,65,69,72,76],{"class":50,"line":64},3,[48,66,68],{"class":67},"svdQ7","CREATE",[48,70,71],{"class":67}," TABLE",[48,73,75],{"class":74},"sIsaT"," billing",[48,77,79],{"class":78},"ssxIu",".invoices (\n",[48,81,83],{"class":50,"line":82},4,[48,84,85],{"class":54},"  -- BIGSERIAL: creates a SEQUENCE + integer column + NOT NULL default.\n",[48,87,89],{"class":50,"line":88},5,[48,90,91],{"class":54},"  -- Sequence is a separate O(1) allocation object; gaps are possible on rollback.\n",[48,93,95,98,101,104],{"class":50,"line":94},6,[48,96,97],{"class":78},"  id              ",[48,99,100],{"class":67},"BIGSERIAL",[48,102,103],{"class":67},"   PRIMARY KEY",[48,105,106],{"class":78},",\n",[48,108,110],{"class":50,"line":109},7,[48,111,113],{"emptyLinePlaceholder":112},true,"\n",[48,115,117],{"class":50,"line":116},8,[48,118,119],{"class":54},"  -- TEXT vs VARCHAR(n): PG has no performance difference; VARCHAR(n) adds a CHECK.\n",[48,121,123],{"class":50,"line":122},9,[48,124,125],{"class":54},"  -- Prefer TEXT unless you need a length cap for business rules.\n",[48,127,129,132,135,138],{"class":50,"line":128},10,[48,130,131],{"class":78},"  invoice_number  ",[48,133,134],{"class":67},"TEXT",[48,136,137],{"class":67},"        NOT NULL",[48,139,106],{"class":78},[48,141,143],{"class":50,"line":142},11,[48,144,113],{"emptyLinePlaceholder":112},[48,146,148],{"class":50,"line":147},12,[48,149,150],{"class":54},"  -- NUMERIC(p,s): exact decimal — no floating-point error. p=total digits, s=scale.\n",[48,152,154],{"class":50,"line":153},13,[48,155,156],{"class":54},"  -- Storage: 2 bytes per 4 digits. Slower than float but safe for money.\n",[48,158,160,163,166,169,173,176,179,182,185],{"class":50,"line":159},14,[48,161,162],{"class":78},"  amount          ",[48,164,165],{"class":67},"NUMERIC",[48,167,168],{"class":78},"(",[48,170,172],{"class":171},"snvgF","12",[48,174,175],{"class":78},",",[48,177,178],{"class":171},"2",[48,180,181],{"class":78},") ",[48,183,184],{"class":67},"NOT NULL",[48,186,106],{"class":78},[48,188,190],{"class":50,"line":189},15,[48,191,113],{"emptyLinePlaceholder":112},[48,193,195],{"class":50,"line":194},16,[48,196,197],{"class":54},"  -- TIMESTAMPTZ stores timestamp + timezone offset, normalized to UTC on disk.\n",[48,199,201],{"class":50,"line":200},17,[48,202,203],{"class":54},"  -- 8 bytes. Use TIMESTAMPTZ over TIMESTAMP unless you have a specific reason.\n",[48,205,207,210,213,216,219,222],{"class":50,"line":206},18,[48,208,209],{"class":78},"  issued_at       ",[48,211,212],{"class":67},"TIMESTAMPTZ",[48,214,215],{"class":67}," NOT NULL",[48,217,218],{"class":67}," DEFAULT",[48,220,221],{"class":67}," now",[48,223,224],{"class":78},"(),\n",[48,226,228],{"class":50,"line":227},19,[48,229,113],{"emptyLinePlaceholder":112},[48,231,233],{"class":50,"line":232},20,[48,234,235],{"class":54},"  -- Dynamic default: now() is STABLE (same value within a statement), not VOLATILE.\n",[48,237,239],{"class":50,"line":238},21,[48,240,241],{"class":54},"  -- The default is evaluated at INSERT time, not at table creation.\n",[48,243,245,248,250,252,254,257,260,263,266,269,273],{"class":50,"line":244},22,[48,246,247],{"class":78},"  due_at          ",[48,249,212],{"class":67},[48,251,215],{"class":67},[48,253,218],{"class":67},[48,255,256],{"class":78}," (",[48,258,259],{"class":67},"now",[48,261,262],{"class":78},"() ",[48,264,265],{"class":67},"+",[48,267,268],{"class":78}," interval ",[48,270,272],{"class":271},"sJ6F3","'30 days'",[48,274,275],{"class":78},"),\n",[48,277,279],{"class":50,"line":278},23,[48,280,113],{"emptyLinePlaceholder":112},[48,282,284],{"class":50,"line":283},24,[48,285,286],{"class":54},"  -- JSONB: binary JSON, GIN-indexable, reordered on storage. 1 byte overhead\u002Fjson.\n",[48,288,290],{"class":50,"line":289},25,[48,291,292],{"class":54},"  -- Keys are stored sorted; duplicate keys keep last value.\n",[48,294,296,299,301,303,306],{"class":50,"line":295},26,[48,297,298],{"class":78},"  metadata        JSONB       ",[48,300,184],{"class":67},[48,302,218],{"class":67},[48,304,305],{"class":271}," '{}'",[48,307,308],{"class":78},"::jsonb,\n",[48,310,312],{"class":50,"line":311},27,[48,313,113],{"emptyLinePlaceholder":112},[48,315,317],{"class":50,"line":316},28,[48,318,319],{"class":54},"  -- Generated STORED column: computed on write, physically stored, read at no cost.\n",[48,321,323],{"class":50,"line":322},29,[48,324,325],{"class":54},"  -- Cannot be written to (INSERT\u002FUPDATE on this column errors).\n",[48,327,329],{"class":50,"line":328},30,[48,330,331],{"class":54},"  -- Cannot reference other tables, subqueries, or non-immutable functions.\n",[48,333,335,338,340,342,344,346,348,350,353,356,359,362,365,368,370,373],{"class":50,"line":334},31,[48,336,337],{"class":78},"  amount_usd      ",[48,339,165],{"class":67},[48,341,168],{"class":78},[48,343,172],{"class":171},[48,345,175],{"class":78},[48,347,178],{"class":171},[48,349,181],{"class":78},[48,351,352],{"class":67},"GENERATED",[48,354,355],{"class":67}," ALWAYS",[48,357,358],{"class":67}," AS",[48,360,361],{"class":78}," (amount ",[48,363,364],{"class":67},"*",[48,366,367],{"class":171}," 1",[48,369,29],{"class":78},[48,371,372],{"class":171},"0",[48,374,375],{"class":78},") STORED,\n",[48,377,379],{"class":50,"line":378},32,[48,380,113],{"emptyLinePlaceholder":112},[48,382,384],{"class":50,"line":383},33,[48,385,386],{"class":54},"  -- Partial unique: only one non-draft invoice per number.\n",[48,388,390],{"class":50,"line":389},34,[48,391,392],{"class":54},"  -- Implemented as a partial unique index under the hood.\n",[48,394,396,399,402,405],{"class":50,"line":395},35,[48,397,398],{"class":67},"  CONSTRAINT",[48,400,401],{"class":78}," invoices_unique_number ",[48,403,404],{"class":67},"UNIQUE",[48,406,407],{"class":78}," (invoice_number)\n",[48,409,411,414,417,420,423,426,429],{"class":50,"line":410},36,[48,412,413],{"class":67},"    WHERE",[48,415,416],{"class":78}," metadata",[48,418,419],{"class":67},"->>",[48,421,422],{"class":271},"'status'",[48,424,425],{"class":67}," \u003C>",[48,427,428],{"class":271}," 'draft'",[48,430,106],{"class":78},[48,432,434],{"class":50,"line":433},37,[48,435,113],{"emptyLinePlaceholder":112},[48,437,439],{"class":50,"line":438},38,[48,440,441],{"class":54},"  -- CHECK constraint: per-row boolean; NULL (UNKNOWN) passes.\n",[48,443,445],{"class":50,"line":444},39,[48,446,447],{"class":54},"  -- CHECK can reference only the current row — no subqueries, no other tables.\n",[48,449,451,453,456,459,461,464,467],{"class":50,"line":450},40,[48,452,398],{"class":67},[48,454,455],{"class":78}," invoices_valid_amount ",[48,457,458],{"class":67},"CHECK",[48,460,361],{"class":78},[48,462,463],{"class":67},">=",[48,465,466],{"class":171}," 0",[48,468,275],{"class":78},[48,470,472],{"class":50,"line":471},41,[48,473,113],{"emptyLinePlaceholder":112},[48,475,477],{"class":50,"line":476},42,[48,478,479],{"class":54},"  -- Table-level FK with explicit name for readable error messages.\n",[48,481,483],{"class":50,"line":482},43,[48,484,485],{"class":54},"  -- ON DELETE RESTRICT prevents deleting a customer with invoices.\n",[48,487,489,491],{"class":50,"line":488},44,[48,490,398],{"class":67},[48,492,493],{"class":78}," invoices_customer_fk\n",[48,495,497,500,503,505,508],{"class":50,"line":496},45,[48,498,499],{"class":67},"    FOREIGN KEY",[48,501,502],{"class":78}," (metadata",[48,504,419],{"class":67},[48,506,507],{"class":271},"'customer_id'",[48,509,510],{"class":78},")\n",[48,512,514,517,519,521,524],{"class":50,"line":513},46,[48,515,516],{"class":67},"    REFERENCES",[48,518,75],{"class":171},[48,520,29],{"class":78},[48,522,523],{"class":171},"customers",[48,525,526],{"class":78},"(id)\n",[48,528,530,533],{"class":50,"line":529},47,[48,531,532],{"class":67},"    ON DELETE",[48,534,535],{"class":78}," RESTRICT\n",[48,537,539],{"class":50,"line":538},48,[48,540,541],{"class":78},");\n",[543,544,546],"h3",{"id":545},"column-level-vs-table-level-constraints","Column-Level vs Table-Level Constraints",[14,548,549],{},"Column-level constraints appear inline with the column definition; table-level constraints appear as separate clauses. Table-level is required for multi-column constraints and for naming.",[36,551,552],{"language":38},[40,553,555],{"className":42,"code":554,"language":38,"meta":44,"style":44},"-- Column-level: compact, no name control (auto-generated name).\nCREATE TABLE t1 (\n  email TEXT NOT NULL UNIQUE,                   -- constraint name: t1_email_key\n  amount NUMERIC CHECK (amount > 0)             -- constraint name: t1_amount_check\n);\n\n-- Table-level: explicit names, multi-column support.\nCREATE TABLE t2 (\n  tenant_id   BIGINT  NOT NULL,\n  email       TEXT    NOT NULL,\n  amount      NUMERIC NOT NULL,\n  -- Named constraints → readable error messages, easy DROP CONSTRAINT.\n  CONSTRAINT t2_tenant_email_unique UNIQUE (tenant_id, email),\n  CONSTRAINT t2_amount_positive     CHECK (amount > 0)\n);\n",[18,556,557,562,574,592,615,619,623,628,639,652,664,675,680,692,709],{"__ignoreMap":44},[48,558,559],{"class":50,"line":51},[48,560,561],{"class":54},"-- Column-level: compact, no name control (auto-generated name).\n",[48,563,564,566,568,571],{"class":50,"line":58},[48,565,68],{"class":67},[48,567,71],{"class":67},[48,569,570],{"class":74}," t1",[48,572,573],{"class":78}," (\n",[48,575,576,579,581,583,586,589],{"class":50,"line":64},[48,577,578],{"class":78},"  email ",[48,580,134],{"class":67},[48,582,215],{"class":67},[48,584,585],{"class":67}," UNIQUE",[48,587,588],{"class":78},",                   ",[48,590,591],{"class":54},"-- constraint name: t1_email_key\n",[48,593,594,597,599,602,604,607,609,612],{"class":50,"line":82},[48,595,596],{"class":78},"  amount ",[48,598,165],{"class":67},[48,600,601],{"class":67}," CHECK",[48,603,361],{"class":78},[48,605,606],{"class":67},">",[48,608,466],{"class":171},[48,610,611],{"class":78},")             ",[48,613,614],{"class":54},"-- constraint name: t1_amount_check\n",[48,616,617],{"class":50,"line":88},[48,618,541],{"class":78},[48,620,621],{"class":50,"line":94},[48,622,113],{"emptyLinePlaceholder":112},[48,624,625],{"class":50,"line":109},[48,626,627],{"class":54},"-- Table-level: explicit names, multi-column support.\n",[48,629,630,632,634,637],{"class":50,"line":116},[48,631,68],{"class":67},[48,633,71],{"class":67},[48,635,636],{"class":74}," t2",[48,638,573],{"class":78},[48,640,641,644,647,650],{"class":50,"line":122},[48,642,643],{"class":78},"  tenant_id   ",[48,645,646],{"class":67},"BIGINT",[48,648,649],{"class":67},"  NOT NULL",[48,651,106],{"class":78},[48,653,654,657,659,662],{"class":50,"line":128},[48,655,656],{"class":78},"  email       ",[48,658,134],{"class":67},[48,660,661],{"class":67},"    NOT NULL",[48,663,106],{"class":78},[48,665,666,669,671,673],{"class":50,"line":142},[48,667,668],{"class":78},"  amount      ",[48,670,165],{"class":67},[48,672,215],{"class":67},[48,674,106],{"class":78},[48,676,677],{"class":50,"line":147},[48,678,679],{"class":54},"  -- Named constraints → readable error messages, easy DROP CONSTRAINT.\n",[48,681,682,684,687,689],{"class":50,"line":153},[48,683,398],{"class":67},[48,685,686],{"class":78}," t2_tenant_email_unique ",[48,688,404],{"class":67},[48,690,691],{"class":78}," (tenant_id, email),\n",[48,693,694,696,699,701,703,705,707],{"class":50,"line":159},[48,695,398],{"class":67},[48,697,698],{"class":78}," t2_amount_positive     ",[48,700,458],{"class":67},[48,702,361],{"class":78},[48,704,606],{"class":67},[48,706,466],{"class":171},[48,708,510],{"class":78},[48,710,711],{"class":50,"line":189},[48,712,541],{"class":78},[543,714,716],{"id":715},"named-constraints","Named Constraints",[14,718,719,720,723,724,727],{},"Named constraints appear in error messages (",[18,721,722],{},"violates foreign key constraint \"invoices_customer_fk\"",") and can be dropped by name. Auto-generated names (",[18,725,726],{},"t1_email_key",") work but are brittle across schema diff tools.",[36,729,730],{"language":38},[40,731,733],{"className":42,"code":732,"language":38,"meta":44,"style":44},"-- Drop by name — no need to look up the auto-generated identifier.\nALTER TABLE billing.invoices DROP CONSTRAINT invoices_valid_amount;\n\n-- Rename if you inherited a badly named constraint.\nALTER TABLE billing.invoices RENAME CONSTRAINT invoices_customer_fk TO invoices_fk_customer;\n",[18,734,735,740,762,766,771],{"__ignoreMap":44},[48,736,737],{"class":50,"line":51},[48,738,739],{"class":54},"-- Drop by name — no need to look up the auto-generated identifier.\n",[48,741,742,744,746,748,750,753,756,759],{"class":50,"line":58},[48,743,20],{"class":67},[48,745,71],{"class":67},[48,747,75],{"class":171},[48,749,29],{"class":78},[48,751,752],{"class":171},"invoices",[48,754,755],{"class":67}," DROP",[48,757,758],{"class":67}," CONSTRAINT",[48,760,761],{"class":78}," invoices_valid_amount;\n",[48,763,764],{"class":50,"line":64},[48,765,113],{"emptyLinePlaceholder":112},[48,767,768],{"class":50,"line":82},[48,769,770],{"class":54},"-- Rename if you inherited a badly named constraint.\n",[48,772,773,775,777,779,781,783,786,789,792,795],{"class":50,"line":88},[48,774,20],{"class":67},[48,776,71],{"class":67},[48,778,75],{"class":171},[48,780,29],{"class":78},[48,782,752],{"class":171},[48,784,785],{"class":78}," RENAME ",[48,787,788],{"class":67},"CONSTRAINT",[48,790,791],{"class":78}," invoices_customer_fk ",[48,793,794],{"class":67},"TO",[48,796,797],{"class":78}," invoices_fk_customer;\n",[31,799,801],{"id":800},"default-values","DEFAULT Values",[36,803,804],{"language":38},[40,805,807],{"className":42,"code":806,"language":38,"meta":44,"style":44},"CREATE TABLE events (\n  id          BIGSERIAL   PRIMARY KEY,\n  -- Constant default: metadata-only when added to existing tables (PG 11+).\n  status      TEXT        NOT NULL DEFAULT 'pending',\n\n  -- Dynamic default: evaluated at INSERT. now() is STABLE → still metadata-only\n  -- for ADD COLUMN (PG 11+), because the planner treats it as a per-row expression.\n  created_at  TIMESTAMPTZ NOT NULL DEFAULT now(),\n\n  -- gen_random_uuid() is VOLATILE → ADD COLUMN DEFAULT requires a full table rewrite\n  -- even in PG 11+ because each row needs a unique value computed at rewrite time.\n  uuid        UUID        NOT NULL DEFAULT gen_random_uuid(),\n\n  -- Expression default: any immutable\u002Fstable expression works.\n  expires_at  TIMESTAMPTZ NOT NULL DEFAULT (now() + interval '1 hour'),\n\n  -- Casted literal default — ensures type matches even if input coercion differs.\n  config      JSONB       NOT NULL DEFAULT '{}'::jsonb\n);\n",[18,808,809,820,831,836,853,857,862,867,882,886,891,896,908,912,917,943,947,952,966],{"__ignoreMap":44},[48,810,811,813,815,818],{"class":50,"line":51},[48,812,68],{"class":67},[48,814,71],{"class":67},[48,816,817],{"class":74}," events",[48,819,573],{"class":78},[48,821,822,825,827,829],{"class":50,"line":58},[48,823,824],{"class":78},"  id          ",[48,826,100],{"class":67},[48,828,103],{"class":67},[48,830,106],{"class":78},[48,832,833],{"class":50,"line":64},[48,834,835],{"class":54},"  -- Constant default: metadata-only when added to existing tables (PG 11+).\n",[48,837,838,841,844,846,848,851],{"class":50,"line":82},[48,839,840],{"class":67},"  status",[48,842,843],{"class":67},"      TEXT",[48,845,137],{"class":67},[48,847,218],{"class":67},[48,849,850],{"class":271}," 'pending'",[48,852,106],{"class":78},[48,854,855],{"class":50,"line":88},[48,856,113],{"emptyLinePlaceholder":112},[48,858,859],{"class":50,"line":94},[48,860,861],{"class":54},"  -- Dynamic default: evaluated at INSERT. now() is STABLE → still metadata-only\n",[48,863,864],{"class":50,"line":109},[48,865,866],{"class":54},"  -- for ADD COLUMN (PG 11+), because the planner treats it as a per-row expression.\n",[48,868,869,872,874,876,878,880],{"class":50,"line":116},[48,870,871],{"class":78},"  created_at  ",[48,873,212],{"class":67},[48,875,215],{"class":67},[48,877,218],{"class":67},[48,879,221],{"class":67},[48,881,224],{"class":78},[48,883,884],{"class":50,"line":122},[48,885,113],{"emptyLinePlaceholder":112},[48,887,888],{"class":50,"line":128},[48,889,890],{"class":54},"  -- gen_random_uuid() is VOLATILE → ADD COLUMN DEFAULT requires a full table rewrite\n",[48,892,893],{"class":50,"line":142},[48,894,895],{"class":54},"  -- even in PG 11+ because each row needs a unique value computed at rewrite time.\n",[48,897,898,901,903,905],{"class":50,"line":147},[48,899,900],{"class":78},"  uuid        UUID        ",[48,902,184],{"class":67},[48,904,218],{"class":67},[48,906,907],{"class":78}," gen_random_uuid(),\n",[48,909,910],{"class":50,"line":153},[48,911,113],{"emptyLinePlaceholder":112},[48,913,914],{"class":50,"line":159},[48,915,916],{"class":54},"  -- Expression default: any immutable\u002Fstable expression works.\n",[48,918,919,922,924,926,928,930,932,934,936,938,941],{"class":50,"line":189},[48,920,921],{"class":78},"  expires_at  ",[48,923,212],{"class":67},[48,925,215],{"class":67},[48,927,218],{"class":67},[48,929,256],{"class":78},[48,931,259],{"class":67},[48,933,262],{"class":78},[48,935,265],{"class":67},[48,937,268],{"class":78},[48,939,940],{"class":271},"'1 hour'",[48,942,275],{"class":78},[48,944,945],{"class":50,"line":194},[48,946,113],{"emptyLinePlaceholder":112},[48,948,949],{"class":50,"line":200},[48,950,951],{"class":54},"  -- Casted literal default — ensures type matches even if input coercion differs.\n",[48,953,954,957,959,961,963],{"class":50,"line":206},[48,955,956],{"class":78},"  config      JSONB       ",[48,958,184],{"class":67},[48,960,218],{"class":67},[48,962,305],{"class":271},[48,964,965],{"class":78},"::jsonb\n",[48,967,968],{"class":50,"line":227},[48,969,541],{"class":78},[14,971,972,982,983,986,987,990,991,993],{},[973,974,975,978,979,29],"strong",{},[18,976,977],{},"DEFAULT"," does not override an explicit ",[18,980,981],{},"NULL"," If the column is omitted from ",[18,984,985],{},"INSERT",", the default fires. If you explicitly ",[18,988,989],{},"INSERT ... VALUES (NULL)",", NULL is stored. Add ",[18,992,184],{}," to forbid NULLs entirely.",[31,995,997],{"id":996},"generated-columns","Generated Columns",[36,999,1000],{"language":38},[40,1001,1003],{"className":42,"code":1002,"language":38,"meta":44,"style":44},"CREATE TABLE products (\n  id          BIGSERIAL   PRIMARY KEY,\n  price       NUMERIC(10,2) NOT NULL,\n  qty         INTEGER     NOT NULL,\n\n  -- STORED: computed on INSERT\u002FUPDATE of price or qty, physically written to disk.\n  -- Read cost: zero (it's a normal column). Write cost: recomputation on dependent change.\n  -- The expression MUST be immutable — no now(), no random(), no subqueries.\n  total_value NUMERIC(10,2) GENERATED ALWAYS AS (price * qty) STORED\n);\n\n-- PG does not support VIRTUAL generated columns (MySQL 8.0+ does).\n-- VIRTUAL computes on read, zero storage, but recomputed per query.\n\n-- Generated columns can be indexed — useful for expression indexes without\n-- repeating the expression in every query.\nCREATE INDEX products_total_value_idx ON products(total_value);\n",[18,1004,1005,1016,1026,1048,1061,1065,1070,1075,1080,1111,1115,1119,1124,1129,1133,1138,1143],{"__ignoreMap":44},[48,1006,1007,1009,1011,1014],{"class":50,"line":51},[48,1008,68],{"class":67},[48,1010,71],{"class":67},[48,1012,1013],{"class":74}," products",[48,1015,573],{"class":78},[48,1017,1018,1020,1022,1024],{"class":50,"line":58},[48,1019,824],{"class":78},[48,1021,100],{"class":67},[48,1023,103],{"class":67},[48,1025,106],{"class":78},[48,1027,1028,1031,1033,1035,1038,1040,1042,1044,1046],{"class":50,"line":64},[48,1029,1030],{"class":78},"  price       ",[48,1032,165],{"class":67},[48,1034,168],{"class":78},[48,1036,1037],{"class":171},"10",[48,1039,175],{"class":78},[48,1041,178],{"class":171},[48,1043,181],{"class":78},[48,1045,184],{"class":67},[48,1047,106],{"class":78},[48,1049,1050,1053,1056,1059],{"class":50,"line":82},[48,1051,1052],{"class":78},"  qty         ",[48,1054,1055],{"class":67},"INTEGER",[48,1057,1058],{"class":67},"     NOT NULL",[48,1060,106],{"class":78},[48,1062,1063],{"class":50,"line":88},[48,1064,113],{"emptyLinePlaceholder":112},[48,1066,1067],{"class":50,"line":94},[48,1068,1069],{"class":54},"  -- STORED: computed on INSERT\u002FUPDATE of price or qty, physically written to disk.\n",[48,1071,1072],{"class":50,"line":109},[48,1073,1074],{"class":54},"  -- Read cost: zero (it's a normal column). Write cost: recomputation on dependent change.\n",[48,1076,1077],{"class":50,"line":116},[48,1078,1079],{"class":54},"  -- The expression MUST be immutable — no now(), no random(), no subqueries.\n",[48,1081,1082,1085,1087,1089,1091,1093,1095,1097,1099,1101,1103,1106,1108],{"class":50,"line":122},[48,1083,1084],{"class":78},"  total_value ",[48,1086,165],{"class":67},[48,1088,168],{"class":78},[48,1090,1037],{"class":171},[48,1092,175],{"class":78},[48,1094,178],{"class":171},[48,1096,181],{"class":78},[48,1098,352],{"class":67},[48,1100,355],{"class":67},[48,1102,358],{"class":67},[48,1104,1105],{"class":78}," (price ",[48,1107,364],{"class":67},[48,1109,1110],{"class":78}," qty) STORED\n",[48,1112,1113],{"class":50,"line":128},[48,1114,541],{"class":78},[48,1116,1117],{"class":50,"line":142},[48,1118,113],{"emptyLinePlaceholder":112},[48,1120,1121],{"class":50,"line":147},[48,1122,1123],{"class":54},"-- PG does not support VIRTUAL generated columns (MySQL 8.0+ does).\n",[48,1125,1126],{"class":50,"line":153},[48,1127,1128],{"class":54},"-- VIRTUAL computes on read, zero storage, but recomputed per query.\n",[48,1130,1131],{"class":50,"line":159},[48,1132,113],{"emptyLinePlaceholder":112},[48,1134,1135],{"class":50,"line":189},[48,1136,1137],{"class":54},"-- Generated columns can be indexed — useful for expression indexes without\n",[48,1139,1140],{"class":50,"line":194},[48,1141,1142],{"class":54},"-- repeating the expression in every query.\n",[48,1144,1145,1147,1150,1153,1156],{"class":50,"line":200},[48,1146,68],{"class":67},[48,1148,1149],{"class":67}," INDEX",[48,1151,1152],{"class":74}," products_total_value_idx",[48,1154,1155],{"class":67}," ON",[48,1157,1158],{"class":78}," products(total_value);\n",[14,1160,1161,1164,1165,1168],{},[973,1162,1163],{},"Use case:"," precompute a value you query often, or normalize an expression for indexing (e.g., ",[18,1166,1167],{},"lower(email)"," as a generated column with a unique index for case-insensitive uniqueness).",[31,1170,1172],{"id":1171},"schemas-multi-tenant-module-separation","Schemas — Multi-Tenant & Module Separation",[36,1174,1175],{"language":38},[40,1176,1178],{"className":42,"code":1177,"language":38,"meta":44,"style":44},"-- A schema is a namespace within a database. Default is 'public'.\nCREATE SCHEMA IF NOT EXISTS billing;\nCREATE SCHEMA IF NOT EXISTS analytics;\n\n-- Tables live in a schema: schema.table\nCREATE TABLE billing.invoices   (id BIGSERIAL PRIMARY KEY, amount NUMERIC);\nCREATE TABLE analytics.reports  (id BIGSERIAL PRIMARY KEY, name TEXT);\n\n-- search_path: like $PATH for schemas. First match wins.\nSET search_path TO billing, public;\nSELECT * FROM invoices;  -- resolves to billing.invoices\n\n-- Permanently for a role:\nALTER ROLE app_user SET search_path TO billing, public;\n",[18,1179,1180,1185,1204,1219,1223,1228,1251,1278,1282,1287,1300,1317,1321,1326],{"__ignoreMap":44},[48,1181,1182],{"class":50,"line":51},[48,1183,1184],{"class":54},"-- A schema is a namespace within a database. Default is 'public'.\n",[48,1186,1187,1189,1192,1195,1198,1201],{"class":50,"line":58},[48,1188,68],{"class":67},[48,1190,1191],{"class":67}," SCHEMA",[48,1193,1194],{"class":74}," IF",[48,1196,1197],{"class":67}," NOT",[48,1199,1200],{"class":67}," EXISTS",[48,1202,1203],{"class":78}," billing;\n",[48,1205,1206,1208,1210,1212,1214,1216],{"class":50,"line":64},[48,1207,68],{"class":67},[48,1209,1191],{"class":67},[48,1211,1194],{"class":74},[48,1213,1197],{"class":67},[48,1215,1200],{"class":67},[48,1217,1218],{"class":78}," analytics;\n",[48,1220,1221],{"class":50,"line":82},[48,1222,113],{"emptyLinePlaceholder":112},[48,1224,1225],{"class":50,"line":88},[48,1226,1227],{"class":54},"-- Tables live in a schema: schema.table\n",[48,1229,1230,1232,1234,1236,1239,1241,1244,1247,1249],{"class":50,"line":94},[48,1231,68],{"class":67},[48,1233,71],{"class":67},[48,1235,75],{"class":74},[48,1237,1238],{"class":78},".invoices   (id ",[48,1240,100],{"class":67},[48,1242,1243],{"class":67}," PRIMARY KEY",[48,1245,1246],{"class":78},", amount ",[48,1248,165],{"class":67},[48,1250,541],{"class":78},[48,1252,1253,1255,1257,1260,1263,1265,1267,1270,1273,1276],{"class":50,"line":109},[48,1254,68],{"class":67},[48,1256,71],{"class":67},[48,1258,1259],{"class":74}," analytics",[48,1261,1262],{"class":78},".reports  (id ",[48,1264,100],{"class":67},[48,1266,1243],{"class":67},[48,1268,1269],{"class":78},", ",[48,1271,1272],{"class":67},"name",[48,1274,1275],{"class":67}," TEXT",[48,1277,541],{"class":78},[48,1279,1280],{"class":50,"line":116},[48,1281,113],{"emptyLinePlaceholder":112},[48,1283,1284],{"class":50,"line":122},[48,1285,1286],{"class":54},"-- search_path: like $PATH for schemas. First match wins.\n",[48,1288,1289,1292,1295,1297],{"class":50,"line":128},[48,1290,1291],{"class":67},"SET",[48,1293,1294],{"class":78}," search_path ",[48,1296,794],{"class":67},[48,1298,1299],{"class":78}," billing, public;\n",[48,1301,1302,1305,1308,1311,1314],{"class":50,"line":142},[48,1303,1304],{"class":67},"SELECT",[48,1306,1307],{"class":67}," *",[48,1309,1310],{"class":67}," FROM",[48,1312,1313],{"class":78}," invoices;  ",[48,1315,1316],{"class":54},"-- resolves to billing.invoices\n",[48,1318,1319],{"class":50,"line":147},[48,1320,113],{"emptyLinePlaceholder":112},[48,1322,1323],{"class":50,"line":153},[48,1324,1325],{"class":54},"-- Permanently for a role:\n",[48,1327,1328,1330,1333,1336,1338,1340,1342],{"class":50,"line":159},[48,1329,20],{"class":67},[48,1331,1332],{"class":67}," ROLE",[48,1334,1335],{"class":78}," app_user ",[48,1337,1291],{"class":67},[48,1339,1294],{"class":78},[48,1341,794],{"class":67},[48,1343,1299],{"class":78},[543,1345,1347],{"id":1346},"multi-tenant-patterns","Multi-Tenant Patterns",[1349,1350,1351,1370],"table",{},[1352,1353,1354],"thead",{},[1355,1356,1357,1361,1364,1367],"tr",{},[1358,1359,1360],"th",{},"Pattern",[1358,1362,1363],{},"Isolation",[1358,1365,1366],{},"Scalability",[1358,1368,1369],{},"Operational Cost",[1371,1372,1373,1388,1406],"tbody",{},[1355,1374,1375,1379,1382,1385],{},[1376,1377,1378],"td",{},"Schema-per-tenant",[1376,1380,1381],{},"Strong",[1376,1383,1384],{},"Moderate (schema count limits)",[1376,1386,1387],{},"High — DDL per tenant",[1355,1389,1390,1397,1400,1403],{},[1376,1391,1392,1393,1396],{},"Shared schema + ",[18,1394,1395],{},"tenant_id"," + RLS",[1376,1398,1399],{},"Moderate",[1376,1401,1402],{},"High",[1376,1404,1405],{},"Low — one schema",[1355,1407,1408,1411,1414,1417],{},[1376,1409,1410],{},"Database-per-tenant",[1376,1412,1413],{},"Maximum",[1376,1415,1416],{},"Low (conn count)",[1376,1418,1419],{},"Highest",[36,1421,1422],{"language":38},[40,1423,1425],{"className":42,"code":1424,"language":38,"meta":44,"style":44},"-- Schema-per-tenant: each tenant gets isolated tables.\nCREATE SCHEMA tenant_acme;\nCREATE TABLE tenant_acme.invoices (id BIGSERIAL PRIMARY KEY, amount NUMERIC);\n\n-- Shared schema with tenant_id: one table, RLS enforces isolation.\nCREATE TABLE shared.invoices (\n  id         BIGSERIAL PRIMARY KEY,\n  tenant_id  BIGINT NOT NULL,\n  amount     NUMERIC NOT NULL\n);\n-- RLS policy (Chapter 24) ensures each tenant sees only their rows.\n",[18,1426,1427,1432,1444,1465,1469,1474,1485,1496,1507,1517,1521],{"__ignoreMap":44},[48,1428,1429],{"class":50,"line":51},[48,1430,1431],{"class":54},"-- Schema-per-tenant: each tenant gets isolated tables.\n",[48,1433,1434,1436,1438,1441],{"class":50,"line":58},[48,1435,68],{"class":67},[48,1437,1191],{"class":67},[48,1439,1440],{"class":74}," tenant_acme",[48,1442,1443],{"class":78},";\n",[48,1445,1446,1448,1450,1452,1455,1457,1459,1461,1463],{"class":50,"line":64},[48,1447,68],{"class":67},[48,1449,71],{"class":67},[48,1451,1440],{"class":74},[48,1453,1454],{"class":78},".invoices (id ",[48,1456,100],{"class":67},[48,1458,1243],{"class":67},[48,1460,1246],{"class":78},[48,1462,165],{"class":67},[48,1464,541],{"class":78},[48,1466,1467],{"class":50,"line":82},[48,1468,113],{"emptyLinePlaceholder":112},[48,1470,1471],{"class":50,"line":88},[48,1472,1473],{"class":54},"-- Shared schema with tenant_id: one table, RLS enforces isolation.\n",[48,1475,1476,1478,1480,1483],{"class":50,"line":94},[48,1477,68],{"class":67},[48,1479,71],{"class":67},[48,1481,1482],{"class":74}," shared",[48,1484,79],{"class":78},[48,1486,1487,1490,1492,1494],{"class":50,"line":109},[48,1488,1489],{"class":78},"  id         ",[48,1491,100],{"class":67},[48,1493,1243],{"class":67},[48,1495,106],{"class":78},[48,1497,1498,1501,1503,1505],{"class":50,"line":116},[48,1499,1500],{"class":78},"  tenant_id  ",[48,1502,646],{"class":67},[48,1504,215],{"class":67},[48,1506,106],{"class":78},[48,1508,1509,1512,1514],{"class":50,"line":122},[48,1510,1511],{"class":78},"  amount     ",[48,1513,165],{"class":67},[48,1515,1516],{"class":67}," NOT NULL\n",[48,1518,1519],{"class":50,"line":128},[48,1520,541],{"class":78},[48,1522,1523],{"class":50,"line":142},[48,1524,1525],{"class":54},"-- RLS policy (Chapter 24) ensures each tenant sees only their rows.\n",[31,1527,1529],{"id":1528},"temporary-tables","Temporary Tables",[36,1531,1532],{"language":38},[40,1533,1535],{"className":42,"code":1534,"language":38,"meta":44,"style":44},"-- TEMP tables are session-local: no other session sees them.\n-- Dropped at session end (or COMMIT with ON COMMIT DROP).\n-- Created in pg_temp schema, which is searched first in search_path.\nCREATE TEMP TABLE staging_orders AS\n  SELECT * FROM billing.invoices WHERE issued_at >= '2024-01-01';\n\n-- ON COMMIT DROP: vanished after each transaction — good for per-batch scratch.\nCREATE TEMP TABLE batch_scratch (id bigint, val text) ON COMMIT DROP;\n\n-- ON COMMIT DELETE ROWS: structure persists for the session, rows cleared per txn.\nCREATE TEMP TABLE session_cache (key text, value jsonb) ON COMMIT DELETE ROWS;\n\n-- TEMP tables are NOT autovacuumed by default — ANALYZE manually after bulk loads.\nANALYZE staging_orders;\n",[18,1536,1537,1542,1547,1552,1568,1596,1600,1605,1637,1641,1646,1683,1687,1692],{"__ignoreMap":44},[48,1538,1539],{"class":50,"line":51},[48,1540,1541],{"class":54},"-- TEMP tables are session-local: no other session sees them.\n",[48,1543,1544],{"class":50,"line":58},[48,1545,1546],{"class":54},"-- Dropped at session end (or COMMIT with ON COMMIT DROP).\n",[48,1548,1549],{"class":50,"line":64},[48,1550,1551],{"class":54},"-- Created in pg_temp schema, which is searched first in search_path.\n",[48,1553,1554,1556,1559,1562,1565],{"class":50,"line":82},[48,1555,68],{"class":67},[48,1557,1558],{"class":78}," TEMP ",[48,1560,1561],{"class":67},"TABLE",[48,1563,1564],{"class":78}," staging_orders ",[48,1566,1567],{"class":67},"AS\n",[48,1569,1570,1573,1575,1577,1579,1581,1583,1586,1589,1591,1594],{"class":50,"line":88},[48,1571,1572],{"class":67},"  SELECT",[48,1574,1307],{"class":67},[48,1576,1310],{"class":67},[48,1578,75],{"class":171},[48,1580,29],{"class":78},[48,1582,752],{"class":171},[48,1584,1585],{"class":67}," WHERE",[48,1587,1588],{"class":78}," issued_at ",[48,1590,463],{"class":67},[48,1592,1593],{"class":271}," '2024-01-01'",[48,1595,1443],{"class":78},[48,1597,1598],{"class":50,"line":94},[48,1599,113],{"emptyLinePlaceholder":112},[48,1601,1602],{"class":50,"line":109},[48,1603,1604],{"class":54},"-- ON COMMIT DROP: vanished after each transaction — good for per-batch scratch.\n",[48,1606,1607,1609,1611,1613,1616,1619,1622,1625,1627,1630,1633,1635],{"class":50,"line":116},[48,1608,68],{"class":67},[48,1610,1558],{"class":78},[48,1612,1561],{"class":67},[48,1614,1615],{"class":78}," batch_scratch (id ",[48,1617,1618],{"class":67},"bigint",[48,1620,1621],{"class":78},", val ",[48,1623,1624],{"class":67},"text",[48,1626,181],{"class":78},[48,1628,1629],{"class":67},"ON",[48,1631,1632],{"class":67}," COMMIT",[48,1634,755],{"class":67},[48,1636,1443],{"class":78},[48,1638,1639],{"class":50,"line":122},[48,1640,113],{"emptyLinePlaceholder":112},[48,1642,1643],{"class":50,"line":128},[48,1644,1645],{"class":54},"-- ON COMMIT DELETE ROWS: structure persists for the session, rows cleared per txn.\n",[48,1647,1648,1650,1652,1654,1657,1660,1663,1665,1668,1671,1673,1675,1678,1681],{"class":50,"line":142},[48,1649,68],{"class":67},[48,1651,1558],{"class":78},[48,1653,1561],{"class":67},[48,1655,1656],{"class":78}," session_cache (",[48,1658,1659],{"class":67},"key",[48,1661,1662],{"class":67}," text",[48,1664,1269],{"class":78},[48,1666,1667],{"class":67},"value",[48,1669,1670],{"class":78}," jsonb) ",[48,1672,1629],{"class":67},[48,1674,1632],{"class":67},[48,1676,1677],{"class":67}," DELETE",[48,1679,1680],{"class":67}," ROWS",[48,1682,1443],{"class":78},[48,1684,1685],{"class":50,"line":147},[48,1686,113],{"emptyLinePlaceholder":112},[48,1688,1689],{"class":50,"line":153},[48,1690,1691],{"class":54},"-- TEMP tables are NOT autovacuumed by default — ANALYZE manually after bulk loads.\n",[48,1693,1694],{"class":50,"line":159},[48,1695,1696],{"class":78},"ANALYZE staging_orders;\n",[31,1698,1700],{"id":1699},"unlogged-tables","UNLOGGED Tables",[36,1702,1703],{"language":38},[40,1704,1706],{"className":42,"code":1705,"language":38,"meta":44,"style":44},"-- UNLOGGED: skips WAL writes → 2-3x faster INSERT\u002FUPDATE.\n-- Trade-off: NOT crash-safe. On crash, table is truncated.\n-- Ideal for caches, ETL staging, ephemeral derived data.\nCREATE UNLOGGED TABLE cache.user_scores (\n  user_id BIGINT PRIMARY KEY,\n  score   INTEGER NOT NULL\n);\n\n-- Convert to logged if it becomes important:\nALTER TABLE cache.user_scores SET LOGGED;\n",[18,1707,1708,1713,1718,1723,1742,1753,1762,1766,1770,1775],{"__ignoreMap":44},[48,1709,1710],{"class":50,"line":51},[48,1711,1712],{"class":54},"-- UNLOGGED: skips WAL writes → 2-3x faster INSERT\u002FUPDATE.\n",[48,1714,1715],{"class":50,"line":58},[48,1716,1717],{"class":54},"-- Trade-off: NOT crash-safe. On crash, table is truncated.\n",[48,1719,1720],{"class":50,"line":64},[48,1721,1722],{"class":54},"-- Ideal for caches, ETL staging, ephemeral derived data.\n",[48,1724,1725,1727,1730,1732,1735,1737,1740],{"class":50,"line":82},[48,1726,68],{"class":67},[48,1728,1729],{"class":78}," UNLOGGED ",[48,1731,1561],{"class":67},[48,1733,1734],{"class":171}," cache",[48,1736,29],{"class":78},[48,1738,1739],{"class":171},"user_scores",[48,1741,573],{"class":78},[48,1743,1744,1747,1749,1751],{"class":50,"line":88},[48,1745,1746],{"class":78},"  user_id ",[48,1748,646],{"class":67},[48,1750,1243],{"class":67},[48,1752,106],{"class":78},[48,1754,1755,1758,1760],{"class":50,"line":94},[48,1756,1757],{"class":78},"  score   ",[48,1759,1055],{"class":67},[48,1761,1516],{"class":67},[48,1763,1764],{"class":50,"line":109},[48,1765,541],{"class":78},[48,1767,1768],{"class":50,"line":116},[48,1769,113],{"emptyLinePlaceholder":112},[48,1771,1772],{"class":50,"line":122},[48,1773,1774],{"class":54},"-- Convert to logged if it becomes important:\n",[48,1776,1777,1779,1781,1783,1785,1787,1790],{"class":50,"line":128},[48,1778,20],{"class":67},[48,1780,71],{"class":67},[48,1782,1734],{"class":171},[48,1784,29],{"class":78},[48,1786,1739],{"class":171},[48,1788,1789],{"class":67}," SET",[48,1791,1792],{"class":78}," LOGGED;\n",[31,1794,1796],{"id":1795},"alter-table-patterns","ALTER TABLE Patterns",[36,1798,1799],{"language":38},[40,1800,1802],{"className":42,"code":1801,"language":38,"meta":44,"style":44},"-- ADD COLUMN: nullable, no default → instant (metadata-only, all PG versions).\nALTER TABLE billing.invoices ADD COLUMN notes TEXT;\n\n-- ADD COLUMN with constant default (PG 11+): metadata-only, no rewrite.\n-- PG stores the default in pg_attrdef and returns it for existing rows on read.\nALTER TABLE billing.invoices ADD COLUMN currency TEXT NOT NULL DEFAULT 'USD';\n\n-- ADD COLUMN with VOLATILE default (gen_random_uuid(), random()): REWRITES the table.\n-- Each existing row needs a unique value computed at rewrite time → full table scan\n-- under ACCESS EXCLUSIVE lock. On a billion-row table: hours of downtime.\nALTER TABLE billing.invoices ADD COLUMN ref_uuid UUID DEFAULT gen_random_uuid();\n\n-- DROP COLUMN: marks column dead in catalog (fast), physical space reclaimed by VACUUM.\nALTER TABLE billing.invoices DROP COLUMN notes;\n\n-- RENAME COLUMN: metadata-only, no data change.\nALTER TABLE billing.invoices RENAME COLUMN amount TO total;\n\n-- ALTER COLUMN TYPE: rewrites the entire table (unless binary-compatible).\n-- USING clause controls the conversion expression.\nALTER TABLE billing.invoices ALTER COLUMN amount TYPE NUMERIC(14,2) USING amount::numeric(14,2);\n\n-- Add constraint normally: scans the table, holds ACCESS EXCLUSIVE lock.\nALTER TABLE billing.invoices ADD CONSTRAINT amount_positive CHECK (amount >= 0);\n\n-- Add constraint NOT VALID: metadata-only, skips existing rows (instant).\n-- VALIDATE later: scans with SHARE UPDATE EXCLUSIVE lock (reads\u002Fwrites continue).\nALTER TABLE billing.invoices ADD CONSTRAINT amount_positive CHECK (amount >= 0) NOT VALID;\nALTER TABLE billing.invoices VALIDATE CONSTRAINT amount_positive;\n",[18,1803,1804,1809,1831,1835,1840,1845,1873,1877,1882,1887,1892,1914,1918,1923,1940,1944,1949,1969,1973,1978,1983,2037,2041,2046,2075,2079,2084,2089,2123],{"__ignoreMap":44},[48,1805,1806],{"class":50,"line":51},[48,1807,1808],{"class":54},"-- ADD COLUMN: nullable, no default → instant (metadata-only, all PG versions).\n",[48,1810,1811,1813,1815,1817,1819,1821,1824,1827,1829],{"class":50,"line":58},[48,1812,20],{"class":67},[48,1814,71],{"class":67},[48,1816,75],{"class":171},[48,1818,29],{"class":78},[48,1820,752],{"class":171},[48,1822,1823],{"class":67}," ADD",[48,1825,1826],{"class":78}," COLUMN notes ",[48,1828,134],{"class":67},[48,1830,1443],{"class":78},[48,1832,1833],{"class":50,"line":64},[48,1834,113],{"emptyLinePlaceholder":112},[48,1836,1837],{"class":50,"line":82},[48,1838,1839],{"class":54},"-- ADD COLUMN with constant default (PG 11+): metadata-only, no rewrite.\n",[48,1841,1842],{"class":50,"line":88},[48,1843,1844],{"class":54},"-- PG stores the default in pg_attrdef and returns it for existing rows on read.\n",[48,1846,1847,1849,1851,1853,1855,1857,1859,1862,1864,1866,1868,1871],{"class":50,"line":94},[48,1848,20],{"class":67},[48,1850,71],{"class":67},[48,1852,75],{"class":171},[48,1854,29],{"class":78},[48,1856,752],{"class":171},[48,1858,1823],{"class":67},[48,1860,1861],{"class":78}," COLUMN currency ",[48,1863,134],{"class":67},[48,1865,215],{"class":67},[48,1867,218],{"class":67},[48,1869,1870],{"class":271}," 'USD'",[48,1872,1443],{"class":78},[48,1874,1875],{"class":50,"line":109},[48,1876,113],{"emptyLinePlaceholder":112},[48,1878,1879],{"class":50,"line":116},[48,1880,1881],{"class":54},"-- ADD COLUMN with VOLATILE default (gen_random_uuid(), random()): REWRITES the table.\n",[48,1883,1884],{"class":50,"line":122},[48,1885,1886],{"class":54},"-- Each existing row needs a unique value computed at rewrite time → full table scan\n",[48,1888,1889],{"class":50,"line":128},[48,1890,1891],{"class":54},"-- under ACCESS EXCLUSIVE lock. On a billion-row table: hours of downtime.\n",[48,1893,1894,1896,1898,1900,1902,1904,1906,1909,1911],{"class":50,"line":142},[48,1895,20],{"class":67},[48,1897,71],{"class":67},[48,1899,75],{"class":171},[48,1901,29],{"class":78},[48,1903,752],{"class":171},[48,1905,1823],{"class":67},[48,1907,1908],{"class":78}," COLUMN ref_uuid UUID ",[48,1910,977],{"class":67},[48,1912,1913],{"class":78}," gen_random_uuid();\n",[48,1915,1916],{"class":50,"line":147},[48,1917,113],{"emptyLinePlaceholder":112},[48,1919,1920],{"class":50,"line":153},[48,1921,1922],{"class":54},"-- DROP COLUMN: marks column dead in catalog (fast), physical space reclaimed by VACUUM.\n",[48,1924,1925,1927,1929,1931,1933,1935,1937],{"class":50,"line":159},[48,1926,20],{"class":67},[48,1928,71],{"class":67},[48,1930,75],{"class":171},[48,1932,29],{"class":78},[48,1934,752],{"class":171},[48,1936,755],{"class":67},[48,1938,1939],{"class":78}," COLUMN notes;\n",[48,1941,1942],{"class":50,"line":189},[48,1943,113],{"emptyLinePlaceholder":112},[48,1945,1946],{"class":50,"line":194},[48,1947,1948],{"class":54},"-- RENAME COLUMN: metadata-only, no data change.\n",[48,1950,1951,1953,1955,1957,1959,1961,1964,1966],{"class":50,"line":200},[48,1952,20],{"class":67},[48,1954,71],{"class":67},[48,1956,75],{"class":171},[48,1958,29],{"class":78},[48,1960,752],{"class":171},[48,1962,1963],{"class":78}," RENAME COLUMN amount ",[48,1965,794],{"class":67},[48,1967,1968],{"class":78}," total;\n",[48,1970,1971],{"class":50,"line":206},[48,1972,113],{"emptyLinePlaceholder":112},[48,1974,1975],{"class":50,"line":227},[48,1976,1977],{"class":54},"-- ALTER COLUMN TYPE: rewrites the entire table (unless binary-compatible).\n",[48,1979,1980],{"class":50,"line":232},[48,1981,1982],{"class":54},"-- USING clause controls the conversion expression.\n",[48,1984,1985,1987,1989,1991,1993,1995,1998,2001,2004,2007,2009,2012,2014,2016,2018,2021,2024,2027,2029,2031,2033,2035],{"class":50,"line":238},[48,1986,20],{"class":67},[48,1988,71],{"class":67},[48,1990,75],{"class":171},[48,1992,29],{"class":78},[48,1994,752],{"class":171},[48,1996,1997],{"class":67}," ALTER",[48,1999,2000],{"class":78}," COLUMN amount ",[48,2002,2003],{"class":67},"TYPE",[48,2005,2006],{"class":67}," NUMERIC",[48,2008,168],{"class":78},[48,2010,2011],{"class":171},"14",[48,2013,175],{"class":78},[48,2015,178],{"class":171},[48,2017,181],{"class":78},[48,2019,2020],{"class":67},"USING",[48,2022,2023],{"class":78}," amount::",[48,2025,2026],{"class":67},"numeric",[48,2028,168],{"class":78},[48,2030,2011],{"class":171},[48,2032,175],{"class":78},[48,2034,178],{"class":171},[48,2036,541],{"class":78},[48,2038,2039],{"class":50,"line":244},[48,2040,113],{"emptyLinePlaceholder":112},[48,2042,2043],{"class":50,"line":278},[48,2044,2045],{"class":54},"-- Add constraint normally: scans the table, holds ACCESS EXCLUSIVE lock.\n",[48,2047,2048,2050,2052,2054,2056,2058,2060,2062,2065,2067,2069,2071,2073],{"class":50,"line":283},[48,2049,20],{"class":67},[48,2051,71],{"class":67},[48,2053,75],{"class":171},[48,2055,29],{"class":78},[48,2057,752],{"class":171},[48,2059,1823],{"class":67},[48,2061,758],{"class":67},[48,2063,2064],{"class":78}," amount_positive ",[48,2066,458],{"class":67},[48,2068,361],{"class":78},[48,2070,463],{"class":67},[48,2072,466],{"class":171},[48,2074,541],{"class":78},[48,2076,2077],{"class":50,"line":289},[48,2078,113],{"emptyLinePlaceholder":112},[48,2080,2081],{"class":50,"line":295},[48,2082,2083],{"class":54},"-- Add constraint NOT VALID: metadata-only, skips existing rows (instant).\n",[48,2085,2086],{"class":50,"line":311},[48,2087,2088],{"class":54},"-- VALIDATE later: scans with SHARE UPDATE EXCLUSIVE lock (reads\u002Fwrites continue).\n",[48,2090,2091,2093,2095,2097,2099,2101,2103,2105,2107,2109,2111,2113,2115,2117,2120],{"class":50,"line":316},[48,2092,20],{"class":67},[48,2094,71],{"class":67},[48,2096,75],{"class":171},[48,2098,29],{"class":78},[48,2100,752],{"class":171},[48,2102,1823],{"class":67},[48,2104,758],{"class":67},[48,2106,2064],{"class":78},[48,2108,458],{"class":67},[48,2110,361],{"class":78},[48,2112,463],{"class":67},[48,2114,466],{"class":171},[48,2116,181],{"class":78},[48,2118,2119],{"class":67},"NOT",[48,2121,2122],{"class":78}," VALID;\n",[48,2124,2125,2127,2129,2131,2133,2135,2138,2140],{"class":50,"line":322},[48,2126,20],{"class":67},[48,2128,71],{"class":67},[48,2130,75],{"class":171},[48,2132,29],{"class":78},[48,2134,752],{"class":171},[48,2136,2137],{"class":78}," VALIDATE ",[48,2139,788],{"class":67},[48,2141,2142],{"class":78}," amount_positive;\n",[543,2144,2146],{"id":2145},"safe-type-change-on-a-large-table","Safe Type Change on a Large Table",[36,2148,2149],{"language":38},[40,2150,2152],{"className":42,"code":2151,"language":38,"meta":44,"style":44},"-- ANTI-PATTERN: rewrites the entire table under ACCESS EXCLUSIVE lock.\n-- ❌ On a 500M-row table, this is hours of downtime.\nALTER TABLE events ALTER COLUMN user_id TYPE bigint USING user_id::bigint;\n\n-- CORRECT: staged, lock-light migration.\n-- Step 1: add new column (nullable, no default — instant).\nALTER TABLE events ADD COLUMN user_id_new bigint;\n\n-- Step 2: backfill in batches (no long lock).\nUPDATE events SET user_id_new = user_id::bigint\nWHERE id BETWEEN 1 AND 100000;  -- repeat in batches by id range.\n\n-- Step 3: add NOT VALID + VALIDATE (light lock).\nALTER TABLE events ADD CONSTRAINT events_user_id_new_nn CHECK (user_id_new IS NOT NULL) NOT VALID;\nALTER TABLE events VALIDATE CONSTRAINT events_user_id_new_nn;\n\n-- Step 4: swap in a single transaction (brief lock for rename + default).\nBEGIN;\nALTER TABLE events RENAME COLUMN user_id     TO user_id_old;\nALTER TABLE events RENAME COLUMN user_id_new TO user_id;\nALTER TABLE events ALTER COLUMN user_id SET NOT NULL;\nCOMMIT;\n\n-- Step 5: drop old column (fast — marked dead; VACUUM reclaims later).\nALTER TABLE events DROP COLUMN user_id_old;\n",[18,2153,2154,2159,2164,2193,2197,2202,2207,2225,2229,2234,2254,2279,2283,2288,2317,2331,2335,2340,2347,2361,2375,2393,2400,2404,2409],{"__ignoreMap":44},[48,2155,2156],{"class":50,"line":51},[48,2157,2158],{"class":54},"-- ANTI-PATTERN: rewrites the entire table under ACCESS EXCLUSIVE lock.\n",[48,2160,2161],{"class":50,"line":58},[48,2162,2163],{"class":54},"-- ❌ On a 500M-row table, this is hours of downtime.\n",[48,2165,2166,2168,2170,2173,2175,2178,2180,2183,2186,2189,2191],{"class":50,"line":64},[48,2167,20],{"class":67},[48,2169,71],{"class":67},[48,2171,2172],{"class":78}," events ",[48,2174,20],{"class":67},[48,2176,2177],{"class":78}," COLUMN user_id ",[48,2179,2003],{"class":67},[48,2181,2182],{"class":67}," bigint",[48,2184,2185],{"class":67}," USING",[48,2187,2188],{"class":78}," user_id::",[48,2190,1618],{"class":67},[48,2192,1443],{"class":78},[48,2194,2195],{"class":50,"line":82},[48,2196,113],{"emptyLinePlaceholder":112},[48,2198,2199],{"class":50,"line":88},[48,2200,2201],{"class":54},"-- CORRECT: staged, lock-light migration.\n",[48,2203,2204],{"class":50,"line":94},[48,2205,2206],{"class":54},"-- Step 1: add new column (nullable, no default — instant).\n",[48,2208,2209,2211,2213,2215,2218,2221,2223],{"class":50,"line":109},[48,2210,20],{"class":67},[48,2212,71],{"class":67},[48,2214,2172],{"class":78},[48,2216,2217],{"class":67},"ADD",[48,2219,2220],{"class":78}," COLUMN user_id_new ",[48,2222,1618],{"class":67},[48,2224,1443],{"class":78},[48,2226,2227],{"class":50,"line":116},[48,2228,113],{"emptyLinePlaceholder":112},[48,2230,2231],{"class":50,"line":122},[48,2232,2233],{"class":54},"-- Step 2: backfill in batches (no long lock).\n",[48,2235,2236,2239,2241,2243,2246,2249,2251],{"class":50,"line":128},[48,2237,2238],{"class":67},"UPDATE",[48,2240,2172],{"class":78},[48,2242,1291],{"class":67},[48,2244,2245],{"class":78}," user_id_new ",[48,2247,2248],{"class":67},"=",[48,2250,2188],{"class":78},[48,2252,2253],{"class":67},"bigint\n",[48,2255,2256,2259,2262,2265,2267,2270,2273,2276],{"class":50,"line":142},[48,2257,2258],{"class":67},"WHERE",[48,2260,2261],{"class":78}," id ",[48,2263,2264],{"class":67},"BETWEEN",[48,2266,367],{"class":171},[48,2268,2269],{"class":67}," AND",[48,2271,2272],{"class":171}," 100000",[48,2274,2275],{"class":78},";  ",[48,2277,2278],{"class":54},"-- repeat in batches by id range.\n",[48,2280,2281],{"class":50,"line":147},[48,2282,113],{"emptyLinePlaceholder":112},[48,2284,2285],{"class":50,"line":153},[48,2286,2287],{"class":54},"-- Step 3: add NOT VALID + VALIDATE (light lock).\n",[48,2289,2290,2292,2294,2296,2298,2300,2303,2305,2308,2311,2313,2315],{"class":50,"line":159},[48,2291,20],{"class":67},[48,2293,71],{"class":67},[48,2295,2172],{"class":78},[48,2297,2217],{"class":67},[48,2299,758],{"class":67},[48,2301,2302],{"class":78}," events_user_id_new_nn ",[48,2304,458],{"class":67},[48,2306,2307],{"class":78}," (user_id_new ",[48,2309,2310],{"class":67},"IS NOT NULL",[48,2312,181],{"class":78},[48,2314,2119],{"class":67},[48,2316,2122],{"class":78},[48,2318,2319,2321,2323,2326,2328],{"class":50,"line":189},[48,2320,20],{"class":67},[48,2322,71],{"class":67},[48,2324,2325],{"class":78}," events VALIDATE ",[48,2327,788],{"class":67},[48,2329,2330],{"class":78}," events_user_id_new_nn;\n",[48,2332,2333],{"class":50,"line":194},[48,2334,113],{"emptyLinePlaceholder":112},[48,2336,2337],{"class":50,"line":200},[48,2338,2339],{"class":54},"-- Step 4: swap in a single transaction (brief lock for rename + default).\n",[48,2341,2342,2345],{"class":50,"line":206},[48,2343,2344],{"class":67},"BEGIN",[48,2346,1443],{"class":78},[48,2348,2349,2351,2353,2356,2358],{"class":50,"line":227},[48,2350,20],{"class":67},[48,2352,71],{"class":67},[48,2354,2355],{"class":78}," events RENAME COLUMN user_id     ",[48,2357,794],{"class":67},[48,2359,2360],{"class":78}," user_id_old;\n",[48,2362,2363,2365,2367,2370,2372],{"class":50,"line":232},[48,2364,20],{"class":67},[48,2366,71],{"class":67},[48,2368,2369],{"class":78}," events RENAME COLUMN user_id_new ",[48,2371,794],{"class":67},[48,2373,2374],{"class":78}," user_id;\n",[48,2376,2377,2379,2381,2383,2385,2387,2389,2391],{"class":50,"line":238},[48,2378,20],{"class":67},[48,2380,71],{"class":67},[48,2382,2172],{"class":78},[48,2384,20],{"class":67},[48,2386,2177],{"class":78},[48,2388,1291],{"class":67},[48,2390,215],{"class":67},[48,2392,1443],{"class":78},[48,2394,2395,2398],{"class":50,"line":244},[48,2396,2397],{"class":67},"COMMIT",[48,2399,1443],{"class":78},[48,2401,2402],{"class":50,"line":278},[48,2403,113],{"emptyLinePlaceholder":112},[48,2405,2406],{"class":50,"line":283},[48,2407,2408],{"class":54},"-- Step 5: drop old column (fast — marked dead; VACUUM reclaims later).\n",[48,2410,2411,2413,2415,2417,2420],{"class":50,"line":289},[48,2412,20],{"class":67},[48,2414,71],{"class":67},[48,2416,2172],{"class":78},[48,2418,2419],{"class":67},"DROP",[48,2421,2422],{"class":78}," COLUMN user_id_old;\n",[31,2424,2426],{"id":2425},"drop-table","DROP TABLE",[36,2428,2429],{"language":38},[40,2430,2432],{"className":42,"code":2431,"language":38,"meta":44,"style":44},"DROP TABLE billing.invoices;                    -- fails if dependent objects exist.\nDROP TABLE IF EXISTS billing.invoices;           -- idempotent.\nDROP TABLE billing.invoices CASCADE;             -- drops dependent views, FKs, etc.\n\n-- CASCADE is nuclear: it drops every view, materialized view, and FK that references\n-- the table — silently. Always check dependencies first:\nSELECT depender_ns.nspname AS depender_schema,\n       depender.relname    AS depender_name,\n       dependee.relname    AS dependee_name\nFROM pg_depend d\nJOIN pg_class depender ON depender.oid = d.objid\nJOIN pg_class dependee ON dependee.oid = d.refobjid\nJOIN pg_namespace depender_ns ON depender_ns.oid = depender.relnamespace\nWHERE dependee.relname = 'invoices';\n",[18,2433,2434,2452,2474,2492,2496,2501,2506,2523,2539,2553,2561,2590,2615,2639],{"__ignoreMap":44},[48,2435,2436,2438,2440,2442,2444,2446,2449],{"class":50,"line":51},[48,2437,2419],{"class":67},[48,2439,71],{"class":67},[48,2441,75],{"class":171},[48,2443,29],{"class":78},[48,2445,752],{"class":171},[48,2447,2448],{"class":78},";                    ",[48,2450,2451],{"class":54},"-- fails if dependent objects exist.\n",[48,2453,2454,2456,2458,2460,2462,2464,2466,2468,2471],{"class":50,"line":58},[48,2455,2419],{"class":67},[48,2457,71],{"class":67},[48,2459,1194],{"class":67},[48,2461,1200],{"class":67},[48,2463,75],{"class":171},[48,2465,29],{"class":78},[48,2467,752],{"class":171},[48,2469,2470],{"class":78},";           ",[48,2472,2473],{"class":54},"-- idempotent.\n",[48,2475,2476,2478,2480,2482,2484,2486,2489],{"class":50,"line":64},[48,2477,2419],{"class":67},[48,2479,71],{"class":67},[48,2481,75],{"class":171},[48,2483,29],{"class":78},[48,2485,752],{"class":171},[48,2487,2488],{"class":78}," CASCADE;             ",[48,2490,2491],{"class":54},"-- drops dependent views, FKs, etc.\n",[48,2493,2494],{"class":50,"line":82},[48,2495,113],{"emptyLinePlaceholder":112},[48,2497,2498],{"class":50,"line":88},[48,2499,2500],{"class":54},"-- CASCADE is nuclear: it drops every view, materialized view, and FK that references\n",[48,2502,2503],{"class":50,"line":94},[48,2504,2505],{"class":54},"-- the table — silently. Always check dependencies first:\n",[48,2507,2508,2510,2513,2515,2518,2520],{"class":50,"line":109},[48,2509,1304],{"class":67},[48,2511,2512],{"class":171}," depender_ns",[48,2514,29],{"class":78},[48,2516,2517],{"class":171},"nspname",[48,2519,358],{"class":67},[48,2521,2522],{"class":78}," depender_schema,\n",[48,2524,2525,2528,2530,2533,2536],{"class":50,"line":116},[48,2526,2527],{"class":171},"       depender",[48,2529,29],{"class":78},[48,2531,2532],{"class":171},"relname",[48,2534,2535],{"class":67},"    AS",[48,2537,2538],{"class":78}," depender_name,\n",[48,2540,2541,2544,2546,2548,2550],{"class":50,"line":122},[48,2542,2543],{"class":171},"       dependee",[48,2545,29],{"class":78},[48,2547,2532],{"class":171},[48,2549,2535],{"class":67},[48,2551,2552],{"class":78}," dependee_name\n",[48,2554,2555,2558],{"class":50,"line":128},[48,2556,2557],{"class":67},"FROM",[48,2559,2560],{"class":78}," pg_depend d\n",[48,2562,2563,2566,2569,2571,2574,2576,2579,2582,2585,2587],{"class":50,"line":142},[48,2564,2565],{"class":67},"JOIN",[48,2567,2568],{"class":78}," pg_class depender ",[48,2570,1629],{"class":67},[48,2572,2573],{"class":171}," depender",[48,2575,29],{"class":78},[48,2577,2578],{"class":171},"oid",[48,2580,2581],{"class":67}," =",[48,2583,2584],{"class":171}," d",[48,2586,29],{"class":78},[48,2588,2589],{"class":171},"objid\n",[48,2591,2592,2594,2597,2599,2602,2604,2606,2608,2610,2612],{"class":50,"line":147},[48,2593,2565],{"class":67},[48,2595,2596],{"class":78}," pg_class dependee ",[48,2598,1629],{"class":67},[48,2600,2601],{"class":171}," dependee",[48,2603,29],{"class":78},[48,2605,2578],{"class":171},[48,2607,2581],{"class":67},[48,2609,2584],{"class":171},[48,2611,29],{"class":78},[48,2613,2614],{"class":171},"refobjid\n",[48,2616,2617,2619,2622,2624,2626,2628,2630,2632,2634,2636],{"class":50,"line":153},[48,2618,2565],{"class":67},[48,2620,2621],{"class":78}," pg_namespace depender_ns ",[48,2623,1629],{"class":67},[48,2625,2512],{"class":171},[48,2627,29],{"class":78},[48,2629,2578],{"class":171},[48,2631,2581],{"class":67},[48,2633,2573],{"class":171},[48,2635,29],{"class":78},[48,2637,2638],{"class":171},"relnamespace\n",[48,2640,2641,2643,2645,2647,2649,2651,2654],{"class":50,"line":159},[48,2642,2258],{"class":67},[48,2644,2601],{"class":171},[48,2646,29],{"class":78},[48,2648,2532],{"class":171},[48,2650,2581],{"class":67},[48,2652,2653],{"class":271}," 'invoices'",[48,2655,1443],{"class":78},[31,2657,2659],{"id":2658},"truncate-vs-delete","TRUNCATE vs DELETE",[36,2661,2662],{"language":38},[40,2663,2665],{"className":42,"code":2664,"language":38,"meta":44,"style":44},"-- TRUNCATE: O(1). Removes all rows by unlinking the data file. No per-row WAL.\n-- No triggers fire (by default). Not MVCC-visible to pre-existing transactions.\nTRUNCATE billing.invoices;\n\n-- TRUNCATE multiple tables in one command (single implicit transaction).\nTRUNCATE billing.invoices, billing.invoice_items;\n\n-- RESTART IDENTITY: resets SERIAL sequences to initial value.\n-- Without it, sequences continue from their last value → gap after truncation.\nTRUNCATE billing.invoices RESTART IDENTITY;\n\n-- CASCADE: also truncates tables with FKs pointing INTO this table.\n-- ⚠️ DANGEROUS — can empty tables you didn't name. Check FK graph first.\nTRUNCATE billing.invoices CASCADE;\n\n-- DELETE: O(N). Row-by-row removal, WAL per row, triggers fire, MVCC-visible.\n-- Can be rolled back. Use for partial removals.\nDELETE FROM billing.invoices WHERE issued_at \u003C '2023-01-01';\n\n-- TRUNCATE is transactional in PostgreSQL (can ROLLBACK).\n-- TRUNCATE is NOT transactional in MySQL (implicit commit).\n",[18,2666,2667,2672,2677,2689,2693,2698,2720,2724,2729,2734,2752,2756,2761,2766,2779,2783,2788,2793,2817,2821,2826],{"__ignoreMap":44},[48,2668,2669],{"class":50,"line":51},[48,2670,2671],{"class":54},"-- TRUNCATE: O(1). Removes all rows by unlinking the data file. No per-row WAL.\n",[48,2673,2674],{"class":50,"line":58},[48,2675,2676],{"class":54},"-- No triggers fire (by default). Not MVCC-visible to pre-existing transactions.\n",[48,2678,2679,2681,2683,2685,2687],{"class":50,"line":64},[48,2680,24],{"class":67},[48,2682,75],{"class":171},[48,2684,29],{"class":78},[48,2686,752],{"class":171},[48,2688,1443],{"class":78},[48,2690,2691],{"class":50,"line":82},[48,2692,113],{"emptyLinePlaceholder":112},[48,2694,2695],{"class":50,"line":88},[48,2696,2697],{"class":54},"-- TRUNCATE multiple tables in one command (single implicit transaction).\n",[48,2699,2700,2702,2704,2706,2708,2710,2713,2715,2718],{"class":50,"line":94},[48,2701,24],{"class":67},[48,2703,75],{"class":171},[48,2705,29],{"class":78},[48,2707,752],{"class":171},[48,2709,1269],{"class":78},[48,2711,2712],{"class":171},"billing",[48,2714,29],{"class":78},[48,2716,2717],{"class":171},"invoice_items",[48,2719,1443],{"class":78},[48,2721,2722],{"class":50,"line":109},[48,2723,113],{"emptyLinePlaceholder":112},[48,2725,2726],{"class":50,"line":116},[48,2727,2728],{"class":54},"-- RESTART IDENTITY: resets SERIAL sequences to initial value.\n",[48,2730,2731],{"class":50,"line":122},[48,2732,2733],{"class":54},"-- Without it, sequences continue from their last value → gap after truncation.\n",[48,2735,2736,2738,2740,2742,2744,2747,2750],{"class":50,"line":128},[48,2737,24],{"class":67},[48,2739,75],{"class":171},[48,2741,29],{"class":78},[48,2743,752],{"class":171},[48,2745,2746],{"class":67}," RESTART",[48,2748,2749],{"class":67}," IDENTITY",[48,2751,1443],{"class":78},[48,2753,2754],{"class":50,"line":142},[48,2755,113],{"emptyLinePlaceholder":112},[48,2757,2758],{"class":50,"line":147},[48,2759,2760],{"class":54},"-- CASCADE: also truncates tables with FKs pointing INTO this table.\n",[48,2762,2763],{"class":50,"line":153},[48,2764,2765],{"class":54},"-- ⚠️ DANGEROUS — can empty tables you didn't name. Check FK graph first.\n",[48,2767,2768,2770,2772,2774,2776],{"class":50,"line":159},[48,2769,24],{"class":67},[48,2771,75],{"class":171},[48,2773,29],{"class":78},[48,2775,752],{"class":171},[48,2777,2778],{"class":78}," CASCADE;\n",[48,2780,2781],{"class":50,"line":189},[48,2782,113],{"emptyLinePlaceholder":112},[48,2784,2785],{"class":50,"line":194},[48,2786,2787],{"class":54},"-- DELETE: O(N). Row-by-row removal, WAL per row, triggers fire, MVCC-visible.\n",[48,2789,2790],{"class":50,"line":200},[48,2791,2792],{"class":54},"-- Can be rolled back. Use for partial removals.\n",[48,2794,2795,2797,2799,2801,2803,2805,2807,2809,2812,2815],{"class":50,"line":206},[48,2796,28],{"class":67},[48,2798,1310],{"class":67},[48,2800,75],{"class":171},[48,2802,29],{"class":78},[48,2804,752],{"class":171},[48,2806,1585],{"class":67},[48,2808,1588],{"class":78},[48,2810,2811],{"class":67},"\u003C",[48,2813,2814],{"class":271}," '2023-01-01'",[48,2816,1443],{"class":78},[48,2818,2819],{"class":50,"line":227},[48,2820,113],{"emptyLinePlaceholder":112},[48,2822,2823],{"class":50,"line":232},[48,2824,2825],{"class":54},"-- TRUNCATE is transactional in PostgreSQL (can ROLLBACK).\n",[48,2827,2828],{"class":50,"line":238},[48,2829,2830],{"class":54},"-- TRUNCATE is NOT transactional in MySQL (implicit commit).\n",[31,2832,2834],{"id":2833},"create-table-like-create-table-as","CREATE TABLE LIKE & CREATE TABLE AS",[36,2836,2837],{"language":38},[40,2838,2840],{"className":42,"code":2839,"language":38,"meta":44,"style":44},"-- LIKE: copy structure (no data). INCLUDING ALL copies defaults, constraints, indexes.\nCREATE TABLE billing.invoices_archive (LIKE billing.invoices INCLUDING ALL);\n\n-- AS: copy structure + data. Does NOT copy constraints, PKs, defaults, or indexes.\nCREATE TABLE analytics.invoices_2024 AS\n  SELECT * FROM billing.invoices WHERE issued_at >= '2024-01-01';\n\n-- To copy structure + data + constraints: combine LIKE + INSERT.\nCREATE TABLE billing.invoices_backup (LIKE billing.invoices INCLUDING ALL);\nINSERT INTO billing.invoices_backup SELECT * FROM billing.invoices;\n",[18,2841,2842,2847,2870,2874,2879,2892,2916,2920,2925,2946],{"__ignoreMap":44},[48,2843,2844],{"class":50,"line":51},[48,2845,2846],{"class":54},"-- LIKE: copy structure (no data). INCLUDING ALL copies defaults, constraints, indexes.\n",[48,2848,2849,2851,2853,2855,2858,2861,2863,2865,2867],{"class":50,"line":58},[48,2850,68],{"class":67},[48,2852,71],{"class":67},[48,2854,75],{"class":74},[48,2856,2857],{"class":78},".invoices_archive (",[48,2859,2860],{"class":67},"LIKE",[48,2862,75],{"class":171},[48,2864,29],{"class":78},[48,2866,752],{"class":171},[48,2868,2869],{"class":78}," INCLUDING ALL);\n",[48,2871,2872],{"class":50,"line":64},[48,2873,113],{"emptyLinePlaceholder":112},[48,2875,2876],{"class":50,"line":82},[48,2877,2878],{"class":54},"-- AS: copy structure + data. Does NOT copy constraints, PKs, defaults, or indexes.\n",[48,2880,2881,2883,2885,2887,2890],{"class":50,"line":88},[48,2882,68],{"class":67},[48,2884,71],{"class":67},[48,2886,1259],{"class":74},[48,2888,2889],{"class":78},".invoices_2024 ",[48,2891,1567],{"class":67},[48,2893,2894,2896,2898,2900,2902,2904,2906,2908,2910,2912,2914],{"class":50,"line":94},[48,2895,1572],{"class":67},[48,2897,1307],{"class":67},[48,2899,1310],{"class":67},[48,2901,75],{"class":171},[48,2903,29],{"class":78},[48,2905,752],{"class":171},[48,2907,1585],{"class":67},[48,2909,1588],{"class":78},[48,2911,463],{"class":67},[48,2913,1593],{"class":271},[48,2915,1443],{"class":78},[48,2917,2918],{"class":50,"line":109},[48,2919,113],{"emptyLinePlaceholder":112},[48,2921,2922],{"class":50,"line":116},[48,2923,2924],{"class":54},"-- To copy structure + data + constraints: combine LIKE + INSERT.\n",[48,2926,2927,2929,2931,2933,2936,2938,2940,2942,2944],{"class":50,"line":122},[48,2928,68],{"class":67},[48,2930,71],{"class":67},[48,2932,75],{"class":74},[48,2934,2935],{"class":78},".invoices_backup (",[48,2937,2860],{"class":67},[48,2939,75],{"class":171},[48,2941,29],{"class":78},[48,2943,752],{"class":171},[48,2945,2869],{"class":78},[48,2947,2948,2951,2953,2955,2958,2961,2963,2965,2967,2969,2971],{"class":50,"line":128},[48,2949,2950],{"class":67},"INSERT INTO",[48,2952,75],{"class":171},[48,2954,29],{"class":78},[48,2956,2957],{"class":171},"invoices_backup",[48,2959,2960],{"class":67}," SELECT",[48,2962,1307],{"class":67},[48,2964,1310],{"class":67},[48,2966,75],{"class":171},[48,2968,29],{"class":78},[48,2970,752],{"class":171},[48,2972,1443],{"class":78},[31,2974,2976],{"id":2975},"if-not-exists-if-exists-idempotent-migrations","IF NOT EXISTS \u002F IF EXISTS — Idempotent Migrations",[36,2978,2979],{"language":38},[40,2980,2982],{"className":42,"code":2981,"language":38,"meta":44,"style":44},"CREATE TABLE IF NOT EXISTS billing.invoices (...);\nDROP TABLE IF EXISTS billing.invoices;\nALTER TABLE billing.invoices ADD COLUMN IF NOT EXISTS notes TEXT;\nALTER TABLE billing.invoices DROP COLUMN IF EXISTS notes;\n\n-- ⚠️ IF NOT EXISTS masks schema drift: if the table exists with DIFFERENT columns,\n-- this is a no-op (returns a NOTICE). It does NOT verify the schema matches.\n-- Use it for re-runnable migrations, not for schema correctness enforcement.\n",[18,2983,2984,3005,3023,3054,3077,3081,3086,3091],{"__ignoreMap":44},[48,2985,2986,2988,2990,2992,2994,2996,2998,3000,3002],{"class":50,"line":51},[48,2987,68],{"class":67},[48,2989,71],{"class":67},[48,2991,1194],{"class":74},[48,2993,1197],{"class":67},[48,2995,1200],{"class":67},[48,2997,75],{"class":171},[48,2999,29],{"class":78},[48,3001,752],{"class":171},[48,3003,3004],{"class":78}," (...);\n",[48,3006,3007,3009,3011,3013,3015,3017,3019,3021],{"class":50,"line":58},[48,3008,2419],{"class":67},[48,3010,71],{"class":67},[48,3012,1194],{"class":67},[48,3014,1200],{"class":67},[48,3016,75],{"class":171},[48,3018,29],{"class":78},[48,3020,752],{"class":171},[48,3022,1443],{"class":78},[48,3024,3025,3027,3029,3031,3033,3035,3037,3040,3043,3045,3047,3050,3052],{"class":50,"line":64},[48,3026,20],{"class":67},[48,3028,71],{"class":67},[48,3030,75],{"class":171},[48,3032,29],{"class":78},[48,3034,752],{"class":171},[48,3036,1823],{"class":67},[48,3038,3039],{"class":78}," COLUMN ",[48,3041,3042],{"class":67},"IF",[48,3044,1197],{"class":67},[48,3046,1200],{"class":67},[48,3048,3049],{"class":78}," notes ",[48,3051,134],{"class":67},[48,3053,1443],{"class":78},[48,3055,3056,3058,3060,3062,3064,3066,3068,3070,3072,3074],{"class":50,"line":82},[48,3057,20],{"class":67},[48,3059,71],{"class":67},[48,3061,75],{"class":171},[48,3063,29],{"class":78},[48,3065,752],{"class":171},[48,3067,755],{"class":67},[48,3069,3039],{"class":78},[48,3071,3042],{"class":67},[48,3073,1200],{"class":67},[48,3075,3076],{"class":78}," notes;\n",[48,3078,3079],{"class":50,"line":88},[48,3080,113],{"emptyLinePlaceholder":112},[48,3082,3083],{"class":50,"line":94},[48,3084,3085],{"class":54},"-- ⚠️ IF NOT EXISTS masks schema drift: if the table exists with DIFFERENT columns,\n",[48,3087,3088],{"class":50,"line":109},[48,3089,3090],{"class":54},"-- this is a no-op (returns a NOTICE). It does NOT verify the schema matches.\n",[48,3092,3093],{"class":50,"line":116},[48,3094,3095],{"class":54},"-- Use it for re-runnable migrations, not for schema correctness enforcement.\n",[31,3097,3099],{"id":3098},"complex-implementation-production-multi-table-schema","Complex Implementation: Production Multi-Table Schema",[36,3101,3102],{"language":38},[40,3103,3105],{"className":42,"code":3104,"language":38,"meta":44,"style":44},"-- A SaaS billing schema with proper constraints, defaults, generated columns,\n-- and schema-based organization. Every clause has a production reason.\n\nCREATE SCHEMA IF NOT EXISTS billing;\n\n-- Customers: surrogate PK, soft-delete via deleted_at, partial unique on active email.\nCREATE TABLE billing.customers (\n  id           BIGSERIAL    PRIMARY KEY,\n  email        TEXT         NOT NULL,\n  name         TEXT         NOT NULL,\n  created_at   TIMESTAMPTZ  NOT NULL DEFAULT now(),\n  deleted_at   TIMESTAMPTZ,                         -- NULL = active; set = soft-deleted\n  -- Partial unique: only one active customer per email (deleted customers can reuse).\n  CONSTRAINT customers_email_unique UNIQUE (email) WHERE deleted_at IS NULL,\n  CONSTRAINT customers_email_format CHECK (email ~ '^[^@]+@[^@]+\\.[^@]+$')\n);\n\nCREATE INDEX customers_active_idx ON billing.customers(id) WHERE deleted_at IS NULL;\n\n-- Invoices: FK to customers with RESTRICT (don't delete a customer with invoices).\n-- Generated column for due date; CHECK for business rules.\nCREATE TABLE billing.invoices (\n  id            BIGSERIAL    PRIMARY KEY,\n  customer_id   BIGINT       NOT NULL,\n  invoice_num   TEXT         NOT NULL,\n  amount        NUMERIC(12,2) NOT NULL,\n  currency      TEXT         NOT NULL DEFAULT 'USD',\n  issued_at     TIMESTAMPTZ  NOT NULL DEFAULT now(),\n  -- Generated STORED: due_at = issued_at + 30 days. Recomputed if issued_at changes.\n  due_at        TIMESTAMPTZ  GENERATED ALWAYS AS (issued_at + interval '30 days') STORED,\n  paid_at       TIMESTAMPTZ,\n  metadata      JSONB        NOT NULL DEFAULT '{}'::jsonb,\n  -- Named constraints for readable errors and targeted drops.\n  CONSTRAINT invoices_customer_fk\n    FOREIGN KEY (customer_id) REFERENCES billing.customers(id)\n    ON DELETE RESTRICT,\n  CONSTRAINT invoices_num_unique UNIQUE (customer_id, invoice_num),\n  CONSTRAINT invoices_amount_positive CHECK (amount >= 0),\n  -- Business rule: if paid, paid_at must be after issued_at.\n  CONSTRAINT invoices_paid_after_issued\n    CHECK (paid_at IS NULL OR paid_at >= issued_at)\n);\n\n-- Index FK column — FK constraint does NOT auto-create an index.\n-- Without this, ON DELETE RESTRICT scans all invoices for each customer delete.\nCREATE INDEX invoices_customer_id_idx ON billing.invoices(customer_id);\n\n-- Invoice items: composite PK (natural junction), cascade delete with parent.\nCREATE TABLE billing.invoice_items (\n  invoice_id  BIGINT       NOT NULL,\n  line_num    INTEGER      NOT NULL,\n  sku         TEXT         NOT NULL,\n  qty         INTEGER      NOT NULL CHECK (qty > 0),\n  unit_price  NUMERIC(10,2) NOT NULL CHECK (unit_price >= 0),\n  -- Generated: line total = qty * unit_price. Indexed for fast sums.\n  line_total  NUMERIC(12,2) GENERATED ALWAYS AS (qty * unit_price) STORED,\n  PRIMARY KEY (invoice_id, line_num),\n  CONSTRAINT items_invoice_fk\n    FOREIGN KEY (invoice_id) REFERENCES billing.invoices(id)\n    ON DELETE CASCADE\n);\n\nCREATE INDEX items_invoice_id_idx ON billing.invoice_items(invoice_id);\n",[18,3106,3107,3112,3117,3121,3135,3139,3144,3155,3167,3179,3191,3206,3219,3224,3249,3266,3270,3274,3304,3308,3313,3318,3328,3339,3351,3362,3383,3398,3413,3418,3443,3452,3465,3470,3476,3494,3501,3513,3530,3535,3542,3565,3569,3573,3578,3583,3603,3607,3612,3624,3636,3649,3661,3681,3712,3718,3749,3758,3766,3784,3790,3795,3800],{"__ignoreMap":44},[48,3108,3109],{"class":50,"line":51},[48,3110,3111],{"class":54},"-- A SaaS billing schema with proper constraints, defaults, generated columns,\n",[48,3113,3114],{"class":50,"line":58},[48,3115,3116],{"class":54},"-- and schema-based organization. Every clause has a production reason.\n",[48,3118,3119],{"class":50,"line":64},[48,3120,113],{"emptyLinePlaceholder":112},[48,3122,3123,3125,3127,3129,3131,3133],{"class":50,"line":82},[48,3124,68],{"class":67},[48,3126,1191],{"class":67},[48,3128,1194],{"class":74},[48,3130,1197],{"class":67},[48,3132,1200],{"class":67},[48,3134,1203],{"class":78},[48,3136,3137],{"class":50,"line":88},[48,3138,113],{"emptyLinePlaceholder":112},[48,3140,3141],{"class":50,"line":94},[48,3142,3143],{"class":54},"-- Customers: surrogate PK, soft-delete via deleted_at, partial unique on active email.\n",[48,3145,3146,3148,3150,3152],{"class":50,"line":109},[48,3147,68],{"class":67},[48,3149,71],{"class":67},[48,3151,75],{"class":74},[48,3153,3154],{"class":78},".customers (\n",[48,3156,3157,3160,3162,3165],{"class":50,"line":116},[48,3158,3159],{"class":78},"  id           ",[48,3161,100],{"class":67},[48,3163,3164],{"class":67},"    PRIMARY KEY",[48,3166,106],{"class":78},[48,3168,3169,3172,3174,3177],{"class":50,"line":122},[48,3170,3171],{"class":78},"  email        ",[48,3173,134],{"class":67},[48,3175,3176],{"class":67},"         NOT NULL",[48,3178,106],{"class":78},[48,3180,3181,3184,3187,3189],{"class":50,"line":128},[48,3182,3183],{"class":67},"  name",[48,3185,3186],{"class":67},"         TEXT",[48,3188,3176],{"class":67},[48,3190,106],{"class":78},[48,3192,3193,3196,3198,3200,3202,3204],{"class":50,"line":142},[48,3194,3195],{"class":78},"  created_at   ",[48,3197,212],{"class":67},[48,3199,649],{"class":67},[48,3201,218],{"class":67},[48,3203,221],{"class":67},[48,3205,224],{"class":78},[48,3207,3208,3211,3213,3216],{"class":50,"line":147},[48,3209,3210],{"class":78},"  deleted_at   ",[48,3212,212],{"class":67},[48,3214,3215],{"class":78},",                         ",[48,3217,3218],{"class":54},"-- NULL = active; set = soft-deleted\n",[48,3220,3221],{"class":50,"line":153},[48,3222,3223],{"class":54},"  -- Partial unique: only one active customer per email (deleted customers can reuse).\n",[48,3225,3226,3228,3231,3233,3236,3238,3241,3244,3247],{"class":50,"line":159},[48,3227,398],{"class":67},[48,3229,3230],{"class":78}," customers_email_unique ",[48,3232,404],{"class":67},[48,3234,3235],{"class":78}," (email) ",[48,3237,2258],{"class":67},[48,3239,3240],{"class":78}," deleted_at ",[48,3242,3243],{"class":67},"IS",[48,3245,3246],{"class":67}," NULL",[48,3248,106],{"class":78},[48,3250,3251,3253,3256,3258,3261,3264],{"class":50,"line":189},[48,3252,398],{"class":67},[48,3254,3255],{"class":78}," customers_email_format ",[48,3257,458],{"class":67},[48,3259,3260],{"class":78}," (email ~ ",[48,3262,3263],{"class":271},"'^[^@]+@[^@]+\\.[^@]+$'",[48,3265,510],{"class":78},[48,3267,3268],{"class":50,"line":194},[48,3269,541],{"class":78},[48,3271,3272],{"class":50,"line":200},[48,3273,113],{"emptyLinePlaceholder":112},[48,3275,3276,3278,3280,3283,3285,3287,3289,3291,3294,3296,3298,3300,3302],{"class":50,"line":206},[48,3277,68],{"class":67},[48,3279,1149],{"class":67},[48,3281,3282],{"class":74}," customers_active_idx",[48,3284,1155],{"class":67},[48,3286,75],{"class":171},[48,3288,29],{"class":78},[48,3290,523],{"class":171},[48,3292,3293],{"class":78},"(id) ",[48,3295,2258],{"class":67},[48,3297,3240],{"class":78},[48,3299,3243],{"class":67},[48,3301,3246],{"class":67},[48,3303,1443],{"class":78},[48,3305,3306],{"class":50,"line":227},[48,3307,113],{"emptyLinePlaceholder":112},[48,3309,3310],{"class":50,"line":232},[48,3311,3312],{"class":54},"-- Invoices: FK to customers with RESTRICT (don't delete a customer with invoices).\n",[48,3314,3315],{"class":50,"line":238},[48,3316,3317],{"class":54},"-- Generated column for due date; CHECK for business rules.\n",[48,3319,3320,3322,3324,3326],{"class":50,"line":244},[48,3321,68],{"class":67},[48,3323,71],{"class":67},[48,3325,75],{"class":74},[48,3327,79],{"class":78},[48,3329,3330,3333,3335,3337],{"class":50,"line":278},[48,3331,3332],{"class":78},"  id            ",[48,3334,100],{"class":67},[48,3336,3164],{"class":67},[48,3338,106],{"class":78},[48,3340,3341,3344,3346,3349],{"class":50,"line":283},[48,3342,3343],{"class":78},"  customer_id   ",[48,3345,646],{"class":67},[48,3347,3348],{"class":67},"       NOT NULL",[48,3350,106],{"class":78},[48,3352,3353,3356,3358,3360],{"class":50,"line":289},[48,3354,3355],{"class":78},"  invoice_num   ",[48,3357,134],{"class":67},[48,3359,3176],{"class":67},[48,3361,106],{"class":78},[48,3363,3364,3367,3369,3371,3373,3375,3377,3379,3381],{"class":50,"line":295},[48,3365,3366],{"class":78},"  amount        ",[48,3368,165],{"class":67},[48,3370,168],{"class":78},[48,3372,172],{"class":171},[48,3374,175],{"class":78},[48,3376,178],{"class":171},[48,3378,181],{"class":78},[48,3380,184],{"class":67},[48,3382,106],{"class":78},[48,3384,3385,3388,3390,3392,3394,3396],{"class":50,"line":311},[48,3386,3387],{"class":78},"  currency      ",[48,3389,134],{"class":67},[48,3391,3176],{"class":67},[48,3393,218],{"class":67},[48,3395,1870],{"class":271},[48,3397,106],{"class":78},[48,3399,3400,3403,3405,3407,3409,3411],{"class":50,"line":316},[48,3401,3402],{"class":78},"  issued_at     ",[48,3404,212],{"class":67},[48,3406,649],{"class":67},[48,3408,218],{"class":67},[48,3410,221],{"class":67},[48,3412,224],{"class":78},[48,3414,3415],{"class":50,"line":322},[48,3416,3417],{"class":54},"  -- Generated STORED: due_at = issued_at + 30 days. Recomputed if issued_at changes.\n",[48,3419,3420,3423,3425,3428,3430,3432,3435,3437,3439,3441],{"class":50,"line":328},[48,3421,3422],{"class":78},"  due_at        ",[48,3424,212],{"class":67},[48,3426,3427],{"class":67},"  GENERATED",[48,3429,355],{"class":67},[48,3431,358],{"class":67},[48,3433,3434],{"class":78}," (issued_at ",[48,3436,265],{"class":67},[48,3438,268],{"class":78},[48,3440,272],{"class":271},[48,3442,375],{"class":78},[48,3444,3445,3448,3450],{"class":50,"line":334},[48,3446,3447],{"class":78},"  paid_at       ",[48,3449,212],{"class":67},[48,3451,106],{"class":78},[48,3453,3454,3457,3459,3461,3463],{"class":50,"line":378},[48,3455,3456],{"class":78},"  metadata      JSONB        ",[48,3458,184],{"class":67},[48,3460,218],{"class":67},[48,3462,305],{"class":271},[48,3464,308],{"class":78},[48,3466,3467],{"class":50,"line":383},[48,3468,3469],{"class":54},"  -- Named constraints for readable errors and targeted drops.\n",[48,3471,3472,3474],{"class":50,"line":389},[48,3473,398],{"class":67},[48,3475,493],{"class":78},[48,3477,3478,3480,3483,3486,3488,3490,3492],{"class":50,"line":395},[48,3479,499],{"class":67},[48,3481,3482],{"class":78}," (customer_id) ",[48,3484,3485],{"class":67},"REFERENCES",[48,3487,75],{"class":171},[48,3489,29],{"class":78},[48,3491,523],{"class":171},[48,3493,526],{"class":78},[48,3495,3496,3498],{"class":50,"line":410},[48,3497,532],{"class":67},[48,3499,3500],{"class":78}," RESTRICT,\n",[48,3502,3503,3505,3508,3510],{"class":50,"line":433},[48,3504,398],{"class":67},[48,3506,3507],{"class":78}," invoices_num_unique ",[48,3509,404],{"class":67},[48,3511,3512],{"class":78}," (customer_id, invoice_num),\n",[48,3514,3515,3517,3520,3522,3524,3526,3528],{"class":50,"line":438},[48,3516,398],{"class":67},[48,3518,3519],{"class":78}," invoices_amount_positive ",[48,3521,458],{"class":67},[48,3523,361],{"class":78},[48,3525,463],{"class":67},[48,3527,466],{"class":171},[48,3529,275],{"class":78},[48,3531,3532],{"class":50,"line":444},[48,3533,3534],{"class":54},"  -- Business rule: if paid, paid_at must be after issued_at.\n",[48,3536,3537,3539],{"class":50,"line":450},[48,3538,398],{"class":67},[48,3540,3541],{"class":78}," invoices_paid_after_issued\n",[48,3543,3544,3547,3550,3552,3554,3557,3560,3562],{"class":50,"line":471},[48,3545,3546],{"class":67},"    CHECK",[48,3548,3549],{"class":78}," (paid_at ",[48,3551,3243],{"class":67},[48,3553,3246],{"class":67},[48,3555,3556],{"class":67}," OR",[48,3558,3559],{"class":78}," paid_at ",[48,3561,463],{"class":67},[48,3563,3564],{"class":78}," issued_at)\n",[48,3566,3567],{"class":50,"line":476},[48,3568,541],{"class":78},[48,3570,3571],{"class":50,"line":482},[48,3572,113],{"emptyLinePlaceholder":112},[48,3574,3575],{"class":50,"line":488},[48,3576,3577],{"class":54},"-- Index FK column — FK constraint does NOT auto-create an index.\n",[48,3579,3580],{"class":50,"line":496},[48,3581,3582],{"class":54},"-- Without this, ON DELETE RESTRICT scans all invoices for each customer delete.\n",[48,3584,3585,3587,3589,3592,3594,3596,3598,3600],{"class":50,"line":513},[48,3586,68],{"class":67},[48,3588,1149],{"class":67},[48,3590,3591],{"class":74}," invoices_customer_id_idx",[48,3593,1155],{"class":67},[48,3595,75],{"class":171},[48,3597,29],{"class":78},[48,3599,752],{"class":171},[48,3601,3602],{"class":78},"(customer_id);\n",[48,3604,3605],{"class":50,"line":529},[48,3606,113],{"emptyLinePlaceholder":112},[48,3608,3609],{"class":50,"line":538},[48,3610,3611],{"class":54},"-- Invoice items: composite PK (natural junction), cascade delete with parent.\n",[48,3613,3615,3617,3619,3621],{"class":50,"line":3614},49,[48,3616,68],{"class":67},[48,3618,71],{"class":67},[48,3620,75],{"class":74},[48,3622,3623],{"class":78},".invoice_items (\n",[48,3625,3627,3630,3632,3634],{"class":50,"line":3626},50,[48,3628,3629],{"class":78},"  invoice_id  ",[48,3631,646],{"class":67},[48,3633,3348],{"class":67},[48,3635,106],{"class":78},[48,3637,3639,3642,3644,3647],{"class":50,"line":3638},51,[48,3640,3641],{"class":78},"  line_num    ",[48,3643,1055],{"class":67},[48,3645,3646],{"class":67},"      NOT NULL",[48,3648,106],{"class":78},[48,3650,3652,3655,3657,3659],{"class":50,"line":3651},52,[48,3653,3654],{"class":78},"  sku         ",[48,3656,134],{"class":67},[48,3658,3176],{"class":67},[48,3660,106],{"class":78},[48,3662,3664,3666,3668,3670,3672,3675,3677,3679],{"class":50,"line":3663},53,[48,3665,1052],{"class":78},[48,3667,1055],{"class":67},[48,3669,3646],{"class":67},[48,3671,601],{"class":67},[48,3673,3674],{"class":78}," (qty ",[48,3676,606],{"class":67},[48,3678,466],{"class":171},[48,3680,275],{"class":78},[48,3682,3684,3687,3689,3691,3693,3695,3697,3699,3701,3703,3706,3708,3710],{"class":50,"line":3683},54,[48,3685,3686],{"class":78},"  unit_price  ",[48,3688,165],{"class":67},[48,3690,168],{"class":78},[48,3692,1037],{"class":171},[48,3694,175],{"class":78},[48,3696,178],{"class":171},[48,3698,181],{"class":78},[48,3700,184],{"class":67},[48,3702,601],{"class":67},[48,3704,3705],{"class":78}," (unit_price ",[48,3707,463],{"class":67},[48,3709,466],{"class":171},[48,3711,275],{"class":78},[48,3713,3715],{"class":50,"line":3714},55,[48,3716,3717],{"class":54},"  -- Generated: line total = qty * unit_price. Indexed for fast sums.\n",[48,3719,3721,3724,3726,3728,3730,3732,3734,3736,3738,3740,3742,3744,3746],{"class":50,"line":3720},56,[48,3722,3723],{"class":78},"  line_total  ",[48,3725,165],{"class":67},[48,3727,168],{"class":78},[48,3729,172],{"class":171},[48,3731,175],{"class":78},[48,3733,178],{"class":171},[48,3735,181],{"class":78},[48,3737,352],{"class":67},[48,3739,355],{"class":67},[48,3741,358],{"class":67},[48,3743,3674],{"class":78},[48,3745,364],{"class":67},[48,3747,3748],{"class":78}," unit_price) STORED,\n",[48,3750,3752,3755],{"class":50,"line":3751},57,[48,3753,3754],{"class":67},"  PRIMARY KEY",[48,3756,3757],{"class":78}," (invoice_id, line_num),\n",[48,3759,3761,3763],{"class":50,"line":3760},58,[48,3762,398],{"class":67},[48,3764,3765],{"class":78}," items_invoice_fk\n",[48,3767,3769,3771,3774,3776,3778,3780,3782],{"class":50,"line":3768},59,[48,3770,499],{"class":67},[48,3772,3773],{"class":78}," (invoice_id) ",[48,3775,3485],{"class":67},[48,3777,75],{"class":171},[48,3779,29],{"class":78},[48,3781,752],{"class":171},[48,3783,526],{"class":78},[48,3785,3787],{"class":50,"line":3786},60,[48,3788,3789],{"class":67},"    ON DELETE CASCADE\n",[48,3791,3793],{"class":50,"line":3792},61,[48,3794,541],{"class":78},[48,3796,3798],{"class":50,"line":3797},62,[48,3799,113],{"emptyLinePlaceholder":112},[48,3801,3803,3805,3807,3810,3812,3814,3816,3818],{"class":50,"line":3802},63,[48,3804,68],{"class":67},[48,3806,1149],{"class":67},[48,3808,3809],{"class":74}," items_invoice_id_idx",[48,3811,1155],{"class":67},[48,3813,75],{"class":171},[48,3815,29],{"class":78},[48,3817,2717],{"class":171},[48,3819,3820],{"class":78},"(invoice_id);\n",[31,3822,3824],{"id":3823},"anti-pattern-alter-table-add-column-default-on-a-huge-table","Anti-Pattern: ALTER TABLE ADD COLUMN DEFAULT on a Huge Table",[36,3826,3827],{"language":38},[40,3828,3830],{"className":42,"code":3829,"language":38,"meta":44,"style":44},"-- ❌ WRONG: VOLATILE default forces a full table rewrite even in PG 11+.\n-- gen_random_uuid() is VOLATILE → each existing row needs a unique value computed\n-- at rewrite time → ACCESS EXCLUSIVE lock for the entire duration.\nALTER TABLE billing.invoices\n  ADD COLUMN ref_uuid UUID NOT NULL DEFAULT gen_random_uuid();\n-- On a 500M-row table: 20+ minutes of total downtime.\n\n-- ✅ CORRECT: staged rollout, no long lock.\n-- Step 1: add nullable column (instant, metadata-only).\nALTER TABLE billing.invoices ADD COLUMN ref_uuid UUID;\n\n-- Step 2: backfill in batches by id range (no long lock).\nUPDATE billing.invoices SET ref_uuid = gen_random_uuid()\nWHERE id BETWEEN 1 AND 100000 AND ref_uuid IS NULL;\n-- Repeat for next batches...\n\n-- Step 3: set default for future inserts (metadata-only).\nALTER TABLE billing.invoices ALTER COLUMN ref_uuid SET DEFAULT gen_random_uuid();\n\n-- Step 4: add NOT NULL via NOT VALID + VALIDATE (light lock).\nALTER TABLE billing.invoices ADD CONSTRAINT invoices_ref_uuid_nn\n  CHECK (ref_uuid IS NOT NULL) NOT VALID;\nALTER TABLE billing.invoices VALIDATE CONSTRAINT invoices_ref_uuid_nn;\n",[18,3831,3832,3837,3842,3847,3860,3873,3878,3882,3887,3892,3909,3913,3918,3938,3962,3967,3971,3976,3999,4003,4008,4027,4043],{"__ignoreMap":44},[48,3833,3834],{"class":50,"line":51},[48,3835,3836],{"class":54},"-- ❌ WRONG: VOLATILE default forces a full table rewrite even in PG 11+.\n",[48,3838,3839],{"class":50,"line":58},[48,3840,3841],{"class":54},"-- gen_random_uuid() is VOLATILE → each existing row needs a unique value computed\n",[48,3843,3844],{"class":50,"line":64},[48,3845,3846],{"class":54},"-- at rewrite time → ACCESS EXCLUSIVE lock for the entire duration.\n",[48,3848,3849,3851,3853,3855,3857],{"class":50,"line":82},[48,3850,20],{"class":67},[48,3852,71],{"class":67},[48,3854,75],{"class":171},[48,3856,29],{"class":78},[48,3858,3859],{"class":171},"invoices\n",[48,3861,3862,3865,3867,3869,3871],{"class":50,"line":88},[48,3863,3864],{"class":67},"  ADD",[48,3866,1908],{"class":78},[48,3868,184],{"class":67},[48,3870,218],{"class":67},[48,3872,1913],{"class":78},[48,3874,3875],{"class":50,"line":94},[48,3876,3877],{"class":54},"-- On a 500M-row table: 20+ minutes of total downtime.\n",[48,3879,3880],{"class":50,"line":109},[48,3881,113],{"emptyLinePlaceholder":112},[48,3883,3884],{"class":50,"line":116},[48,3885,3886],{"class":54},"-- ✅ CORRECT: staged rollout, no long lock.\n",[48,3888,3889],{"class":50,"line":122},[48,3890,3891],{"class":54},"-- Step 1: add nullable column (instant, metadata-only).\n",[48,3893,3894,3896,3898,3900,3902,3904,3906],{"class":50,"line":128},[48,3895,20],{"class":67},[48,3897,71],{"class":67},[48,3899,75],{"class":171},[48,3901,29],{"class":78},[48,3903,752],{"class":171},[48,3905,1823],{"class":67},[48,3907,3908],{"class":78}," COLUMN ref_uuid UUID;\n",[48,3910,3911],{"class":50,"line":142},[48,3912,113],{"emptyLinePlaceholder":112},[48,3914,3915],{"class":50,"line":147},[48,3916,3917],{"class":54},"-- Step 2: backfill in batches by id range (no long lock).\n",[48,3919,3920,3922,3924,3926,3928,3930,3933,3935],{"class":50,"line":153},[48,3921,2238],{"class":67},[48,3923,75],{"class":171},[48,3925,29],{"class":78},[48,3927,752],{"class":171},[48,3929,1789],{"class":67},[48,3931,3932],{"class":78}," ref_uuid ",[48,3934,2248],{"class":67},[48,3936,3937],{"class":78}," gen_random_uuid()\n",[48,3939,3940,3942,3944,3946,3948,3950,3952,3954,3956,3958,3960],{"class":50,"line":159},[48,3941,2258],{"class":67},[48,3943,2261],{"class":78},[48,3945,2264],{"class":67},[48,3947,367],{"class":171},[48,3949,2269],{"class":67},[48,3951,2272],{"class":171},[48,3953,2269],{"class":67},[48,3955,3932],{"class":78},[48,3957,3243],{"class":67},[48,3959,3246],{"class":67},[48,3961,1443],{"class":78},[48,3963,3964],{"class":50,"line":189},[48,3965,3966],{"class":54},"-- Repeat for next batches...\n",[48,3968,3969],{"class":50,"line":194},[48,3970,113],{"emptyLinePlaceholder":112},[48,3972,3973],{"class":50,"line":200},[48,3974,3975],{"class":54},"-- Step 3: set default for future inserts (metadata-only).\n",[48,3977,3978,3980,3982,3984,3986,3988,3990,3993,3995,3997],{"class":50,"line":206},[48,3979,20],{"class":67},[48,3981,71],{"class":67},[48,3983,75],{"class":171},[48,3985,29],{"class":78},[48,3987,752],{"class":171},[48,3989,1997],{"class":67},[48,3991,3992],{"class":78}," COLUMN ref_uuid ",[48,3994,1291],{"class":67},[48,3996,218],{"class":67},[48,3998,1913],{"class":78},[48,4000,4001],{"class":50,"line":227},[48,4002,113],{"emptyLinePlaceholder":112},[48,4004,4005],{"class":50,"line":232},[48,4006,4007],{"class":54},"-- Step 4: add NOT NULL via NOT VALID + VALIDATE (light lock).\n",[48,4009,4010,4012,4014,4016,4018,4020,4022,4024],{"class":50,"line":238},[48,4011,20],{"class":67},[48,4013,71],{"class":67},[48,4015,75],{"class":171},[48,4017,29],{"class":78},[48,4019,752],{"class":171},[48,4021,1823],{"class":67},[48,4023,758],{"class":67},[48,4025,4026],{"class":78}," invoices_ref_uuid_nn\n",[48,4028,4029,4032,4035,4037,4039,4041],{"class":50,"line":244},[48,4030,4031],{"class":67},"  CHECK",[48,4033,4034],{"class":78}," (ref_uuid ",[48,4036,2310],{"class":67},[48,4038,181],{"class":78},[48,4040,2119],{"class":67},[48,4042,2122],{"class":78},[48,4044,4045,4047,4049,4051,4053,4055,4057,4059],{"class":50,"line":278},[48,4046,20],{"class":67},[48,4048,71],{"class":67},[48,4050,75],{"class":171},[48,4052,29],{"class":78},[48,4054,752],{"class":171},[48,4056,2137],{"class":78},[48,4058,788],{"class":67},[48,4060,4061],{"class":78}," invoices_ref_uuid_nn;\n",[31,4063,4065],{"id":4064},"tips-tricks","💡 Tips & Tricks",[4067,4068,4069,4091,4108,4125,4140,4149,4162],"ul",{},[4070,4071,4072,4075,4076,4079,4080,4082,4083,4086,4087,4090],"li",{},[973,4073,4074],{},"Idiom — name every constraint",": ",[18,4077,4078],{},"CONSTRAINT invoices_customer_fk FOREIGN KEY ..."," gives readable errors (",[18,4081,722],{},") and lets you ",[18,4084,4085],{},"DROP CONSTRAINT"," by name. Auto-names like ",[18,4088,4089],{},"invoices_customer_id_fkey"," are brittle across schema diff tools.",[4070,4092,4093,4096,4097,4100,4101,4104,4105,4107],{},[973,4094,4095],{},"Performance — PG 11+ ADD COLUMN DEFAULT constant is metadata-only",": adding ",[18,4098,4099],{},"status TEXT NOT NULL DEFAULT 'active'"," to a billion-row table is instant. PG stores the default in ",[18,4102,4103],{},"pg_attrdef"," and returns it for existing rows on read. But ",[18,4106,184],{}," with a VOLATILE default still requires a rewrite — use the nullable + backfill + NOT VALID pattern for zero-downtime.",[4070,4109,4110,4075,4113,4116,4117,4120,4121,4124],{},[973,4111,4112],{},"Idiom — NOT VALID + VALIDATE for zero-downtime constraints",[18,4114,4115],{},"ADD CONSTRAINT ... NOT VALID"," is metadata-only (no scan). ",[18,4118,4119],{},"VALIDATE CONSTRAINT"," scans with ",[18,4122,4123],{},"SHARE UPDATE EXCLUSIVE"," (concurrent reads\u002Fwrites continue). Standard for adding FK\u002FCHECK to large tables.",[4070,4126,4127,4075,4130,4132,4133,4136,4137,4139],{},[973,4128,4129],{},"Idiom — TRUNCATE over DELETE for whole-table clears",[18,4131,24],{}," is O(1) (unlinks data file, no per-row WAL, no triggers). ",[18,4134,4135],{},"DELETE FROM table"," is O(N), creates dead tuples, and bloats until VACUUM. Reserve ",[18,4138,28],{}," for partial removals.",[4070,4141,4142,4075,4145,4148],{},[973,4143,4144],{},"Idiom — UNLOGGED for ephemeral data",[18,4146,4147],{},"CREATE UNLOGGED TABLE"," skips WAL → 2-3x faster writes. Trade-off: truncated on crash. Ideal for caches, ETL staging, derived tables that can be rebuilt.",[4070,4150,4151,4075,4154,4157,4158,4161],{},[973,4152,4153],{},"Idiom — generated columns for indexed expressions",[18,4155,4156],{},"lower(email) GENERATED ALWAYS AS (lower(email)) STORED"," + a unique index on it gives case-insensitive uniqueness without repeating ",[18,4159,4160],{},"lower()"," in every query.",[4070,4163,4164,4171,4172,4174,4175,29],{},[973,4165,4166,4167,4170],{},"Portability — ",[18,4168,4169],{},"CREATE TABLE LIKE INCLUDING ALL"," is PG-specific",": MySQL has ",[18,4173,2860],{}," (no options); SQLite lacks it. For portable structure copying, generate DDL from ",[18,4176,4177],{},"information_schema",[31,4179,4181],{"id":4180},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[4067,4183,4184,4206,4223,4239,4256,4271,4292,4312,4334,4350,4359],{},[4070,4185,4186,4195,4196,4191,4199,4201,4202,4205],{},[973,4187,4188,4191,4192,4194],{},[18,4189,4190],{},"SERIAL","\u002F",[18,4193,100],{}," are conveniences, not types",": they create a sequence + column default + NOT NULL. The column's actual type is ",[18,4197,4198],{},"integer",[18,4200,1618],{},". The sequence survives even if you drop the default. Prefer ",[18,4203,4204],{},"GENERATED ... AS IDENTITY"," (standard SQL, PG 10+) going forward — it's tied to the column and dropped with it.",[4070,4207,4208,4075,4211,4214,4215,4218,4219,4222],{},[973,4209,4210],{},"VOLATILE defaults force rewrite",[18,4212,4213],{},"DEFAULT now()"," is STABLE (metadata-only in PG 11+), but ",[18,4216,4217],{},"DEFAULT gen_random_uuid()"," is VOLATILE → full table rewrite even in PG 11+. Use the nullable + backfill + ",[18,4220,4221],{},"SET DEFAULT"," + NOT VALID pattern.",[4070,4224,4225,4231,4232,4235,4236,4238],{},[973,4226,4227,4230],{},[18,4228,4229],{},"ALTER COLUMN ... TYPE"," always rewrites"," (unless binary-compatible): ",[18,4233,4234],{},"int"," → ",[18,4237,1618],{}," rewrites. On large tables, use the staged new-column + backfill + swap pattern.",[4070,4240,4241,4247,4248,4251,4252,4255],{},[973,4242,4243,4246],{},[18,4244,4245],{},"DROP TABLE CASCADE"," is nuclear",": it silently drops every view, materialized view, and FK referencing the table. Always check ",[18,4249,4250],{},"pg_depend"," first. A ",[18,4253,4254],{},"CASCADE"," on a central table can drop dozens of views you didn't intend to lose.",[4070,4257,4258,4263,4264,4267,4268,4270],{},[973,4259,4260,4262],{},[18,4261,24],{}," doesn't reset sequences by default",": use ",[18,4265,4266],{},"RESTART IDENTITY"," to reset ",[18,4269,4190],{}," sequences. Without it, new inserts continue from the last sequence value, creating a gap.",[4070,4272,4273,4279,4280,4282,4283,1269,4285,4288,4289,4291],{},[973,4274,4275,4278],{},[18,4276,4277],{},"TRUNCATE CASCADE"," truncates dependent tables",": if ",[18,4281,2717],{}," has a FK to ",[18,4284,752],{},[18,4286,4287],{},"TRUNCATE invoices CASCADE"," also empties ",[18,4290,2717],{}," — silently. Always check the FK graph.",[4070,4293,4294,4300,4301,4304,4305,4308,4309,4311],{},[973,4295,4296,4299],{},[18,4297,4298],{},"DROP COLUMN"," doesn't reclaim space immediately",": PG marks the column dead (fast) but physical space is only reclaimed by ",[18,4302,4303],{},"VACUUM FULL"," (or ",[18,4306,4307],{},"pg_repack","). Disk usage doesn't drop right after ",[18,4310,4298],{}," on a large table.",[4070,4313,4314,4317,4318,4321,4322,4325,4326,4329,4330,4333],{},[973,4315,4316],{},"Temp tables shadow permanent tables",": temp tables are created in ",[18,4319,4320],{},"pg_temp",", which is searched first in ",[18,4323,4324],{},"search_path",". ",[18,4327,4328],{},"CREATE TEMP TABLE users (...)"," shadows ",[18,4331,4332],{},"public.users"," for the session — a source of \"why isn't my data persisting\" confusion.",[4070,4335,4336,4342,4343,4346,4347,4349],{},[973,4337,4338,4341],{},[18,4339,4340],{},"CREATE TABLE AS"," doesn't copy constraints",": it copies columns and data but no PK, FK, UNIQUE, CHECK, or defaults. Add them afterward, or use ",[18,4344,4345],{},"LIKE ... INCLUDING ALL"," + ",[18,4348,985],{}," for structure + data.",[4070,4351,4352,4075,4355,4358],{},[973,4353,4354],{},"Generated columns can't reference other tables",[18,4356,4357],{},"GENERATED ALWAYS AS (...)"," can only use columns of the same row, no subqueries, no non-immutable functions. For cross-table derivations, use a view or trigger.",[4070,4360,4361,4075,4367,4370],{},[973,4362,4363,4366],{},[18,4364,4365],{},"IF NOT EXISTS"," masks schema drift",[18,4368,4369],{},"CREATE TABLE IF NOT EXISTS x (...)"," is a no-op if the table exists — even if the existing table has different columns. It's idempotent, not a correctness check.",[31,4372,4374],{"id":4373},"spot-the-bug","🧠 Spot the Bug",[14,4376,4377,4378,4381],{},"A team runs this migration on a 500M-row ",[18,4379,4380],{},"events"," table and the database locks up for 20 minutes:",[36,4383,4384],{"language":38},[40,4385,4387],{"className":42,"code":4386,"language":38,"meta":44,"style":44},"ALTER TABLE events\n  ADD COLUMN ref_uuid UUID NOT NULL DEFAULT gen_random_uuid();\n",[18,4388,4389,4398],{"__ignoreMap":44},[48,4390,4391,4393,4395],{"class":50,"line":51},[48,4392,20],{"class":67},[48,4394,71],{"class":67},[48,4396,4397],{"class":78}," events\n",[48,4399,4400,4402,4404,4406,4408],{"class":50,"line":58},[48,4401,3864],{"class":67},[48,4403,1908],{"class":78},[48,4405,184],{"class":67},[48,4407,218],{"class":67},[48,4409,1913],{"class":78},[14,4411,4412],{},"What went wrong, and how should they have done it?",[4414,4415,4416,4420,4434,4437,4590],"details",{},[4417,4418,4419],"summary",{},"Answer",[14,4421,4422,4425,4426,4429,4430,4433],{},[18,4423,4424],{},"gen_random_uuid()"," is a ",[18,4427,4428],{},"VOLATILE"," function — each existing row needs a unique value computed at rewrite time. Even in PG 11+ (which made constant defaults metadata-only), VOLATILE defaults force a full table rewrite under an ",[18,4431,4432],{},"ACCESS EXCLUSIVE"," lock. On 500M rows, that's 20+ minutes of total downtime (no reads, no writes).",[14,4435,4436],{},"The fix is a staged, lock-light rollout:",[36,4438,4439],{"language":38},[40,4440,4442],{"className":42,"code":4441,"language":38,"meta":44,"style":44},"-- Step 1: add nullable column (metadata-only, instant — no default, no rewrite).\nALTER TABLE events ADD COLUMN ref_uuid UUID;\n\n-- Step 2: backfill in batches by id range (short locks, concurrent reads continue).\nUPDATE events SET ref_uuid = gen_random_uuid()\nWHERE id BETWEEN 1 AND 100000 AND ref_uuid IS NULL;\n-- Repeat for subsequent id ranges...\n\n-- Step 3: set default for future inserts (metadata-only).\nALTER TABLE events ALTER COLUMN ref_uuid SET DEFAULT gen_random_uuid();\n\n-- Step 4: enforce NOT NULL via NOT VALID + VALIDATE (light lock, no rewrite).\nALTER TABLE events ADD CONSTRAINT events_ref_uuid_nn\n  CHECK (ref_uuid IS NOT NULL) NOT VALID;\nALTER TABLE events VALIDATE CONSTRAINT events_ref_uuid_nn;\n",[18,4443,4444,4449,4461,4465,4470,4484,4508,4513,4517,4521,4539,4543,4548,4563,4577],{"__ignoreMap":44},[48,4445,4446],{"class":50,"line":51},[48,4447,4448],{"class":54},"-- Step 1: add nullable column (metadata-only, instant — no default, no rewrite).\n",[48,4450,4451,4453,4455,4457,4459],{"class":50,"line":58},[48,4452,20],{"class":67},[48,4454,71],{"class":67},[48,4456,2172],{"class":78},[48,4458,2217],{"class":67},[48,4460,3908],{"class":78},[48,4462,4463],{"class":50,"line":64},[48,4464,113],{"emptyLinePlaceholder":112},[48,4466,4467],{"class":50,"line":82},[48,4468,4469],{"class":54},"-- Step 2: backfill in batches by id range (short locks, concurrent reads continue).\n",[48,4471,4472,4474,4476,4478,4480,4482],{"class":50,"line":88},[48,4473,2238],{"class":67},[48,4475,2172],{"class":78},[48,4477,1291],{"class":67},[48,4479,3932],{"class":78},[48,4481,2248],{"class":67},[48,4483,3937],{"class":78},[48,4485,4486,4488,4490,4492,4494,4496,4498,4500,4502,4504,4506],{"class":50,"line":94},[48,4487,2258],{"class":67},[48,4489,2261],{"class":78},[48,4491,2264],{"class":67},[48,4493,367],{"class":171},[48,4495,2269],{"class":67},[48,4497,2272],{"class":171},[48,4499,2269],{"class":67},[48,4501,3932],{"class":78},[48,4503,3243],{"class":67},[48,4505,3246],{"class":67},[48,4507,1443],{"class":78},[48,4509,4510],{"class":50,"line":109},[48,4511,4512],{"class":54},"-- Repeat for subsequent id ranges...\n",[48,4514,4515],{"class":50,"line":116},[48,4516,113],{"emptyLinePlaceholder":112},[48,4518,4519],{"class":50,"line":122},[48,4520,3975],{"class":54},[48,4522,4523,4525,4527,4529,4531,4533,4535,4537],{"class":50,"line":128},[48,4524,20],{"class":67},[48,4526,71],{"class":67},[48,4528,2172],{"class":78},[48,4530,20],{"class":67},[48,4532,3992],{"class":78},[48,4534,1291],{"class":67},[48,4536,218],{"class":67},[48,4538,1913],{"class":78},[48,4540,4541],{"class":50,"line":142},[48,4542,113],{"emptyLinePlaceholder":112},[48,4544,4545],{"class":50,"line":147},[48,4546,4547],{"class":54},"-- Step 4: enforce NOT NULL via NOT VALID + VALIDATE (light lock, no rewrite).\n",[48,4549,4550,4552,4554,4556,4558,4560],{"class":50,"line":153},[48,4551,20],{"class":67},[48,4553,71],{"class":67},[48,4555,2172],{"class":78},[48,4557,2217],{"class":67},[48,4559,758],{"class":67},[48,4561,4562],{"class":78}," events_ref_uuid_nn\n",[48,4564,4565,4567,4569,4571,4573,4575],{"class":50,"line":159},[48,4566,4031],{"class":67},[48,4568,4034],{"class":78},[48,4570,2310],{"class":67},[48,4572,181],{"class":78},[48,4574,2119],{"class":67},[48,4576,2122],{"class":78},[48,4578,4579,4581,4583,4585,4587],{"class":50,"line":189},[48,4580,20],{"class":67},[48,4582,71],{"class":67},[48,4584,2325],{"class":78},[48,4586,788],{"class":67},[48,4588,4589],{"class":78}," events_ref_uuid_nn;\n",[14,4591,4592,4075,4595,4346,4597,4599,4600,4346,4603,29],{},[973,4593,4594],{},"The lesson",[18,4596,184],{},[18,4598,4428],{}," default on a new column with existing rows forces a full table rewrite under an exclusive lock. For zero-downtime, add nullable → backfill in batches → set default → add NOT NULL via ",[18,4601,4602],{},"NOT VALID",[18,4604,4605],{},"VALIDATE",[4607,4608,4609],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}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 .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}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":44,"searchDepth":58,"depth":58,"links":4611},[4612,4616,4617,4618,4621,4622,4623,4626,4627,4628,4629,4630,4631,4632,4633,4634],{"id":33,"depth":58,"text":34,"children":4613},[4614,4615],{"id":545,"depth":64,"text":546},{"id":715,"depth":64,"text":716},{"id":800,"depth":58,"text":801},{"id":996,"depth":58,"text":997},{"id":1171,"depth":58,"text":1172,"children":4619},[4620],{"id":1346,"depth":64,"text":1347},{"id":1528,"depth":58,"text":1529},{"id":1699,"depth":58,"text":1700},{"id":1795,"depth":58,"text":1796,"children":4624},[4625],{"id":2145,"depth":64,"text":2146},{"id":2425,"depth":58,"text":2426},{"id":2658,"depth":58,"text":2659},{"id":2833,"depth":58,"text":2834},{"id":2975,"depth":58,"text":2976},{"id":3098,"depth":58,"text":3099},{"id":3823,"depth":58,"text":3824},{"id":4064,"depth":58,"text":4065},{"id":4180,"depth":58,"text":4181},{"id":4373,"depth":58,"text":4374},"DDL defines the skeleton: tables, schemas, constraints, types. Get it wrong and every query downstream pays for it. This chapter is a code-first reference for production DDL — constraints, defaults, generated columns, temp tables, safe ALTER patterns, and TRUNCATE vs DELETE.","md",{},"\u002Fsql\u002F11-tables-and-ddl",{"title":5,"description":4635},"sql\u002F11-tables-and-ddl","xtiM57dU2fkg6iNDeTZ_ZfY9N8i15--sow3_dAYIr-4",1789924654424]