[{"data":1,"prerenderedAt":5126},["ShallowReactive",2],{"page-\u002Fsql\u002F30-exercises-and-projects":3},{"id":4,"title":5,"body":6,"description":43,"extension":5120,"meta":5121,"navigation":682,"path":5122,"seo":5123,"stem":5124,"__hash__":5125},"content\u002Fsql\u002F30-exercises-and-projects.md","30 — Exercises & Project Ideas",{"type":7,"value":8,"toc":5085},"minimark",[9,13,18,23,27,33,47,186,190,193,197,205,398,402,405,607,611,621,766,770,781,951,955,959,965,969,977,1107,1111,1122,1238,1242,1251,1587,1591,1602,1910,1914,1917,2110,2114,2123,2375,2379,2394,2620,2624,2628,2635,2807,2811,2814,3034,3038,3055,3485,3489,3500,3774,3778,3784,4021,4025,4040,4317,4321,4328,4592,4596,4599,4863,4867,4871,4874,4879,4895,4899,4914,4918,4944,4948,4962,4966,5001,5005,5012,5016,5033,5037,5081],[10,11,5],"h1",{"id":12},"_30-exercises-project-ideas",[14,15,17],"h2",{"id":16},"beginner","Beginner",[19,20,22],"h3",{"id":21},"_1-customer-lookup-select-where-order-by","1. Customer Lookup (SELECT, WHERE, ORDER BY)",[24,25,26],"p",{},"Find all customers in NYC or LA, sorted by name. Add a column showing whether their name starts with a vowel.",[24,28,29],{},[30,31,32],"strong",{},"Expected output:",[34,35,37],"code-wrapper",{"language":36},"text",[38,39,44],"pre",{"className":40,"code":42,"language":36,"meta":43},[41],"language-text"," name     | city | starts_with_vowel\n----------+------+-------------------\n Alice    | NYC  | t\n Bob      | LA   | f\n Eve      | NYC  | t\n","",[45,46,42],"code",{"__ignoreMap":43},[48,49,50,54],"details",{},[51,52,53],"summary",{},"Solution",[34,55,57],{"language":56},"sql",[38,58,61],{"className":59,"code":60,"language":56,"meta":43,"style":43},"language-sql shiki shiki-themes github-light github-dark","SELECT name, city,\n  CASE WHEN lower(name) ~ '^[aeiou]' THEN true ELSE false END AS starts_with_vowel\nFROM customers\nWHERE city IN ('NYC', 'LA')\nORDER BY name;\n-- ~ is PostgreSQL regex match operator; ^[aeiou] anchors to start\n-- lower() makes it case-insensitive (PostgreSQL regex is case-sensitive by default)\n",[45,62,63,79,126,135,162,173,180],{"__ignoreMap":43},[64,65,68,72,75],"span",{"class":66,"line":67},"line",1,[64,69,71],{"class":70},"svdQ7","SELECT",[64,73,74],{"class":70}," name",[64,76,78],{"class":77},"ssxIu",", city,\n",[64,80,82,85,88,92,95,98,101,105,108,111,114,117,120,123],{"class":66,"line":81},2,[64,83,84],{"class":70},"  CASE",[64,86,87],{"class":70}," WHEN",[64,89,91],{"class":90},"snvgF"," lower",[64,93,94],{"class":77},"(",[64,96,97],{"class":70},"name",[64,99,100],{"class":77},") ~ ",[64,102,104],{"class":103},"sJ6F3","'^[aeiou]'",[64,106,107],{"class":70}," THEN",[64,109,110],{"class":77}," true ",[64,112,113],{"class":70},"ELSE",[64,115,116],{"class":77}," false ",[64,118,119],{"class":70},"END",[64,121,122],{"class":70}," AS",[64,124,125],{"class":77}," starts_with_vowel\n",[64,127,129,132],{"class":66,"line":128},3,[64,130,131],{"class":70},"FROM",[64,133,134],{"class":77}," customers\n",[64,136,138,141,144,147,150,153,156,159],{"class":66,"line":137},4,[64,139,140],{"class":70},"WHERE",[64,142,143],{"class":77}," city ",[64,145,146],{"class":70},"IN",[64,148,149],{"class":77}," (",[64,151,152],{"class":103},"'NYC'",[64,154,155],{"class":77},", ",[64,157,158],{"class":103},"'LA'",[64,160,161],{"class":77},")\n",[64,163,165,168,170],{"class":66,"line":164},5,[64,166,167],{"class":70},"ORDER BY",[64,169,74],{"class":70},[64,171,172],{"class":77},";\n",[64,174,176],{"class":66,"line":175},6,[64,177,179],{"class":178},"sdCPZ","-- ~ is PostgreSQL regex match operator; ^[aeiou] anchors to start\n",[64,181,183],{"class":66,"line":182},7,[64,184,185],{"class":178},"-- lower() makes it case-insensitive (PostgreSQL regex is case-sensitive by default)\n",[19,187,189],{"id":188},"_2-order-summary-aggregation-group-by","2. Order Summary (Aggregation, GROUP BY)",[24,191,192],{},"For each customer, show their name, number of orders, total spent, and average order amount. Sort by total spent descending. Include customers with zero orders.",[24,194,195],{},[30,196,32],{},[34,198,199],{"language":36},[38,200,203],{"className":201,"code":202,"language":36,"meta":43},[41]," name    | order_count | total_spent | avg_order\n---------+-------------+-------------+----------\n Alice   |          15 |     2340.50 |   156.03\n Bob     |           3 |      450.00 |   150.00\n Charlie |           0 |        0.00 |    0.00\n",[45,204,202],{"__ignoreMap":43},[48,206,207,209],{},[51,208,53],{},[34,210,211],{"language":56},[38,212,214],{"className":59,"code":213,"language":56,"meta":43,"style":43},"SELECT c.name,\n  COUNT(o.id) AS order_count,               -- COUNT(o.id) skips NULLs (unmatched LEFT JOIN)\n  COALESCE(SUM(o.amount), 0) AS total_spent, -- SUM of empty set is NULL → COALESCE to 0\n  COALESCE(AVG(o.amount), 0) AS avg_order    -- AVG of empty set is NULL → COALESCE to 0\nFROM customers c\nLEFT JOIN orders o ON c.id = o.customer_id   -- LEFT JOIN: customers with 0 orders included\nGROUP BY c.id, c.name                         -- group by PK + name for correctness\nORDER BY total_spent DESC;\n",[45,215,216,231,258,293,324,331,362,385],{"__ignoreMap":43},[64,217,218,220,223,226,228],{"class":66,"line":67},[64,219,71],{"class":70},[64,221,222],{"class":90}," c",[64,224,225],{"class":77},".",[64,227,97],{"class":90},[64,229,230],{"class":77},",\n",[64,232,233,236,238,241,243,246,249,252,255],{"class":66,"line":81},[64,234,235],{"class":90},"  COUNT",[64,237,94],{"class":77},[64,239,240],{"class":90},"o",[64,242,225],{"class":77},[64,244,245],{"class":90},"id",[64,247,248],{"class":77},") ",[64,250,251],{"class":70},"AS",[64,253,254],{"class":77}," order_count,               ",[64,256,257],{"class":178},"-- COUNT(o.id) skips NULLs (unmatched LEFT JOIN)\n",[64,259,260,263,265,268,270,272,274,277,280,283,285,287,290],{"class":66,"line":128},[64,261,262],{"class":90},"  COALESCE",[64,264,94],{"class":77},[64,266,267],{"class":90},"SUM",[64,269,94],{"class":77},[64,271,240],{"class":90},[64,273,225],{"class":77},[64,275,276],{"class":90},"amount",[64,278,279],{"class":77},"), ",[64,281,282],{"class":90},"0",[64,284,248],{"class":77},[64,286,251],{"class":70},[64,288,289],{"class":77}," total_spent, ",[64,291,292],{"class":178},"-- SUM of empty set is NULL → COALESCE to 0\n",[64,294,295,297,299,302,304,306,308,310,312,314,316,318,321],{"class":66,"line":137},[64,296,262],{"class":90},[64,298,94],{"class":77},[64,300,301],{"class":90},"AVG",[64,303,94],{"class":77},[64,305,240],{"class":90},[64,307,225],{"class":77},[64,309,276],{"class":90},[64,311,279],{"class":77},[64,313,282],{"class":90},[64,315,248],{"class":77},[64,317,251],{"class":70},[64,319,320],{"class":77}," avg_order    ",[64,322,323],{"class":178},"-- AVG of empty set is NULL → COALESCE to 0\n",[64,325,326,328],{"class":66,"line":164},[64,327,131],{"class":70},[64,329,330],{"class":77}," customers c\n",[64,332,333,336,339,342,344,346,348,351,354,356,359],{"class":66,"line":175},[64,334,335],{"class":70},"LEFT JOIN",[64,337,338],{"class":77}," orders o ",[64,340,341],{"class":70},"ON",[64,343,222],{"class":90},[64,345,225],{"class":77},[64,347,245],{"class":90},[64,349,350],{"class":70}," =",[64,352,353],{"class":90}," o",[64,355,225],{"class":77},[64,357,358],{"class":90},"customer_id",[64,360,361],{"class":178},"   -- LEFT JOIN: customers with 0 orders included\n",[64,363,364,367,369,371,373,375,378,380,382],{"class":66,"line":182},[64,365,366],{"class":70},"GROUP BY",[64,368,222],{"class":90},[64,370,225],{"class":77},[64,372,245],{"class":90},[64,374,155],{"class":77},[64,376,377],{"class":90},"c",[64,379,225],{"class":77},[64,381,97],{"class":90},[64,383,384],{"class":178},"                         -- group by PK + name for correctness\n",[64,386,388,390,393,396],{"class":66,"line":387},8,[64,389,167],{"class":70},[64,391,392],{"class":77}," total_spent ",[64,394,395],{"class":70},"DESC",[64,397,172],{"class":77},[19,399,401],{"id":400},"_3-city-report-group-by-having","3. City Report (GROUP BY, HAVING)",[24,403,404],{},"List cities with at least 2 customers, showing the count and the average customer's total spend.",[48,406,407,409],{},[51,408,53],{},[34,410,411],{"language":56},[38,412,414],{"className":59,"code":413,"language":56,"meta":43,"style":43},"SELECT c.city,\n  COUNT(DISTINCT c.id) AS customer_count,     -- DISTINCT: one customer per row even with multiple orders\n  ROUND(AVG(o.total_per_customer), 2) AS avg_customer_spend\nFROM customers c\nLEFT JOIN LATERAL (\n  -- LATERAL: per-customer aggregate, then average across customers per city\n  SELECT SUM(amount) AS total_per_customer\n  FROM orders o WHERE o.customer_id = c.id\n) o ON true\nGROUP BY c.city\nHAVING COUNT(DISTINCT c.id) >= 2              -- HAVING filters on aggregates (WHERE filters per-row)\nORDER BY avg_customer_spend DESC;\n",[45,415,416,429,454,484,490,497,502,518,542,553,565,595],{"__ignoreMap":43},[64,417,418,420,422,424,427],{"class":66,"line":67},[64,419,71],{"class":70},[64,421,222],{"class":90},[64,423,225],{"class":77},[64,425,426],{"class":90},"city",[64,428,230],{"class":77},[64,430,431,433,435,438,440,442,444,446,448,451],{"class":66,"line":81},[64,432,235],{"class":90},[64,434,94],{"class":77},[64,436,437],{"class":70},"DISTINCT",[64,439,222],{"class":90},[64,441,225],{"class":77},[64,443,245],{"class":90},[64,445,248],{"class":77},[64,447,251],{"class":70},[64,449,450],{"class":77}," customer_count,     ",[64,452,453],{"class":178},"-- DISTINCT: one customer per row even with multiple orders\n",[64,455,456,459,461,463,465,467,469,472,474,477,479,481],{"class":66,"line":128},[64,457,458],{"class":90},"  ROUND",[64,460,94],{"class":77},[64,462,301],{"class":90},[64,464,94],{"class":77},[64,466,240],{"class":90},[64,468,225],{"class":77},[64,470,471],{"class":90},"total_per_customer",[64,473,279],{"class":77},[64,475,476],{"class":90},"2",[64,478,248],{"class":77},[64,480,251],{"class":70},[64,482,483],{"class":77}," avg_customer_spend\n",[64,485,486,488],{"class":66,"line":137},[64,487,131],{"class":70},[64,489,330],{"class":77},[64,491,492,494],{"class":66,"line":164},[64,493,335],{"class":70},[64,495,496],{"class":77}," LATERAL (\n",[64,498,499],{"class":66,"line":175},[64,500,501],{"class":178},"  -- LATERAL: per-customer aggregate, then average across customers per city\n",[64,503,504,507,510,513,515],{"class":66,"line":182},[64,505,506],{"class":70},"  SELECT",[64,508,509],{"class":90}," SUM",[64,511,512],{"class":77},"(amount) ",[64,514,251],{"class":70},[64,516,517],{"class":77}," total_per_customer\n",[64,519,520,523,525,527,529,531,533,535,537,539],{"class":66,"line":387},[64,521,522],{"class":70},"  FROM",[64,524,338],{"class":77},[64,526,140],{"class":70},[64,528,353],{"class":90},[64,530,225],{"class":77},[64,532,358],{"class":90},[64,534,350],{"class":70},[64,536,222],{"class":90},[64,538,225],{"class":77},[64,540,541],{"class":90},"id\n",[64,543,545,548,550],{"class":66,"line":544},9,[64,546,547],{"class":77},") o ",[64,549,341],{"class":70},[64,551,552],{"class":77}," true\n",[64,554,556,558,560,562],{"class":66,"line":555},10,[64,557,366],{"class":70},[64,559,222],{"class":90},[64,561,225],{"class":77},[64,563,564],{"class":90},"city\n",[64,566,568,571,574,576,578,580,582,584,586,589,592],{"class":66,"line":567},11,[64,569,570],{"class":70},"HAVING",[64,572,573],{"class":90}," COUNT",[64,575,94],{"class":77},[64,577,437],{"class":70},[64,579,222],{"class":90},[64,581,225],{"class":77},[64,583,245],{"class":90},[64,585,248],{"class":77},[64,587,588],{"class":70},">=",[64,590,591],{"class":90}," 2",[64,593,594],{"class":178},"              -- HAVING filters on aggregates (WHERE filters per-row)\n",[64,596,598,600,603,605],{"class":66,"line":597},12,[64,599,167],{"class":70},[64,601,602],{"class":77}," avg_customer_spend ",[64,604,395],{"class":70},[64,606,172],{"class":77},[19,608,610],{"id":609},"_4-keyset-pagination-limit-where","4. Keyset Pagination (LIMIT, WHERE)",[24,612,613,614,617,618,225],{},"Implement keyset pagination for orders ordered by ",[45,615,616],{},"(ordered_on DESC, id DESC)",". Write the query for page 1, then page 2 given the last row's ",[45,619,620],{},"(ordered_on, id)",[48,622,623,625],{},[51,624,53],{},[34,626,627],{"language":56},[38,628,630],{"className":59,"code":629,"language":56,"meta":43,"style":43},"-- Page 1: no cursor, just the first 20 rows\nSELECT id, customer_id, amount, ordered_on\nFROM orders\nORDER BY ordered_on DESC, id DESC  -- unique tie-breaker prevents row drift\nLIMIT 20;\n\n-- Page 2: use the last row from page 1 as the cursor\n-- Suppose page 1 ended with ordered_on='2024-06-15', id=12345\nSELECT id, customer_id, amount, ordered_on\nFROM orders\nWHERE (ordered_on, id) \u003C ('2024-06-15', 12345)  -- row-wise comparison: skip everything ≤ cursor\nORDER BY ordered_on DESC, id DESC\nLIMIT 20;\n-- The (ordered_on, id) \u003C (cursor) comparison uses the index efficiently\n-- O(log N) to find the start point, vs O(N) for OFFSET pagination\n",[45,631,632,637,644,651,668,678,684,689,694,700,706,732,745,754,760],{"__ignoreMap":43},[64,633,634],{"class":66,"line":67},[64,635,636],{"class":178},"-- Page 1: no cursor, just the first 20 rows\n",[64,638,639,641],{"class":66,"line":81},[64,640,71],{"class":70},[64,642,643],{"class":77}," id, customer_id, amount, ordered_on\n",[64,645,646,648],{"class":66,"line":128},[64,647,131],{"class":70},[64,649,650],{"class":77}," orders\n",[64,652,653,655,658,660,663,665],{"class":66,"line":137},[64,654,167],{"class":70},[64,656,657],{"class":77}," ordered_on ",[64,659,395],{"class":70},[64,661,662],{"class":77},", id ",[64,664,395],{"class":70},[64,666,667],{"class":178},"  -- unique tie-breaker prevents row drift\n",[64,669,670,673,676],{"class":66,"line":164},[64,671,672],{"class":70},"LIMIT",[64,674,675],{"class":90}," 20",[64,677,172],{"class":77},[64,679,680],{"class":66,"line":175},[64,681,683],{"emptyLinePlaceholder":682},true,"\n",[64,685,686],{"class":66,"line":182},[64,687,688],{"class":178},"-- Page 2: use the last row from page 1 as the cursor\n",[64,690,691],{"class":66,"line":387},[64,692,693],{"class":178},"-- Suppose page 1 ended with ordered_on='2024-06-15', id=12345\n",[64,695,696,698],{"class":66,"line":544},[64,697,71],{"class":70},[64,699,643],{"class":77},[64,701,702,704],{"class":66,"line":555},[64,703,131],{"class":70},[64,705,650],{"class":77},[64,707,708,710,713,716,718,721,723,726,729],{"class":66,"line":567},[64,709,140],{"class":70},[64,711,712],{"class":77}," (ordered_on, id) ",[64,714,715],{"class":70},"\u003C",[64,717,149],{"class":77},[64,719,720],{"class":103},"'2024-06-15'",[64,722,155],{"class":77},[64,724,725],{"class":90},"12345",[64,727,728],{"class":77},")  ",[64,730,731],{"class":178},"-- row-wise comparison: skip everything ≤ cursor\n",[64,733,734,736,738,740,742],{"class":66,"line":597},[64,735,167],{"class":70},[64,737,657],{"class":77},[64,739,395],{"class":70},[64,741,662],{"class":77},[64,743,744],{"class":70},"DESC\n",[64,746,748,750,752],{"class":66,"line":747},13,[64,749,672],{"class":70},[64,751,675],{"class":90},[64,753,172],{"class":77},[64,755,757],{"class":66,"line":756},14,[64,758,759],{"class":178},"-- The (ordered_on, id) \u003C (cursor) comparison uses the index efficiently\n",[64,761,763],{"class":66,"line":762},15,[64,764,765],{"class":178},"-- O(log N) to find the start point, vs O(N) for OFFSET pagination\n",[19,767,769],{"id":768},"_5-anti-join-customers-without-orders","5. Anti-Join (Customers Without Orders)",[24,771,772,773,776,777,780],{},"Find customers who have never placed an order, using both ",[45,774,775],{},"LEFT JOIN ... IS NULL"," and ",[45,778,779],{},"NOT EXISTS",". Compare the two approaches.",[48,782,783,785],{},[51,784,53],{},[34,786,787],{"language":56},[38,788,790],{"className":59,"code":789,"language":56,"meta":43,"style":43},"-- Approach 1: LEFT JOIN + IS NULL\nSELECT c.name\nFROM customers c\nLEFT JOIN orders o ON c.id = o.customer_id\nWHERE o.id IS NULL;  -- only unmatched rows (o.* is NULL)\n-- The LEFT JOIN produces all customers; the WHERE keeps only unmatched ones\n\n-- Approach 2: NOT EXISTS (NULL-safe, often faster as an Anti Join)\nSELECT c.name\nFROM customers c\nWHERE NOT EXISTS (\n  SELECT 1 FROM orders o WHERE o.customer_id = c.id\n);\n-- The planner can implement this as an Anti Join: scan orders and stop\n-- at the first match per customer — no need to collect all matches\n-- NOT EXISTS is also NULL-safe (unlike NOT IN, which breaks on NULLs)\n",[45,791,792,797,808,814,837,859,864,868,873,883,889,902,930,935,940,945],{"__ignoreMap":43},[64,793,794],{"class":66,"line":67},[64,795,796],{"class":178},"-- Approach 1: LEFT JOIN + IS NULL\n",[64,798,799,801,803,805],{"class":66,"line":81},[64,800,71],{"class":70},[64,802,222],{"class":90},[64,804,225],{"class":77},[64,806,807],{"class":90},"name\n",[64,809,810,812],{"class":66,"line":128},[64,811,131],{"class":70},[64,813,330],{"class":77},[64,815,816,818,820,822,824,826,828,830,832,834],{"class":66,"line":137},[64,817,335],{"class":70},[64,819,338],{"class":77},[64,821,341],{"class":70},[64,823,222],{"class":90},[64,825,225],{"class":77},[64,827,245],{"class":90},[64,829,350],{"class":70},[64,831,353],{"class":90},[64,833,225],{"class":77},[64,835,836],{"class":90},"customer_id\n",[64,838,839,841,843,845,847,850,853,856],{"class":66,"line":164},[64,840,140],{"class":70},[64,842,353],{"class":90},[64,844,225],{"class":77},[64,846,245],{"class":90},[64,848,849],{"class":70}," IS",[64,851,852],{"class":70}," NULL",[64,854,855],{"class":77},";  ",[64,857,858],{"class":178},"-- only unmatched rows (o.* is NULL)\n",[64,860,861],{"class":66,"line":175},[64,862,863],{"class":178},"-- The LEFT JOIN produces all customers; the WHERE keeps only unmatched ones\n",[64,865,866],{"class":66,"line":182},[64,867,683],{"emptyLinePlaceholder":682},[64,869,870],{"class":66,"line":387},[64,871,872],{"class":178},"-- Approach 2: NOT EXISTS (NULL-safe, often faster as an Anti Join)\n",[64,874,875,877,879,881],{"class":66,"line":544},[64,876,71],{"class":70},[64,878,222],{"class":90},[64,880,225],{"class":77},[64,882,807],{"class":90},[64,884,885,887],{"class":66,"line":555},[64,886,131],{"class":70},[64,888,330],{"class":77},[64,890,891,893,896,899],{"class":66,"line":567},[64,892,140],{"class":70},[64,894,895],{"class":70}," NOT",[64,897,898],{"class":70}," EXISTS",[64,900,901],{"class":77}," (\n",[64,903,904,906,909,912,914,916,918,920,922,924,926,928],{"class":66,"line":597},[64,905,506],{"class":70},[64,907,908],{"class":90}," 1",[64,910,911],{"class":70}," FROM",[64,913,338],{"class":77},[64,915,140],{"class":70},[64,917,353],{"class":90},[64,919,225],{"class":77},[64,921,358],{"class":90},[64,923,350],{"class":70},[64,925,222],{"class":90},[64,927,225],{"class":77},[64,929,541],{"class":90},[64,931,932],{"class":66,"line":747},[64,933,934],{"class":77},");\n",[64,936,937],{"class":66,"line":756},[64,938,939],{"class":178},"-- The planner can implement this as an Anti Join: scan orders and stop\n",[64,941,942],{"class":66,"line":762},[64,943,944],{"class":178},"-- at the first match per customer — no need to collect all matches\n",[64,946,948],{"class":66,"line":947},16,[64,949,950],{"class":178},"-- NOT EXISTS is also NULL-safe (unlike NOT IN, which breaks on NULLs)\n",[14,952,954],{"id":953},"intermediate","Intermediate",[19,956,958],{"id":957},"_6-top-3-orders-per-customer-window-functions","6. Top 3 Orders per Customer (Window Functions)",[24,960,961,962,225],{},"For each customer, show their top 3 orders by amount. Use ",[45,963,964],{},"ROW_NUMBER() OVER (PARTITION BY ...)",[24,966,967],{},[30,968,32],{},[34,970,971],{"language":36},[38,972,975],{"className":973,"code":974,"language":36,"meta":43},[41]," customer_id | order_id | amount | rank\n-------------+----------+--------+------\n           1 |      245 | 500.00 |    1\n           1 |      189 | 350.00 |    2\n           1 |      412 | 280.00 |    3\n           2 |      567 | 900.00 |    1\n",[45,976,974],{"__ignoreMap":43},[48,978,979,981],{},[51,980,53],{},[34,982,983],{"language":56},[38,984,986],{"className":59,"code":985,"language":56,"meta":43,"style":43},"WITH ranked AS (\n  SELECT customer_id, id AS order_id, amount,\n    ROW_NUMBER() OVER (PARTITION BY customer_id ORDER BY amount DESC) AS rn\n  FROM orders\n  -- ROW_NUMBER assigns 1, 2, 3... within each customer partition\n  -- Ties get different numbers (use RANK() if ties should share a rank)\n)\nSELECT customer_id, order_id, amount, rn AS rank\nFROM ranked\nWHERE rn \u003C= 3\nORDER BY customer_id, rn;\n",[45,987,988,1000,1012,1048,1054,1059,1064,1068,1080,1087,1100],{"__ignoreMap":43},[64,989,990,993,996,998],{"class":66,"line":67},[64,991,992],{"class":70},"WITH",[64,994,995],{"class":77}," ranked ",[64,997,251],{"class":70},[64,999,901],{"class":77},[64,1001,1002,1004,1007,1009],{"class":66,"line":81},[64,1003,506],{"class":70},[64,1005,1006],{"class":77}," customer_id, id ",[64,1008,251],{"class":70},[64,1010,1011],{"class":77}," order_id, amount,\n",[64,1013,1014,1017,1020,1023,1025,1028,1031,1034,1036,1039,1041,1043,1045],{"class":66,"line":128},[64,1015,1016],{"class":90},"    ROW_NUMBER",[64,1018,1019],{"class":77},"() ",[64,1021,1022],{"class":70},"OVER",[64,1024,149],{"class":77},[64,1026,1027],{"class":70},"PARTITION",[64,1029,1030],{"class":70}," BY",[64,1032,1033],{"class":77}," customer_id ",[64,1035,167],{"class":70},[64,1037,1038],{"class":77}," amount ",[64,1040,395],{"class":70},[64,1042,248],{"class":77},[64,1044,251],{"class":70},[64,1046,1047],{"class":77}," rn\n",[64,1049,1050,1052],{"class":66,"line":137},[64,1051,522],{"class":70},[64,1053,650],{"class":77},[64,1055,1056],{"class":66,"line":164},[64,1057,1058],{"class":178},"  -- ROW_NUMBER assigns 1, 2, 3... within each customer partition\n",[64,1060,1061],{"class":66,"line":175},[64,1062,1063],{"class":178},"  -- Ties get different numbers (use RANK() if ties should share a rank)\n",[64,1065,1066],{"class":66,"line":182},[64,1067,161],{"class":77},[64,1069,1070,1072,1075,1077],{"class":66,"line":387},[64,1071,71],{"class":70},[64,1073,1074],{"class":77}," customer_id, order_id, amount, rn ",[64,1076,251],{"class":70},[64,1078,1079],{"class":77}," rank\n",[64,1081,1082,1084],{"class":66,"line":544},[64,1083,131],{"class":70},[64,1085,1086],{"class":77}," ranked\n",[64,1088,1089,1091,1094,1097],{"class":66,"line":555},[64,1090,140],{"class":70},[64,1092,1093],{"class":77}," rn ",[64,1095,1096],{"class":70},"\u003C=",[64,1098,1099],{"class":90}," 3\n",[64,1101,1102,1104],{"class":66,"line":567},[64,1103,167],{"class":70},[64,1105,1106],{"class":77}," customer_id, rn;\n",[19,1108,1110],{"id":1109},"_7-running-total-with-ties-window-functions","7. Running Total with Ties (Window Functions)",[24,1112,1113,1114,1117,1118,1121],{},"For each customer, show each order with a running total of their spending, ordered by date. Handle ties (same-day orders) with ",[45,1115,1116],{},"ROWS"," (not ",[45,1119,1120],{},"RANGE",").",[48,1123,1124,1126],{},[51,1125,53],{},[34,1127,1128],{"language":56},[38,1129,1131],{"className":59,"code":1130,"language":56,"meta":43,"style":43},"SELECT customer_id, id, ordered_on, amount,\n  SUM(amount) OVER (\n    PARTITION BY customer_id\n    ORDER BY ordered_on, id  -- unique tie-breaker for deterministic ordering\n    ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW\n  ) AS running_total\nFROM orders\nORDER BY customer_id, ordered_on, id;\n-- ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: strict per-row accumulation\n-- Without ROWS, the default RANGE includes PEERS (rows with same ordered_on),\n-- causing the running total to \"jump\" — all same-day rows get the same total\n-- ROWS ensures each row's running total includes only rows up to that exact row\n",[45,1132,1133,1140,1151,1161,1172,1195,1205,1211,1218,1223,1228,1233],{"__ignoreMap":43},[64,1134,1135,1137],{"class":66,"line":67},[64,1136,71],{"class":70},[64,1138,1139],{"class":77}," customer_id, id, ordered_on, amount,\n",[64,1141,1142,1145,1147,1149],{"class":66,"line":81},[64,1143,1144],{"class":90},"  SUM",[64,1146,512],{"class":77},[64,1148,1022],{"class":70},[64,1150,901],{"class":77},[64,1152,1153,1156,1158],{"class":66,"line":128},[64,1154,1155],{"class":70},"    PARTITION",[64,1157,1030],{"class":70},[64,1159,1160],{"class":77}," customer_id\n",[64,1162,1163,1166,1169],{"class":66,"line":137},[64,1164,1165],{"class":70},"    ORDER BY",[64,1167,1168],{"class":77}," ordered_on, id  ",[64,1170,1171],{"class":178},"-- unique tie-breaker for deterministic ordering\n",[64,1173,1174,1177,1180,1183,1186,1189,1192],{"class":66,"line":164},[64,1175,1176],{"class":70},"    ROWS",[64,1178,1179],{"class":70}," BETWEEN",[64,1181,1182],{"class":70}," UNBOUNDED",[64,1184,1185],{"class":70}," PRECEDING",[64,1187,1188],{"class":70}," AND",[64,1190,1191],{"class":77}," CURRENT ",[64,1193,1194],{"class":70},"ROW\n",[64,1196,1197,1200,1202],{"class":66,"line":175},[64,1198,1199],{"class":77},"  ) ",[64,1201,251],{"class":70},[64,1203,1204],{"class":77}," running_total\n",[64,1206,1207,1209],{"class":66,"line":182},[64,1208,131],{"class":70},[64,1210,650],{"class":77},[64,1212,1213,1215],{"class":66,"line":387},[64,1214,167],{"class":70},[64,1216,1217],{"class":77}," customer_id, ordered_on, id;\n",[64,1219,1220],{"class":66,"line":544},[64,1221,1222],{"class":178},"-- ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW: strict per-row accumulation\n",[64,1224,1225],{"class":66,"line":555},[64,1226,1227],{"class":178},"-- Without ROWS, the default RANGE includes PEERS (rows with same ordered_on),\n",[64,1229,1230],{"class":66,"line":567},[64,1231,1232],{"class":178},"-- causing the running total to \"jump\" — all same-day rows get the same total\n",[64,1234,1235],{"class":66,"line":597},[64,1236,1237],{"class":178},"-- ROWS ensures each row's running total includes only rows up to that exact row\n",[19,1239,1241],{"id":1240},"_8-monthly-revenue-with-zero-months-date_trunc-generate_series","8. Monthly Revenue with Zero Months (date_trunc, generate_series)",[24,1243,1244,1245,1248,1249,225],{},"Build a monthly revenue report for 2024, including months with zero revenue. Use ",[45,1246,1247],{},"generate_series"," + ",[45,1250,335],{},[48,1252,1253,1255],{},[51,1254,53],{},[34,1256,1257],{"language":56},[38,1258,1260],{"className":59,"code":1259,"language":56,"meta":43,"style":43},"WITH months AS (\n  -- generate_series produces one row per month, including months with no orders\n  SELECT generate_series(\n    '2024-01-01'::timestamptz,\n    '2024-12-01'::timestamptz,\n    '1 month'::interval\n  ) AS month\n),\nmonthly_revenue AS (\n  SELECT date_trunc('month', ordered_on) AS month, SUM(amount) AS revenue\n  FROM orders\n  WHERE ordered_on >= '2024-01-01' AND ordered_on \u003C '2025-01-01'\n  GROUP BY month\n)\nSELECT m.month,\n  COALESCE(mr.revenue, 0) AS revenue,  -- months with no orders show 0, not NULL\n  COUNT(o.id) AS order_count            -- COUNT via a LEFT JOIN to orders\nFROM months m\nLEFT JOIN monthly_revenue mr ON mr.month = m.month\nLEFT JOIN orders o ON date_trunc('month', o.ordered_on) = m.month\nGROUP BY m.month, mr.revenue\nORDER BY m.month;\n-- The LEFT JOIN from months → revenue ensures all 12 months appear\n-- COALESCE converts NULL (no match) to 0 for display\n",[45,1261,1262,1273,1278,1288,1301,1312,1320,1329,1334,1343,1372,1378,1399,1406,1410,1424,1452,1475,1483,1509,1542,1562,1575,1581],{"__ignoreMap":43},[64,1263,1264,1266,1269,1271],{"class":66,"line":67},[64,1265,992],{"class":70},[64,1267,1268],{"class":77}," months ",[64,1270,251],{"class":70},[64,1272,901],{"class":77},[64,1274,1275],{"class":66,"line":81},[64,1276,1277],{"class":178},"  -- generate_series produces one row per month, including months with no orders\n",[64,1279,1280,1282,1285],{"class":66,"line":128},[64,1281,506],{"class":70},[64,1283,1284],{"class":90}," generate_series",[64,1286,1287],{"class":77},"(\n",[64,1289,1290,1293,1296,1299],{"class":66,"line":137},[64,1291,1292],{"class":103},"    '2024-01-01'",[64,1294,1295],{"class":77},"::",[64,1297,1298],{"class":70},"timestamptz",[64,1300,230],{"class":77},[64,1302,1303,1306,1308,1310],{"class":66,"line":164},[64,1304,1305],{"class":103},"    '2024-12-01'",[64,1307,1295],{"class":77},[64,1309,1298],{"class":70},[64,1311,230],{"class":77},[64,1313,1314,1317],{"class":66,"line":175},[64,1315,1316],{"class":103},"    '1 month'",[64,1318,1319],{"class":77},"::interval\n",[64,1321,1322,1324,1326],{"class":66,"line":182},[64,1323,1199],{"class":77},[64,1325,251],{"class":70},[64,1327,1328],{"class":70}," month\n",[64,1330,1331],{"class":66,"line":387},[64,1332,1333],{"class":77},"),\n",[64,1335,1336,1339,1341],{"class":66,"line":544},[64,1337,1338],{"class":77},"monthly_revenue ",[64,1340,251],{"class":70},[64,1342,901],{"class":77},[64,1344,1345,1347,1350,1353,1356,1358,1361,1363,1365,1367,1369],{"class":66,"line":555},[64,1346,506],{"class":70},[64,1348,1349],{"class":77}," date_trunc(",[64,1351,1352],{"class":103},"'month'",[64,1354,1355],{"class":77},", ordered_on) ",[64,1357,251],{"class":70},[64,1359,1360],{"class":70}," month",[64,1362,155],{"class":77},[64,1364,267],{"class":90},[64,1366,512],{"class":77},[64,1368,251],{"class":70},[64,1370,1371],{"class":77}," revenue\n",[64,1373,1374,1376],{"class":66,"line":567},[64,1375,522],{"class":70},[64,1377,650],{"class":77},[64,1379,1380,1383,1385,1387,1390,1392,1394,1396],{"class":66,"line":597},[64,1381,1382],{"class":70},"  WHERE",[64,1384,657],{"class":77},[64,1386,588],{"class":70},[64,1388,1389],{"class":103}," '2024-01-01'",[64,1391,1188],{"class":70},[64,1393,657],{"class":77},[64,1395,715],{"class":70},[64,1397,1398],{"class":103}," '2025-01-01'\n",[64,1400,1401,1404],{"class":66,"line":747},[64,1402,1403],{"class":70},"  GROUP BY",[64,1405,1328],{"class":70},[64,1407,1408],{"class":66,"line":756},[64,1409,161],{"class":77},[64,1411,1412,1414,1417,1419,1422],{"class":66,"line":762},[64,1413,71],{"class":70},[64,1415,1416],{"class":90}," m",[64,1418,225],{"class":77},[64,1420,1421],{"class":90},"month",[64,1423,230],{"class":77},[64,1425,1426,1428,1430,1433,1435,1438,1440,1442,1444,1446,1449],{"class":66,"line":947},[64,1427,262],{"class":90},[64,1429,94],{"class":77},[64,1431,1432],{"class":90},"mr",[64,1434,225],{"class":77},[64,1436,1437],{"class":90},"revenue",[64,1439,155],{"class":77},[64,1441,282],{"class":90},[64,1443,248],{"class":77},[64,1445,251],{"class":70},[64,1447,1448],{"class":77}," revenue,  ",[64,1450,1451],{"class":178},"-- months with no orders show 0, not NULL\n",[64,1453,1455,1457,1459,1461,1463,1465,1467,1469,1472],{"class":66,"line":1454},17,[64,1456,235],{"class":90},[64,1458,94],{"class":77},[64,1460,240],{"class":90},[64,1462,225],{"class":77},[64,1464,245],{"class":90},[64,1466,248],{"class":77},[64,1468,251],{"class":70},[64,1470,1471],{"class":77}," order_count            ",[64,1473,1474],{"class":178},"-- COUNT via a LEFT JOIN to orders\n",[64,1476,1478,1480],{"class":66,"line":1477},18,[64,1479,131],{"class":70},[64,1481,1482],{"class":77}," months m\n",[64,1484,1486,1488,1491,1493,1496,1498,1500,1502,1504,1506],{"class":66,"line":1485},19,[64,1487,335],{"class":70},[64,1489,1490],{"class":77}," monthly_revenue mr ",[64,1492,341],{"class":70},[64,1494,1495],{"class":90}," mr",[64,1497,225],{"class":77},[64,1499,1421],{"class":90},[64,1501,350],{"class":70},[64,1503,1416],{"class":90},[64,1505,225],{"class":77},[64,1507,1508],{"class":90},"month\n",[64,1510,1512,1514,1516,1518,1520,1522,1524,1526,1528,1531,1533,1536,1538,1540],{"class":66,"line":1511},20,[64,1513,335],{"class":70},[64,1515,338],{"class":77},[64,1517,341],{"class":70},[64,1519,1349],{"class":77},[64,1521,1352],{"class":103},[64,1523,155],{"class":77},[64,1525,240],{"class":90},[64,1527,225],{"class":77},[64,1529,1530],{"class":90},"ordered_on",[64,1532,248],{"class":77},[64,1534,1535],{"class":70},"=",[64,1537,1416],{"class":90},[64,1539,225],{"class":77},[64,1541,1508],{"class":90},[64,1543,1545,1547,1549,1551,1553,1555,1557,1559],{"class":66,"line":1544},21,[64,1546,366],{"class":70},[64,1548,1416],{"class":90},[64,1550,225],{"class":77},[64,1552,1421],{"class":90},[64,1554,155],{"class":77},[64,1556,1432],{"class":90},[64,1558,225],{"class":77},[64,1560,1561],{"class":90},"revenue\n",[64,1563,1565,1567,1569,1571,1573],{"class":66,"line":1564},22,[64,1566,167],{"class":70},[64,1568,1416],{"class":90},[64,1570,225],{"class":77},[64,1572,1421],{"class":90},[64,1574,172],{"class":77},[64,1576,1578],{"class":66,"line":1577},23,[64,1579,1580],{"class":178},"-- The LEFT JOIN from months → revenue ensures all 12 months appear\n",[64,1582,1584],{"class":66,"line":1583},24,[64,1585,1586],{"class":178},"-- COALESCE converts NULL (no match) to 0 for display\n",[19,1588,1590],{"id":1589},"_9-gaps-and-islands-longest-active-streak","9. Gaps and Islands — Longest Active Streak",[24,1592,1593,1594,1597,1598,1601],{},"Given an ",[45,1595,1596],{},"activity"," table ",[45,1599,1600],{},"(user_id, activity_date)",", find the longest streak of consecutive active days per user.",[48,1603,1604,1606],{},[51,1605,53],{},[34,1607,1608],{"language":56},[38,1609,1611],{"className":59,"code":1610,"language":56,"meta":43,"style":43},"WITH day_marks AS (\n  SELECT DISTINCT user_id, activity_date::date AS day  -- DISTINCT: deduplicate same-day events\n  FROM activity\n),\nnumbered AS (\n  SELECT user_id, day,\n    day - ROW_NUMBER() OVER (PARTITION BY user_id ORDER BY day) AS grp\n    -- day - rn is constant within a run of consecutive days (gaps-and-islands trick)\n  FROM day_marks\n),\nstreaks AS (\n  SELECT user_id, grp, COUNT(*) AS streak_length,\n    MIN(day) AS streak_start, MAX(day) AS streak_end\n  FROM numbered\n  GROUP BY user_id, grp\n)\nSELECT user_id, streak_length, streak_start, streak_end\nFROM streaks\nWHERE streak_length = (\n  -- Find the max streak per user (if there are multiple max-length streaks, pick any)\n  SELECT MAX(s2.streak_length) FROM streaks s2 WHERE s2.user_id = streaks.user_id\n)\nORDER BY streak_length DESC;\n-- Or simpler with DISTINCT ON (PostgreSQL extension):\n-- SELECT DISTINCT ON (user_id) user_id, streak_length, streak_start, streak_end\n-- FROM streaks ORDER BY user_id, streak_length DESC;\n",[45,1612,1613,1624,1643,1650,1654,1663,1675,1710,1715,1722,1726,1735,1757,1787,1794,1801,1805,1812,1819,1830,1835,1879,1883,1893,1898,1904],{"__ignoreMap":43},[64,1614,1615,1617,1620,1622],{"class":66,"line":67},[64,1616,992],{"class":70},[64,1618,1619],{"class":77}," day_marks ",[64,1621,251],{"class":70},[64,1623,901],{"class":77},[64,1625,1626,1629,1632,1635,1637,1640],{"class":66,"line":81},[64,1627,1628],{"class":70},"  SELECT DISTINCT",[64,1630,1631],{"class":77}," user_id, activity_date::",[64,1633,1634],{"class":70},"date",[64,1636,122],{"class":70},[64,1638,1639],{"class":70}," day",[64,1641,1642],{"class":178},"  -- DISTINCT: deduplicate same-day events\n",[64,1644,1645,1647],{"class":66,"line":128},[64,1646,522],{"class":70},[64,1648,1649],{"class":77}," activity\n",[64,1651,1652],{"class":66,"line":137},[64,1653,1333],{"class":77},[64,1655,1656,1659,1661],{"class":66,"line":164},[64,1657,1658],{"class":77},"numbered ",[64,1660,251],{"class":70},[64,1662,901],{"class":77},[64,1664,1665,1667,1670,1673],{"class":66,"line":175},[64,1666,506],{"class":70},[64,1668,1669],{"class":77}," user_id, ",[64,1671,1672],{"class":70},"day",[64,1674,230],{"class":77},[64,1676,1677,1680,1683,1686,1688,1690,1692,1694,1696,1699,1701,1703,1705,1707],{"class":66,"line":182},[64,1678,1679],{"class":70},"    day",[64,1681,1682],{"class":70}," -",[64,1684,1685],{"class":90}," ROW_NUMBER",[64,1687,1019],{"class":77},[64,1689,1022],{"class":70},[64,1691,149],{"class":77},[64,1693,1027],{"class":70},[64,1695,1030],{"class":70},[64,1697,1698],{"class":77}," user_id ",[64,1700,167],{"class":70},[64,1702,1639],{"class":70},[64,1704,248],{"class":77},[64,1706,251],{"class":70},[64,1708,1709],{"class":77}," grp\n",[64,1711,1712],{"class":66,"line":387},[64,1713,1714],{"class":178},"    -- day - rn is constant within a run of consecutive days (gaps-and-islands trick)\n",[64,1716,1717,1719],{"class":66,"line":544},[64,1718,522],{"class":70},[64,1720,1721],{"class":77}," day_marks\n",[64,1723,1724],{"class":66,"line":555},[64,1725,1333],{"class":77},[64,1727,1728,1731,1733],{"class":66,"line":567},[64,1729,1730],{"class":77},"streaks ",[64,1732,251],{"class":70},[64,1734,901],{"class":77},[64,1736,1737,1739,1742,1745,1747,1750,1752,1754],{"class":66,"line":597},[64,1738,506],{"class":70},[64,1740,1741],{"class":77}," user_id, grp, ",[64,1743,1744],{"class":90},"COUNT",[64,1746,94],{"class":77},[64,1748,1749],{"class":70},"*",[64,1751,248],{"class":77},[64,1753,251],{"class":70},[64,1755,1756],{"class":77}," streak_length,\n",[64,1758,1759,1762,1764,1766,1768,1770,1773,1776,1778,1780,1782,1784],{"class":66,"line":747},[64,1760,1761],{"class":90},"    MIN",[64,1763,94],{"class":77},[64,1765,1672],{"class":70},[64,1767,248],{"class":77},[64,1769,251],{"class":70},[64,1771,1772],{"class":77}," streak_start, ",[64,1774,1775],{"class":90},"MAX",[64,1777,94],{"class":77},[64,1779,1672],{"class":70},[64,1781,248],{"class":77},[64,1783,251],{"class":70},[64,1785,1786],{"class":77}," streak_end\n",[64,1788,1789,1791],{"class":66,"line":756},[64,1790,522],{"class":70},[64,1792,1793],{"class":77}," numbered\n",[64,1795,1796,1798],{"class":66,"line":762},[64,1797,1403],{"class":70},[64,1799,1800],{"class":77}," user_id, grp\n",[64,1802,1803],{"class":66,"line":947},[64,1804,161],{"class":77},[64,1806,1807,1809],{"class":66,"line":1454},[64,1808,71],{"class":70},[64,1810,1811],{"class":77}," user_id, streak_length, streak_start, streak_end\n",[64,1813,1814,1816],{"class":66,"line":1477},[64,1815,131],{"class":70},[64,1817,1818],{"class":77}," streaks\n",[64,1820,1821,1823,1826,1828],{"class":66,"line":1485},[64,1822,140],{"class":70},[64,1824,1825],{"class":77}," streak_length ",[64,1827,1535],{"class":70},[64,1829,901],{"class":77},[64,1831,1832],{"class":66,"line":1511},[64,1833,1834],{"class":178},"  -- Find the max streak per user (if there are multiple max-length streaks, pick any)\n",[64,1836,1837,1839,1842,1844,1847,1849,1852,1854,1856,1859,1861,1864,1866,1869,1871,1874,1876],{"class":66,"line":1544},[64,1838,506],{"class":70},[64,1840,1841],{"class":90}," MAX",[64,1843,94],{"class":77},[64,1845,1846],{"class":90},"s2",[64,1848,225],{"class":77},[64,1850,1851],{"class":90},"streak_length",[64,1853,248],{"class":77},[64,1855,131],{"class":70},[64,1857,1858],{"class":77}," streaks s2 ",[64,1860,140],{"class":70},[64,1862,1863],{"class":90}," s2",[64,1865,225],{"class":77},[64,1867,1868],{"class":90},"user_id",[64,1870,350],{"class":70},[64,1872,1873],{"class":90}," streaks",[64,1875,225],{"class":77},[64,1877,1878],{"class":90},"user_id\n",[64,1880,1881],{"class":66,"line":1564},[64,1882,161],{"class":77},[64,1884,1885,1887,1889,1891],{"class":66,"line":1577},[64,1886,167],{"class":70},[64,1888,1825],{"class":77},[64,1890,395],{"class":70},[64,1892,172],{"class":77},[64,1894,1895],{"class":66,"line":1583},[64,1896,1897],{"class":178},"-- Or simpler with DISTINCT ON (PostgreSQL extension):\n",[64,1899,1901],{"class":66,"line":1900},25,[64,1902,1903],{"class":178},"-- SELECT DISTINCT ON (user_id) user_id, streak_length, streak_start, streak_end\n",[64,1905,1907],{"class":66,"line":1906},26,[64,1908,1909],{"class":178},"-- FROM streaks ORDER BY user_id, streak_length DESC;\n",[19,1911,1913],{"id":1912},"_10-pivot-report-conditional-aggregation","10. Pivot Report (Conditional Aggregation)",[24,1915,1916],{},"Pivot monthly revenue into a cross-tab: one row per customer, one column per month (Jan, Feb, Mar).",[48,1918,1919,1921],{},[51,1920,53],{},[34,1922,1923],{"language":56},[38,1924,1926],{"className":59,"code":1925,"language":56,"meta":43,"style":43},"SELECT\n  customer_id,\n  SUM(amount) FILTER (WHERE EXTRACT(MONTH FROM ordered_on) = 1) AS jan,\n  SUM(amount) FILTER (WHERE EXTRACT(MONTH FROM ordered_on) = 2) AS feb,\n  SUM(amount) FILTER (WHERE EXTRACT(MONTH FROM ordered_on) = 3) AS mar,\n  SUM(amount) AS total\nFROM orders\nWHERE ordered_on >= '2024-01-01' AND ordered_on \u003C '2024-04-01'\nGROUP BY customer_id\nORDER BY total DESC NULLS LAST;\n-- FILTER is SQL standard, one pass over the data\n-- Returns NULL for months with no matching rows (SUM of empty set = NULL)\n-- Use COALESCE(SUM(...) FILTER (...), 0) for 0 instead of NULL\n",[45,1927,1928,1933,1938,1973,2004,2036,2047,2053,2072,2078,2095,2100,2105],{"__ignoreMap":43},[64,1929,1930],{"class":66,"line":67},[64,1931,1932],{"class":70},"SELECT\n",[64,1934,1935],{"class":66,"line":81},[64,1936,1937],{"class":77},"  customer_id,\n",[64,1939,1940,1942,1944,1947,1949,1951,1954,1957,1959,1962,1964,1966,1968,1970],{"class":66,"line":128},[64,1941,1144],{"class":90},[64,1943,512],{"class":77},[64,1945,1946],{"class":70},"FILTER",[64,1948,149],{"class":77},[64,1950,140],{"class":70},[64,1952,1953],{"class":77}," EXTRACT(",[64,1955,1956],{"class":70},"MONTH",[64,1958,911],{"class":70},[64,1960,1961],{"class":77}," ordered_on) ",[64,1963,1535],{"class":70},[64,1965,908],{"class":90},[64,1967,248],{"class":77},[64,1969,251],{"class":70},[64,1971,1972],{"class":77}," jan,\n",[64,1974,1975,1977,1979,1981,1983,1985,1987,1989,1991,1993,1995,1997,1999,2001],{"class":66,"line":137},[64,1976,1144],{"class":90},[64,1978,512],{"class":77},[64,1980,1946],{"class":70},[64,1982,149],{"class":77},[64,1984,140],{"class":70},[64,1986,1953],{"class":77},[64,1988,1956],{"class":70},[64,1990,911],{"class":70},[64,1992,1961],{"class":77},[64,1994,1535],{"class":70},[64,1996,591],{"class":90},[64,1998,248],{"class":77},[64,2000,251],{"class":70},[64,2002,2003],{"class":77}," feb,\n",[64,2005,2006,2008,2010,2012,2014,2016,2018,2020,2022,2024,2026,2029,2031,2033],{"class":66,"line":164},[64,2007,1144],{"class":90},[64,2009,512],{"class":77},[64,2011,1946],{"class":70},[64,2013,149],{"class":77},[64,2015,140],{"class":70},[64,2017,1953],{"class":77},[64,2019,1956],{"class":70},[64,2021,911],{"class":70},[64,2023,1961],{"class":77},[64,2025,1535],{"class":70},[64,2027,2028],{"class":90}," 3",[64,2030,248],{"class":77},[64,2032,251],{"class":70},[64,2034,2035],{"class":77}," mar,\n",[64,2037,2038,2040,2042,2044],{"class":66,"line":175},[64,2039,1144],{"class":90},[64,2041,512],{"class":77},[64,2043,251],{"class":70},[64,2045,2046],{"class":77}," total\n",[64,2048,2049,2051],{"class":66,"line":182},[64,2050,131],{"class":70},[64,2052,650],{"class":77},[64,2054,2055,2057,2059,2061,2063,2065,2067,2069],{"class":66,"line":387},[64,2056,140],{"class":70},[64,2058,657],{"class":77},[64,2060,588],{"class":70},[64,2062,1389],{"class":103},[64,2064,1188],{"class":70},[64,2066,657],{"class":77},[64,2068,715],{"class":70},[64,2070,2071],{"class":103}," '2024-04-01'\n",[64,2073,2074,2076],{"class":66,"line":544},[64,2075,366],{"class":70},[64,2077,1160],{"class":77},[64,2079,2080,2082,2085,2087,2090,2093],{"class":66,"line":555},[64,2081,167],{"class":70},[64,2083,2084],{"class":77}," total ",[64,2086,395],{"class":70},[64,2088,2089],{"class":70}," NULLS",[64,2091,2092],{"class":70}," LAST",[64,2094,172],{"class":77},[64,2096,2097],{"class":66,"line":567},[64,2098,2099],{"class":178},"-- FILTER is SQL standard, one pass over the data\n",[64,2101,2102],{"class":66,"line":597},[64,2103,2104],{"class":178},"-- Returns NULL for months with no matching rows (SUM of empty set = NULL)\n",[64,2106,2107],{"class":66,"line":747},[64,2108,2109],{"class":178},"-- Use COALESCE(SUM(...) FILTER (...), 0) for 0 instead of NULL\n",[19,2111,2113],{"id":2112},"_11-recursive-cte-org-chart","11. Recursive CTE — Org Chart",[24,2115,1593,2116,1597,2119,2122],{},[45,2117,2118],{},"employees",[45,2120,2121],{},"(id, name, manager_id)",", list all descendants of employee 1 with their depth. Then, build the management chain path as an array.",[48,2124,2125,2127],{},[51,2126,53],{},[34,2128,2129],{"language":56},[38,2130,2132],{"className":59,"code":2131,"language":56,"meta":43,"style":43},"-- List all descendants with depth\nWITH RECURSIVE org_tree AS (\n  -- Anchor: the root employee\n  SELECT id, name, manager_id, 0 AS depth, ARRAY[id] AS path\n  FROM employees WHERE id = 1\n  UNION ALL\n  -- Recursive: find direct reports of each employee in the tree\n  SELECT e.id, e.name, e.manager_id, t.depth + 1, t.path || e.id\n  FROM employees e\n  JOIN org_tree t ON e.manager_id = t.id\n  -- ⚠️ Ensure there are no cycles — a cycle causes infinite recursion\n  -- Add: WHERE NOT e.id = ANY(t.path)  (prevents revisiting a node)\n  WHERE NOT e.id = ANY(t.path)\n)\nSELECT id, name, depth, path FROM org_tree ORDER BY depth, id;\n-- path is an array of employee IDs from root to current: {1, 5, 12, 34}\n",[45,2133,2134,2139,2153,2158,2188,2205,2210,2215,2277,2284,2309,2314,2319,2344,2348,2370],{"__ignoreMap":43},[64,2135,2136],{"class":66,"line":67},[64,2137,2138],{"class":178},"-- List all descendants with depth\n",[64,2140,2141,2143,2146,2149,2151],{"class":66,"line":81},[64,2142,992],{"class":70},[64,2144,2145],{"class":70}," RECURSIVE",[64,2147,2148],{"class":77}," org_tree ",[64,2150,251],{"class":70},[64,2152,901],{"class":77},[64,2154,2155],{"class":66,"line":128},[64,2156,2157],{"class":178},"  -- Anchor: the root employee\n",[64,2159,2160,2162,2165,2167,2170,2172,2174,2177,2180,2183,2185],{"class":66,"line":137},[64,2161,506],{"class":70},[64,2163,2164],{"class":77}," id, ",[64,2166,97],{"class":70},[64,2168,2169],{"class":77},", manager_id, ",[64,2171,282],{"class":90},[64,2173,122],{"class":70},[64,2175,2176],{"class":77}," depth, ",[64,2178,2179],{"class":70},"ARRAY",[64,2181,2182],{"class":77},"[id] ",[64,2184,251],{"class":70},[64,2186,2187],{"class":70}," path\n",[64,2189,2190,2192,2195,2197,2200,2202],{"class":66,"line":164},[64,2191,522],{"class":70},[64,2193,2194],{"class":77}," employees ",[64,2196,140],{"class":70},[64,2198,2199],{"class":77}," id ",[64,2201,1535],{"class":70},[64,2203,2204],{"class":90}," 1\n",[64,2206,2207],{"class":66,"line":175},[64,2208,2209],{"class":70},"  UNION ALL\n",[64,2211,2212],{"class":66,"line":182},[64,2213,2214],{"class":178},"  -- Recursive: find direct reports of each employee in the tree\n",[64,2216,2217,2219,2222,2224,2226,2228,2231,2233,2235,2237,2239,2241,2244,2246,2249,2251,2254,2257,2259,2261,2263,2265,2268,2271,2273,2275],{"class":66,"line":387},[64,2218,506],{"class":70},[64,2220,2221],{"class":90}," e",[64,2223,225],{"class":77},[64,2225,245],{"class":90},[64,2227,155],{"class":77},[64,2229,2230],{"class":90},"e",[64,2232,225],{"class":77},[64,2234,97],{"class":90},[64,2236,155],{"class":77},[64,2238,2230],{"class":90},[64,2240,225],{"class":77},[64,2242,2243],{"class":90},"manager_id",[64,2245,155],{"class":77},[64,2247,2248],{"class":90},"t",[64,2250,225],{"class":77},[64,2252,2253],{"class":90},"depth",[64,2255,2256],{"class":70}," +",[64,2258,908],{"class":90},[64,2260,155],{"class":77},[64,2262,2248],{"class":90},[64,2264,225],{"class":77},[64,2266,2267],{"class":90},"path",[64,2269,2270],{"class":70}," ||",[64,2272,2221],{"class":90},[64,2274,225],{"class":77},[64,2276,541],{"class":90},[64,2278,2279,2281],{"class":66,"line":544},[64,2280,522],{"class":70},[64,2282,2283],{"class":77}," employees e\n",[64,2285,2286,2289,2292,2294,2296,2298,2300,2302,2305,2307],{"class":66,"line":555},[64,2287,2288],{"class":70},"  JOIN",[64,2290,2291],{"class":77}," org_tree t ",[64,2293,341],{"class":70},[64,2295,2221],{"class":90},[64,2297,225],{"class":77},[64,2299,2243],{"class":90},[64,2301,350],{"class":70},[64,2303,2304],{"class":90}," t",[64,2306,225],{"class":77},[64,2308,541],{"class":90},[64,2310,2311],{"class":66,"line":567},[64,2312,2313],{"class":178},"  -- ⚠️ Ensure there are no cycles — a cycle causes infinite recursion\n",[64,2315,2316],{"class":66,"line":597},[64,2317,2318],{"class":178},"  -- Add: WHERE NOT e.id = ANY(t.path)  (prevents revisiting a node)\n",[64,2320,2321,2323,2325,2327,2329,2331,2333,2336,2338,2340,2342],{"class":66,"line":747},[64,2322,1382],{"class":70},[64,2324,895],{"class":70},[64,2326,2221],{"class":90},[64,2328,225],{"class":77},[64,2330,245],{"class":90},[64,2332,350],{"class":70},[64,2334,2335],{"class":77}," ANY(",[64,2337,2248],{"class":90},[64,2339,225],{"class":77},[64,2341,2267],{"class":90},[64,2343,161],{"class":77},[64,2345,2346],{"class":66,"line":756},[64,2347,161],{"class":77},[64,2349,2350,2352,2354,2356,2359,2361,2363,2365,2367],{"class":66,"line":762},[64,2351,71],{"class":70},[64,2353,2164],{"class":77},[64,2355,97],{"class":70},[64,2357,2358],{"class":77},", depth, ",[64,2360,2267],{"class":70},[64,2362,911],{"class":70},[64,2364,2148],{"class":77},[64,2366,167],{"class":70},[64,2368,2369],{"class":77}," depth, id;\n",[64,2371,2372],{"class":66,"line":947},[64,2373,2374],{"class":178},"-- path is an array of employee IDs from root to current: {1, 5, 12, 34}\n",[19,2376,2378],{"id":2377},"_12-jsonb-events-with-gin-index","12. JSONB Events with GIN Index",[24,2380,2381,2382,2385,2386,2389,2390,2393],{},"Create an ",[45,2383,2384],{},"events"," table with a ",[45,2387,2388],{},"JSONB payload",". Insert events with ",[45,2391,2392],{},"{\"type\": \"click\", \"user_id\": 42, \"amount\": 10}",". Create a GIN index and query for all \"click\" events for user 42 with amount > 5.",[48,2395,2396,2398],{},[51,2397,53],{},[34,2399,2400],{"language":56},[38,2401,2403],{"className":59,"code":2402,"language":56,"meta":43,"style":43},"CREATE TABLE events (\n  id BIGSERIAL PRIMARY KEY,\n  payload JSONB NOT NULL,\n  created_at TIMESTAMPTZ DEFAULT now()\n);\n\n-- GIN index on the entire JSONB document (supports any key lookup)\nCREATE INDEX ON events USING gin (payload);\n\nINSERT INTO events (payload) VALUES\n  ('{\"type\": \"click\", \"user_id\": 42, \"amount\": 10}'),\n  ('{\"type\": \"click\", \"user_id\": 42, \"amount\": 3}'),\n  ('{\"type\": \"view\", \"user_id\": 42, \"amount\": 0}'),\n  ('{\"type\": \"click\", \"user_id\": 99, \"amount\": 50}');\n\n-- Query using the @> containment operator (GIN-indexed)\nSELECT * FROM events\nWHERE payload @> '{\"type\": \"click\", \"user_id\": 42}'\n  AND (payload->>'amount')::numeric > 5;\n-- @> checks if the JSONB contains the given key-value pairs (index scan)\n-- ->> extracts as text, ::numeric casts for numeric comparison\n-- Only the first row matches (click, user 42, amount 10 > 5)\n",[45,2404,2405,2419,2432,2442,2459,2463,2467,2472,2491,2495,2506,2516,2525,2534,2543,2547,2552,2564,2577,2605,2610,2615],{"__ignoreMap":43},[64,2406,2407,2410,2413,2417],{"class":66,"line":67},[64,2408,2409],{"class":70},"CREATE",[64,2411,2412],{"class":70}," TABLE",[64,2414,2416],{"class":2415},"sIsaT"," events",[64,2418,901],{"class":77},[64,2420,2421,2424,2427,2430],{"class":66,"line":81},[64,2422,2423],{"class":77},"  id ",[64,2425,2426],{"class":70},"BIGSERIAL",[64,2428,2429],{"class":70}," PRIMARY KEY",[64,2431,230],{"class":77},[64,2433,2434,2437,2440],{"class":66,"line":128},[64,2435,2436],{"class":77},"  payload JSONB ",[64,2438,2439],{"class":70},"NOT NULL",[64,2441,230],{"class":77},[64,2443,2444,2447,2450,2453,2456],{"class":66,"line":137},[64,2445,2446],{"class":77},"  created_at ",[64,2448,2449],{"class":70},"TIMESTAMPTZ",[64,2451,2452],{"class":70}," DEFAULT",[64,2454,2455],{"class":70}," now",[64,2457,2458],{"class":77},"()\n",[64,2460,2461],{"class":66,"line":164},[64,2462,934],{"class":77},[64,2464,2465],{"class":66,"line":175},[64,2466,683],{"emptyLinePlaceholder":682},[64,2468,2469],{"class":66,"line":182},[64,2470,2471],{"class":178},"-- GIN index on the entire JSONB document (supports any key lookup)\n",[64,2473,2474,2476,2479,2482,2485,2488],{"class":66,"line":387},[64,2475,2409],{"class":70},[64,2477,2478],{"class":70}," INDEX",[64,2480,2481],{"class":2415}," ON",[64,2483,2484],{"class":77}," events ",[64,2486,2487],{"class":70},"USING",[64,2489,2490],{"class":77}," gin (payload);\n",[64,2492,2493],{"class":66,"line":544},[64,2494,683],{"emptyLinePlaceholder":682},[64,2496,2497,2500,2503],{"class":66,"line":555},[64,2498,2499],{"class":70},"INSERT INTO",[64,2501,2502],{"class":77}," events (payload) ",[64,2504,2505],{"class":70},"VALUES\n",[64,2507,2508,2511,2514],{"class":66,"line":567},[64,2509,2510],{"class":77},"  (",[64,2512,2513],{"class":103},"'{\"type\": \"click\", \"user_id\": 42, \"amount\": 10}'",[64,2515,1333],{"class":77},[64,2517,2518,2520,2523],{"class":66,"line":597},[64,2519,2510],{"class":77},[64,2521,2522],{"class":103},"'{\"type\": \"click\", \"user_id\": 42, \"amount\": 3}'",[64,2524,1333],{"class":77},[64,2526,2527,2529,2532],{"class":66,"line":747},[64,2528,2510],{"class":77},[64,2530,2531],{"class":103},"'{\"type\": \"view\", \"user_id\": 42, \"amount\": 0}'",[64,2533,1333],{"class":77},[64,2535,2536,2538,2541],{"class":66,"line":756},[64,2537,2510],{"class":77},[64,2539,2540],{"class":103},"'{\"type\": \"click\", \"user_id\": 99, \"amount\": 50}'",[64,2542,934],{"class":77},[64,2544,2545],{"class":66,"line":762},[64,2546,683],{"emptyLinePlaceholder":682},[64,2548,2549],{"class":66,"line":947},[64,2550,2551],{"class":178},"-- Query using the @> containment operator (GIN-indexed)\n",[64,2553,2554,2556,2559,2561],{"class":66,"line":1454},[64,2555,71],{"class":70},[64,2557,2558],{"class":70}," *",[64,2560,911],{"class":70},[64,2562,2563],{"class":77}," events\n",[64,2565,2566,2568,2571,2574],{"class":66,"line":1477},[64,2567,140],{"class":70},[64,2569,2570],{"class":77}," payload @",[64,2572,2573],{"class":70},">",[64,2575,2576],{"class":103}," '{\"type\": \"click\", \"user_id\": 42}'\n",[64,2578,2579,2582,2585,2588,2591,2594,2597,2600,2603],{"class":66,"line":1485},[64,2580,2581],{"class":70},"  AND",[64,2583,2584],{"class":77}," (payload",[64,2586,2587],{"class":70},"->>",[64,2589,2590],{"class":103},"'amount'",[64,2592,2593],{"class":77},")::",[64,2595,2596],{"class":70},"numeric",[64,2598,2599],{"class":70}," >",[64,2601,2602],{"class":90}," 5",[64,2604,172],{"class":77},[64,2606,2607],{"class":66,"line":1511},[64,2608,2609],{"class":178},"-- @> checks if the JSONB contains the given key-value pairs (index scan)\n",[64,2611,2612],{"class":66,"line":1544},[64,2613,2614],{"class":178},"-- ->> extracts as text, ::numeric casts for numeric comparison\n",[64,2616,2617],{"class":66,"line":1564},[64,2618,2619],{"class":178},"-- Only the first row matches (click, user 42, amount 10 > 5)\n",[14,2621,2623],{"id":2622},"advanced","Advanced",[19,2625,2627],{"id":2626},"_13-inventory-reservation-transactions-for-update","13. Inventory Reservation (Transactions, FOR UPDATE)",[24,2629,2630,2631,2634],{},"Write a transaction that reserves an item: check stock, decrement if available, record the reservation. Use ",[45,2632,2633],{},"SELECT ... FOR UPDATE"," to prevent overselling under concurrency.",[48,2636,2637,2639],{},[51,2638,53],{},[34,2640,2641],{"language":56},[38,2642,2644],{"className":59,"code":2643,"language":56,"meta":43,"style":43},"-- CONCURRENCY-SAFE: FOR UPDATE locks the row so concurrent transactions\n-- wait until this transaction commits\u002Frolls back before reading the stock\nBEGIN;\nSELECT qty FROM inventory WHERE product_id = 5 FOR UPDATE;\n-- Application checks: if qty > 0, proceed; else ROLLBACK\n\n-- Only execute if qty > 0 (checked in application logic):\nUPDATE inventory SET qty = qty - 1 WHERE product_id = 5;\nINSERT INTO reservations (product_id, reserved_at) VALUES (5, now());\nCOMMIT;\n\n-- ⚠️ Alternative: do it in one atomic statement (no race possible):\n-- INSERT INTO reservations (product_id, reserved_at)\n-- SELECT 5, now()\n-- FROM inventory\n-- WHERE product_id = 5 AND qty > 0\n-- RETURNING (UPDATE inventory SET qty = qty - 1 WHERE product_id = 5) AS new_qty;\n-- This uses a CTE with a writable CTE (UPDATE + INSERT in one statement)\n",[45,2645,2646,2651,2656,2663,2692,2697,2701,2706,2738,2761,2768,2772,2777,2782,2787,2792,2797,2802],{"__ignoreMap":43},[64,2647,2648],{"class":66,"line":67},[64,2649,2650],{"class":178},"-- CONCURRENCY-SAFE: FOR UPDATE locks the row so concurrent transactions\n",[64,2652,2653],{"class":66,"line":81},[64,2654,2655],{"class":178},"-- wait until this transaction commits\u002Frolls back before reading the stock\n",[64,2657,2658,2661],{"class":66,"line":128},[64,2659,2660],{"class":70},"BEGIN",[64,2662,172],{"class":77},[64,2664,2665,2667,2670,2672,2675,2677,2680,2682,2684,2687,2690],{"class":66,"line":137},[64,2666,71],{"class":70},[64,2668,2669],{"class":77}," qty ",[64,2671,131],{"class":70},[64,2673,2674],{"class":77}," inventory ",[64,2676,140],{"class":70},[64,2678,2679],{"class":77}," product_id ",[64,2681,1535],{"class":70},[64,2683,2602],{"class":90},[64,2685,2686],{"class":70}," FOR",[64,2688,2689],{"class":70}," UPDATE",[64,2691,172],{"class":77},[64,2693,2694],{"class":66,"line":164},[64,2695,2696],{"class":178},"-- Application checks: if qty > 0, proceed; else ROLLBACK\n",[64,2698,2699],{"class":66,"line":175},[64,2700,683],{"emptyLinePlaceholder":682},[64,2702,2703],{"class":66,"line":182},[64,2704,2705],{"class":178},"-- Only execute if qty > 0 (checked in application logic):\n",[64,2707,2708,2711,2713,2716,2718,2720,2722,2725,2727,2730,2732,2734,2736],{"class":66,"line":387},[64,2709,2710],{"class":70},"UPDATE",[64,2712,2674],{"class":77},[64,2714,2715],{"class":70},"SET",[64,2717,2669],{"class":77},[64,2719,1535],{"class":70},[64,2721,2669],{"class":77},[64,2723,2724],{"class":70},"-",[64,2726,908],{"class":90},[64,2728,2729],{"class":70}," WHERE",[64,2731,2679],{"class":77},[64,2733,1535],{"class":70},[64,2735,2602],{"class":90},[64,2737,172],{"class":77},[64,2739,2740,2742,2745,2748,2750,2753,2755,2758],{"class":66,"line":544},[64,2741,2499],{"class":70},[64,2743,2744],{"class":77}," reservations (product_id, reserved_at) ",[64,2746,2747],{"class":70},"VALUES",[64,2749,149],{"class":77},[64,2751,2752],{"class":90},"5",[64,2754,155],{"class":77},[64,2756,2757],{"class":70},"now",[64,2759,2760],{"class":77},"());\n",[64,2762,2763,2766],{"class":66,"line":555},[64,2764,2765],{"class":70},"COMMIT",[64,2767,172],{"class":77},[64,2769,2770],{"class":66,"line":567},[64,2771,683],{"emptyLinePlaceholder":682},[64,2773,2774],{"class":66,"line":597},[64,2775,2776],{"class":178},"-- ⚠️ Alternative: do it in one atomic statement (no race possible):\n",[64,2778,2779],{"class":66,"line":747},[64,2780,2781],{"class":178},"-- INSERT INTO reservations (product_id, reserved_at)\n",[64,2783,2784],{"class":66,"line":756},[64,2785,2786],{"class":178},"-- SELECT 5, now()\n",[64,2788,2789],{"class":66,"line":762},[64,2790,2791],{"class":178},"-- FROM inventory\n",[64,2793,2794],{"class":66,"line":947},[64,2795,2796],{"class":178},"-- WHERE product_id = 5 AND qty > 0\n",[64,2798,2799],{"class":66,"line":1454},[64,2800,2801],{"class":178},"-- RETURNING (UPDATE inventory SET qty = qty - 1 WHERE product_id = 5) AS new_qty;\n",[64,2803,2804],{"class":66,"line":1477},[64,2805,2806],{"class":178},"-- This uses a CTE with a writable CTE (UPDATE + INSERT in one statement)\n",[19,2808,2810],{"id":2809},"_14-serializable-transfer-with-retry","14. SERIALIZABLE Transfer with Retry",[24,2812,2813],{},"Implement a money transfer between two accounts at SERIALIZABLE isolation, with retry on serialization failure. Test that concurrent transfers don't lose money.",[48,2815,2816,2818],{},[51,2817,53],{},[34,2819,2820],{"language":56},[38,2821,2823],{"className":59,"code":2822,"language":56,"meta":43,"style":43},"-- The transfer: atomic decrement + increment, always lock in ascending order\n-- to prevent deadlocks (see pitfall #26)\nBEGIN ISOLATION LEVEL SERIALIZABLE;\n-- Lock accounts in ascending id order to prevent deadlocks\n-- If from_id > to_id, swap the lock order\nUPDATE accounts SET balance = balance - 100 WHERE id = LEAST(1, 2);\nUPDATE accounts SET balance = balance + 100 WHERE id = GREATEST(1, 2);\nCOMMIT;\n-- If this fails with SQLSTATE 40001 (serialization_failure), the application\n-- must ROLLBACK and retry the entire transaction from the beginning\n\n-- Application retry logic (pseudo-code):\n-- max_retries = 3\n-- for attempt in range(max_retries):\n--     try:\n--         conn.execute(\"BEGIN ISOLATION LEVEL SERIALIZABLE\")\n--         conn.execute(\"UPDATE accounts SET balance = balance - 100 WHERE id = $1\", from_id)\n--         conn.execute(\"UPDATE accounts SET balance = balance + 100 WHERE id = $1\", to_id)\n--         conn.execute(\"COMMIT\")\n--         break\n--     except sql_error as e:\n--         conn.execute(\"ROLLBACK\")\n--         if e.sqlstate == '40001' and attempt \u003C max_retries - 1:\n--             continue  # retry\n--         else:\n--             raise  # non-retryable error or max retries exceeded\n",[45,2824,2825,2830,2835,2850,2855,2860,2901,2939,2945,2950,2955,2959,2964,2969,2974,2979,2984,2989,2994,2999,3004,3009,3014,3019,3024,3029],{"__ignoreMap":43},[64,2826,2827],{"class":66,"line":67},[64,2828,2829],{"class":178},"-- The transfer: atomic decrement + increment, always lock in ascending order\n",[64,2831,2832],{"class":66,"line":81},[64,2833,2834],{"class":178},"-- to prevent deadlocks (see pitfall #26)\n",[64,2836,2837,2839,2842,2845,2848],{"class":66,"line":128},[64,2838,2660],{"class":70},[64,2840,2841],{"class":70}," ISOLATION",[64,2843,2844],{"class":70}," LEVEL",[64,2846,2847],{"class":70}," SERIALIZABLE",[64,2849,172],{"class":77},[64,2851,2852],{"class":66,"line":137},[64,2853,2854],{"class":178},"-- Lock accounts in ascending id order to prevent deadlocks\n",[64,2856,2857],{"class":66,"line":164},[64,2858,2859],{"class":178},"-- If from_id > to_id, swap the lock order\n",[64,2861,2862,2864,2867,2869,2872,2874,2876,2878,2881,2883,2885,2887,2890,2892,2895,2897,2899],{"class":66,"line":175},[64,2863,2710],{"class":70},[64,2865,2866],{"class":77}," accounts ",[64,2868,2715],{"class":70},[64,2870,2871],{"class":77}," balance ",[64,2873,1535],{"class":70},[64,2875,2871],{"class":77},[64,2877,2724],{"class":70},[64,2879,2880],{"class":90}," 100",[64,2882,2729],{"class":70},[64,2884,2199],{"class":77},[64,2886,1535],{"class":70},[64,2888,2889],{"class":90}," LEAST",[64,2891,94],{"class":77},[64,2893,2894],{"class":90},"1",[64,2896,155],{"class":77},[64,2898,476],{"class":90},[64,2900,934],{"class":77},[64,2902,2903,2905,2907,2909,2911,2913,2915,2918,2920,2922,2924,2926,2929,2931,2933,2935,2937],{"class":66,"line":182},[64,2904,2710],{"class":70},[64,2906,2866],{"class":77},[64,2908,2715],{"class":70},[64,2910,2871],{"class":77},[64,2912,1535],{"class":70},[64,2914,2871],{"class":77},[64,2916,2917],{"class":70},"+",[64,2919,2880],{"class":90},[64,2921,2729],{"class":70},[64,2923,2199],{"class":77},[64,2925,1535],{"class":70},[64,2927,2928],{"class":90}," GREATEST",[64,2930,94],{"class":77},[64,2932,2894],{"class":90},[64,2934,155],{"class":77},[64,2936,476],{"class":90},[64,2938,934],{"class":77},[64,2940,2941,2943],{"class":66,"line":387},[64,2942,2765],{"class":70},[64,2944,172],{"class":77},[64,2946,2947],{"class":66,"line":544},[64,2948,2949],{"class":178},"-- If this fails with SQLSTATE 40001 (serialization_failure), the application\n",[64,2951,2952],{"class":66,"line":555},[64,2953,2954],{"class":178},"-- must ROLLBACK and retry the entire transaction from the beginning\n",[64,2956,2957],{"class":66,"line":567},[64,2958,683],{"emptyLinePlaceholder":682},[64,2960,2961],{"class":66,"line":597},[64,2962,2963],{"class":178},"-- Application retry logic (pseudo-code):\n",[64,2965,2966],{"class":66,"line":747},[64,2967,2968],{"class":178},"-- max_retries = 3\n",[64,2970,2971],{"class":66,"line":756},[64,2972,2973],{"class":178},"-- for attempt in range(max_retries):\n",[64,2975,2976],{"class":66,"line":762},[64,2977,2978],{"class":178},"--     try:\n",[64,2980,2981],{"class":66,"line":947},[64,2982,2983],{"class":178},"--         conn.execute(\"BEGIN ISOLATION LEVEL SERIALIZABLE\")\n",[64,2985,2986],{"class":66,"line":1454},[64,2987,2988],{"class":178},"--         conn.execute(\"UPDATE accounts SET balance = balance - 100 WHERE id = $1\", from_id)\n",[64,2990,2991],{"class":66,"line":1477},[64,2992,2993],{"class":178},"--         conn.execute(\"UPDATE accounts SET balance = balance + 100 WHERE id = $1\", to_id)\n",[64,2995,2996],{"class":66,"line":1485},[64,2997,2998],{"class":178},"--         conn.execute(\"COMMIT\")\n",[64,3000,3001],{"class":66,"line":1511},[64,3002,3003],{"class":178},"--         break\n",[64,3005,3006],{"class":66,"line":1544},[64,3007,3008],{"class":178},"--     except sql_error as e:\n",[64,3010,3011],{"class":66,"line":1564},[64,3012,3013],{"class":178},"--         conn.execute(\"ROLLBACK\")\n",[64,3015,3016],{"class":66,"line":1577},[64,3017,3018],{"class":178},"--         if e.sqlstate == '40001' and attempt \u003C max_retries - 1:\n",[64,3020,3021],{"class":66,"line":1583},[64,3022,3023],{"class":178},"--             continue  # retry\n",[64,3025,3026],{"class":66,"line":1900},[64,3027,3028],{"class":178},"--         else:\n",[64,3030,3031],{"class":66,"line":1906},[64,3032,3033],{"class":178},"--             raise  # non-retryable error or max retries exceeded\n",[19,3035,3037],{"id":3036},"_15-audit-trigger","15. Audit Trigger",[24,3039,3040,3041,776,3043,3046,3047,3050,3051,3054],{},"Create an audit trigger that logs every ",[45,3042,2710],{},[45,3044,3045],{},"DELETE"," on ",[45,3048,3049],{},"orders"," to an ",[45,3052,3053],{},"orders_audit"," table with the old and new values, timestamp, and user.",[48,3056,3057,3059],{},[51,3058,53],{},[34,3060,3061],{"language":56},[38,3062,3064],{"className":59,"code":3063,"language":56,"meta":43,"style":43},"CREATE TABLE orders_audit (\n  id BIGSERIAL PRIMARY KEY,\n  operation CHAR(1) NOT NULL,        -- 'U' for UPDATE, 'D' for DELETE\n  old_data JSONB,                     -- entire old row as JSONB\n  new_data JSONB,                     -- entire new row as JSONB (NULL for DELETE)\n  changed_by TEXT DEFAULT current_user,\n  changed_at TIMESTAMPTZ DEFAULT now()\n);\n\nCREATE OR REPLACE FUNCTION audit_orders() RETURNS TRIGGER\nLANGUAGE plpgsql\nSET search_path = public, pg_temp  -- SECURITY: explicit search_path prevents hijacking\nAS $$\nBEGIN\n  IF TG_OP = 'DELETE' THEN\n    INSERT INTO orders_audit (operation, old_data, new_data)\n    VALUES ('D', to_jsonb(OLD), NULL);\n    RETURN OLD;\n  ELSIF TG_OP = 'UPDATE' THEN\n    INSERT INTO orders_audit (operation, old_data, new_data)\n    VALUES ('U', to_jsonb(OLD), to_jsonb(NEW));\n    RETURN NEW;\n  END IF;\nEND;\n$$;\n\n-- AFTER trigger: fires after the row is modified, so OLD and NEW are both available\n-- ROW level: fires once per affected row\nCREATE TRIGGER orders_audit_trigger\nAFTER UPDATE OR DELETE ON orders\nFOR EACH ROW EXECUTE FUNCTION audit_orders();\n\n-- ⚠️ Audit tables grow unboundedly — partition by month and add retention\nCREATE TABLE orders_audit (\n  id BIGSERIAL,\n  operation CHAR(1) NOT NULL,\n  old_data JSONB,\n  new_data JSONB,\n  changed_by TEXT DEFAULT current_user,\n  changed_at TIMESTAMPTZ DEFAULT now()\n) PARTITION BY RANGE (changed_at);\n",[45,3065,3066,3077,3087,3109,3117,3125,3138,3151,3155,3159,3178,3186,3201,3208,3213,3229,3237,3255,3263,3275,3281,3293,3300,3310,3316,3321,3325,3331,3337,3348,3366,3386,3391,3397,3408,3417,3434,3440,3446,3457,3470],{"__ignoreMap":43},[64,3067,3068,3070,3072,3075],{"class":66,"line":67},[64,3069,2409],{"class":70},[64,3071,2412],{"class":70},[64,3073,3074],{"class":2415}," orders_audit",[64,3076,901],{"class":77},[64,3078,3079,3081,3083,3085],{"class":66,"line":81},[64,3080,2423],{"class":77},[64,3082,2426],{"class":70},[64,3084,2429],{"class":70},[64,3086,230],{"class":77},[64,3088,3089,3092,3095,3097,3099,3101,3103,3106],{"class":66,"line":128},[64,3090,3091],{"class":77},"  operation ",[64,3093,3094],{"class":70},"CHAR",[64,3096,94],{"class":77},[64,3098,2894],{"class":90},[64,3100,248],{"class":77},[64,3102,2439],{"class":70},[64,3104,3105],{"class":77},",        ",[64,3107,3108],{"class":178},"-- 'U' for UPDATE, 'D' for DELETE\n",[64,3110,3111,3114],{"class":66,"line":137},[64,3112,3113],{"class":77},"  old_data JSONB,                     ",[64,3115,3116],{"class":178},"-- entire old row as JSONB\n",[64,3118,3119,3122],{"class":66,"line":164},[64,3120,3121],{"class":77},"  new_data JSONB,                     ",[64,3123,3124],{"class":178},"-- entire new row as JSONB (NULL for DELETE)\n",[64,3126,3127,3130,3133,3135],{"class":66,"line":175},[64,3128,3129],{"class":77},"  changed_by ",[64,3131,3132],{"class":70},"TEXT",[64,3134,2452],{"class":70},[64,3136,3137],{"class":77}," current_user,\n",[64,3139,3140,3143,3145,3147,3149],{"class":66,"line":182},[64,3141,3142],{"class":77},"  changed_at ",[64,3144,2449],{"class":70},[64,3146,2452],{"class":70},[64,3148,2455],{"class":70},[64,3150,2458],{"class":77},[64,3152,3153],{"class":66,"line":387},[64,3154,934],{"class":77},[64,3156,3157],{"class":66,"line":544},[64,3158,683],{"emptyLinePlaceholder":682},[64,3160,3161,3164,3167,3170,3172,3175],{"class":66,"line":555},[64,3162,3163],{"class":70},"CREATE OR REPLACE",[64,3165,3166],{"class":70}," FUNCTION",[64,3168,3169],{"class":2415}," audit_orders",[64,3171,1019],{"class":77},[64,3173,3174],{"class":70},"RETURNS",[64,3176,3177],{"class":77}," TRIGGER\n",[64,3179,3180,3183],{"class":66,"line":567},[64,3181,3182],{"class":70},"LANGUAGE",[64,3184,3185],{"class":77}," plpgsql\n",[64,3187,3188,3190,3193,3195,3198],{"class":66,"line":597},[64,3189,2715],{"class":70},[64,3191,3192],{"class":77}," search_path ",[64,3194,1535],{"class":70},[64,3196,3197],{"class":77}," public, pg_temp  ",[64,3199,3200],{"class":178},"-- SECURITY: explicit search_path prevents hijacking\n",[64,3202,3203,3205],{"class":66,"line":747},[64,3204,251],{"class":70},[64,3206,3207],{"class":77}," $$\n",[64,3209,3210],{"class":66,"line":756},[64,3211,3212],{"class":70},"BEGIN\n",[64,3214,3215,3218,3221,3223,3226],{"class":66,"line":762},[64,3216,3217],{"class":70},"  IF",[64,3219,3220],{"class":77}," TG_OP ",[64,3222,1535],{"class":70},[64,3224,3225],{"class":103}," 'DELETE'",[64,3227,3228],{"class":70}," THEN\n",[64,3230,3231,3234],{"class":66,"line":947},[64,3232,3233],{"class":70},"    INSERT INTO",[64,3235,3236],{"class":77}," orders_audit (operation, old_data, new_data)\n",[64,3238,3239,3242,3244,3247,3250,3253],{"class":66,"line":1454},[64,3240,3241],{"class":70},"    VALUES",[64,3243,149],{"class":77},[64,3245,3246],{"class":103},"'D'",[64,3248,3249],{"class":77},", to_jsonb(OLD), ",[64,3251,3252],{"class":70},"NULL",[64,3254,934],{"class":77},[64,3256,3257,3260],{"class":66,"line":1477},[64,3258,3259],{"class":70},"    RETURN",[64,3261,3262],{"class":77}," OLD;\n",[64,3264,3265,3268,3270,3273],{"class":66,"line":1485},[64,3266,3267],{"class":77},"  ELSIF TG_OP ",[64,3269,1535],{"class":70},[64,3271,3272],{"class":103}," 'UPDATE'",[64,3274,3228],{"class":70},[64,3276,3277,3279],{"class":66,"line":1511},[64,3278,3233],{"class":70},[64,3280,3236],{"class":77},[64,3282,3283,3285,3287,3290],{"class":66,"line":1544},[64,3284,3241],{"class":70},[64,3286,149],{"class":77},[64,3288,3289],{"class":103},"'U'",[64,3291,3292],{"class":77},", to_jsonb(OLD), to_jsonb(NEW));\n",[64,3294,3295,3297],{"class":66,"line":1564},[64,3296,3259],{"class":70},[64,3298,3299],{"class":77}," NEW;\n",[64,3301,3302,3305,3308],{"class":66,"line":1577},[64,3303,3304],{"class":70},"  END",[64,3306,3307],{"class":70}," IF",[64,3309,172],{"class":77},[64,3311,3312,3314],{"class":66,"line":1583},[64,3313,119],{"class":70},[64,3315,172],{"class":77},[64,3317,3318],{"class":66,"line":1900},[64,3319,3320],{"class":77},"$$;\n",[64,3322,3323],{"class":66,"line":1906},[64,3324,683],{"emptyLinePlaceholder":682},[64,3326,3328],{"class":66,"line":3327},27,[64,3329,3330],{"class":178},"-- AFTER trigger: fires after the row is modified, so OLD and NEW are both available\n",[64,3332,3334],{"class":66,"line":3333},28,[64,3335,3336],{"class":178},"-- ROW level: fires once per affected row\n",[64,3338,3340,3342,3345],{"class":66,"line":3339},29,[64,3341,2409],{"class":70},[64,3343,3344],{"class":70}," TRIGGER",[64,3346,3347],{"class":2415}," orders_audit_trigger\n",[64,3349,3351,3354,3356,3359,3362,3364],{"class":66,"line":3350},30,[64,3352,3353],{"class":70},"AFTER",[64,3355,2689],{"class":70},[64,3357,3358],{"class":70}," OR",[64,3360,3361],{"class":70}," DELETE",[64,3363,2481],{"class":70},[64,3365,650],{"class":77},[64,3367,3369,3372,3375,3378,3381,3383],{"class":66,"line":3368},31,[64,3370,3371],{"class":70},"FOR",[64,3373,3374],{"class":77}," EACH ",[64,3376,3377],{"class":70},"ROW",[64,3379,3380],{"class":70}," EXECUTE",[64,3382,3166],{"class":70},[64,3384,3385],{"class":77}," audit_orders();\n",[64,3387,3389],{"class":66,"line":3388},32,[64,3390,683],{"emptyLinePlaceholder":682},[64,3392,3394],{"class":66,"line":3393},33,[64,3395,3396],{"class":178},"-- ⚠️ Audit tables grow unboundedly — partition by month and add retention\n",[64,3398,3400,3402,3404,3406],{"class":66,"line":3399},34,[64,3401,2409],{"class":70},[64,3403,2412],{"class":70},[64,3405,3074],{"class":2415},[64,3407,901],{"class":77},[64,3409,3411,3413,3415],{"class":66,"line":3410},35,[64,3412,2423],{"class":77},[64,3414,2426],{"class":70},[64,3416,230],{"class":77},[64,3418,3420,3422,3424,3426,3428,3430,3432],{"class":66,"line":3419},36,[64,3421,3091],{"class":77},[64,3423,3094],{"class":70},[64,3425,94],{"class":77},[64,3427,2894],{"class":90},[64,3429,248],{"class":77},[64,3431,2439],{"class":70},[64,3433,230],{"class":77},[64,3435,3437],{"class":66,"line":3436},37,[64,3438,3439],{"class":77},"  old_data JSONB,\n",[64,3441,3443],{"class":66,"line":3442},38,[64,3444,3445],{"class":77},"  new_data JSONB,\n",[64,3447,3449,3451,3453,3455],{"class":66,"line":3448},39,[64,3450,3129],{"class":77},[64,3452,3132],{"class":70},[64,3454,2452],{"class":70},[64,3456,3137],{"class":77},[64,3458,3460,3462,3464,3466,3468],{"class":66,"line":3459},40,[64,3461,3142],{"class":77},[64,3463,2449],{"class":70},[64,3465,2452],{"class":70},[64,3467,2455],{"class":70},[64,3469,2458],{"class":77},[64,3471,3473,3475,3477,3479,3482],{"class":66,"line":3472},41,[64,3474,248],{"class":77},[64,3476,1027],{"class":70},[64,3478,1030],{"class":70},[64,3480,3481],{"class":70}," RANGE",[64,3483,3484],{"class":77}," (changed_at);\n",[19,3486,3488],{"id":3487},"_16-full-text-search-with-ranking","16. Full-Text Search with Ranking",[24,3490,3491,3492,3495,3496,3499],{},"Build a search over an ",[45,3493,3494],{},"articles"," table: generate a ",[45,3497,3498],{},"tsvector"," from title + body (title weighted higher), create a GIN index, and query with ranking and highlighting.",[48,3501,3502,3504],{},[51,3503,53],{},[34,3505,3506],{"language":56},[38,3507,3509],{"className":59,"code":3508,"language":56,"meta":43,"style":43},"CREATE TABLE articles (\n  id BIGSERIAL PRIMARY KEY,\n  title TEXT NOT NULL,\n  body TEXT NOT NULL,\n  -- Generated column: tsvector is computed from title + body\n  -- setweight(A, 1.0) gives title higher rank than body (weight B)\n  search_vector tsvector GENERATED ALWAYS AS (\n    setweight(to_tsvector('english', title), 'A') ||\n    setweight(to_tsvector('english', body), 'B')\n  ) STORED\n);\n\n-- GIN index on the generated tsvector column\nCREATE INDEX ON articles USING gin (search_vector);\n\n-- Search with ranking and highlighting\nSELECT\n  id, title,\n  ts_rank(search_vector, websearch_to_tsquery('english', 'postgresql index')) AS rank,\n  ts_headline('english', title, websearch_to_tsquery('english', 'postgresql index'),\n    'StartSel=\u003Cmark>, StopSel=\u003C\u002Fmark>') AS highlighted_title\nFROM articles\nWHERE search_vector @@ websearch_to_tsquery('english', 'postgresql index')\nORDER BY rank DESC\nLIMIT 20;\n-- @@ is the tsvector match operator (uses the GIN index)\n-- ts_rank scores by term frequency and weight (title matches rank higher)\n-- ts_headline wraps matching terms in \u003Cmark> tags for display\n-- websearch_to_tsquery supports boolean: 'postgresql AND index', 'postgres OR mysql'\n",[45,3510,3511,3522,3532,3544,3555,3560,3565,3580,3599,3613,3618,3622,3626,3631,3647,3651,3656,3660,3665,3685,3703,3715,3722,3737,3746,3754,3759,3764,3769],{"__ignoreMap":43},[64,3512,3513,3515,3517,3520],{"class":66,"line":67},[64,3514,2409],{"class":70},[64,3516,2412],{"class":70},[64,3518,3519],{"class":2415}," articles",[64,3521,901],{"class":77},[64,3523,3524,3526,3528,3530],{"class":66,"line":81},[64,3525,2423],{"class":77},[64,3527,2426],{"class":70},[64,3529,2429],{"class":70},[64,3531,230],{"class":77},[64,3533,3534,3537,3539,3542],{"class":66,"line":128},[64,3535,3536],{"class":77},"  title ",[64,3538,3132],{"class":70},[64,3540,3541],{"class":70}," NOT NULL",[64,3543,230],{"class":77},[64,3545,3546,3549,3551,3553],{"class":66,"line":137},[64,3547,3548],{"class":77},"  body ",[64,3550,3132],{"class":70},[64,3552,3541],{"class":70},[64,3554,230],{"class":77},[64,3556,3557],{"class":66,"line":164},[64,3558,3559],{"class":178},"  -- Generated column: tsvector is computed from title + body\n",[64,3561,3562],{"class":66,"line":175},[64,3563,3564],{"class":178},"  -- setweight(A, 1.0) gives title higher rank than body (weight B)\n",[64,3566,3567,3570,3573,3576,3578],{"class":66,"line":182},[64,3568,3569],{"class":77},"  search_vector tsvector ",[64,3571,3572],{"class":70},"GENERATED",[64,3574,3575],{"class":70}," ALWAYS",[64,3577,122],{"class":70},[64,3579,901],{"class":77},[64,3581,3582,3585,3588,3591,3594,3596],{"class":66,"line":387},[64,3583,3584],{"class":77},"    setweight(to_tsvector(",[64,3586,3587],{"class":103},"'english'",[64,3589,3590],{"class":77},", title), ",[64,3592,3593],{"class":103},"'A'",[64,3595,248],{"class":77},[64,3597,3598],{"class":70},"||\n",[64,3600,3601,3603,3605,3608,3611],{"class":66,"line":544},[64,3602,3584],{"class":77},[64,3604,3587],{"class":103},[64,3606,3607],{"class":77},", body), ",[64,3609,3610],{"class":103},"'B'",[64,3612,161],{"class":77},[64,3614,3615],{"class":66,"line":555},[64,3616,3617],{"class":77},"  ) STORED\n",[64,3619,3620],{"class":66,"line":567},[64,3621,934],{"class":77},[64,3623,3624],{"class":66,"line":597},[64,3625,683],{"emptyLinePlaceholder":682},[64,3627,3628],{"class":66,"line":747},[64,3629,3630],{"class":178},"-- GIN index on the generated tsvector column\n",[64,3632,3633,3635,3637,3639,3642,3644],{"class":66,"line":756},[64,3634,2409],{"class":70},[64,3636,2478],{"class":70},[64,3638,2481],{"class":2415},[64,3640,3641],{"class":77}," articles ",[64,3643,2487],{"class":70},[64,3645,3646],{"class":77}," gin (search_vector);\n",[64,3648,3649],{"class":66,"line":762},[64,3650,683],{"emptyLinePlaceholder":682},[64,3652,3653],{"class":66,"line":947},[64,3654,3655],{"class":178},"-- Search with ranking and highlighting\n",[64,3657,3658],{"class":66,"line":1454},[64,3659,1932],{"class":70},[64,3661,3662],{"class":66,"line":1477},[64,3663,3664],{"class":77},"  id, title,\n",[64,3666,3667,3670,3672,3674,3677,3680,3682],{"class":66,"line":1485},[64,3668,3669],{"class":77},"  ts_rank(search_vector, websearch_to_tsquery(",[64,3671,3587],{"class":103},[64,3673,155],{"class":77},[64,3675,3676],{"class":103},"'postgresql index'",[64,3678,3679],{"class":77},")) ",[64,3681,251],{"class":70},[64,3683,3684],{"class":77}," rank,\n",[64,3686,3687,3690,3692,3695,3697,3699,3701],{"class":66,"line":1511},[64,3688,3689],{"class":77},"  ts_headline(",[64,3691,3587],{"class":103},[64,3693,3694],{"class":77},", title, websearch_to_tsquery(",[64,3696,3587],{"class":103},[64,3698,155],{"class":77},[64,3700,3676],{"class":103},[64,3702,1333],{"class":77},[64,3704,3705,3708,3710,3712],{"class":66,"line":1544},[64,3706,3707],{"class":103},"    'StartSel=\u003Cmark>, StopSel=\u003C\u002Fmark>'",[64,3709,248],{"class":77},[64,3711,251],{"class":70},[64,3713,3714],{"class":77}," highlighted_title\n",[64,3716,3717,3719],{"class":66,"line":1564},[64,3718,131],{"class":70},[64,3720,3721],{"class":77}," articles\n",[64,3723,3724,3726,3729,3731,3733,3735],{"class":66,"line":1577},[64,3725,140],{"class":70},[64,3727,3728],{"class":77}," search_vector @@ websearch_to_tsquery(",[64,3730,3587],{"class":103},[64,3732,155],{"class":77},[64,3734,3676],{"class":103},[64,3736,161],{"class":77},[64,3738,3739,3741,3744],{"class":66,"line":1583},[64,3740,167],{"class":70},[64,3742,3743],{"class":77}," rank ",[64,3745,744],{"class":70},[64,3747,3748,3750,3752],{"class":66,"line":1900},[64,3749,672],{"class":70},[64,3751,675],{"class":90},[64,3753,172],{"class":77},[64,3755,3756],{"class":66,"line":1906},[64,3757,3758],{"class":178},"-- @@ is the tsvector match operator (uses the GIN index)\n",[64,3760,3761],{"class":66,"line":3327},[64,3762,3763],{"class":178},"-- ts_rank scores by term frequency and weight (title matches rank higher)\n",[64,3765,3766],{"class":66,"line":3333},[64,3767,3768],{"class":178},"-- ts_headline wraps matching terms in \u003Cmark> tags for display\n",[64,3770,3771],{"class":66,"line":3339},[64,3772,3773],{"class":178},"-- websearch_to_tsquery supports boolean: 'postgresql AND index', 'postgres OR mysql'\n",[19,3775,3777],{"id":3776},"_17-materialized-view-with-concurrent-refresh","17. Materialized View with Concurrent Refresh",[24,3779,3780,3781,225],{},"Create a materialized view for a dashboard (sales by city by month). Refresh it concurrently (add a unique index first). Schedule a refresh with ",[45,3782,3783],{},"pg_cron",[48,3785,3786,3788],{},[51,3787,53],{},[34,3789,3790],{"language":56},[38,3791,3793],{"className":59,"code":3792,"language":56,"meta":43,"style":43},"-- Materialized view: pre-computes the aggregation, stored on disk\nCREATE MATERIALIZED VIEW sales_by_city_month AS\nSELECT\n  c.city,\n  date_trunc('month', o.ordered_on) AS month,\n  SUM(o.amount) AS revenue,\n  COUNT(*) AS order_count\nFROM orders o\nJOIN customers c ON c.id = o.customer_id\nGROUP BY c.city, date_trunc('month', o.ordered_on);\n\n-- CONCURRENT refresh requires at least one UNIQUE index\n-- Without it, REFRESH CONCURRENTLY fails with an error\nCREATE UNIQUE INDEX ON sales_by_city_month (city, month);\n\n-- Refresh concurrently (doesn't block reads during refresh):\nREFRESH MATERIALIZED VIEW CONCURRENTLY sales_by_city_month;\n-- CONCURRENTLY builds a new version in the background, then swaps\n-- Readers see the old version until the swap — no blocking\n\n-- Schedule with pg_cron (requires the extension):\n-- CREATE EXTENSION pg_cron;\n-- SELECT cron.schedule('refresh_sales', '0 * * * *',\n--   'REFRESH MATERIALIZED VIEW CONCURRENTLY sales_by_city_month');\n-- Runs at minute 0 of every hour\n",[45,3794,3795,3800,3810,3814,3825,3848,3867,3882,3889,3913,3938,3942,3947,3952,3968,3972,3977,3982,3987,3992,3996,4001,4006,4011,4016],{"__ignoreMap":43},[64,3796,3797],{"class":66,"line":67},[64,3798,3799],{"class":178},"-- Materialized view: pre-computes the aggregation, stored on disk\n",[64,3801,3802,3804,3807],{"class":66,"line":81},[64,3803,2409],{"class":70},[64,3805,3806],{"class":77}," MATERIALIZED VIEW sales_by_city_month ",[64,3808,3809],{"class":70},"AS\n",[64,3811,3812],{"class":66,"line":128},[64,3813,1932],{"class":70},[64,3815,3816,3819,3821,3823],{"class":66,"line":137},[64,3817,3818],{"class":90},"  c",[64,3820,225],{"class":77},[64,3822,426],{"class":90},[64,3824,230],{"class":77},[64,3826,3827,3830,3832,3834,3836,3838,3840,3842,3844,3846],{"class":66,"line":164},[64,3828,3829],{"class":77},"  date_trunc(",[64,3831,1352],{"class":103},[64,3833,155],{"class":77},[64,3835,240],{"class":90},[64,3837,225],{"class":77},[64,3839,1530],{"class":90},[64,3841,248],{"class":77},[64,3843,251],{"class":70},[64,3845,1360],{"class":70},[64,3847,230],{"class":77},[64,3849,3850,3852,3854,3856,3858,3860,3862,3864],{"class":66,"line":175},[64,3851,1144],{"class":90},[64,3853,94],{"class":77},[64,3855,240],{"class":90},[64,3857,225],{"class":77},[64,3859,276],{"class":90},[64,3861,248],{"class":77},[64,3863,251],{"class":70},[64,3865,3866],{"class":77}," revenue,\n",[64,3868,3869,3871,3873,3875,3877,3879],{"class":66,"line":182},[64,3870,235],{"class":90},[64,3872,94],{"class":77},[64,3874,1749],{"class":70},[64,3876,248],{"class":77},[64,3878,251],{"class":70},[64,3880,3881],{"class":77}," order_count\n",[64,3883,3884,3886],{"class":66,"line":387},[64,3885,131],{"class":70},[64,3887,3888],{"class":77}," orders o\n",[64,3890,3891,3894,3897,3899,3901,3903,3905,3907,3909,3911],{"class":66,"line":544},[64,3892,3893],{"class":70},"JOIN",[64,3895,3896],{"class":77}," customers c ",[64,3898,341],{"class":70},[64,3900,222],{"class":90},[64,3902,225],{"class":77},[64,3904,245],{"class":90},[64,3906,350],{"class":70},[64,3908,353],{"class":90},[64,3910,225],{"class":77},[64,3912,836],{"class":90},[64,3914,3915,3917,3919,3921,3923,3926,3928,3930,3932,3934,3936],{"class":66,"line":555},[64,3916,366],{"class":70},[64,3918,222],{"class":90},[64,3920,225],{"class":77},[64,3922,426],{"class":90},[64,3924,3925],{"class":77},", date_trunc(",[64,3927,1352],{"class":103},[64,3929,155],{"class":77},[64,3931,240],{"class":90},[64,3933,225],{"class":77},[64,3935,1530],{"class":90},[64,3937,934],{"class":77},[64,3939,3940],{"class":66,"line":567},[64,3941,683],{"emptyLinePlaceholder":682},[64,3943,3944],{"class":66,"line":597},[64,3945,3946],{"class":178},"-- CONCURRENT refresh requires at least one UNIQUE index\n",[64,3948,3949],{"class":66,"line":747},[64,3950,3951],{"class":178},"-- Without it, REFRESH CONCURRENTLY fails with an error\n",[64,3953,3954,3956,3959,3961,3964,3966],{"class":66,"line":756},[64,3955,2409],{"class":70},[64,3957,3958],{"class":70}," UNIQUE INDEX",[64,3960,2481],{"class":2415},[64,3962,3963],{"class":77}," sales_by_city_month (city, ",[64,3965,1421],{"class":70},[64,3967,934],{"class":77},[64,3969,3970],{"class":66,"line":762},[64,3971,683],{"emptyLinePlaceholder":682},[64,3973,3974],{"class":66,"line":947},[64,3975,3976],{"class":178},"-- Refresh concurrently (doesn't block reads during refresh):\n",[64,3978,3979],{"class":66,"line":1454},[64,3980,3981],{"class":77},"REFRESH MATERIALIZED VIEW CONCURRENTLY sales_by_city_month;\n",[64,3983,3984],{"class":66,"line":1477},[64,3985,3986],{"class":178},"-- CONCURRENTLY builds a new version in the background, then swaps\n",[64,3988,3989],{"class":66,"line":1485},[64,3990,3991],{"class":178},"-- Readers see the old version until the swap — no blocking\n",[64,3993,3994],{"class":66,"line":1511},[64,3995,683],{"emptyLinePlaceholder":682},[64,3997,3998],{"class":66,"line":1544},[64,3999,4000],{"class":178},"-- Schedule with pg_cron (requires the extension):\n",[64,4002,4003],{"class":66,"line":1564},[64,4004,4005],{"class":178},"-- CREATE EXTENSION pg_cron;\n",[64,4007,4008],{"class":66,"line":1577},[64,4009,4010],{"class":178},"-- SELECT cron.schedule('refresh_sales', '0 * * * *',\n",[64,4012,4013],{"class":66,"line":1583},[64,4014,4015],{"class":178},"--   'REFRESH MATERIALIZED VIEW CONCURRENTLY sales_by_city_month');\n",[64,4017,4018],{"class":66,"line":1900},[64,4019,4020],{"class":178},"-- Runs at minute 0 of every hour\n",[19,4022,4024],{"id":4023},"_18-multi-tenant-saas-with-rls","18. Multi-Tenant SaaS with RLS",[24,4026,4027,4028,4031,4032,4035,4036,4039],{},"Set up a multi-tenant schema: every table has ",[45,4029,4030],{},"tenant_id",", RLS policies enforce ",[45,4033,4034],{},"tenant_id = current_setting('app.tenant')",", and the app sets ",[45,4037,4038],{},"app.tenant"," per request.",[48,4041,4042,4044],{},[51,4043,53],{},[34,4045,4046],{"language":56},[38,4047,4049],{"className":59,"code":4048,"language":56,"meta":43,"style":43},"CREATE TABLE orders (\n  id BIGSERIAL PRIMARY KEY,\n  tenant_id BIGINT NOT NULL,\n  customer_id BIGINT NOT NULL,\n  amount NUMERIC(10,2) NOT NULL\n);\n\n-- Enable RLS and FORCE it (so even the owner is subject to policies)\nALTER TABLE orders ENABLE ROW LEVEL SECURITY;\nALTER TABLE orders FORCE ROW LEVEL SECURITY;\n\n-- Policy: users can only see rows matching their current tenant\nCREATE POLICY tenant_isolation ON orders\n  USING (tenant_id = current_setting('app.tenant')::bigint)\n  WITH CHECK (tenant_id = current_setting('app.tenant')::bigint);\n-- USING: filters existing rows on SELECT\u002FUPDATE\u002FDELETE\n-- WITH CHECK: validates new rows on INSERT\u002FUPDATE\n\n-- Application sets the tenant per request:\nSET app.tenant = '42';\nSELECT * FROM orders;  -- only sees tenant_id = 42 rows\n-- Tenant 99's rows are invisible — no WHERE clause needed in the query\n\n-- ⚠️ Test: verify cross-tenant isolation\n-- SET app.tenant = '1'; SELECT count(*) FROM orders;  -- only tenant 1's rows\n-- SET app.tenant = '2'; SELECT count(*) FROM orders;  -- only tenant 2's rows\n",[45,4050,4051,4062,4072,4084,4095,4118,4122,4126,4131,4154,4173,4177,4182,4196,4219,4241,4246,4251,4255,4260,4279,4293,4298,4302,4307,4312],{"__ignoreMap":43},[64,4052,4053,4055,4057,4060],{"class":66,"line":67},[64,4054,2409],{"class":70},[64,4056,2412],{"class":70},[64,4058,4059],{"class":2415}," orders",[64,4061,901],{"class":77},[64,4063,4064,4066,4068,4070],{"class":66,"line":81},[64,4065,2423],{"class":77},[64,4067,2426],{"class":70},[64,4069,2429],{"class":70},[64,4071,230],{"class":77},[64,4073,4074,4077,4080,4082],{"class":66,"line":128},[64,4075,4076],{"class":77},"  tenant_id ",[64,4078,4079],{"class":70},"BIGINT",[64,4081,3541],{"class":70},[64,4083,230],{"class":77},[64,4085,4086,4089,4091,4093],{"class":66,"line":137},[64,4087,4088],{"class":77},"  customer_id ",[64,4090,4079],{"class":70},[64,4092,3541],{"class":70},[64,4094,230],{"class":77},[64,4096,4097,4100,4103,4105,4108,4111,4113,4115],{"class":66,"line":164},[64,4098,4099],{"class":77},"  amount ",[64,4101,4102],{"class":70},"NUMERIC",[64,4104,94],{"class":77},[64,4106,4107],{"class":90},"10",[64,4109,4110],{"class":77},",",[64,4112,476],{"class":90},[64,4114,248],{"class":77},[64,4116,4117],{"class":70},"NOT NULL\n",[64,4119,4120],{"class":66,"line":175},[64,4121,934],{"class":77},[64,4123,4124],{"class":66,"line":182},[64,4125,683],{"emptyLinePlaceholder":682},[64,4127,4128],{"class":66,"line":387},[64,4129,4130],{"class":178},"-- Enable RLS and FORCE it (so even the owner is subject to policies)\n",[64,4132,4133,4136,4138,4141,4144,4147,4149,4152],{"class":66,"line":544},[64,4134,4135],{"class":70},"ALTER",[64,4137,2412],{"class":70},[64,4139,4140],{"class":77}," orders ",[64,4142,4143],{"class":70},"ENABLE",[64,4145,4146],{"class":70}," ROW",[64,4148,2844],{"class":70},[64,4150,4151],{"class":70}," SECURITY",[64,4153,172],{"class":77},[64,4155,4156,4158,4160,4162,4165,4167,4169,4171],{"class":66,"line":555},[64,4157,4135],{"class":70},[64,4159,2412],{"class":70},[64,4161,4140],{"class":77},[64,4163,4164],{"class":70},"FORCE",[64,4166,4146],{"class":70},[64,4168,2844],{"class":70},[64,4170,4151],{"class":70},[64,4172,172],{"class":77},[64,4174,4175],{"class":66,"line":567},[64,4176,683],{"emptyLinePlaceholder":682},[64,4178,4179],{"class":66,"line":597},[64,4180,4181],{"class":178},"-- Policy: users can only see rows matching their current tenant\n",[64,4183,4184,4186,4189,4192,4194],{"class":66,"line":747},[64,4185,2409],{"class":70},[64,4187,4188],{"class":70}," POLICY",[64,4190,4191],{"class":77}," tenant_isolation ",[64,4193,341],{"class":70},[64,4195,650],{"class":77},[64,4197,4198,4201,4204,4206,4209,4212,4214,4217],{"class":66,"line":756},[64,4199,4200],{"class":70},"  USING",[64,4202,4203],{"class":77}," (tenant_id ",[64,4205,1535],{"class":70},[64,4207,4208],{"class":77}," current_setting(",[64,4210,4211],{"class":103},"'app.tenant'",[64,4213,2593],{"class":77},[64,4215,4216],{"class":70},"bigint",[64,4218,161],{"class":77},[64,4220,4221,4224,4227,4229,4231,4233,4235,4237,4239],{"class":66,"line":762},[64,4222,4223],{"class":70},"  WITH",[64,4225,4226],{"class":70}," CHECK",[64,4228,4203],{"class":77},[64,4230,1535],{"class":70},[64,4232,4208],{"class":77},[64,4234,4211],{"class":103},[64,4236,2593],{"class":77},[64,4238,4216],{"class":70},[64,4240,934],{"class":77},[64,4242,4243],{"class":66,"line":947},[64,4244,4245],{"class":178},"-- USING: filters existing rows on SELECT\u002FUPDATE\u002FDELETE\n",[64,4247,4248],{"class":66,"line":1454},[64,4249,4250],{"class":178},"-- WITH CHECK: validates new rows on INSERT\u002FUPDATE\n",[64,4252,4253],{"class":66,"line":1477},[64,4254,683],{"emptyLinePlaceholder":682},[64,4256,4257],{"class":66,"line":1485},[64,4258,4259],{"class":178},"-- Application sets the tenant per request:\n",[64,4261,4262,4264,4267,4269,4272,4274,4277],{"class":66,"line":1511},[64,4263,2715],{"class":70},[64,4265,4266],{"class":90}," app",[64,4268,225],{"class":77},[64,4270,4271],{"class":90},"tenant",[64,4273,350],{"class":70},[64,4275,4276],{"class":103}," '42'",[64,4278,172],{"class":77},[64,4280,4281,4283,4285,4287,4290],{"class":66,"line":1544},[64,4282,71],{"class":70},[64,4284,2558],{"class":70},[64,4286,911],{"class":70},[64,4288,4289],{"class":77}," orders;  ",[64,4291,4292],{"class":178},"-- only sees tenant_id = 42 rows\n",[64,4294,4295],{"class":66,"line":1564},[64,4296,4297],{"class":178},"-- Tenant 99's rows are invisible — no WHERE clause needed in the query\n",[64,4299,4300],{"class":66,"line":1577},[64,4301,683],{"emptyLinePlaceholder":682},[64,4303,4304],{"class":66,"line":1583},[64,4305,4306],{"class":178},"-- ⚠️ Test: verify cross-tenant isolation\n",[64,4308,4309],{"class":66,"line":1900},[64,4310,4311],{"class":178},"-- SET app.tenant = '1'; SELECT count(*) FROM orders;  -- only tenant 1's rows\n",[64,4313,4314],{"class":66,"line":1906},[64,4315,4316],{"class":178},"-- SET app.tenant = '2'; SELECT count(*) FROM orders;  -- only tenant 2's rows\n",[19,4318,4320],{"id":4319},"_19-query-optimization-with-explain-analyze","19. Query Optimization with EXPLAIN ANALYZE",[24,4322,4323,4324,4327],{},"Take a slow query (a 3-table join with a filter), run ",[45,4325,4326],{},"EXPLAIN (ANALYZE, BUFFERS)",", identify the bottleneck (seq scan, missing index, bad join order), add an index or rewrite, and measure the improvement.",[48,4329,4330,4332],{},[51,4331,53],{},[34,4333,4334],{"language":56},[38,4335,4337],{"className":59,"code":4336,"language":56,"meta":43,"style":43},"-- The slow query:\nSELECT c.name, p.name AS product_name, SUM(oi.quantity * oi.price) AS revenue\nFROM customers c\nJOIN orders o ON c.id = o.customer_id\nJOIN order_items oi ON o.id = oi.order_id\nJOIN products p ON oi.product_id = p.id\nWHERE c.city = 'NYC' AND o.ordered_on >= '2024-01-01'\nGROUP BY c.name, p.name;\n\n-- Step 1: Diagnose\nEXPLAIN (ANALYZE, BUFFERS) [query above];\n-- Look for: Seq Scan on a large table, Sort spilled to disk, Nested Loop with loops > 1000\n\n-- Step 2: Common fixes\n-- Missing index on orders.customer_id → CREATE INDEX ON orders(customer_id)\n-- Missing index on order_items.order_id → CREATE INDEX ON order_items(order_id)\n-- Missing index on customers.city → CREATE INDEX ON customers(city)\n-- Stale stats → ANALYZE orders; ANALYZE order_items;\n-- Sort spill → SET work_mem = '256MB';\n\n-- Step 3: Verify\nEXPLAIN (ANALYZE, BUFFERS) [query above];\n-- Compare: actual time before vs after, shared reads vs hits, sort method\n",[45,4338,4339,4344,4395,4401,4423,4447,4472,4501,4521,4525,4530,4535,4540,4544,4549,4554,4559,4564,4569,4574,4578,4583,4587],{"__ignoreMap":43},[64,4340,4341],{"class":66,"line":67},[64,4342,4343],{"class":178},"-- The slow query:\n",[64,4345,4346,4348,4350,4352,4354,4356,4358,4360,4362,4364,4367,4369,4371,4374,4376,4379,4381,4384,4386,4389,4391,4393],{"class":66,"line":81},[64,4347,71],{"class":70},[64,4349,222],{"class":90},[64,4351,225],{"class":77},[64,4353,97],{"class":90},[64,4355,155],{"class":77},[64,4357,24],{"class":90},[64,4359,225],{"class":77},[64,4361,97],{"class":90},[64,4363,122],{"class":70},[64,4365,4366],{"class":77}," product_name, ",[64,4368,267],{"class":90},[64,4370,94],{"class":77},[64,4372,4373],{"class":90},"oi",[64,4375,225],{"class":77},[64,4377,4378],{"class":90},"quantity",[64,4380,2558],{"class":70},[64,4382,4383],{"class":90}," oi",[64,4385,225],{"class":77},[64,4387,4388],{"class":90},"price",[64,4390,248],{"class":77},[64,4392,251],{"class":70},[64,4394,1371],{"class":77},[64,4396,4397,4399],{"class":66,"line":128},[64,4398,131],{"class":70},[64,4400,330],{"class":77},[64,4402,4403,4405,4407,4409,4411,4413,4415,4417,4419,4421],{"class":66,"line":137},[64,4404,3893],{"class":70},[64,4406,338],{"class":77},[64,4408,341],{"class":70},[64,4410,222],{"class":90},[64,4412,225],{"class":77},[64,4414,245],{"class":90},[64,4416,350],{"class":70},[64,4418,353],{"class":90},[64,4420,225],{"class":77},[64,4422,836],{"class":90},[64,4424,4425,4427,4430,4432,4434,4436,4438,4440,4442,4444],{"class":66,"line":164},[64,4426,3893],{"class":70},[64,4428,4429],{"class":77}," order_items oi ",[64,4431,341],{"class":70},[64,4433,353],{"class":90},[64,4435,225],{"class":77},[64,4437,245],{"class":90},[64,4439,350],{"class":70},[64,4441,4383],{"class":90},[64,4443,225],{"class":77},[64,4445,4446],{"class":90},"order_id\n",[64,4448,4449,4451,4454,4456,4458,4460,4463,4465,4468,4470],{"class":66,"line":175},[64,4450,3893],{"class":70},[64,4452,4453],{"class":77}," products p ",[64,4455,341],{"class":70},[64,4457,4383],{"class":90},[64,4459,225],{"class":77},[64,4461,4462],{"class":90},"product_id",[64,4464,350],{"class":70},[64,4466,4467],{"class":90}," p",[64,4469,225],{"class":77},[64,4471,541],{"class":90},[64,4473,4474,4476,4478,4480,4482,4484,4487,4489,4491,4493,4495,4498],{"class":66,"line":182},[64,4475,140],{"class":70},[64,4477,222],{"class":90},[64,4479,225],{"class":77},[64,4481,426],{"class":90},[64,4483,350],{"class":70},[64,4485,4486],{"class":103}," 'NYC'",[64,4488,1188],{"class":70},[64,4490,353],{"class":90},[64,4492,225],{"class":77},[64,4494,1530],{"class":90},[64,4496,4497],{"class":70}," >=",[64,4499,4500],{"class":103}," '2024-01-01'\n",[64,4502,4503,4505,4507,4509,4511,4513,4515,4517,4519],{"class":66,"line":387},[64,4504,366],{"class":70},[64,4506,222],{"class":90},[64,4508,225],{"class":77},[64,4510,97],{"class":90},[64,4512,155],{"class":77},[64,4514,24],{"class":90},[64,4516,225],{"class":77},[64,4518,97],{"class":90},[64,4520,172],{"class":77},[64,4522,4523],{"class":66,"line":544},[64,4524,683],{"emptyLinePlaceholder":682},[64,4526,4527],{"class":66,"line":555},[64,4528,4529],{"class":178},"-- Step 1: Diagnose\n",[64,4531,4532],{"class":66,"line":567},[64,4533,4534],{"class":77},"EXPLAIN (ANALYZE, BUFFERS) [query above];\n",[64,4536,4537],{"class":66,"line":597},[64,4538,4539],{"class":178},"-- Look for: Seq Scan on a large table, Sort spilled to disk, Nested Loop with loops > 1000\n",[64,4541,4542],{"class":66,"line":747},[64,4543,683],{"emptyLinePlaceholder":682},[64,4545,4546],{"class":66,"line":756},[64,4547,4548],{"class":178},"-- Step 2: Common fixes\n",[64,4550,4551],{"class":66,"line":762},[64,4552,4553],{"class":178},"-- Missing index on orders.customer_id → CREATE INDEX ON orders(customer_id)\n",[64,4555,4556],{"class":66,"line":947},[64,4557,4558],{"class":178},"-- Missing index on order_items.order_id → CREATE INDEX ON order_items(order_id)\n",[64,4560,4561],{"class":66,"line":1454},[64,4562,4563],{"class":178},"-- Missing index on customers.city → CREATE INDEX ON customers(city)\n",[64,4565,4566],{"class":66,"line":1477},[64,4567,4568],{"class":178},"-- Stale stats → ANALYZE orders; ANALYZE order_items;\n",[64,4570,4571],{"class":66,"line":1485},[64,4572,4573],{"class":178},"-- Sort spill → SET work_mem = '256MB';\n",[64,4575,4576],{"class":66,"line":1511},[64,4577,683],{"emptyLinePlaceholder":682},[64,4579,4580],{"class":66,"line":1544},[64,4581,4582],{"class":178},"-- Step 3: Verify\n",[64,4584,4585],{"class":66,"line":1564},[64,4586,4534],{"class":77},[64,4588,4589],{"class":66,"line":1577},[64,4590,4591],{"class":178},"-- Compare: actual time before vs after, shared reads vs hits, sort method\n",[19,4593,4595],{"id":4594},"_20-batch-upsert-with-on-conflict","20. Batch UPSERT with ON CONFLICT",[24,4597,4598],{},"Write a batch upsert that inserts 10,000 rows, updating existing ones if the primary key conflicts, and returns the count of inserts vs updates.",[48,4600,4601,4603],{},[51,4602,53],{},[34,4604,4605],{"language":56},[38,4606,4608],{"className":59,"code":4607,"language":56,"meta":43,"style":43},"-- Batch upsert with conflict tracking\nWITH upsert AS (\n  INSERT INTO products (id, name, price, updated_at)\n  SELECT id, name, price, now()\n  FROM staging_products\n  ON CONFLICT (id) DO UPDATE\n  SET name = EXCLUDED.name,\n      price = EXCLUDED.price,\n      updated_at = now()\n  WHERE products.name IS DISTINCT FROM EXCLUDED.name  -- only update if changed\n     OR products.price IS DISTINCT FROM EXCLUDED.price\n  RETURNING id, (xmax = 0) AS was_inserted\n  -- xmax = 0 means the row was INSERTed (not updated); xmax ≠ 0 means UPDATEd\n)\nSELECT\n  COUNT(*) FILTER (WHERE was_inserted) AS inserts,\n  COUNT(*) FILTER (WHERE NOT was_inserted) AS updates\nFROM upsert;\n-- The RETURNING clause captures which rows were inserts vs updates\n-- IS DISTINCT FROM prevents unnecessary writes (no WAL\u002Fbloat for unchanged rows)\n",[45,4609,4610,4615,4626,4639,4654,4661,4672,4690,4705,4716,4743,4767,4784,4789,4793,4797,4821,4846,4853,4858],{"__ignoreMap":43},[64,4611,4612],{"class":66,"line":67},[64,4613,4614],{"class":178},"-- Batch upsert with conflict tracking\n",[64,4616,4617,4619,4622,4624],{"class":66,"line":81},[64,4618,992],{"class":70},[64,4620,4621],{"class":77}," upsert ",[64,4623,251],{"class":70},[64,4625,901],{"class":77},[64,4627,4628,4631,4634,4636],{"class":66,"line":128},[64,4629,4630],{"class":70},"  INSERT INTO",[64,4632,4633],{"class":77}," products (id, ",[64,4635,97],{"class":70},[64,4637,4638],{"class":77},", price, updated_at)\n",[64,4640,4641,4643,4645,4647,4650,4652],{"class":66,"line":137},[64,4642,506],{"class":70},[64,4644,2164],{"class":77},[64,4646,97],{"class":70},[64,4648,4649],{"class":77},", price, ",[64,4651,2757],{"class":70},[64,4653,2458],{"class":77},[64,4655,4656,4658],{"class":66,"line":164},[64,4657,522],{"class":70},[64,4659,4660],{"class":77}," staging_products\n",[64,4662,4663,4666,4669],{"class":66,"line":175},[64,4664,4665],{"class":70},"  ON",[64,4667,4668],{"class":77}," CONFLICT (id) DO ",[64,4670,4671],{"class":70},"UPDATE\n",[64,4673,4674,4677,4679,4681,4684,4686,4688],{"class":66,"line":182},[64,4675,4676],{"class":70},"  SET",[64,4678,74],{"class":70},[64,4680,350],{"class":70},[64,4682,4683],{"class":90}," EXCLUDED",[64,4685,225],{"class":77},[64,4687,97],{"class":90},[64,4689,230],{"class":77},[64,4691,4692,4695,4697,4699,4701,4703],{"class":66,"line":387},[64,4693,4694],{"class":77},"      price ",[64,4696,1535],{"class":70},[64,4698,4683],{"class":90},[64,4700,225],{"class":77},[64,4702,4388],{"class":90},[64,4704,230],{"class":77},[64,4706,4707,4710,4712,4714],{"class":66,"line":544},[64,4708,4709],{"class":77},"      updated_at ",[64,4711,1535],{"class":70},[64,4713,2455],{"class":70},[64,4715,2458],{"class":77},[64,4717,4718,4720,4723,4725,4727,4729,4732,4734,4736,4738,4740],{"class":66,"line":555},[64,4719,1382],{"class":70},[64,4721,4722],{"class":90}," products",[64,4724,225],{"class":77},[64,4726,97],{"class":90},[64,4728,849],{"class":70},[64,4730,4731],{"class":70}," DISTINCT",[64,4733,911],{"class":70},[64,4735,4683],{"class":90},[64,4737,225],{"class":77},[64,4739,97],{"class":90},[64,4741,4742],{"class":178},"  -- only update if changed\n",[64,4744,4745,4748,4750,4752,4754,4756,4758,4760,4762,4764],{"class":66,"line":567},[64,4746,4747],{"class":70},"     OR",[64,4749,4722],{"class":90},[64,4751,225],{"class":77},[64,4753,4388],{"class":90},[64,4755,849],{"class":70},[64,4757,4731],{"class":70},[64,4759,911],{"class":70},[64,4761,4683],{"class":90},[64,4763,225],{"class":77},[64,4765,4766],{"class":90},"price\n",[64,4768,4769,4772,4774,4777,4779,4781],{"class":66,"line":597},[64,4770,4771],{"class":77},"  RETURNING id, (xmax ",[64,4773,1535],{"class":70},[64,4775,4776],{"class":90}," 0",[64,4778,248],{"class":77},[64,4780,251],{"class":70},[64,4782,4783],{"class":77}," was_inserted\n",[64,4785,4786],{"class":66,"line":747},[64,4787,4788],{"class":178},"  -- xmax = 0 means the row was INSERTed (not updated); xmax ≠ 0 means UPDATEd\n",[64,4790,4791],{"class":66,"line":756},[64,4792,161],{"class":77},[64,4794,4795],{"class":66,"line":762},[64,4796,1932],{"class":70},[64,4798,4799,4801,4803,4805,4807,4809,4811,4813,4816,4818],{"class":66,"line":947},[64,4800,235],{"class":90},[64,4802,94],{"class":77},[64,4804,1749],{"class":70},[64,4806,248],{"class":77},[64,4808,1946],{"class":70},[64,4810,149],{"class":77},[64,4812,140],{"class":70},[64,4814,4815],{"class":77}," was_inserted) ",[64,4817,251],{"class":70},[64,4819,4820],{"class":77}," inserts,\n",[64,4822,4823,4825,4827,4829,4831,4833,4835,4837,4839,4841,4843],{"class":66,"line":1454},[64,4824,235],{"class":90},[64,4826,94],{"class":77},[64,4828,1749],{"class":70},[64,4830,248],{"class":77},[64,4832,1946],{"class":70},[64,4834,149],{"class":77},[64,4836,140],{"class":70},[64,4838,895],{"class":70},[64,4840,4815],{"class":77},[64,4842,251],{"class":70},[64,4844,4845],{"class":77}," updates\n",[64,4847,4848,4850],{"class":66,"line":1477},[64,4849,131],{"class":70},[64,4851,4852],{"class":77}," upsert;\n",[64,4854,4855],{"class":66,"line":1485},[64,4856,4857],{"class":178},"-- The RETURNING clause captures which rows were inserts vs updates\n",[64,4859,4860],{"class":66,"line":1511},[64,4861,4862],{"class":178},"-- IS DISTINCT FROM prevents unnecessary writes (no WAL\u002Fbloat for unchanged rows)\n",[14,4864,4866],{"id":4865},"capstone-project-ideas","Capstone Project Ideas",[19,4868,4870],{"id":4869},"_21-mini-e-commerce-analytics-database","21. Mini E-Commerce Analytics Database",[24,4872,4873],{},"Design a normalized schema for an e-commerce analytics system: customers, orders, order_items, products, categories (hierarchical). Build a materialized view for \"customer lifetime spend\" with a concurrent refresh strategy. Add keyset pagination for the orders list. Create a GIN index for product name search.",[24,4875,4876],{},[30,4877,4878],{},"Key challenges:",[4880,4881,4882,4886,4889],"ul",{},[4883,4884,4885],"li",{},"Recursive CTE for category hierarchy (products → subcategory → category → department)",[4883,4887,4888],{},"Materialized view refresh strategy (concurrent, scheduled via pg_cron)",[4883,4890,4891,4892],{},"Denormalize a hot query path (customer lifetime spend) with a trigger-maintained summary column on ",[45,4893,4894],{},"customers",[19,4896,4898],{"id":4897},"_22-multi-tenant-saas-schema-with-rls","22. Multi-Tenant SaaS Schema with RLS",[24,4900,4901,4902,4904,4905,4907,4908,4910,4911,225],{},"Every table has ",[45,4903,4030],{},". RLS policies enforce ",[45,4906,4034],{},". The app sets ",[45,4909,4038],{}," per request. Test cross-tenant isolation. Add a superadmin role that bypasses RLS via ",[45,4912,4913],{},"BYPASSRLS",[24,4915,4916],{},[30,4917,4878],{},[4880,4919,4920,4926,4931,4938],{},[4883,4921,4922,4925],{},[45,4923,4924],{},"FORCE ROW LEVEL SECURITY"," so the owner is also subject to policies",[4883,4927,4928,4930],{},[45,4929,4913],{}," attribute for the superadmin role",[4883,4932,4933,4934,4937],{},"Verify that a bug in ",[45,4935,4936],{},"SET app.tenant"," doesn't leak data (test with mismatched tenant)",[4883,4939,4940,4941],{},"Connection pooling: PgBouncer transaction mode + per-transaction ",[45,4942,4943],{},"SET LOCAL app.tenant",[19,4945,4947],{"id":4946},"_23-audit-system-with-triggers","23. Audit System with Triggers",[24,4949,4950,4951,4954,4955,776,4958,4961],{},"Build a generic audit system: a single trigger function that logs any table's changes to a shared ",[45,4952,4953],{},"audit_log"," table using ",[45,4956,4957],{},"to_jsonb(OLD)",[45,4959,4960],{},"to_jsonb(NEW)",". Partition the audit log by month. Add retention (drop partitions older than 1 year).",[24,4963,4964],{},[30,4965,4878],{},[4880,4967,4968,4977,4984,4991],{},[4883,4969,4970,4971,4973,4974,4976],{},"Generic trigger function using ",[45,4972,4957],{}," \u002F ",[45,4975,4960],{}," (works for any table)",[4883,4978,4979,4980,4983],{},"Partition by ",[45,4981,4982],{},"changed_at"," month — automatic partition creation via pg_partman or a cron job",[4883,4985,4986,4987,4990],{},"Retention: ",[45,4988,4989],{},"DROP TABLE audit_log_2023_01"," is instant (no VACUUM needed)",[4883,4992,4993,4994,776,4997,5000],{},"Track the ",[45,4995,4996],{},"search_path",[45,4998,4999],{},"current_user"," for accountability",[19,5002,5004],{"id":5003},"_24-real-time-dashboard-with-materialized-views-keyset-pagination","24. Real-Time Dashboard with Materialized Views + Keyset Pagination",[24,5006,5007,5008,5011],{},"Build a dashboard materialized view (revenue by day by city) refreshed every 5 minutes. Serve the dashboard with keyset pagination over the materialized view. Add a ",[45,5009,5010],{},"REFRESH CONCURRENTLY"," strategy with a unique index.",[24,5013,5014],{},[30,5015,4878],{},[4880,5017,5018,5024,5027,5030],{},[4883,5019,5020,5023],{},[45,5021,5022],{},"REFRESH MATERIALIZED VIEW CONCURRENTLY"," requires a unique index",[4883,5025,5026],{},"Keyset pagination over the materialized view (sorted by revenue DESC, city, date)",[4883,5028,5029],{},"Stale data handling: the dashboard shows \"last refreshed at\" timestamp",[4883,5031,5032],{},"pg_cron schedule with error handling (what if the refresh fails?)",[14,5034,5036],{"id":5035},"further-reading","📚 Further Reading",[4880,5038,5039,5049,5057,5065,5073],{},[4883,5040,5041,5048],{},[5042,5043,5047],"a",{"href":5044,"rel":5045},"https:\u002F\u002Fuse-the-index-luke.com",[5046],"nofollow","Use The Index, Luke"," — indexing explained.",[4883,5050,5051,5056],{},[5042,5052,5055],{"href":5053,"rel":5054},"https:\u002F\u002Ftheartofpostgresql.com",[5046],"The Art of PostgreSQL"," — query writing for developers.",[4883,5058,5059,5064],{},[5042,5060,5063],{"href":5061,"rel":5062},"https:\u002F\u002Fwww.postgresql.org\u002Fdocs\u002F",[5046],"PostgreSQL Docs"," — the reference.",[4883,5066,5067,5072],{},[5042,5068,5071],{"href":5069,"rel":5070},"https:\u002F\u002Fwiki.postgresql.org\u002Fwiki\u002FMain_Page",[5046],"PostgreSQL Wiki"," — performance and tuning guides.",[4883,5074,5075,5080],{},[5042,5076,5079],{"href":5077,"rel":5078},"https:\u002F\u002Fpgexercises.com",[5046],"pgexercises.com"," — interactive practice.",[5082,5083,5084],"style",{},"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 .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 pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html.github-dark .shiki span {color: var(--shiki-github-dark);background: var(--shiki-github-dark-bg);font-style: var(--shiki-github-dark-font-style);font-weight: var(--shiki-github-dark-font-weight);text-decoration: var(--shiki-github-dark-text-decoration);}html pre.shiki code .sIsaT, html code.shiki .sIsaT{--shiki-default:#6F42C1;--shiki-github-dark:#B392F0}",{"title":43,"searchDepth":81,"depth":81,"links":5086},[5087,5094,5103,5113,5119],{"id":16,"depth":81,"text":17,"children":5088},[5089,5090,5091,5092,5093],{"id":21,"depth":128,"text":22},{"id":188,"depth":128,"text":189},{"id":400,"depth":128,"text":401},{"id":609,"depth":128,"text":610},{"id":768,"depth":128,"text":769},{"id":953,"depth":81,"text":954,"children":5095},[5096,5097,5098,5099,5100,5101,5102],{"id":957,"depth":128,"text":958},{"id":1109,"depth":128,"text":1110},{"id":1240,"depth":128,"text":1241},{"id":1589,"depth":128,"text":1590},{"id":1912,"depth":128,"text":1913},{"id":2112,"depth":128,"text":2113},{"id":2377,"depth":128,"text":2378},{"id":2622,"depth":81,"text":2623,"children":5104},[5105,5106,5107,5108,5109,5110,5111,5112],{"id":2626,"depth":128,"text":2627},{"id":2809,"depth":128,"text":2810},{"id":3036,"depth":128,"text":3037},{"id":3487,"depth":128,"text":3488},{"id":3776,"depth":128,"text":3777},{"id":4023,"depth":128,"text":4024},{"id":4319,"depth":128,"text":4320},{"id":4594,"depth":128,"text":4595},{"id":4865,"depth":81,"text":4866,"children":5114},[5115,5116,5117,5118],{"id":4869,"depth":128,"text":4870},{"id":4897,"depth":128,"text":4898},{"id":4946,"depth":128,"text":4947},{"id":5003,"depth":128,"text":5004},{"id":5035,"depth":81,"text":5036},"md",{},"\u002Fsql\u002F30-exercises-and-projects",{"title":5,"description":43},"sql\u002F30-exercises-and-projects","YIrNg0J7LP1HHEx1wPvjjMw6JD3lz_aBV3rOvG-CXIQ",1789924655292]