[{"data":1,"prerenderedAt":4071},["ShallowReactive",2],{"page-\u002Fsql\u002F14-insert-update-delete":3},{"id":4,"title":5,"body":6,"description":4064,"extension":4065,"meta":4066,"navigation":104,"path":4067,"seo":4068,"stem":4069,"__hash__":4070},"content\u002Fsql\u002F14-insert-update-delete.md","14 — INSERT, UPDATE, DELETE",{"type":7,"value":8,"toc":4038},"minimark",[9,13,38,42,297,302,322,326,415,419,422,780,795,802,809,833,956,959,962,1208,1213,1243,1246,1525,1529,1644,1648,1893,1896,2042,2046,2164,2168,2175,2198,2204,2208,2769,2773,3123,3127,3342,3346,3447,3451,3570,3574,3771,3775,3778,3837,4034],[10,11,5],"h1",{"id":12},"_14-insert-update-delete",[14,15,16,17,21,22,25,26,29,30,33,34,37],"p",{},"DML (Data Manipulation Language) modifies rows. This chapter is a code-first reference for ",[18,19,20],"code",{},"INSERT",", upsert (",[18,23,24],{},"ON CONFLICT","), ",[18,27,28],{},"RETURNING",", join-based ",[18,31,32],{},"UPDATE","\u002F",[18,35,36],{},"DELETE",", and the batch patterns that keep large-table modifications from locking your database.",[39,40,20],"h2",{"id":41},"insert",[43,44,46],"code-wrapper",{"language":45},"sql",[47,48,52],"pre",{"className":49,"code":50,"language":45,"meta":51,"style":51},"language-sql shiki shiki-themes github-light github-dark","-- Single row.\nINSERT INTO customers (name, city) VALUES ('Alice', 'NYC');\n\n-- Multi-row: one statement, multiple value tuples. Reduces parse + round-trip overhead.\n-- PG sends one PARSE + N BINDs; the planner plans once, executes N times.\nINSERT INTO customers (name, city) VALUES\n  ('Bob', 'LA'),\n  ('Carol', 'NYC'),\n  ('Dave', 'Chicago');\n\n-- All columns in declaration order (omit column list — fragile, avoid in app code).\n-- If the table's column order changes, this INSERT breaks silently.\nINSERT INTO customers VALUES (DEFAULT, 'Eve', 'Seattle');\n\n-- Insert from a query (data migration, ETL, archiving).\nINSERT INTO archive_orders\nSELECT * FROM orders WHERE ordered_on \u003C '2023-01-01';\n\n-- Insert with default values for all columns.\nINSERT INTO events DEFAULT VALUES;\n","",[18,53,54,63,99,106,112,118,132,149,163,178,183,189,195,222,227,233,241,271,276,282],{"__ignoreMap":51},[55,56,59],"span",{"class":57,"line":58},"line",1,[55,60,62],{"class":61},"sdCPZ","-- Single row.\n",[55,64,66,70,74,77,80,83,86,90,93,96],{"class":57,"line":65},2,[55,67,69],{"class":68},"svdQ7","INSERT INTO",[55,71,73],{"class":72},"ssxIu"," customers (",[55,75,76],{"class":68},"name",[55,78,79],{"class":72},", city) ",[55,81,82],{"class":68},"VALUES",[55,84,85],{"class":72}," (",[55,87,89],{"class":88},"sJ6F3","'Alice'",[55,91,92],{"class":72},", ",[55,94,95],{"class":88},"'NYC'",[55,97,98],{"class":72},");\n",[55,100,102],{"class":57,"line":101},3,[55,103,105],{"emptyLinePlaceholder":104},true,"\n",[55,107,109],{"class":57,"line":108},4,[55,110,111],{"class":61},"-- Multi-row: one statement, multiple value tuples. Reduces parse + round-trip overhead.\n",[55,113,115],{"class":57,"line":114},5,[55,116,117],{"class":61},"-- PG sends one PARSE + N BINDs; the planner plans once, executes N times.\n",[55,119,121,123,125,127,129],{"class":57,"line":120},6,[55,122,69],{"class":68},[55,124,73],{"class":72},[55,126,76],{"class":68},[55,128,79],{"class":72},[55,130,131],{"class":68},"VALUES\n",[55,133,135,138,141,143,146],{"class":57,"line":134},7,[55,136,137],{"class":72},"  (",[55,139,140],{"class":88},"'Bob'",[55,142,92],{"class":72},[55,144,145],{"class":88},"'LA'",[55,147,148],{"class":72},"),\n",[55,150,152,154,157,159,161],{"class":57,"line":151},8,[55,153,137],{"class":72},[55,155,156],{"class":88},"'Carol'",[55,158,92],{"class":72},[55,160,95],{"class":88},[55,162,148],{"class":72},[55,164,166,168,171,173,176],{"class":57,"line":165},9,[55,167,137],{"class":72},[55,169,170],{"class":88},"'Dave'",[55,172,92],{"class":72},[55,174,175],{"class":88},"'Chicago'",[55,177,98],{"class":72},[55,179,181],{"class":57,"line":180},10,[55,182,105],{"emptyLinePlaceholder":104},[55,184,186],{"class":57,"line":185},11,[55,187,188],{"class":61},"-- All columns in declaration order (omit column list — fragile, avoid in app code).\n",[55,190,192],{"class":57,"line":191},12,[55,193,194],{"class":61},"-- If the table's column order changes, this INSERT breaks silently.\n",[55,196,198,200,203,205,207,210,212,215,217,220],{"class":57,"line":197},13,[55,199,69],{"class":68},[55,201,202],{"class":72}," customers ",[55,204,82],{"class":68},[55,206,85],{"class":72},[55,208,209],{"class":68},"DEFAULT",[55,211,92],{"class":72},[55,213,214],{"class":88},"'Eve'",[55,216,92],{"class":72},[55,218,219],{"class":88},"'Seattle'",[55,221,98],{"class":72},[55,223,225],{"class":57,"line":224},14,[55,226,105],{"emptyLinePlaceholder":104},[55,228,230],{"class":57,"line":229},15,[55,231,232],{"class":61},"-- Insert from a query (data migration, ETL, archiving).\n",[55,234,236,238],{"class":57,"line":235},16,[55,237,69],{"class":68},[55,239,240],{"class":72}," archive_orders\n",[55,242,244,247,250,253,256,259,262,265,268],{"class":57,"line":243},17,[55,245,246],{"class":68},"SELECT",[55,248,249],{"class":68}," *",[55,251,252],{"class":68}," FROM",[55,254,255],{"class":72}," orders ",[55,257,258],{"class":68},"WHERE",[55,260,261],{"class":72}," ordered_on ",[55,263,264],{"class":68},"\u003C",[55,266,267],{"class":88}," '2023-01-01'",[55,269,270],{"class":72},";\n",[55,272,274],{"class":57,"line":273},18,[55,275,105],{"emptyLinePlaceholder":104},[55,277,279],{"class":57,"line":278},19,[55,280,281],{"class":61},"-- Insert with default values for all columns.\n",[55,283,285,287,290,292,295],{"class":57,"line":284},20,[55,286,69],{"class":68},[55,288,289],{"class":72}," events ",[55,291,209],{"class":68},[55,293,294],{"class":68}," VALUES",[55,296,270],{"class":72},[298,299,301],"h3",{"id":300},"omitting-columns-with-defaults","Omitting Columns with Defaults",[303,304,305,312,315],"ul",{},[306,307,308,309,311],"li",{},"Column has ",[18,310,209],{}," and is omitted → default value is used.",[306,313,314],{},"Column is nullable with no default and omitted → NULL is stored.",[306,316,317,318,321],{},"Column is ",[18,319,320],{},"NOT NULL"," with no default and omitted → insert fails.",[298,323,325],{"id":324},"bulk-load-copy","Bulk Load: COPY",[43,327,328],{"language":45},[47,329,331],{"className":49,"code":330,"language":45,"meta":51,"style":51},"-- For large loads (100K+ rows), COPY is 10-100x faster than INSERT.\n-- COPY streams rows directly into the table, bypassing most per-row overhead.\nCOPY customers (name, city) FROM '\u002Fpath\u002Fto\u002Fcustomers.csv' WITH (FORMAT csv, HEADER true);\n\n-- COPY from stdin (application drivers support this):\nCOPY customers (name, city) FROM STDIN WITH (FORMAT csv);\n-- Application sends rows terminated by \\n, then \\. to end.\n\n-- For bulk INSERT from application code: batch 100-1000 rows per statement\n-- to reduce network round-trips and parse overhead.\n",[18,332,333,338,343,366,370,375,396,401,405,410],{"__ignoreMap":51},[55,334,335],{"class":57,"line":58},[55,336,337],{"class":61},"-- For large loads (100K+ rows), COPY is 10-100x faster than INSERT.\n",[55,339,340],{"class":57,"line":65},[55,341,342],{"class":61},"-- COPY streams rows directly into the table, bypassing most per-row overhead.\n",[55,344,345,348,350,352,354,357,360,363],{"class":57,"line":101},[55,346,347],{"class":68},"COPY",[55,349,73],{"class":72},[55,351,76],{"class":68},[55,353,79],{"class":72},[55,355,356],{"class":68},"FROM",[55,358,359],{"class":88}," '\u002Fpath\u002Fto\u002Fcustomers.csv'",[55,361,362],{"class":68}," WITH",[55,364,365],{"class":72}," (FORMAT csv, HEADER true);\n",[55,367,368],{"class":57,"line":108},[55,369,105],{"emptyLinePlaceholder":104},[55,371,372],{"class":57,"line":114},[55,373,374],{"class":61},"-- COPY from stdin (application drivers support this):\n",[55,376,377,379,381,383,385,387,390,393],{"class":57,"line":120},[55,378,347],{"class":68},[55,380,73],{"class":72},[55,382,76],{"class":68},[55,384,79],{"class":72},[55,386,356],{"class":68},[55,388,389],{"class":72}," STDIN ",[55,391,392],{"class":68},"WITH",[55,394,395],{"class":72}," (FORMAT csv);\n",[55,397,398],{"class":57,"line":134},[55,399,400],{"class":61},"-- Application sends rows terminated by \\n, then \\. to end.\n",[55,402,403],{"class":57,"line":151},[55,404,105],{"emptyLinePlaceholder":104},[55,406,407],{"class":57,"line":165},[55,408,409],{"class":61},"-- For bulk INSERT from application code: batch 100-1000 rows per statement\n",[55,411,412],{"class":57,"line":180},[55,413,414],{"class":61},"-- to reduce network round-trips and parse overhead.\n",[39,416,418],{"id":417},"insert-on-conflict-upsert","INSERT ... ON CONFLICT (Upsert)",[14,420,421],{},"PostgreSQL's upsert — insert, or if a conflict occurs, update (or do nothing). This is the foundation of idempotent ingestion.",[43,423,424],{"language":45},[47,425,427],{"className":49,"code":426,"language":45,"meta":51,"style":51},"-- DO UPDATE: on conflict on the email unique constraint, update the name.\n-- EXCLUDED is the pseudo-table of \"the row that was proposed for insertion.\"\nINSERT INTO users (email, name) VALUES ('a@x.com', 'Alice')\nON CONFLICT (email)\nDO UPDATE SET name = EXCLUDED.name;       -- overwrite with the new value\n\n-- DO NOTHING: ignore the duplicate (silent skip, no error).\nINSERT INTO subscribers (email) VALUES ('a@x.com')\nON CONFLICT (email) DO NOTHING;\n\n-- Conflict on a specific constraint by name (not column):\nINSERT INTO users (email, name) VALUES ('a@x.com', 'Alice')\nON CONFLICT ON CONSTRAINT users_email_key\nDO UPDATE SET name = EXCLUDED.name;\n\n-- Conflict on composite unique key:\nINSERT INTO daily_stats (day, metric, value) VALUES ('2024-01-01', 'sales', 100)\nON CONFLICT (day, metric)\nDO UPDATE SET value = EXCLUDED.value;\n\n-- Conditional update: only update if the new value is different.\n-- The WHERE clause on DO UPDATE acts as a guard — skip the update if it's false.\nINSERT INTO users (email, name) VALUES ('a@x.com', 'Alice')\nON CONFLICT (email)\nDO UPDATE SET name = EXCLUDED.name\nWHERE users.name IS DISTINCT FROM EXCLUDED.name;\n-- IS DISTINCT FROM handles NULL safely (NULL IS DISTINCT FROM NULL = false).\n",[18,428,429,434,439,465,473,504,508,513,528,535,539,544,566,581,601,605,610,647,659,680,684,690,696,719,726,746,774],{"__ignoreMap":51},[55,430,431],{"class":57,"line":58},[55,432,433],{"class":61},"-- DO UPDATE: on conflict on the email unique constraint, update the name.\n",[55,435,436],{"class":57,"line":65},[55,437,438],{"class":61},"-- EXCLUDED is the pseudo-table of \"the row that was proposed for insertion.\"\n",[55,440,441,443,446,448,451,453,455,458,460,462],{"class":57,"line":101},[55,442,69],{"class":68},[55,444,445],{"class":72}," users (email, ",[55,447,76],{"class":68},[55,449,450],{"class":72},") ",[55,452,82],{"class":68},[55,454,85],{"class":72},[55,456,457],{"class":88},"'a@x.com'",[55,459,92],{"class":72},[55,461,89],{"class":88},[55,463,464],{"class":72},")\n",[55,466,467,470],{"class":57,"line":108},[55,468,469],{"class":68},"ON",[55,471,472],{"class":72}," CONFLICT (email)\n",[55,474,475,478,480,483,486,489,493,496,498,501],{"class":57,"line":114},[55,476,477],{"class":72},"DO ",[55,479,32],{"class":68},[55,481,482],{"class":68}," SET",[55,484,485],{"class":68}," name",[55,487,488],{"class":68}," =",[55,490,492],{"class":491},"snvgF"," EXCLUDED",[55,494,495],{"class":72},".",[55,497,76],{"class":491},[55,499,500],{"class":72},";       ",[55,502,503],{"class":61},"-- overwrite with the new value\n",[55,505,506],{"class":57,"line":120},[55,507,105],{"emptyLinePlaceholder":104},[55,509,510],{"class":57,"line":134},[55,511,512],{"class":61},"-- DO NOTHING: ignore the duplicate (silent skip, no error).\n",[55,514,515,517,520,522,524,526],{"class":57,"line":151},[55,516,69],{"class":68},[55,518,519],{"class":72}," subscribers (email) ",[55,521,82],{"class":68},[55,523,85],{"class":72},[55,525,457],{"class":88},[55,527,464],{"class":72},[55,529,530,532],{"class":57,"line":165},[55,531,469],{"class":68},[55,533,534],{"class":72}," CONFLICT (email) DO NOTHING;\n",[55,536,537],{"class":57,"line":180},[55,538,105],{"emptyLinePlaceholder":104},[55,540,541],{"class":57,"line":185},[55,542,543],{"class":61},"-- Conflict on a specific constraint by name (not column):\n",[55,545,546,548,550,552,554,556,558,560,562,564],{"class":57,"line":191},[55,547,69],{"class":68},[55,549,445],{"class":72},[55,551,76],{"class":68},[55,553,450],{"class":72},[55,555,82],{"class":68},[55,557,85],{"class":72},[55,559,457],{"class":88},[55,561,92],{"class":72},[55,563,89],{"class":88},[55,565,464],{"class":72},[55,567,568,570,573,575,578],{"class":57,"line":197},[55,569,469],{"class":68},[55,571,572],{"class":72}," CONFLICT ",[55,574,469],{"class":68},[55,576,577],{"class":68}," CONSTRAINT",[55,579,580],{"class":72}," users_email_key\n",[55,582,583,585,587,589,591,593,595,597,599],{"class":57,"line":224},[55,584,477],{"class":72},[55,586,32],{"class":68},[55,588,482],{"class":68},[55,590,485],{"class":68},[55,592,488],{"class":68},[55,594,492],{"class":491},[55,596,495],{"class":72},[55,598,76],{"class":491},[55,600,270],{"class":72},[55,602,603],{"class":57,"line":229},[55,604,105],{"emptyLinePlaceholder":104},[55,606,607],{"class":57,"line":235},[55,608,609],{"class":61},"-- Conflict on composite unique key:\n",[55,611,612,614,617,620,623,626,628,630,632,635,637,640,642,645],{"class":57,"line":243},[55,613,69],{"class":68},[55,615,616],{"class":72}," daily_stats (",[55,618,619],{"class":68},"day",[55,621,622],{"class":72},", metric, ",[55,624,625],{"class":68},"value",[55,627,450],{"class":72},[55,629,82],{"class":68},[55,631,85],{"class":72},[55,633,634],{"class":88},"'2024-01-01'",[55,636,92],{"class":72},[55,638,639],{"class":88},"'sales'",[55,641,92],{"class":72},[55,643,644],{"class":491},"100",[55,646,464],{"class":72},[55,648,649,651,654,656],{"class":57,"line":273},[55,650,469],{"class":68},[55,652,653],{"class":72}," CONFLICT (",[55,655,619],{"class":68},[55,657,658],{"class":72},", metric)\n",[55,660,661,663,665,667,670,672,674,676,678],{"class":57,"line":278},[55,662,477],{"class":72},[55,664,32],{"class":68},[55,666,482],{"class":68},[55,668,669],{"class":68}," value",[55,671,488],{"class":68},[55,673,492],{"class":491},[55,675,495],{"class":72},[55,677,625],{"class":491},[55,679,270],{"class":72},[55,681,682],{"class":57,"line":284},[55,683,105],{"emptyLinePlaceholder":104},[55,685,687],{"class":57,"line":686},21,[55,688,689],{"class":61},"-- Conditional update: only update if the new value is different.\n",[55,691,693],{"class":57,"line":692},22,[55,694,695],{"class":61},"-- The WHERE clause on DO UPDATE acts as a guard — skip the update if it's false.\n",[55,697,699,701,703,705,707,709,711,713,715,717],{"class":57,"line":698},23,[55,700,69],{"class":68},[55,702,445],{"class":72},[55,704,76],{"class":68},[55,706,450],{"class":72},[55,708,82],{"class":68},[55,710,85],{"class":72},[55,712,457],{"class":88},[55,714,92],{"class":72},[55,716,89],{"class":88},[55,718,464],{"class":72},[55,720,722,724],{"class":57,"line":721},24,[55,723,469],{"class":68},[55,725,472],{"class":72},[55,727,729,731,733,735,737,739,741,743],{"class":57,"line":728},25,[55,730,477],{"class":72},[55,732,32],{"class":68},[55,734,482],{"class":68},[55,736,485],{"class":68},[55,738,488],{"class":68},[55,740,492],{"class":491},[55,742,495],{"class":72},[55,744,745],{"class":491},"name\n",[55,747,749,751,754,756,758,761,764,766,768,770,772],{"class":57,"line":748},26,[55,750,258],{"class":68},[55,752,753],{"class":491}," users",[55,755,495],{"class":72},[55,757,76],{"class":491},[55,759,760],{"class":68}," IS",[55,762,763],{"class":68}," DISTINCT",[55,765,252],{"class":68},[55,767,492],{"class":491},[55,769,495],{"class":72},[55,771,76],{"class":491},[55,773,270],{"class":72},[55,775,777],{"class":57,"line":776},27,[55,778,779],{"class":61},"-- IS DISTINCT FROM handles NULL safely (NULL IS DISTINCT FROM NULL = false).\n",[14,781,782,786,787,790,791,794],{},[783,784,785],"strong",{},"Dialect differences",": MySQL uses ",[18,788,789],{},"INSERT ... ON DUPLICATE KEY UPDATE"," (similar but less flexible — no conflict target specification). SQLite uses ",[18,792,793],{},"INSERT ... ON CONFLICT(col) DO UPDATE\u002FNOTHING"," (same syntax as PG).",[298,796,798,801],{"id":797},"excluded-vs-table-name",[18,799,800],{},"EXCLUDED"," vs Table Name",[14,803,804,805,808],{},"In ",[18,806,807],{},"DO UPDATE SET col = ...",":",[303,810,811,824],{},[306,812,813,816,817,820,821,823],{},[18,814,815],{},"EXCLUDED.col"," — the ",[783,818,819],{},"new"," value (from the ",[18,822,20],{}," that was proposed).",[306,825,826,816,829,832],{},[18,827,828],{},"users.col",[783,830,831],{},"existing"," value (the row that won the conflict).",[43,834,835],{"language":45},[47,836,838],{"className":49,"code":837,"language":45,"meta":51,"style":51},"-- Overwrite: set name to the new value from the INSERT.\nDO UPDATE SET name = EXCLUDED.name;\n\n-- Increment: add the new value to the existing value.\nDO UPDATE SET count = counters.value + EXCLUDED.value;\n\n-- Conditional: only update if the new value is larger.\nDO UPDATE SET value = EXCLUDED.value\nWHERE EXCLUDED.value > stats.value;\n",[18,839,840,845,865,869,874,906,910,915,934],{"__ignoreMap":51},[55,841,842],{"class":57,"line":58},[55,843,844],{"class":61},"-- Overwrite: set name to the new value from the INSERT.\n",[55,846,847,849,851,853,855,857,859,861,863],{"class":57,"line":65},[55,848,477],{"class":72},[55,850,32],{"class":68},[55,852,482],{"class":68},[55,854,485],{"class":68},[55,856,488],{"class":68},[55,858,492],{"class":491},[55,860,495],{"class":72},[55,862,76],{"class":491},[55,864,270],{"class":72},[55,866,867],{"class":57,"line":101},[55,868,105],{"emptyLinePlaceholder":104},[55,870,871],{"class":57,"line":108},[55,872,873],{"class":61},"-- Increment: add the new value to the existing value.\n",[55,875,876,878,880,882,885,888,891,893,895,898,900,902,904],{"class":57,"line":114},[55,877,477],{"class":72},[55,879,32],{"class":68},[55,881,482],{"class":68},[55,883,884],{"class":72}," count ",[55,886,887],{"class":68},"=",[55,889,890],{"class":491}," counters",[55,892,495],{"class":72},[55,894,625],{"class":491},[55,896,897],{"class":68}," +",[55,899,492],{"class":491},[55,901,495],{"class":72},[55,903,625],{"class":491},[55,905,270],{"class":72},[55,907,908],{"class":57,"line":120},[55,909,105],{"emptyLinePlaceholder":104},[55,911,912],{"class":57,"line":134},[55,913,914],{"class":61},"-- Conditional: only update if the new value is larger.\n",[55,916,917,919,921,923,925,927,929,931],{"class":57,"line":151},[55,918,477],{"class":72},[55,920,32],{"class":68},[55,922,482],{"class":68},[55,924,669],{"class":68},[55,926,488],{"class":68},[55,928,492],{"class":491},[55,930,495],{"class":72},[55,932,933],{"class":491},"value\n",[55,935,936,938,940,942,944,947,950,952,954],{"class":57,"line":165},[55,937,258],{"class":68},[55,939,492],{"class":491},[55,941,495],{"class":72},[55,943,625],{"class":491},[55,945,946],{"class":68}," >",[55,948,949],{"class":491}," stats",[55,951,495],{"class":72},[55,953,625],{"class":491},[55,955,270],{"class":72},[39,957,28],{"id":958},"returning",[14,960,961],{},"PostgreSQL (and MySQL 8.0+, SQLite 3.35+) can return the inserted\u002Fupdated\u002Fdeleted rows — no second query needed.",[43,963,964],{"language":45},[47,965,967],{"className":49,"code":966,"language":45,"meta":51,"style":51},"-- Get the auto-generated id back without a second query.\n-- RETURNING is atomic with the INSERT — no race condition, no currval() fragility.\nINSERT INTO customers (name, city) VALUES ('Frank', 'Boston')\nRETURNING id, name;\n\n-- Multiple rows: returns one row per inserted tuple.\nINSERT INTO orders (customer_id, amount) VALUES (1, 50), (1, 75)\nRETURNING id, amount;\n\n-- UPDATE returning the changed rows.\nUPDATE orders SET status = 'shipped' WHERE status = 'pending'\nRETURNING id, status;\n\n-- DELETE returning what was removed (audit trail).\nDELETE FROM sessions WHERE expires_at \u003C now()\nRETURNING id, user_id;\n\n-- Upsert with RETURNING: get the id whether it was an insert or a conflict.\n-- DO UPDATE is required (DO NOTHING returns nothing on conflict).\nINSERT INTO users (email, name) VALUES ('a@x.com', 'Alice')\nON CONFLICT (email) DO UPDATE SET name = users.name  -- no-op update\nRETURNING id;\n-- Returns the id of the existing row (if conflict) or the new row (if inserted).\n",[18,968,969,974,979,1003,1012,1016,1021,1052,1057,1061,1066,1093,1102,1106,1111,1133,1138,1142,1147,1152,1174,1198,1203],{"__ignoreMap":51},[55,970,971],{"class":57,"line":58},[55,972,973],{"class":61},"-- Get the auto-generated id back without a second query.\n",[55,975,976],{"class":57,"line":65},[55,977,978],{"class":61},"-- RETURNING is atomic with the INSERT — no race condition, no currval() fragility.\n",[55,980,981,983,985,987,989,991,993,996,998,1001],{"class":57,"line":101},[55,982,69],{"class":68},[55,984,73],{"class":72},[55,986,76],{"class":68},[55,988,79],{"class":72},[55,990,82],{"class":68},[55,992,85],{"class":72},[55,994,995],{"class":88},"'Frank'",[55,997,92],{"class":72},[55,999,1000],{"class":88},"'Boston'",[55,1002,464],{"class":72},[55,1004,1005,1008,1010],{"class":57,"line":108},[55,1006,1007],{"class":72},"RETURNING id, ",[55,1009,76],{"class":68},[55,1011,270],{"class":72},[55,1013,1014],{"class":57,"line":114},[55,1015,105],{"emptyLinePlaceholder":104},[55,1017,1018],{"class":57,"line":120},[55,1019,1020],{"class":61},"-- Multiple rows: returns one row per inserted tuple.\n",[55,1022,1023,1025,1028,1030,1032,1035,1037,1040,1043,1045,1047,1050],{"class":57,"line":134},[55,1024,69],{"class":68},[55,1026,1027],{"class":72}," orders (customer_id, amount) ",[55,1029,82],{"class":68},[55,1031,85],{"class":72},[55,1033,1034],{"class":491},"1",[55,1036,92],{"class":72},[55,1038,1039],{"class":491},"50",[55,1041,1042],{"class":72},"), (",[55,1044,1034],{"class":491},[55,1046,92],{"class":72},[55,1048,1049],{"class":491},"75",[55,1051,464],{"class":72},[55,1053,1054],{"class":57,"line":151},[55,1055,1056],{"class":72},"RETURNING id, amount;\n",[55,1058,1059],{"class":57,"line":165},[55,1060,105],{"emptyLinePlaceholder":104},[55,1062,1063],{"class":57,"line":180},[55,1064,1065],{"class":61},"-- UPDATE returning the changed rows.\n",[55,1067,1068,1070,1072,1075,1078,1080,1083,1086,1088,1090],{"class":57,"line":185},[55,1069,32],{"class":68},[55,1071,255],{"class":72},[55,1073,1074],{"class":68},"SET",[55,1076,1077],{"class":68}," status",[55,1079,488],{"class":68},[55,1081,1082],{"class":88}," 'shipped'",[55,1084,1085],{"class":68}," WHERE",[55,1087,1077],{"class":68},[55,1089,488],{"class":68},[55,1091,1092],{"class":88}," 'pending'\n",[55,1094,1095,1097,1100],{"class":57,"line":191},[55,1096,1007],{"class":72},[55,1098,1099],{"class":68},"status",[55,1101,270],{"class":72},[55,1103,1104],{"class":57,"line":197},[55,1105,105],{"emptyLinePlaceholder":104},[55,1107,1108],{"class":57,"line":224},[55,1109,1110],{"class":61},"-- DELETE returning what was removed (audit trail).\n",[55,1112,1113,1115,1117,1120,1122,1125,1127,1130],{"class":57,"line":229},[55,1114,36],{"class":68},[55,1116,252],{"class":68},[55,1118,1119],{"class":68}," sessions",[55,1121,1085],{"class":68},[55,1123,1124],{"class":72}," expires_at ",[55,1126,264],{"class":68},[55,1128,1129],{"class":68}," now",[55,1131,1132],{"class":72},"()\n",[55,1134,1135],{"class":57,"line":235},[55,1136,1137],{"class":72},"RETURNING id, user_id;\n",[55,1139,1140],{"class":57,"line":243},[55,1141,105],{"emptyLinePlaceholder":104},[55,1143,1144],{"class":57,"line":273},[55,1145,1146],{"class":61},"-- Upsert with RETURNING: get the id whether it was an insert or a conflict.\n",[55,1148,1149],{"class":57,"line":278},[55,1150,1151],{"class":61},"-- DO UPDATE is required (DO NOTHING returns nothing on conflict).\n",[55,1153,1154,1156,1158,1160,1162,1164,1166,1168,1170,1172],{"class":57,"line":284},[55,1155,69],{"class":68},[55,1157,445],{"class":72},[55,1159,76],{"class":68},[55,1161,450],{"class":72},[55,1163,82],{"class":68},[55,1165,85],{"class":72},[55,1167,457],{"class":88},[55,1169,92],{"class":72},[55,1171,89],{"class":88},[55,1173,464],{"class":72},[55,1175,1176,1178,1181,1183,1185,1187,1189,1191,1193,1195],{"class":57,"line":686},[55,1177,469],{"class":68},[55,1179,1180],{"class":72}," CONFLICT (email) DO ",[55,1182,32],{"class":68},[55,1184,482],{"class":68},[55,1186,485],{"class":68},[55,1188,488],{"class":68},[55,1190,753],{"class":491},[55,1192,495],{"class":72},[55,1194,76],{"class":491},[55,1196,1197],{"class":61},"  -- no-op update\n",[55,1199,1200],{"class":57,"line":692},[55,1201,1202],{"class":72},"RETURNING id;\n",[55,1204,1205],{"class":57,"line":698},[55,1206,1207],{"class":61},"-- Returns the id of the existing row (if conflict) or the new row (if inserted).\n",[14,1209,1210,1212],{},[18,1211,28],{}," is invaluable for:",[303,1214,1215,1232,1238],{},[306,1216,1217,1218,33,1221,1224,1225,1228,1229,495],{},"Getting auto-generated IDs (",[18,1219,1220],{},"SERIAL",[18,1222,1223],{},"IDENTITY",") after insert — atomic, no ",[18,1226,1227],{},"currval()"," or ",[18,1230,1231],{},"last_insert_id()",[306,1233,1234,1235,1237],{},"Building audit trails (delete + ",[18,1236,28],{}," + insert to archive in one statement).",[306,1239,1240,1241,495],{},"Confirming what changed without a follow-up ",[18,1242,246],{},[39,1244,32],{"id":1245},"update",[43,1247,1248],{"language":45},[47,1249,1251],{"className":49,"code":1250,"language":45,"meta":51,"style":51},"-- Update specific rows.\nUPDATE orders SET status = 'shipped' WHERE id = 42;\n\n-- Update multiple columns.\nUPDATE customers SET city = 'LA', name = 'Bob' WHERE id = 2;\n\n-- Atomic read-modify-write: the expression is evaluated against the current row value.\n-- Safe at any isolation level — no lost update because the DB reads + writes in one step.\nUPDATE products SET price = price * 1.1 WHERE category = 'electronics';\n\n-- Update based on a join (PostgreSQL FROM clause).\n-- The target table (orders) is NOT re-listed in FROM — it's already the target.\nUPDATE orders o\nSET status = c.status\nFROM customers c\nWHERE o.customer_id = c.id AND c.status = 'banned';\n\n-- Update with a subquery (portable, standard SQL).\nUPDATE orders\nSET amount = (SELECT AVG(amount) FROM orders WHERE customer_id = 1)\nWHERE id = 99;\n",[18,1252,1253,1258,1284,1288,1293,1329,1333,1338,1343,1381,1385,1390,1395,1402,1418,1425,1462,1466,1471,1478,1512],{"__ignoreMap":51},[55,1254,1255],{"class":57,"line":58},[55,1256,1257],{"class":61},"-- Update specific rows.\n",[55,1259,1260,1262,1264,1266,1268,1270,1272,1274,1277,1279,1282],{"class":57,"line":65},[55,1261,32],{"class":68},[55,1263,255],{"class":72},[55,1265,1074],{"class":68},[55,1267,1077],{"class":68},[55,1269,488],{"class":68},[55,1271,1082],{"class":88},[55,1273,1085],{"class":68},[55,1275,1276],{"class":72}," id ",[55,1278,887],{"class":68},[55,1280,1281],{"class":491}," 42",[55,1283,270],{"class":72},[55,1285,1286],{"class":57,"line":101},[55,1287,105],{"emptyLinePlaceholder":104},[55,1289,1290],{"class":57,"line":108},[55,1291,1292],{"class":61},"-- Update multiple columns.\n",[55,1294,1295,1297,1299,1301,1304,1306,1309,1311,1313,1315,1318,1320,1322,1324,1327],{"class":57,"line":114},[55,1296,32],{"class":68},[55,1298,202],{"class":72},[55,1300,1074],{"class":68},[55,1302,1303],{"class":72}," city ",[55,1305,887],{"class":68},[55,1307,1308],{"class":88}," 'LA'",[55,1310,92],{"class":72},[55,1312,76],{"class":68},[55,1314,488],{"class":68},[55,1316,1317],{"class":88}," 'Bob'",[55,1319,1085],{"class":68},[55,1321,1276],{"class":72},[55,1323,887],{"class":68},[55,1325,1326],{"class":491}," 2",[55,1328,270],{"class":72},[55,1330,1331],{"class":57,"line":120},[55,1332,105],{"emptyLinePlaceholder":104},[55,1334,1335],{"class":57,"line":134},[55,1336,1337],{"class":61},"-- Atomic read-modify-write: the expression is evaluated against the current row value.\n",[55,1339,1340],{"class":57,"line":151},[55,1341,1342],{"class":61},"-- Safe at any isolation level — no lost update because the DB reads + writes in one step.\n",[55,1344,1345,1347,1350,1352,1355,1357,1359,1362,1365,1367,1369,1371,1374,1376,1379],{"class":57,"line":165},[55,1346,32],{"class":68},[55,1348,1349],{"class":72}," products ",[55,1351,1074],{"class":68},[55,1353,1354],{"class":72}," price ",[55,1356,887],{"class":68},[55,1358,1354],{"class":72},[55,1360,1361],{"class":68},"*",[55,1363,1364],{"class":491}," 1",[55,1366,495],{"class":72},[55,1368,1034],{"class":491},[55,1370,1085],{"class":68},[55,1372,1373],{"class":72}," category ",[55,1375,887],{"class":68},[55,1377,1378],{"class":88}," 'electronics'",[55,1380,270],{"class":72},[55,1382,1383],{"class":57,"line":180},[55,1384,105],{"emptyLinePlaceholder":104},[55,1386,1387],{"class":57,"line":185},[55,1388,1389],{"class":61},"-- Update based on a join (PostgreSQL FROM clause).\n",[55,1391,1392],{"class":57,"line":191},[55,1393,1394],{"class":61},"-- The target table (orders) is NOT re-listed in FROM — it's already the target.\n",[55,1396,1397,1399],{"class":57,"line":197},[55,1398,32],{"class":68},[55,1400,1401],{"class":72}," orders o\n",[55,1403,1404,1406,1408,1410,1413,1415],{"class":57,"line":224},[55,1405,1074],{"class":68},[55,1407,1077],{"class":68},[55,1409,488],{"class":68},[55,1411,1412],{"class":491}," c",[55,1414,495],{"class":72},[55,1416,1417],{"class":491},"status\n",[55,1419,1420,1422],{"class":57,"line":229},[55,1421,356],{"class":68},[55,1423,1424],{"class":72}," customers c\n",[55,1426,1427,1429,1432,1434,1437,1439,1441,1443,1446,1449,1451,1453,1455,1457,1460],{"class":57,"line":235},[55,1428,258],{"class":68},[55,1430,1431],{"class":491}," o",[55,1433,495],{"class":72},[55,1435,1436],{"class":491},"customer_id",[55,1438,488],{"class":68},[55,1440,1412],{"class":491},[55,1442,495],{"class":72},[55,1444,1445],{"class":491},"id",[55,1447,1448],{"class":68}," AND",[55,1450,1412],{"class":491},[55,1452,495],{"class":72},[55,1454,1099],{"class":491},[55,1456,488],{"class":68},[55,1458,1459],{"class":88}," 'banned'",[55,1461,270],{"class":72},[55,1463,1464],{"class":57,"line":243},[55,1465,105],{"emptyLinePlaceholder":104},[55,1467,1468],{"class":57,"line":273},[55,1469,1470],{"class":61},"-- Update with a subquery (portable, standard SQL).\n",[55,1472,1473,1475],{"class":57,"line":278},[55,1474,32],{"class":68},[55,1476,1477],{"class":72}," orders\n",[55,1479,1480,1482,1485,1487,1489,1491,1494,1497,1499,1501,1503,1506,1508,1510],{"class":57,"line":284},[55,1481,1074],{"class":68},[55,1483,1484],{"class":72}," amount ",[55,1486,887],{"class":68},[55,1488,85],{"class":72},[55,1490,246],{"class":68},[55,1492,1493],{"class":491}," AVG",[55,1495,1496],{"class":72},"(amount) ",[55,1498,356],{"class":68},[55,1500,255],{"class":72},[55,1502,258],{"class":68},[55,1504,1505],{"class":72}," customer_id ",[55,1507,887],{"class":68},[55,1509,1364],{"class":491},[55,1511,464],{"class":72},[55,1513,1514,1516,1518,1520,1523],{"class":57,"line":686},[55,1515,258],{"class":68},[55,1517,1276],{"class":72},[55,1519,887],{"class":68},[55,1521,1522],{"class":491}," 99",[55,1524,270],{"class":72},[298,1526,1528],{"id":1527},"update-without-where-the-classic-footgun","UPDATE without WHERE — The Classic Footgun",[43,1530,1531],{"language":45},[47,1532,1534],{"className":49,"code":1533,"language":45,"meta":51,"style":51},"-- ❌ This updates EVERY row in the table. Always include WHERE.\nUPDATE orders SET status = 'shipped';  -- no WHERE → all rows updated\n\n-- ✅ Safety check: run SELECT with the same WHERE first.\nSELECT count(*) FROM orders WHERE status = 'pending' AND id = 42;\n-- Confirm the count is what you expect, then:\nUPDATE orders SET status = 'shipped' WHERE status = 'pending' AND id = 42;\n",[18,1535,1536,1541,1561,1565,1570,1607,1612],{"__ignoreMap":51},[55,1537,1538],{"class":57,"line":58},[55,1539,1540],{"class":61},"-- ❌ This updates EVERY row in the table. Always include WHERE.\n",[55,1542,1543,1545,1547,1549,1551,1553,1555,1558],{"class":57,"line":65},[55,1544,32],{"class":68},[55,1546,255],{"class":72},[55,1548,1074],{"class":68},[55,1550,1077],{"class":68},[55,1552,488],{"class":68},[55,1554,1082],{"class":88},[55,1556,1557],{"class":72},";  ",[55,1559,1560],{"class":61},"-- no WHERE → all rows updated\n",[55,1562,1563],{"class":57,"line":101},[55,1564,105],{"emptyLinePlaceholder":104},[55,1566,1567],{"class":57,"line":108},[55,1568,1569],{"class":61},"-- ✅ Safety check: run SELECT with the same WHERE first.\n",[55,1571,1572,1574,1577,1580,1582,1584,1586,1588,1590,1592,1594,1597,1599,1601,1603,1605],{"class":57,"line":114},[55,1573,246],{"class":68},[55,1575,1576],{"class":491}," count",[55,1578,1579],{"class":72},"(",[55,1581,1361],{"class":68},[55,1583,450],{"class":72},[55,1585,356],{"class":68},[55,1587,255],{"class":72},[55,1589,258],{"class":68},[55,1591,1077],{"class":68},[55,1593,488],{"class":68},[55,1595,1596],{"class":88}," 'pending'",[55,1598,1448],{"class":68},[55,1600,1276],{"class":72},[55,1602,887],{"class":68},[55,1604,1281],{"class":491},[55,1606,270],{"class":72},[55,1608,1609],{"class":57,"line":120},[55,1610,1611],{"class":61},"-- Confirm the count is what you expect, then:\n",[55,1613,1614,1616,1618,1620,1622,1624,1626,1628,1630,1632,1634,1636,1638,1640,1642],{"class":57,"line":134},[55,1615,32],{"class":68},[55,1617,255],{"class":72},[55,1619,1074],{"class":68},[55,1621,1077],{"class":68},[55,1623,488],{"class":68},[55,1625,1082],{"class":88},[55,1627,1085],{"class":68},[55,1629,1077],{"class":68},[55,1631,488],{"class":68},[55,1633,1596],{"class":88},[55,1635,1448],{"class":68},[55,1637,1276],{"class":72},[55,1639,887],{"class":68},[55,1641,1281],{"class":491},[55,1643,270],{"class":72},[298,1645,1647],{"id":1646},"update-from-join-based-updates-portability","UPDATE FROM — Join-Based Updates (Portability)",[43,1649,1650],{"language":45},[47,1651,1653],{"className":49,"code":1652,"language":45,"meta":51,"style":51},"-- PostgreSQL: UPDATE ... FROM (the target table is NOT in FROM).\nUPDATE orders o\nSET status = c.status\nFROM customers c\nWHERE o.customer_id = c.id AND c.status = 'banned';\n\n-- MySQL: UPDATE ... JOIN.\nUPDATE orders o JOIN customers c ON o.customer_id = c.id\nSET o.status = c.status\nWHERE c.status = 'banned';\n\n-- Standard SQL (portable): correlated subquery.\nUPDATE orders o\nSET status = (SELECT c.status FROM customers c WHERE c.id = o.customer_id)\nWHERE EXISTS (SELECT 1 FROM customers c WHERE c.id = o.customer_id AND c.status = 'banned');\n",[18,1654,1655,1660,1666,1680,1686,1718,1722,1727,1757,1775,1791,1795,1800,1806,1846],{"__ignoreMap":51},[55,1656,1657],{"class":57,"line":58},[55,1658,1659],{"class":61},"-- PostgreSQL: UPDATE ... FROM (the target table is NOT in FROM).\n",[55,1661,1662,1664],{"class":57,"line":65},[55,1663,32],{"class":68},[55,1665,1401],{"class":72},[55,1667,1668,1670,1672,1674,1676,1678],{"class":57,"line":101},[55,1669,1074],{"class":68},[55,1671,1077],{"class":68},[55,1673,488],{"class":68},[55,1675,1412],{"class":491},[55,1677,495],{"class":72},[55,1679,1417],{"class":491},[55,1681,1682,1684],{"class":57,"line":108},[55,1683,356],{"class":68},[55,1685,1424],{"class":72},[55,1687,1688,1690,1692,1694,1696,1698,1700,1702,1704,1706,1708,1710,1712,1714,1716],{"class":57,"line":114},[55,1689,258],{"class":68},[55,1691,1431],{"class":491},[55,1693,495],{"class":72},[55,1695,1436],{"class":491},[55,1697,488],{"class":68},[55,1699,1412],{"class":491},[55,1701,495],{"class":72},[55,1703,1445],{"class":491},[55,1705,1448],{"class":68},[55,1707,1412],{"class":491},[55,1709,495],{"class":72},[55,1711,1099],{"class":491},[55,1713,488],{"class":68},[55,1715,1459],{"class":88},[55,1717,270],{"class":72},[55,1719,1720],{"class":57,"line":120},[55,1721,105],{"emptyLinePlaceholder":104},[55,1723,1724],{"class":57,"line":134},[55,1725,1726],{"class":61},"-- MySQL: UPDATE ... JOIN.\n",[55,1728,1729,1731,1734,1737,1740,1742,1744,1746,1748,1750,1752,1754],{"class":57,"line":151},[55,1730,32],{"class":68},[55,1732,1733],{"class":72}," orders o ",[55,1735,1736],{"class":68},"JOIN",[55,1738,1739],{"class":72}," customers c ",[55,1741,469],{"class":68},[55,1743,1431],{"class":491},[55,1745,495],{"class":72},[55,1747,1436],{"class":491},[55,1749,488],{"class":68},[55,1751,1412],{"class":491},[55,1753,495],{"class":72},[55,1755,1756],{"class":491},"id\n",[55,1758,1759,1761,1763,1765,1767,1769,1771,1773],{"class":57,"line":165},[55,1760,1074],{"class":68},[55,1762,1431],{"class":491},[55,1764,495],{"class":72},[55,1766,1099],{"class":491},[55,1768,488],{"class":68},[55,1770,1412],{"class":491},[55,1772,495],{"class":72},[55,1774,1417],{"class":491},[55,1776,1777,1779,1781,1783,1785,1787,1789],{"class":57,"line":180},[55,1778,258],{"class":68},[55,1780,1412],{"class":491},[55,1782,495],{"class":72},[55,1784,1099],{"class":491},[55,1786,488],{"class":68},[55,1788,1459],{"class":88},[55,1790,270],{"class":72},[55,1792,1793],{"class":57,"line":185},[55,1794,105],{"emptyLinePlaceholder":104},[55,1796,1797],{"class":57,"line":191},[55,1798,1799],{"class":61},"-- Standard SQL (portable): correlated subquery.\n",[55,1801,1802,1804],{"class":57,"line":197},[55,1803,32],{"class":68},[55,1805,1401],{"class":72},[55,1807,1808,1810,1812,1814,1816,1818,1820,1822,1824,1826,1828,1830,1832,1834,1836,1838,1840,1842,1844],{"class":57,"line":224},[55,1809,1074],{"class":68},[55,1811,1077],{"class":68},[55,1813,488],{"class":68},[55,1815,85],{"class":72},[55,1817,246],{"class":68},[55,1819,1412],{"class":491},[55,1821,495],{"class":72},[55,1823,1099],{"class":491},[55,1825,252],{"class":68},[55,1827,1739],{"class":72},[55,1829,258],{"class":68},[55,1831,1412],{"class":491},[55,1833,495],{"class":72},[55,1835,1445],{"class":491},[55,1837,488],{"class":68},[55,1839,1431],{"class":491},[55,1841,495],{"class":72},[55,1843,1436],{"class":491},[55,1845,464],{"class":72},[55,1847,1848,1850,1853,1855,1857,1859,1861,1863,1865,1867,1869,1871,1873,1875,1877,1879,1881,1883,1885,1887,1889,1891],{"class":57,"line":229},[55,1849,258],{"class":68},[55,1851,1852],{"class":68}," EXISTS",[55,1854,85],{"class":72},[55,1856,246],{"class":68},[55,1858,1364],{"class":491},[55,1860,252],{"class":68},[55,1862,1739],{"class":72},[55,1864,258],{"class":68},[55,1866,1412],{"class":491},[55,1868,495],{"class":72},[55,1870,1445],{"class":491},[55,1872,488],{"class":68},[55,1874,1431],{"class":491},[55,1876,495],{"class":72},[55,1878,1436],{"class":491},[55,1880,1448],{"class":68},[55,1882,1412],{"class":491},[55,1884,495],{"class":72},[55,1886,1099],{"class":491},[55,1888,488],{"class":68},[55,1890,1459],{"class":88},[55,1892,98],{"class":72},[39,1894,36],{"id":1895},"delete",[43,1897,1898],{"language":45},[47,1899,1901],{"className":49,"code":1900,"language":45,"meta":51,"style":51},"-- Delete specific rows.\nDELETE FROM orders WHERE id = 42;\n\n-- Delete all rows (slow — use TRUNCATE for whole-table).\n-- Creates N dead tuples, fires triggers, WAL per row, bloats until VACUUM.\nDELETE FROM orders;\n\n-- Delete based on a join (PostgreSQL USING).\nDELETE FROM orders o\nUSING customers c\nWHERE o.customer_id = c.id AND c.status = 'banned';\n\n-- Delete returning what was removed.\nDELETE FROM sessions WHERE expires_at \u003C now() RETURNING id, user_id;\n\n-- ⚠️ DELETE without WHERE empties the table — same footgun as UPDATE.\n",[18,1902,1903,1908,1926,1930,1935,1940,1949,1953,1958,1966,1973,2005,2009,2014,2033,2037],{"__ignoreMap":51},[55,1904,1905],{"class":57,"line":58},[55,1906,1907],{"class":61},"-- Delete specific rows.\n",[55,1909,1910,1912,1914,1916,1918,1920,1922,1924],{"class":57,"line":65},[55,1911,36],{"class":68},[55,1913,252],{"class":68},[55,1915,255],{"class":72},[55,1917,258],{"class":68},[55,1919,1276],{"class":72},[55,1921,887],{"class":68},[55,1923,1281],{"class":491},[55,1925,270],{"class":72},[55,1927,1928],{"class":57,"line":101},[55,1929,105],{"emptyLinePlaceholder":104},[55,1931,1932],{"class":57,"line":108},[55,1933,1934],{"class":61},"-- Delete all rows (slow — use TRUNCATE for whole-table).\n",[55,1936,1937],{"class":57,"line":114},[55,1938,1939],{"class":61},"-- Creates N dead tuples, fires triggers, WAL per row, bloats until VACUUM.\n",[55,1941,1942,1944,1946],{"class":57,"line":120},[55,1943,36],{"class":68},[55,1945,252],{"class":68},[55,1947,1948],{"class":72}," orders;\n",[55,1950,1951],{"class":57,"line":134},[55,1952,105],{"emptyLinePlaceholder":104},[55,1954,1955],{"class":57,"line":151},[55,1956,1957],{"class":61},"-- Delete based on a join (PostgreSQL USING).\n",[55,1959,1960,1962,1964],{"class":57,"line":165},[55,1961,36],{"class":68},[55,1963,252],{"class":68},[55,1965,1401],{"class":72},[55,1967,1968,1971],{"class":57,"line":180},[55,1969,1970],{"class":68},"USING",[55,1972,1424],{"class":72},[55,1974,1975,1977,1979,1981,1983,1985,1987,1989,1991,1993,1995,1997,1999,2001,2003],{"class":57,"line":185},[55,1976,258],{"class":68},[55,1978,1431],{"class":491},[55,1980,495],{"class":72},[55,1982,1436],{"class":491},[55,1984,488],{"class":68},[55,1986,1412],{"class":491},[55,1988,495],{"class":72},[55,1990,1445],{"class":491},[55,1992,1448],{"class":68},[55,1994,1412],{"class":491},[55,1996,495],{"class":72},[55,1998,1099],{"class":491},[55,2000,488],{"class":68},[55,2002,1459],{"class":88},[55,2004,270],{"class":72},[55,2006,2007],{"class":57,"line":191},[55,2008,105],{"emptyLinePlaceholder":104},[55,2010,2011],{"class":57,"line":197},[55,2012,2013],{"class":61},"-- Delete returning what was removed.\n",[55,2015,2016,2018,2020,2022,2024,2026,2028,2030],{"class":57,"line":224},[55,2017,36],{"class":68},[55,2019,252],{"class":68},[55,2021,1119],{"class":68},[55,2023,1085],{"class":68},[55,2025,1124],{"class":72},[55,2027,264],{"class":68},[55,2029,1129],{"class":68},[55,2031,2032],{"class":72},"() RETURNING id, user_id;\n",[55,2034,2035],{"class":57,"line":229},[55,2036,105],{"emptyLinePlaceholder":104},[55,2038,2039],{"class":57,"line":235},[55,2040,2041],{"class":61},"-- ⚠️ DELETE without WHERE empties the table — same footgun as UPDATE.\n",[298,2043,2045],{"id":2044},"delete-with-subquery-vs-using","DELETE with Subquery vs USING",[43,2047,2048],{"language":45},[47,2049,2051],{"className":49,"code":2050,"language":45,"meta":51,"style":51},"-- PostgreSQL USING: join-based delete, concise.\nDELETE FROM orders o\nUSING customers c\nWHERE o.customer_id = c.id AND c.status = 'banned';\n\n-- Standard SQL subquery (portable, but may be slower if planner can't optimize):\nDELETE FROM orders\nWHERE customer_id IN (SELECT id FROM customers WHERE status = 'banned');\n\n-- ⚠️ If multiple `c` rows match one `o` row, `o` is still deleted ONCE (not multiple times).\n--    USING is safe — no duplicate deletion.\n",[18,2052,2053,2058,2066,2072,2104,2108,2113,2121,2150,2154,2159],{"__ignoreMap":51},[55,2054,2055],{"class":57,"line":58},[55,2056,2057],{"class":61},"-- PostgreSQL USING: join-based delete, concise.\n",[55,2059,2060,2062,2064],{"class":57,"line":65},[55,2061,36],{"class":68},[55,2063,252],{"class":68},[55,2065,1401],{"class":72},[55,2067,2068,2070],{"class":57,"line":101},[55,2069,1970],{"class":68},[55,2071,1424],{"class":72},[55,2073,2074,2076,2078,2080,2082,2084,2086,2088,2090,2092,2094,2096,2098,2100,2102],{"class":57,"line":108},[55,2075,258],{"class":68},[55,2077,1431],{"class":491},[55,2079,495],{"class":72},[55,2081,1436],{"class":491},[55,2083,488],{"class":68},[55,2085,1412],{"class":491},[55,2087,495],{"class":72},[55,2089,1445],{"class":491},[55,2091,1448],{"class":68},[55,2093,1412],{"class":491},[55,2095,495],{"class":72},[55,2097,1099],{"class":491},[55,2099,488],{"class":68},[55,2101,1459],{"class":88},[55,2103,270],{"class":72},[55,2105,2106],{"class":57,"line":114},[55,2107,105],{"emptyLinePlaceholder":104},[55,2109,2110],{"class":57,"line":120},[55,2111,2112],{"class":61},"-- Standard SQL subquery (portable, but may be slower if planner can't optimize):\n",[55,2114,2115,2117,2119],{"class":57,"line":134},[55,2116,36],{"class":68},[55,2118,252],{"class":68},[55,2120,1477],{"class":72},[55,2122,2123,2125,2127,2130,2132,2134,2136,2138,2140,2142,2144,2146,2148],{"class":57,"line":151},[55,2124,258],{"class":68},[55,2126,1505],{"class":72},[55,2128,2129],{"class":68},"IN",[55,2131,85],{"class":72},[55,2133,246],{"class":68},[55,2135,1276],{"class":72},[55,2137,356],{"class":68},[55,2139,202],{"class":72},[55,2141,258],{"class":68},[55,2143,1077],{"class":68},[55,2145,488],{"class":68},[55,2147,1459],{"class":88},[55,2149,98],{"class":72},[55,2151,2152],{"class":57,"line":165},[55,2153,105],{"emptyLinePlaceholder":104},[55,2155,2156],{"class":57,"line":180},[55,2157,2158],{"class":61},"-- ⚠️ If multiple `c` rows match one `o` row, `o` is still deleted ONCE (not multiple times).\n",[55,2160,2161],{"class":57,"line":185},[55,2162,2163],{"class":61},"--    USING is safe — no duplicate deletion.\n",[298,2165,2167],{"id":2166},"delete-and-foreign-keys","DELETE and Foreign Keys",[14,2169,2170,2171,2174],{},"Deleting a row referenced by a FK triggers the ",[18,2172,2173],{},"ON DELETE"," action:",[303,2176,2177,2186,2192],{},[306,2178,2179,33,2182,2185],{},[18,2180,2181],{},"RESTRICT",[18,2183,2184],{},"NO ACTION"," — delete fails if referencing rows exist.",[306,2187,2188,2191],{},[18,2189,2190],{},"CASCADE"," — referencing rows are deleted too (propagates through the cascade chain).",[306,2193,2194,2197],{},[18,2195,2196],{},"SET NULL"," — referencing rows' FK column is set to NULL.",[14,2199,2200,2201,2203],{},"Plan deletions in dependency order (children first), or use ",[18,2202,2190],{}," deliberately.",[39,2205,2207],{"id":2206},"complex-implementation-production-upsert-for-real-time-stats-aggregation","Complex Implementation: Production Upsert for Real-Time Stats Aggregation",[43,2209,2210],{"language":45},[47,2211,2213],{"className":49,"code":2212,"language":45,"meta":51,"style":51},"-- Real-time stats aggregation: events arrive continuously, stats are upserted.\n-- The same (day, metric) key may arrive hundreds of times per second.\n\nCREATE TABLE daily_stats (\n  day        DATE         NOT NULL,\n  metric     TEXT         NOT NULL,\n  count      BIGINT       NOT NULL DEFAULT 0,\n  sum_value  NUMERIC(20,2) NOT NULL DEFAULT 0,\n  max_value  NUMERIC(20,2),\n  updated_at TIMESTAMPTZ  NOT NULL DEFAULT now(),\n  PRIMARY KEY (day, metric)\n);\n\n-- Upsert: on conflict, increment count, add to sum, take max, update timestamp.\n-- EXCLUDED.count is the proposed insert value (the batch's count for this key).\n-- stats.count is the existing row's value.\n-- IS DISTINCT FROM guards against pointless updates (reduces write amplification).\nINSERT INTO daily_stats (day, metric, count, sum_value, max_value)\nVALUES ('2024-01-01', 'sales', 1, 150.00, 150.00)\nON CONFLICT (day, metric)\nDO UPDATE SET\n  count      = daily_stats.count + EXCLUDED.count,\n  sum_value  = daily_stats.sum_value + EXCLUDED.sum_value,\n  max_value  = LEAST(daily_stats.max_value, EXCLUDED.max_value),\n  updated_at = now()\nWHERE EXCLUDED.count > 0;  -- skip no-op upserts\n\n-- Join-based UPDATE for denormalized data sync:\n-- Sync the customer's total_orders and total_amount into a denormalized cache.\nUPDATE customer_stats cs\nSET\n  total_orders  = agg.order_count,\n  total_amount  = agg.total_amount,\n  updated_at    = now()\nFROM (\n  SELECT customer_id,\n         count(*)   AS order_count,\n         sum(amount) AS total_amount\n  FROM orders\n  WHERE ordered_on >= now() - interval '1 day'\n  GROUP BY customer_id\n) agg\nWHERE cs.customer_id = agg.customer_id;\n-- The subquery aggregates once; the UPDATE joins to it by customer_id.\n-- Without the subquery (correlated subquery per row), this would be O(N*M).\n",[18,2214,2215,2220,2225,2229,2244,2258,2270,2289,2318,2335,2353,2364,2368,2372,2377,2382,2387,2392,2403,2439,2449,2458,2481,2504,2533,2543,2562,2566,2572,2578,2586,2592,2610,2627,2639,2646,2655,2674,2687,2695,2720,2729,2735,2757,2763],{"__ignoreMap":51},[55,2216,2217],{"class":57,"line":58},[55,2218,2219],{"class":61},"-- Real-time stats aggregation: events arrive continuously, stats are upserted.\n",[55,2221,2222],{"class":57,"line":65},[55,2223,2224],{"class":61},"-- The same (day, metric) key may arrive hundreds of times per second.\n",[55,2226,2227],{"class":57,"line":101},[55,2228,105],{"emptyLinePlaceholder":104},[55,2230,2231,2234,2237,2241],{"class":57,"line":108},[55,2232,2233],{"class":68},"CREATE",[55,2235,2236],{"class":68}," TABLE",[55,2238,2240],{"class":2239},"sIsaT"," daily_stats",[55,2242,2243],{"class":72}," (\n",[55,2245,2246,2249,2252,2255],{"class":57,"line":114},[55,2247,2248],{"class":68},"  day",[55,2250,2251],{"class":68},"        DATE",[55,2253,2254],{"class":68},"         NOT NULL",[55,2256,2257],{"class":72},",\n",[55,2259,2260,2263,2266,2268],{"class":57,"line":120},[55,2261,2262],{"class":72},"  metric     ",[55,2264,2265],{"class":68},"TEXT",[55,2267,2254],{"class":68},[55,2269,2257],{"class":72},[55,2271,2272,2275,2278,2281,2284,2287],{"class":57,"line":134},[55,2273,2274],{"class":72},"  count      ",[55,2276,2277],{"class":68},"BIGINT",[55,2279,2280],{"class":68},"       NOT NULL",[55,2282,2283],{"class":68}," DEFAULT",[55,2285,2286],{"class":491}," 0",[55,2288,2257],{"class":72},[55,2290,2291,2294,2297,2299,2302,2305,2308,2310,2312,2314,2316],{"class":57,"line":151},[55,2292,2293],{"class":72},"  sum_value  ",[55,2295,2296],{"class":68},"NUMERIC",[55,2298,1579],{"class":72},[55,2300,2301],{"class":491},"20",[55,2303,2304],{"class":72},",",[55,2306,2307],{"class":491},"2",[55,2309,450],{"class":72},[55,2311,320],{"class":68},[55,2313,2283],{"class":68},[55,2315,2286],{"class":491},[55,2317,2257],{"class":72},[55,2319,2320,2323,2325,2327,2329,2331,2333],{"class":57,"line":165},[55,2321,2322],{"class":72},"  max_value  ",[55,2324,2296],{"class":68},[55,2326,1579],{"class":72},[55,2328,2301],{"class":491},[55,2330,2304],{"class":72},[55,2332,2307],{"class":491},[55,2334,148],{"class":72},[55,2336,2337,2340,2343,2346,2348,2350],{"class":57,"line":180},[55,2338,2339],{"class":72},"  updated_at ",[55,2341,2342],{"class":68},"TIMESTAMPTZ",[55,2344,2345],{"class":68},"  NOT NULL",[55,2347,2283],{"class":68},[55,2349,1129],{"class":68},[55,2351,2352],{"class":72},"(),\n",[55,2354,2355,2358,2360,2362],{"class":57,"line":185},[55,2356,2357],{"class":68},"  PRIMARY KEY",[55,2359,85],{"class":72},[55,2361,619],{"class":68},[55,2363,658],{"class":72},[55,2365,2366],{"class":57,"line":191},[55,2367,98],{"class":72},[55,2369,2370],{"class":57,"line":197},[55,2371,105],{"emptyLinePlaceholder":104},[55,2373,2374],{"class":57,"line":224},[55,2375,2376],{"class":61},"-- Upsert: on conflict, increment count, add to sum, take max, update timestamp.\n",[55,2378,2379],{"class":57,"line":229},[55,2380,2381],{"class":61},"-- EXCLUDED.count is the proposed insert value (the batch's count for this key).\n",[55,2383,2384],{"class":57,"line":235},[55,2385,2386],{"class":61},"-- stats.count is the existing row's value.\n",[55,2388,2389],{"class":57,"line":243},[55,2390,2391],{"class":61},"-- IS DISTINCT FROM guards against pointless updates (reduces write amplification).\n",[55,2393,2394,2396,2398,2400],{"class":57,"line":273},[55,2395,69],{"class":68},[55,2397,616],{"class":72},[55,2399,619],{"class":68},[55,2401,2402],{"class":72},", metric, count, sum_value, max_value)\n",[55,2404,2405,2407,2409,2411,2413,2415,2417,2419,2421,2424,2426,2429,2431,2433,2435,2437],{"class":57,"line":278},[55,2406,82],{"class":68},[55,2408,85],{"class":72},[55,2410,634],{"class":88},[55,2412,92],{"class":72},[55,2414,639],{"class":88},[55,2416,92],{"class":72},[55,2418,1034],{"class":491},[55,2420,92],{"class":72},[55,2422,2423],{"class":491},"150",[55,2425,495],{"class":72},[55,2427,2428],{"class":491},"00",[55,2430,92],{"class":72},[55,2432,2423],{"class":491},[55,2434,495],{"class":72},[55,2436,2428],{"class":491},[55,2438,464],{"class":72},[55,2440,2441,2443,2445,2447],{"class":57,"line":284},[55,2442,469],{"class":68},[55,2444,653],{"class":72},[55,2446,619],{"class":68},[55,2448,658],{"class":72},[55,2450,2451,2453,2455],{"class":57,"line":686},[55,2452,477],{"class":72},[55,2454,32],{"class":68},[55,2456,2457],{"class":68}," SET\n",[55,2459,2460,2462,2464,2466,2468,2471,2473,2475,2477,2479],{"class":57,"line":692},[55,2461,2274],{"class":72},[55,2463,887],{"class":68},[55,2465,2240],{"class":491},[55,2467,495],{"class":72},[55,2469,2470],{"class":491},"count",[55,2472,897],{"class":68},[55,2474,492],{"class":491},[55,2476,495],{"class":72},[55,2478,2470],{"class":491},[55,2480,2257],{"class":72},[55,2482,2483,2485,2487,2489,2491,2494,2496,2498,2500,2502],{"class":57,"line":698},[55,2484,2293],{"class":72},[55,2486,887],{"class":68},[55,2488,2240],{"class":491},[55,2490,495],{"class":72},[55,2492,2493],{"class":491},"sum_value",[55,2495,897],{"class":68},[55,2497,492],{"class":491},[55,2499,495],{"class":72},[55,2501,2493],{"class":491},[55,2503,2257],{"class":72},[55,2505,2506,2508,2510,2513,2515,2518,2520,2523,2525,2527,2529,2531],{"class":57,"line":721},[55,2507,2322],{"class":72},[55,2509,887],{"class":68},[55,2511,2512],{"class":491}," LEAST",[55,2514,1579],{"class":72},[55,2516,2517],{"class":491},"daily_stats",[55,2519,495],{"class":72},[55,2521,2522],{"class":491},"max_value",[55,2524,92],{"class":72},[55,2526,800],{"class":491},[55,2528,495],{"class":72},[55,2530,2522],{"class":491},[55,2532,148],{"class":72},[55,2534,2535,2537,2539,2541],{"class":57,"line":728},[55,2536,2339],{"class":72},[55,2538,887],{"class":68},[55,2540,1129],{"class":68},[55,2542,1132],{"class":72},[55,2544,2545,2547,2549,2551,2553,2555,2557,2559],{"class":57,"line":748},[55,2546,258],{"class":68},[55,2548,492],{"class":491},[55,2550,495],{"class":72},[55,2552,2470],{"class":491},[55,2554,946],{"class":68},[55,2556,2286],{"class":491},[55,2558,1557],{"class":72},[55,2560,2561],{"class":61},"-- skip no-op upserts\n",[55,2563,2564],{"class":57,"line":776},[55,2565,105],{"emptyLinePlaceholder":104},[55,2567,2569],{"class":57,"line":2568},28,[55,2570,2571],{"class":61},"-- Join-based UPDATE for denormalized data sync:\n",[55,2573,2575],{"class":57,"line":2574},29,[55,2576,2577],{"class":61},"-- Sync the customer's total_orders and total_amount into a denormalized cache.\n",[55,2579,2581,2583],{"class":57,"line":2580},30,[55,2582,32],{"class":68},[55,2584,2585],{"class":72}," customer_stats cs\n",[55,2587,2589],{"class":57,"line":2588},31,[55,2590,2591],{"class":68},"SET\n",[55,2593,2595,2598,2600,2603,2605,2608],{"class":57,"line":2594},32,[55,2596,2597],{"class":72},"  total_orders  ",[55,2599,887],{"class":68},[55,2601,2602],{"class":491}," agg",[55,2604,495],{"class":72},[55,2606,2607],{"class":491},"order_count",[55,2609,2257],{"class":72},[55,2611,2613,2616,2618,2620,2622,2625],{"class":57,"line":2612},33,[55,2614,2615],{"class":72},"  total_amount  ",[55,2617,887],{"class":68},[55,2619,2602],{"class":491},[55,2621,495],{"class":72},[55,2623,2624],{"class":491},"total_amount",[55,2626,2257],{"class":72},[55,2628,2630,2633,2635,2637],{"class":57,"line":2629},34,[55,2631,2632],{"class":72},"  updated_at    ",[55,2634,887],{"class":68},[55,2636,1129],{"class":68},[55,2638,1132],{"class":72},[55,2640,2642,2644],{"class":57,"line":2641},35,[55,2643,356],{"class":68},[55,2645,2243],{"class":72},[55,2647,2649,2652],{"class":57,"line":2648},36,[55,2650,2651],{"class":68},"  SELECT",[55,2653,2654],{"class":72}," customer_id,\n",[55,2656,2658,2661,2663,2665,2668,2671],{"class":57,"line":2657},37,[55,2659,2660],{"class":491},"         count",[55,2662,1579],{"class":72},[55,2664,1361],{"class":68},[55,2666,2667],{"class":72},")   ",[55,2669,2670],{"class":68},"AS",[55,2672,2673],{"class":72}," order_count,\n",[55,2675,2677,2680,2682,2684],{"class":57,"line":2676},38,[55,2678,2679],{"class":491},"         sum",[55,2681,1496],{"class":72},[55,2683,2670],{"class":68},[55,2685,2686],{"class":72}," total_amount\n",[55,2688,2690,2693],{"class":57,"line":2689},39,[55,2691,2692],{"class":68},"  FROM",[55,2694,1477],{"class":72},[55,2696,2698,2701,2703,2706,2708,2711,2714,2717],{"class":57,"line":2697},40,[55,2699,2700],{"class":68},"  WHERE",[55,2702,261],{"class":72},[55,2704,2705],{"class":68},">=",[55,2707,1129],{"class":68},[55,2709,2710],{"class":72},"() ",[55,2712,2713],{"class":68},"-",[55,2715,2716],{"class":72}," interval ",[55,2718,2719],{"class":88},"'1 day'\n",[55,2721,2723,2726],{"class":57,"line":2722},41,[55,2724,2725],{"class":68},"  GROUP BY",[55,2727,2728],{"class":72}," customer_id\n",[55,2730,2732],{"class":57,"line":2731},42,[55,2733,2734],{"class":72},") agg\n",[55,2736,2738,2740,2743,2745,2747,2749,2751,2753,2755],{"class":57,"line":2737},43,[55,2739,258],{"class":68},[55,2741,2742],{"class":491}," cs",[55,2744,495],{"class":72},[55,2746,1436],{"class":491},[55,2748,488],{"class":68},[55,2750,2602],{"class":491},[55,2752,495],{"class":72},[55,2754,1436],{"class":491},[55,2756,270],{"class":72},[55,2758,2760],{"class":57,"line":2759},44,[55,2761,2762],{"class":61},"-- The subquery aggregates once; the UPDATE joins to it by customer_id.\n",[55,2764,2766],{"class":57,"line":2765},45,[55,2767,2768],{"class":61},"-- Without the subquery (correlated subquery per row), this would be O(N*M).\n",[39,2770,2772],{"id":2771},"complex-implementation-batched-delete-for-large-tables","Complex Implementation: Batched DELETE for Large Tables",[43,2774,2775],{"language":45},[47,2776,2778],{"className":49,"code":2777,"language":45,"meta":51,"style":51},"-- Deleting millions of rows in one statement holds locks for a long time,\n-- creates millions of dead tuples, and can block VACUUM.\n-- Batch it: delete N rows per statement, repeat until no rows are deleted.\n\n-- PostgreSQL doesn't support DELETE ... LIMIT (MySQL does).\n-- Use the id IN (SELECT ... LIMIT n) pattern.\n\nDO $$\nDECLARE\n  deleted_count INTEGER;\n  batch_size    INTEGER := 10000;\nBEGIN\n  LOOP\n    DELETE FROM logs\n    WHERE created_at \u003C '2023-01-01'\n      AND id IN (\n        SELECT id FROM logs\n        WHERE created_at \u003C '2023-01-01'\n        ORDER BY id\n        LIMIT batch_size\n      );\n    GET DIAGNOSTICS deleted_count = ROW_COUNT;\n    EXIT WHEN deleted_count = 0;\n    PERFORM pg_sleep(0.01);  -- brief pause to let other queries proceed\n  END LOOP;\nEND $$;\n\n-- For updates, use a CTE to select IDs to update, then update in batches:\nWITH batch AS (\n  SELECT id FROM orders\n  WHERE status = 'pending' AND created_at \u003C '2024-01-01'\n  ORDER BY id\n  LIMIT 10000\n  FOR UPDATE SKIP LOCKED   -- skip rows locked by other workers\n)\nUPDATE orders SET status = 'expired'\nWHERE id IN (SELECT id FROM batch);\n",[18,2779,2780,2785,2790,2795,2799,2804,2809,2813,2818,2823,2833,2850,2855,2860,2870,2883,2894,2905,2916,2924,2932,2937,2950,2967,2986,2996,3004,3008,3013,3024,3034,3053,3060,3068,3085,3089,3104],{"__ignoreMap":51},[55,2781,2782],{"class":57,"line":58},[55,2783,2784],{"class":61},"-- Deleting millions of rows in one statement holds locks for a long time,\n",[55,2786,2787],{"class":57,"line":65},[55,2788,2789],{"class":61},"-- creates millions of dead tuples, and can block VACUUM.\n",[55,2791,2792],{"class":57,"line":101},[55,2793,2794],{"class":61},"-- Batch it: delete N rows per statement, repeat until no rows are deleted.\n",[55,2796,2797],{"class":57,"line":108},[55,2798,105],{"emptyLinePlaceholder":104},[55,2800,2801],{"class":57,"line":114},[55,2802,2803],{"class":61},"-- PostgreSQL doesn't support DELETE ... LIMIT (MySQL does).\n",[55,2805,2806],{"class":57,"line":120},[55,2807,2808],{"class":61},"-- Use the id IN (SELECT ... LIMIT n) pattern.\n",[55,2810,2811],{"class":57,"line":134},[55,2812,105],{"emptyLinePlaceholder":104},[55,2814,2815],{"class":57,"line":151},[55,2816,2817],{"class":72},"DO $$\n",[55,2819,2820],{"class":57,"line":165},[55,2821,2822],{"class":68},"DECLARE\n",[55,2824,2825,2828,2831],{"class":57,"line":180},[55,2826,2827],{"class":72},"  deleted_count ",[55,2829,2830],{"class":68},"INTEGER",[55,2832,270],{"class":72},[55,2834,2835,2838,2840,2843,2845,2848],{"class":57,"line":185},[55,2836,2837],{"class":72},"  batch_size    ",[55,2839,2830],{"class":68},[55,2841,2842],{"class":72}," :",[55,2844,887],{"class":68},[55,2846,2847],{"class":491}," 10000",[55,2849,270],{"class":72},[55,2851,2852],{"class":57,"line":191},[55,2853,2854],{"class":68},"BEGIN\n",[55,2856,2857],{"class":57,"line":197},[55,2858,2859],{"class":68},"  LOOP\n",[55,2861,2862,2865,2867],{"class":57,"line":224},[55,2863,2864],{"class":68},"    DELETE",[55,2866,252],{"class":68},[55,2868,2869],{"class":72}," logs\n",[55,2871,2872,2875,2878,2880],{"class":57,"line":229},[55,2873,2874],{"class":68},"    WHERE",[55,2876,2877],{"class":72}," created_at ",[55,2879,264],{"class":68},[55,2881,2882],{"class":88}," '2023-01-01'\n",[55,2884,2885,2888,2890,2892],{"class":57,"line":235},[55,2886,2887],{"class":68},"      AND",[55,2889,1276],{"class":72},[55,2891,2129],{"class":68},[55,2893,2243],{"class":72},[55,2895,2896,2899,2901,2903],{"class":57,"line":243},[55,2897,2898],{"class":68},"        SELECT",[55,2900,1276],{"class":72},[55,2902,356],{"class":68},[55,2904,2869],{"class":72},[55,2906,2907,2910,2912,2914],{"class":57,"line":273},[55,2908,2909],{"class":68},"        WHERE",[55,2911,2877],{"class":72},[55,2913,264],{"class":68},[55,2915,2882],{"class":88},[55,2917,2918,2921],{"class":57,"line":278},[55,2919,2920],{"class":68},"        ORDER BY",[55,2922,2923],{"class":72}," id\n",[55,2925,2926,2929],{"class":57,"line":284},[55,2927,2928],{"class":68},"        LIMIT",[55,2930,2931],{"class":72}," batch_size\n",[55,2933,2934],{"class":57,"line":686},[55,2935,2936],{"class":72},"      );\n",[55,2938,2939,2942,2945,2947],{"class":57,"line":692},[55,2940,2941],{"class":68},"    GET",[55,2943,2944],{"class":72}," DIAGNOSTICS deleted_count ",[55,2946,887],{"class":68},[55,2948,2949],{"class":72}," ROW_COUNT;\n",[55,2951,2952,2955,2958,2961,2963,2965],{"class":57,"line":698},[55,2953,2954],{"class":72},"    EXIT ",[55,2956,2957],{"class":68},"WHEN",[55,2959,2960],{"class":72}," deleted_count ",[55,2962,887],{"class":68},[55,2964,2286],{"class":491},[55,2966,270],{"class":72},[55,2968,2969,2972,2975,2977,2980,2983],{"class":57,"line":721},[55,2970,2971],{"class":72},"    PERFORM pg_sleep(",[55,2973,2974],{"class":491},"0",[55,2976,495],{"class":72},[55,2978,2979],{"class":491},"01",[55,2981,2982],{"class":72},");  ",[55,2984,2985],{"class":61},"-- brief pause to let other queries proceed\n",[55,2987,2988,2991,2994],{"class":57,"line":728},[55,2989,2990],{"class":68},"  END",[55,2992,2993],{"class":68}," LOOP",[55,2995,270],{"class":72},[55,2997,2998,3001],{"class":57,"line":748},[55,2999,3000],{"class":68},"END",[55,3002,3003],{"class":72}," $$;\n",[55,3005,3006],{"class":57,"line":776},[55,3007,105],{"emptyLinePlaceholder":104},[55,3009,3010],{"class":57,"line":2568},[55,3011,3012],{"class":61},"-- For updates, use a CTE to select IDs to update, then update in batches:\n",[55,3014,3015,3017,3020,3022],{"class":57,"line":2574},[55,3016,392],{"class":68},[55,3018,3019],{"class":72}," batch ",[55,3021,2670],{"class":68},[55,3023,2243],{"class":72},[55,3025,3026,3028,3030,3032],{"class":57,"line":2580},[55,3027,2651],{"class":68},[55,3029,1276],{"class":72},[55,3031,356],{"class":68},[55,3033,1477],{"class":72},[55,3035,3036,3038,3040,3042,3044,3046,3048,3050],{"class":57,"line":2588},[55,3037,2700],{"class":68},[55,3039,1077],{"class":68},[55,3041,488],{"class":68},[55,3043,1596],{"class":88},[55,3045,1448],{"class":68},[55,3047,2877],{"class":72},[55,3049,264],{"class":68},[55,3051,3052],{"class":88}," '2024-01-01'\n",[55,3054,3055,3058],{"class":57,"line":2594},[55,3056,3057],{"class":68},"  ORDER BY",[55,3059,2923],{"class":72},[55,3061,3062,3065],{"class":57,"line":2612},[55,3063,3064],{"class":68},"  LIMIT",[55,3066,3067],{"class":491}," 10000\n",[55,3069,3070,3073,3076,3079,3082],{"class":57,"line":2629},[55,3071,3072],{"class":68},"  FOR",[55,3074,3075],{"class":68}," UPDATE",[55,3077,3078],{"class":68}," SKIP",[55,3080,3081],{"class":72}," LOCKED   ",[55,3083,3084],{"class":61},"-- skip rows locked by other workers\n",[55,3086,3087],{"class":57,"line":2641},[55,3088,464],{"class":72},[55,3090,3091,3093,3095,3097,3099,3101],{"class":57,"line":2648},[55,3092,32],{"class":68},[55,3094,255],{"class":72},[55,3096,1074],{"class":68},[55,3098,1077],{"class":68},[55,3100,488],{"class":68},[55,3102,3103],{"class":88}," 'expired'\n",[55,3105,3106,3108,3110,3112,3114,3116,3118,3120],{"class":57,"line":2657},[55,3107,258],{"class":68},[55,3109,1276],{"class":72},[55,3111,2129],{"class":68},[55,3113,85],{"class":72},[55,3115,246],{"class":68},[55,3117,1276],{"class":72},[55,3119,356],{"class":68},[55,3121,3122],{"class":72}," batch);\n",[39,3124,3126],{"id":3125},"anti-pattern-row-by-row-updates-in-a-loop-vs-set-based-update","Anti-Pattern: Row-by-Row Updates in a Loop vs Set-Based UPDATE",[43,3128,3129],{"language":45},[47,3130,3132],{"className":49,"code":3131,"language":45,"meta":51,"style":51},"-- ❌ WRONG: row-by-row updates from application code.\n-- N separate queries = N round-trips, N parses, N plan executions.\n-- For 100K rows: 100K queries, ~100 seconds of round-trip overhead alone.\n\n-- Pseudo-code (application layer):\n-- for row in rows:\n--     db.execute(\"UPDATE products SET price = ? WHERE id = ?\", new_price, row.id)\n\n-- ✅ CORRECT: set-based UPDATE — one query, one plan, one scan.\nUPDATE products SET price = price * 1.1 WHERE category = 'electronics';\n-- 100K rows updated in one statement, ~1 second.\n\n-- ✅ If the new values are heterogeneous (different price per row), use a VALUES join:\nUPDATE products p\nSET price = v.new_price\nFROM (VALUES\n  (1, 10.99),\n  (2, 25.50),\n  (3, 7.00)\n) AS v(id, new_price)\nWHERE p.id = v.id;\n-- One statement, N rows — the planner can use an index on p.id for the join.\n",[18,3133,3134,3139,3144,3149,3153,3158,3163,3168,3172,3177,3209,3214,3218,3223,3230,3246,3254,3272,3289,3307,3316,3337],{"__ignoreMap":51},[55,3135,3136],{"class":57,"line":58},[55,3137,3138],{"class":61},"-- ❌ WRONG: row-by-row updates from application code.\n",[55,3140,3141],{"class":57,"line":65},[55,3142,3143],{"class":61},"-- N separate queries = N round-trips, N parses, N plan executions.\n",[55,3145,3146],{"class":57,"line":101},[55,3147,3148],{"class":61},"-- For 100K rows: 100K queries, ~100 seconds of round-trip overhead alone.\n",[55,3150,3151],{"class":57,"line":108},[55,3152,105],{"emptyLinePlaceholder":104},[55,3154,3155],{"class":57,"line":114},[55,3156,3157],{"class":61},"-- Pseudo-code (application layer):\n",[55,3159,3160],{"class":57,"line":120},[55,3161,3162],{"class":61},"-- for row in rows:\n",[55,3164,3165],{"class":57,"line":134},[55,3166,3167],{"class":61},"--     db.execute(\"UPDATE products SET price = ? WHERE id = ?\", new_price, row.id)\n",[55,3169,3170],{"class":57,"line":151},[55,3171,105],{"emptyLinePlaceholder":104},[55,3173,3174],{"class":57,"line":165},[55,3175,3176],{"class":61},"-- ✅ CORRECT: set-based UPDATE — one query, one plan, one scan.\n",[55,3178,3179,3181,3183,3185,3187,3189,3191,3193,3195,3197,3199,3201,3203,3205,3207],{"class":57,"line":180},[55,3180,32],{"class":68},[55,3182,1349],{"class":72},[55,3184,1074],{"class":68},[55,3186,1354],{"class":72},[55,3188,887],{"class":68},[55,3190,1354],{"class":72},[55,3192,1361],{"class":68},[55,3194,1364],{"class":491},[55,3196,495],{"class":72},[55,3198,1034],{"class":491},[55,3200,1085],{"class":68},[55,3202,1373],{"class":72},[55,3204,887],{"class":68},[55,3206,1378],{"class":88},[55,3208,270],{"class":72},[55,3210,3211],{"class":57,"line":185},[55,3212,3213],{"class":61},"-- 100K rows updated in one statement, ~1 second.\n",[55,3215,3216],{"class":57,"line":191},[55,3217,105],{"emptyLinePlaceholder":104},[55,3219,3220],{"class":57,"line":197},[55,3221,3222],{"class":61},"-- ✅ If the new values are heterogeneous (different price per row), use a VALUES join:\n",[55,3224,3225,3227],{"class":57,"line":224},[55,3226,32],{"class":68},[55,3228,3229],{"class":72}," products p\n",[55,3231,3232,3234,3236,3238,3241,3243],{"class":57,"line":229},[55,3233,1074],{"class":68},[55,3235,1354],{"class":72},[55,3237,887],{"class":68},[55,3239,3240],{"class":491}," v",[55,3242,495],{"class":72},[55,3244,3245],{"class":491},"new_price\n",[55,3247,3248,3250,3252],{"class":57,"line":235},[55,3249,356],{"class":68},[55,3251,85],{"class":72},[55,3253,131],{"class":68},[55,3255,3256,3258,3260,3262,3265,3267,3270],{"class":57,"line":243},[55,3257,137],{"class":72},[55,3259,1034],{"class":491},[55,3261,92],{"class":72},[55,3263,3264],{"class":491},"10",[55,3266,495],{"class":72},[55,3268,3269],{"class":491},"99",[55,3271,148],{"class":72},[55,3273,3274,3276,3278,3280,3283,3285,3287],{"class":57,"line":273},[55,3275,137],{"class":72},[55,3277,2307],{"class":491},[55,3279,92],{"class":72},[55,3281,3282],{"class":491},"25",[55,3284,495],{"class":72},[55,3286,1039],{"class":491},[55,3288,148],{"class":72},[55,3290,3291,3293,3296,3298,3301,3303,3305],{"class":57,"line":278},[55,3292,137],{"class":72},[55,3294,3295],{"class":491},"3",[55,3297,92],{"class":72},[55,3299,3300],{"class":491},"7",[55,3302,495],{"class":72},[55,3304,2428],{"class":491},[55,3306,464],{"class":72},[55,3308,3309,3311,3313],{"class":57,"line":284},[55,3310,450],{"class":72},[55,3312,2670],{"class":68},[55,3314,3315],{"class":72}," v(id, new_price)\n",[55,3317,3318,3320,3323,3325,3327,3329,3331,3333,3335],{"class":57,"line":686},[55,3319,258],{"class":68},[55,3321,3322],{"class":491}," p",[55,3324,495],{"class":72},[55,3326,1445],{"class":491},[55,3328,488],{"class":68},[55,3330,3240],{"class":491},[55,3332,495],{"class":72},[55,3334,1445],{"class":491},[55,3336,270],{"class":72},[55,3338,3339],{"class":57,"line":692},[55,3340,3341],{"class":61},"-- One statement, N rows — the planner can use an index on p.id for the join.\n",[39,3343,3345],{"id":3344},"anti-pattern-update-without-where","Anti-Pattern: UPDATE without WHERE",[43,3347,3348],{"language":45},[47,3349,3351],{"className":49,"code":3350,"language":45,"meta":51,"style":51},"-- ❌ This updates every row in the table — the classic footgun.\nUPDATE orders SET status = 'shipped';\n\n-- ✅ Always include WHERE. Run a SELECT with the same predicate first.\nSELECT count(*) FROM orders WHERE status = 'pending';\n-- Confirm the count, then:\nUPDATE orders SET status = 'shipped' WHERE status = 'pending';\n\n-- Some tools (mysql --safe-updates, psql with \\set) block WHERE-less DML.\n",[18,3352,3353,3358,3374,3378,3383,3409,3414,3438,3442],{"__ignoreMap":51},[55,3354,3355],{"class":57,"line":58},[55,3356,3357],{"class":61},"-- ❌ This updates every row in the table — the classic footgun.\n",[55,3359,3360,3362,3364,3366,3368,3370,3372],{"class":57,"line":65},[55,3361,32],{"class":68},[55,3363,255],{"class":72},[55,3365,1074],{"class":68},[55,3367,1077],{"class":68},[55,3369,488],{"class":68},[55,3371,1082],{"class":88},[55,3373,270],{"class":72},[55,3375,3376],{"class":57,"line":101},[55,3377,105],{"emptyLinePlaceholder":104},[55,3379,3380],{"class":57,"line":108},[55,3381,3382],{"class":61},"-- ✅ Always include WHERE. Run a SELECT with the same predicate first.\n",[55,3384,3385,3387,3389,3391,3393,3395,3397,3399,3401,3403,3405,3407],{"class":57,"line":114},[55,3386,246],{"class":68},[55,3388,1576],{"class":491},[55,3390,1579],{"class":72},[55,3392,1361],{"class":68},[55,3394,450],{"class":72},[55,3396,356],{"class":68},[55,3398,255],{"class":72},[55,3400,258],{"class":68},[55,3402,1077],{"class":68},[55,3404,488],{"class":68},[55,3406,1596],{"class":88},[55,3408,270],{"class":72},[55,3410,3411],{"class":57,"line":120},[55,3412,3413],{"class":61},"-- Confirm the count, then:\n",[55,3415,3416,3418,3420,3422,3424,3426,3428,3430,3432,3434,3436],{"class":57,"line":134},[55,3417,32],{"class":68},[55,3419,255],{"class":72},[55,3421,1074],{"class":68},[55,3423,1077],{"class":68},[55,3425,488],{"class":68},[55,3427,1082],{"class":88},[55,3429,1085],{"class":68},[55,3431,1077],{"class":68},[55,3433,488],{"class":68},[55,3435,1596],{"class":88},[55,3437,270],{"class":72},[55,3439,3440],{"class":57,"line":151},[55,3441,105],{"emptyLinePlaceholder":104},[55,3443,3444],{"class":57,"line":165},[55,3445,3446],{"class":61},"-- Some tools (mysql --safe-updates, psql with \\set) block WHERE-less DML.\n",[39,3448,3450],{"id":3449},"tips-tricks","💡 Tips & Tricks",[303,3452,3453,3476,3488,3506,3521,3530,3542,3554],{},[306,3454,3455,3466,3467,3469,3470,3472,3473,3475],{},[783,3456,3457,3458,3461,3462,33,3464],{},"Idiom — ",[18,3459,3460],{},"INSERT ... RETURNING id"," over ",[18,3463,1227],{},[18,3465,1231],{},": ",[18,3468,28],{}," is atomic, works for multi-row inserts, returns all generated columns, and doesn't rely on session state. ",[18,3471,1227],{}," can be polluted by triggers; ",[18,3474,1231],{}," is per-connection.",[306,3477,3478,3483,3484,3487],{},[783,3479,3457,3480,3482],{},[18,3481,24],{}," for idempotent upserts",": replay-safe ingestion (ETL, webhooks, retries) where the same row may arrive multiple times. ",[18,3485,3486],{},"ON CONFLICT (key) DO UPDATE SET ... = EXCLUDED..."," is the standard pattern.",[306,3489,3490,3497,3498,3501,3502,3505],{},[783,3491,3492,3493,33,3495],{},"Performance — batch large ",[18,3494,32],{},[18,3496,36],{},": 10K rows per batch with ",[18,3499,3500],{},"LIMIT"," subquery + ",[18,3503,3504],{},"FOR UPDATE SKIP LOCKED",". Keeps locks short, allows concurrent queries, and limits dead tuple creation per transaction.",[306,3507,3508,3466,3514,3517,3518,3520],{},[783,3509,3457,3510,3513],{},[18,3511,3512],{},"INSERT FROM SELECT"," for data migration",[18,3515,3516],{},"INSERT INTO archive SELECT * FROM source WHERE ..."," — one statement, no application loop. Add ",[18,3519,28],{}," for audit.",[306,3522,3523,3529],{},[783,3524,3457,3525,3528],{},[18,3526,3527],{},"UPDATE ... FROM"," for join-based updates",": one statement, planner can use indexes on the join key. Avoid correlated subqueries when a join is clearer and faster.",[306,3531,3532,3538,3539,3541],{},[783,3533,3534,3535,3537],{},"Performance — ",[18,3536,347],{}," for bulk loads",": 10-100x faster than per-row ",[18,3540,20],{},". Stream from CSV or stdin, bypasses most per-row overhead.",[306,3543,3544,3466,3550,3553],{},[783,3545,3457,3546,3549],{},[18,3547,3548],{},"IS DISTINCT FROM"," for conditional upserts",[18,3551,3552],{},"WHERE users.name IS DISTINCT FROM EXCLUDED.name"," skips no-op updates, reducing write amplification and WAL volume.",[306,3555,3556,3566,3567,3569],{},[783,3557,3558,3559,3561,3562,33,3564],{},"Idiom — run ",[18,3560,246],{}," before ",[18,3563,32],{},[18,3565,36],{},": preview affected rows with the same ",[18,3568,258],{}," to catch \"forgot WHERE\" and \"matches more than expected\" before damage is done.",[39,3571,3573],{"id":3572},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[303,3575,3576,3598,3616,3634,3657,3676,3691,3709,3721,3730,3742,3755],{},[306,3577,3578,3588,3589,3591,3592,3594,3595,3597],{},[783,3579,3580,33,3582,3584,3585,3587],{},[18,3581,32],{},[18,3583,36],{}," without ",[18,3586,258],{}," affects all rows",": the classic footgun. Always include ",[18,3590,258],{},", or run a ",[18,3593,246],{}," with the same predicate first. Some tools have a \"safe updates\" mode that blocks ",[18,3596,258],{},"-less DML.",[306,3599,3600,3466,3605,3608,3609,3612,3613,495],{},[783,3601,3602,3604],{},[18,3603,24],{}," requires a unique index\u002Fconstraint on the conflict target",[18,3606,3607],{},"ON CONFLICT (email)"," needs a unique index on ",[18,3610,3611],{},"email",". Without it: ",[18,3614,3615],{},"ERROR: there is no unique or exclusion constraint matching the ON CONFLICT specification",[306,3617,3618,3623,3624,25,3627,3630,3631,3633],{},[783,3619,3620,3622],{},[18,3621,24],{}," partial index matching",": if you have a partial unique index (",[18,3625,3626],{},"WHERE active = true",[18,3628,3629],{},"ON CONFLICT (email) WHERE active = true"," must match the partial index's predicate exactly. A plain ",[18,3632,3607],{}," won't match a partial index.",[306,3635,3636,3466,3641,3644,3645,3648,3649,3652,3653,3656],{},[783,3637,3638,3640],{},[18,3639,800],{}," vs table name confusion",[18,3642,3643],{},"DO UPDATE SET col = EXCLUDED.col"," (new value) vs ",[18,3646,3647],{},"SET col = table.col"," (existing value). ",[18,3650,3651],{},"SET count = EXCLUDED.count"," overwrites; ",[18,3654,3655],{},"SET count = table.count + EXCLUDED.count"," accumulates. Pick deliberately.",[306,3658,3659,3466,3668,3671,3672,3675],{},[783,3660,3661,3663,3664,3667],{},[18,3662,28],{}," on ",[18,3665,3666],{},"DO NOTHING"," returns nothing",[18,3669,3670],{},"ON CONFLICT DO NOTHING"," returns only actually-inserted rows; conflicting rows don't appear. To get the existing row too, use the no-op ",[18,3673,3674],{},"DO UPDATE SET col = table.col"," trick.",[306,3677,3678,3683,3684,3687,3688,3690],{},[783,3679,3680,3682],{},[18,3681,3527],{}," ambiguity",": in PostgreSQL, ",[18,3685,3686],{},"UPDATE orders o SET ... FROM customers c ..."," — the target table can't be referenced in the ",[18,3689,356],{}," (it's already the target). Re-listing it errors.",[306,3692,3693,3700,3701,3704,3705,3708],{},[783,3694,3695,3697,3698],{},[18,3696,36],{}," with ",[18,3699,3500],{},": PostgreSQL doesn't support ",[18,3702,3703],{},"DELETE ... LIMIT"," (MySQL does). For batched deletes in PG, use the ",[18,3706,3707],{},"id IN (SELECT ... LIMIT n)"," subquery pattern.",[306,3710,3711,3716,3717,3720],{},[783,3712,3713,3715],{},[18,3714,32],{}," of a PK column",": changing a primary key value updates every FK that references it (with ",[18,3718,3719],{},"ON UPDATE CASCADE","). On large referenced tables, this is expensive and locks. Prefer surrogate keys that never change.",[306,3722,3723,3466,3726,3729],{},[783,3724,3725],{},"Cascading FK deletes ordering",[18,3727,3728],{},"ON DELETE CASCADE"," propagates through the entire FK chain. Deleting a parent can delete thousands of descendants — plan the order or accept the cascade.",[306,3731,3732,3738,3739,3741],{},[783,3733,3734,3735,3737],{},"Large ",[18,3736,36],{}," lock duration",": a single ",[18,3740,36],{}," of millions of rows holds row locks for the entire duration. Other queries touching those rows block. Batch it.",[306,3743,3744,3466,3747,3750,3751,3754],{},[783,3745,3746],{},"Upsert on composite unique key",[18,3748,3749],{},"ON CONFLICT (col1, col2)"," requires a unique index\u002Fconstraint on ",[18,3752,3753],{},"(col1, col2)"," — not separate indexes on each column. The conflict target must match the index exactly.",[306,3756,3757,3762,3763,3766,3767,3770],{},[783,3758,3759,3761],{},[18,3760,20],{}," into a view",": only simple views (single-table, no aggregation) are auto-updatable in PG. Complex views need ",[18,3764,3765],{},"INSTEAD OF"," triggers or ",[18,3768,3769],{},"INSTEAD"," rules.",[39,3772,3774],{"id":3773},"spot-the-bug","🧠 Spot the Bug",[14,3776,3777],{},"A developer writes this to \"increment a counter or create it if missing,\" but it occasionally overwrites the counter to 1 instead of incrementing. Why?",[43,3779,3780],{"language":45},[47,3781,3783],{"className":49,"code":3782,"language":45,"meta":51,"style":51},"INSERT INTO counters (key, value) VALUES ('visits', 1)\nON CONFLICT (key) DO UPDATE SET value = 1;\n",[18,3784,3785,3814],{"__ignoreMap":51},[55,3786,3787,3789,3792,3795,3797,3799,3801,3803,3805,3808,3810,3812],{"class":57,"line":58},[55,3788,69],{"class":68},[55,3790,3791],{"class":72}," counters (",[55,3793,3794],{"class":68},"key",[55,3796,92],{"class":72},[55,3798,625],{"class":68},[55,3800,450],{"class":72},[55,3802,82],{"class":68},[55,3804,85],{"class":72},[55,3806,3807],{"class":88},"'visits'",[55,3809,92],{"class":72},[55,3811,1034],{"class":491},[55,3813,464],{"class":72},[55,3815,3816,3818,3820,3822,3825,3827,3829,3831,3833,3835],{"class":57,"line":65},[55,3817,469],{"class":68},[55,3819,653],{"class":72},[55,3821,3794],{"class":68},[55,3823,3824],{"class":72},") DO ",[55,3826,32],{"class":68},[55,3828,482],{"class":68},[55,3830,669],{"class":68},[55,3832,488],{"class":68},[55,3834,1364],{"class":491},[55,3836,270],{"class":72},[3838,3839,3840,3844,3860,3867,4004,4019],"details",{},[3841,3842,3843],"summary",{},"Answer",[14,3845,3846,3847,3850,3851,3853,3854,3856,3857,3859],{},"The ",[18,3848,3849],{},"DO UPDATE SET value = 1"," always sets ",[18,3852,625],{}," to 1, regardless of whether it was a conflict or an insert — it's not \"set to 1 if inserting, increment if conflicting.\" The ",[18,3855,1034],{}," is the literal from the ",[18,3858,82],{}," clause, applied unconditionally on conflict.",[14,3861,3862,3863,3866],{},"The fix: use ",[18,3864,3865],{},"EXCLUDED.value"," (the proposed insert value) plus the existing value, or just increment the existing row:",[43,3868,3869],{"language":45},[47,3870,3872],{"className":49,"code":3871,"language":45,"meta":51,"style":51},"-- On conflict, increment the existing value by the proposed amount.\nINSERT INTO counters (key, value) VALUES ('visits', 1)\nON CONFLICT (key) DO UPDATE SET value = counters.value + EXCLUDED.value;\n\n-- Or, if you always want to add 1 on conflict regardless of the INSERT value:\nINSERT INTO counters (key, value) VALUES ('visits', 1)\nON CONFLICT (key) DO UPDATE SET value = counters.value + 1;\n",[18,3873,3874,3879,3905,3939,3943,3948,3974],{"__ignoreMap":51},[55,3875,3876],{"class":57,"line":58},[55,3877,3878],{"class":61},"-- On conflict, increment the existing value by the proposed amount.\n",[55,3880,3881,3883,3885,3887,3889,3891,3893,3895,3897,3899,3901,3903],{"class":57,"line":65},[55,3882,69],{"class":68},[55,3884,3791],{"class":72},[55,3886,3794],{"class":68},[55,3888,92],{"class":72},[55,3890,625],{"class":68},[55,3892,450],{"class":72},[55,3894,82],{"class":68},[55,3896,85],{"class":72},[55,3898,3807],{"class":88},[55,3900,92],{"class":72},[55,3902,1034],{"class":491},[55,3904,464],{"class":72},[55,3906,3907,3909,3911,3913,3915,3917,3919,3921,3923,3925,3927,3929,3931,3933,3935,3937],{"class":57,"line":101},[55,3908,469],{"class":68},[55,3910,653],{"class":72},[55,3912,3794],{"class":68},[55,3914,3824],{"class":72},[55,3916,32],{"class":68},[55,3918,482],{"class":68},[55,3920,669],{"class":68},[55,3922,488],{"class":68},[55,3924,890],{"class":491},[55,3926,495],{"class":72},[55,3928,625],{"class":491},[55,3930,897],{"class":68},[55,3932,492],{"class":491},[55,3934,495],{"class":72},[55,3936,625],{"class":491},[55,3938,270],{"class":72},[55,3940,3941],{"class":57,"line":108},[55,3942,105],{"emptyLinePlaceholder":104},[55,3944,3945],{"class":57,"line":114},[55,3946,3947],{"class":61},"-- Or, if you always want to add 1 on conflict regardless of the INSERT value:\n",[55,3949,3950,3952,3954,3956,3958,3960,3962,3964,3966,3968,3970,3972],{"class":57,"line":120},[55,3951,69],{"class":68},[55,3953,3791],{"class":72},[55,3955,3794],{"class":68},[55,3957,92],{"class":72},[55,3959,625],{"class":68},[55,3961,450],{"class":72},[55,3963,82],{"class":68},[55,3965,85],{"class":72},[55,3967,3807],{"class":88},[55,3969,92],{"class":72},[55,3971,1034],{"class":491},[55,3973,464],{"class":72},[55,3975,3976,3978,3980,3982,3984,3986,3988,3990,3992,3994,3996,3998,4000,4002],{"class":57,"line":134},[55,3977,469],{"class":68},[55,3979,653],{"class":72},[55,3981,3794],{"class":68},[55,3983,3824],{"class":72},[55,3985,32],{"class":68},[55,3987,482],{"class":68},[55,3989,669],{"class":68},[55,3991,488],{"class":68},[55,3993,890],{"class":491},[55,3995,495],{"class":72},[55,3997,625],{"class":491},[55,3999,897],{"class":68},[55,4001,1364],{"class":491},[55,4003,270],{"class":72},[14,4005,4006,4009,4010,4012,4013,4015,4016,495],{},[18,4007,4008],{},"counters.value"," is the existing row's value; ",[18,4011,3865],{}," is the value from the ",[18,4014,82],{}," clause (the \"excluded\" insert that lost the conflict). For an increment-on-conflict, you want the existing value plus the proposed increment — ",[18,4017,4018],{},"counters.value + EXCLUDED.value",[14,4020,4021,3466,4024,3652,4027,4030,4031,4033],{},[783,4022,4023],{},"The lesson",[18,4025,4026],{},"DO UPDATE SET col = \u003Cliteral>",[18,4028,4029],{},"DO UPDATE SET col = table.col + EXCLUDED.col"," accumulates. Always reference ",[18,4032,800],{}," for the proposed values and the table name for existing values; don't restate the literal.",[4035,4036,4037],"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 .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}html .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);}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}",{"title":51,"searchDepth":65,"depth":65,"links":4039},[4040,4044,4048,4049,4053,4057,4058,4059,4060,4061,4062,4063],{"id":41,"depth":65,"text":20,"children":4041},[4042,4043],{"id":300,"depth":101,"text":301},{"id":324,"depth":101,"text":325},{"id":417,"depth":65,"text":418,"children":4045},[4046],{"id":797,"depth":101,"text":4047},"EXCLUDED vs Table Name",{"id":958,"depth":65,"text":28},{"id":1245,"depth":65,"text":32,"children":4050},[4051,4052],{"id":1527,"depth":101,"text":1528},{"id":1646,"depth":101,"text":1647},{"id":1895,"depth":65,"text":36,"children":4054},[4055,4056],{"id":2044,"depth":101,"text":2045},{"id":2166,"depth":101,"text":2167},{"id":2206,"depth":65,"text":2207},{"id":2771,"depth":65,"text":2772},{"id":3125,"depth":65,"text":3126},{"id":3344,"depth":65,"text":3345},{"id":3449,"depth":65,"text":3450},{"id":3572,"depth":65,"text":3573},{"id":3773,"depth":65,"text":3774},"DML (Data Manipulation Language) modifies rows. This chapter is a code-first reference for INSERT, upsert (ON CONFLICT), RETURNING, join-based UPDATE\u002FDELETE, and the batch patterns that keep large-table modifications from locking your database.","md",{},"\u002Fsql\u002F14-insert-update-delete",{"title":5,"description":4064},"sql\u002F14-insert-update-delete","_MkQWgEmtrsZzoYtaCN_d3M-PnC1wt1xpp_Ohnmg3as",1789924654574]