[{"data":1,"prerenderedAt":3518},["ShallowReactive",2],{"page-\u002Fsql\u002F06-subqueries":3},{"id":4,"title":5,"body":6,"description":3511,"extension":3512,"meta":3513,"navigation":298,"path":3514,"seo":3515,"stem":3516,"__hash__":3517},"content\u002Fsql\u002F06-subqueries.md","06 — Subqueries",{"type":7,"value":8,"toc":3479},"minimark",[9,13,32,37,44,187,193,209,213,216,459,468,472,475,592,603,607,622,627,679,683,688,742,769,772,939,948,952,972,1141,1147,1168,1172,1185,1311,1327,1331,1410,1414,1418,1560,1564,1638,1642,1809,1813,1824,1960,1973,1977,2494,2498,2502,2611,2615,2726,2730,2733,2813,2816,2940,2944,3065,3069,3217,3221,3228,3311,3455,3458,3475],[10,11,5],"h1",{"id":12},"_06-subqueries",[14,15,16,17,21,22,21,25,21,28,31],"p",{},"A subquery is a query nested inside another query. Subqueries appear in ",[18,19,20],"code",{},"WHERE",", ",[18,23,24],{},"SELECT",[18,26,27],{},"FROM",[18,29,30],{},"HAVING",", and join clauses. They compose queries before reaching for CTEs (chapter 07) or joins.",[33,34,36],"h2",{"id":35},"subquery-classification-two-axes","Subquery Classification — Two Axes",[14,38,39,43],{},[40,41,42],"strong",{},"Return shape"," determines where a subquery is legal and which operators accept it:",[45,46,47,69],"table",{},[48,49,50],"thead",{},[51,52,53,57,60,63,66],"tr",{},[54,55,56],"th",{},"Shape",[54,58,59],{},"Rows",[54,61,62],{},"Columns",[54,64,65],{},"Valid in",[54,67,68],{},"Operators",[70,71,72,113,142],"tbody",{},[51,73,74,80,83,86,98],{},[75,76,77],"td",{},[40,78,79],{},"Scalar",[75,81,82],{},"≤1",[75,84,85],{},"1",[75,87,88,21,90,92,93,21,95],{},[18,89,24],{},[18,91,20],{}," (comparison), ",[18,94,30],{},[18,96,97],{},"VALUES",[75,99,100,21,103,21,106,21,109,112],{},[18,101,102],{},"=",[18,104,105],{},"\u003C>",[18,107,108],{},"\u003C",[18,110,111],{},">",", arithmetic",[51,114,115,120,122,125,130],{},[75,116,117],{},[40,118,119],{},"Row",[75,121,82],{},[75,123,124],{},"N",[75,126,127,129],{},[18,128,20],{}," (row constructor)",[75,131,132,21,134,21,136,21,139],{},[18,133,102],{},[18,135,105],{},[18,137,138],{},"IN",[18,140,141],{},"IS DISTINCT FROM",[51,143,144,149,151,154,174],{},[75,145,146],{},[40,147,148],{},"Table",[75,150,124],{},[75,152,153],{},"M",[75,155,156,158,159,161,162,161,165,161,168,171,172],{},[18,157,20],{}," (",[18,160,138],{},"\u002F",[18,163,164],{},"ANY",[18,166,167],{},"ALL",[18,169,170],{},"EXISTS","), ",[18,173,27],{},[75,175,176,21,178,21,181,21,183,21,185],{},[18,177,138],{},[18,179,180],{},"NOT IN",[18,182,164],{},[18,184,167],{},[18,186,170],{},[14,188,189,192],{},[40,190,191],{},"Correlation"," determines execution cost:",[194,195,196,203],"ul",{},[197,198,199,202],"li",{},[40,200,201],{},"Uncorrelated"," — no reference to the outer query; executes once, result cached.",[197,204,205,208],{},[40,206,207],{},"Correlated"," — references outer columns; logically re-executes per outer row (planner may decorrelate into a join, but not always).",[33,210,212],{"id":211},"scalar-subqueries-one-row-one-column","Scalar Subqueries — One Row, One Column",[14,214,215],{},"A scalar subquery returns exactly one row and one column. It can appear anywhere a single value is legal.",[217,218,220],"code-wrapper",{"language":219},"sql",[221,222,226],"pre",{"className":223,"code":224,"language":219,"meta":225,"style":225},"language-sql shiki shiki-themes github-light github-dark","-- Orders above the average order amount.\n-- The scalar subquery (SELECT AVG(...)) executes ONCE (uncorrelated),\n-- its result is cached, and the outer query compares every row to that constant.\nSELECT id, amount\nFROM orders\nWHERE amount > (SELECT AVG(amount) FROM orders);\n\n-- Each order with the overall average attached for comparison.\n-- The subquery runs once; the cached value is attached to every output row.\nSELECT id,\n       amount,\n       (SELECT AVG(amount) FROM orders) AS overall_avg  -- scalar in SELECT list\nFROM orders;\n\n-- Per-customer order count via a CORRELATED scalar subquery.\n-- The subquery references c.id from the outer row → re-executes per customer.\n-- For 1M customers this is 1M subquery executions unless the planner decorrelates it.\nSELECT c.name,\n       (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS order_count\nFROM customers c;\n","",[18,227,228,237,243,249,259,267,293,300,306,312,320,326,352,360,365,371,377,383,400,451],{"__ignoreMap":225},[229,230,233],"span",{"class":231,"line":232},"line",1,[229,234,236],{"class":235},"sdCPZ","-- Orders above the average order amount.\n",[229,238,240],{"class":231,"line":239},2,[229,241,242],{"class":235},"-- The scalar subquery (SELECT AVG(...)) executes ONCE (uncorrelated),\n",[229,244,246],{"class":231,"line":245},3,[229,247,248],{"class":235},"-- its result is cached, and the outer query compares every row to that constant.\n",[229,250,252,255],{"class":231,"line":251},4,[229,253,24],{"class":254},"svdQ7",[229,256,258],{"class":257},"ssxIu"," id, amount\n",[229,260,262,264],{"class":231,"line":261},5,[229,263,27],{"class":254},[229,265,266],{"class":257}," orders\n",[229,268,270,272,275,277,279,281,285,288,290],{"class":231,"line":269},6,[229,271,20],{"class":254},[229,273,274],{"class":257}," amount ",[229,276,111],{"class":254},[229,278,158],{"class":257},[229,280,24],{"class":254},[229,282,284],{"class":283},"snvgF"," AVG",[229,286,287],{"class":257},"(amount) ",[229,289,27],{"class":254},[229,291,292],{"class":257}," orders);\n",[229,294,296],{"class":231,"line":295},7,[229,297,299],{"emptyLinePlaceholder":298},true,"\n",[229,301,303],{"class":231,"line":302},8,[229,304,305],{"class":235},"-- Each order with the overall average attached for comparison.\n",[229,307,309],{"class":231,"line":308},9,[229,310,311],{"class":235},"-- The subquery runs once; the cached value is attached to every output row.\n",[229,313,315,317],{"class":231,"line":314},10,[229,316,24],{"class":254},[229,318,319],{"class":257}," id,\n",[229,321,323],{"class":231,"line":322},11,[229,324,325],{"class":257},"       amount,\n",[229,327,329,332,334,336,338,340,343,346,349],{"class":231,"line":328},12,[229,330,331],{"class":257},"       (",[229,333,24],{"class":254},[229,335,284],{"class":283},[229,337,287],{"class":257},[229,339,27],{"class":254},[229,341,342],{"class":257}," orders) ",[229,344,345],{"class":254},"AS",[229,347,348],{"class":257}," overall_avg  ",[229,350,351],{"class":235},"-- scalar in SELECT list\n",[229,353,355,357],{"class":231,"line":354},13,[229,356,27],{"class":254},[229,358,359],{"class":257}," orders;\n",[229,361,363],{"class":231,"line":362},14,[229,364,299],{"emptyLinePlaceholder":298},[229,366,368],{"class":231,"line":367},15,[229,369,370],{"class":235},"-- Per-customer order count via a CORRELATED scalar subquery.\n",[229,372,374],{"class":231,"line":373},16,[229,375,376],{"class":235},"-- The subquery references c.id from the outer row → re-executes per customer.\n",[229,378,380],{"class":231,"line":379},17,[229,381,382],{"class":235},"-- For 1M customers this is 1M subquery executions unless the planner decorrelates it.\n",[229,384,386,388,391,394,397],{"class":231,"line":385},18,[229,387,24],{"class":254},[229,389,390],{"class":283}," c",[229,392,393],{"class":257},".",[229,395,396],{"class":283},"name",[229,398,399],{"class":257},",\n",[229,401,403,405,407,410,413,416,419,421,424,426,429,431,434,437,439,441,444,446,448],{"class":231,"line":402},19,[229,404,331],{"class":257},[229,406,24],{"class":254},[229,408,409],{"class":283}," COUNT",[229,411,412],{"class":257},"(",[229,414,415],{"class":254},"*",[229,417,418],{"class":257},") ",[229,420,27],{"class":254},[229,422,423],{"class":257}," orders o ",[229,425,20],{"class":254},[229,427,428],{"class":283}," o",[229,430,393],{"class":257},[229,432,433],{"class":283},"customer_id",[229,435,436],{"class":254}," =",[229,438,390],{"class":283},[229,440,393],{"class":257},[229,442,443],{"class":283},"id",[229,445,418],{"class":257},[229,447,345],{"class":254},[229,449,450],{"class":257}," order_count\n",[229,452,454,456],{"class":231,"line":453},20,[229,455,27],{"class":254},[229,457,458],{"class":257}," customers c;\n",[14,460,461,464,465,393],{},[40,462,463],{},"Scalar subquery returning zero rows → NULL."," Returning more than one row → runtime error: ",[18,466,467],{},"ERROR: more than one row returned by a subquery used as an expression",[33,469,471],{"id":470},"row-subqueries-one-row-multiple-columns","Row Subqueries — One Row, Multiple Columns",[14,473,474],{},"A row subquery returns one row with N columns. Compare against a row constructor:",[217,476,477],{"language":219},[221,478,480],{"className":223,"code":479,"language":219,"meta":225,"style":225},"-- Row constructor comparison: (customer_id, amount) = (1, 99.50)\n-- Expands to: customer_id = 1 AND amount = 99.50\n-- Lexicographic, AND-connected → any NULL makes the whole predicate UNKNOWN.\nSELECT * FROM orders\nWHERE (customer_id, amount) = (1, 99.50);\n\n-- Row subquery form — find orders matching customer 1's first order exactly\nSELECT * FROM orders\nWHERE (customer_id, amount) = (\n  SELECT customer_id, amount FROM orders WHERE id = 1\n);\n",[18,481,482,487,492,497,509,535,539,544,554,565,588],{"__ignoreMap":225},[229,483,484],{"class":231,"line":232},[229,485,486],{"class":235},"-- Row constructor comparison: (customer_id, amount) = (1, 99.50)\n",[229,488,489],{"class":231,"line":239},[229,490,491],{"class":235},"-- Expands to: customer_id = 1 AND amount = 99.50\n",[229,493,494],{"class":231,"line":245},[229,495,496],{"class":235},"-- Lexicographic, AND-connected → any NULL makes the whole predicate UNKNOWN.\n",[229,498,499,501,504,507],{"class":231,"line":251},[229,500,24],{"class":254},[229,502,503],{"class":254}," *",[229,505,506],{"class":254}," FROM",[229,508,266],{"class":257},[229,510,511,513,516,518,520,522,524,527,529,532],{"class":231,"line":261},[229,512,20],{"class":254},[229,514,515],{"class":257}," (customer_id, amount) ",[229,517,102],{"class":254},[229,519,158],{"class":257},[229,521,85],{"class":283},[229,523,21],{"class":257},[229,525,526],{"class":283},"99",[229,528,393],{"class":257},[229,530,531],{"class":283},"50",[229,533,534],{"class":257},");\n",[229,536,537],{"class":231,"line":269},[229,538,299],{"emptyLinePlaceholder":298},[229,540,541],{"class":231,"line":295},[229,542,543],{"class":235},"-- Row subquery form — find orders matching customer 1's first order exactly\n",[229,545,546,548,550,552],{"class":231,"line":302},[229,547,24],{"class":254},[229,549,503],{"class":254},[229,551,506],{"class":254},[229,553,266],{"class":257},[229,555,556,558,560,562],{"class":231,"line":308},[229,557,20],{"class":254},[229,559,515],{"class":257},[229,561,102],{"class":254},[229,563,564],{"class":257}," (\n",[229,566,567,570,573,575,578,580,583,585],{"class":231,"line":314},[229,568,569],{"class":254},"  SELECT",[229,571,572],{"class":257}," customer_id, amount ",[229,574,27],{"class":254},[229,576,577],{"class":257}," orders ",[229,579,20],{"class":254},[229,581,582],{"class":257}," id ",[229,584,102],{"class":254},[229,586,587],{"class":283}," 1\n",[229,589,590],{"class":231,"line":322},[229,591,534],{"class":257},[14,593,594,595,598,599,602],{},"Row comparisons follow three-valued logic — ",[18,596,597],{},"(a, b) = (x, y)"," is ",[18,600,601],{},"a = x AND b = y",", so any NULL makes the whole thing UNKNOWN.",[33,604,606],{"id":605},"table-subqueries-in-any-all-exists","Table Subqueries — IN, ANY, ALL, EXISTS",[14,608,609,610,21,612,161,614,21,616,618,619,621],{},"A table subquery returns multiple rows. Use it with ",[18,611,138],{},[18,613,164],{},[18,615,167],{},[18,617,170],{},", or in the ",[18,620,27],{}," clause.",[623,624,626],"h3",{"id":625},"in-membership-test","IN — Membership Test",[217,628,629],{"language":219},[221,630,632],{"className":223,"code":631,"language":219,"meta":225,"style":225},"-- Customers who have placed at least one order.\n-- IN expands to: c.id = o1.customer_id OR c.id = o2.customer_id OR ...\n-- The planner typically implements this as a semi-join (hash or index).\nSELECT * FROM customers\nWHERE id IN (SELECT customer_id FROM orders);\n",[18,633,634,639,644,649,660],{"__ignoreMap":225},[229,635,636],{"class":231,"line":232},[229,637,638],{"class":235},"-- Customers who have placed at least one order.\n",[229,640,641],{"class":231,"line":239},[229,642,643],{"class":235},"-- IN expands to: c.id = o1.customer_id OR c.id = o2.customer_id OR ...\n",[229,645,646],{"class":231,"line":245},[229,647,648],{"class":235},"-- The planner typically implements this as a semi-join (hash or index).\n",[229,650,651,653,655,657],{"class":231,"line":251},[229,652,24],{"class":254},[229,654,503],{"class":254},[229,656,506],{"class":254},[229,658,659],{"class":257}," customers\n",[229,661,662,664,666,668,670,672,675,677],{"class":231,"line":261},[229,663,20],{"class":254},[229,665,582],{"class":257},[229,667,138],{"class":254},[229,669,158],{"class":257},[229,671,24],{"class":254},[229,673,674],{"class":257}," customer_id ",[229,676,27],{"class":254},[229,678,292],{"class":257},[623,680,682],{"id":681},"not-in-the-null-poisoning-trap-dangerous","NOT IN — The NULL Poisoning Trap (Dangerous!)",[14,684,685,687],{},[18,686,180],{}," breaks if the subquery can produce NULL:",[217,689,690],{"language":219},[221,691,693],{"className":223,"code":692,"language":219,"meta":225,"style":225},"-- ❌ WRONG: Returns ZERO rows if ANY customer_id in the subquery is NULL.\n-- x NOT IN (a, b, NULL) expands to: x \u003C> a AND x \u003C> b AND x \u003C> NULL\n-- x \u003C> NULL is UNKNOWN → the entire AND chain becomes UNKNOWN → WHERE keeps nothing.\nSELECT * FROM customers\nWHERE id NOT IN (SELECT customer_id FROM orders);\n",[18,694,695,700,705,710,720],{"__ignoreMap":225},[229,696,697],{"class":231,"line":232},[229,698,699],{"class":235},"-- ❌ WRONG: Returns ZERO rows if ANY customer_id in the subquery is NULL.\n",[229,701,702],{"class":231,"line":239},[229,703,704],{"class":235},"-- x NOT IN (a, b, NULL) expands to: x \u003C> a AND x \u003C> b AND x \u003C> NULL\n",[229,706,707],{"class":231,"line":245},[229,708,709],{"class":235},"-- x \u003C> NULL is UNKNOWN → the entire AND chain becomes UNKNOWN → WHERE keeps nothing.\n",[229,711,712,714,716,718],{"class":231,"line":251},[229,713,24],{"class":254},[229,715,503],{"class":254},[229,717,506],{"class":254},[229,719,659],{"class":257},[229,721,722,724,726,729,732,734,736,738,740],{"class":231,"line":261},[229,723,20],{"class":254},[229,725,582],{"class":257},[229,727,728],{"class":254},"NOT",[229,730,731],{"class":254}," IN",[229,733,158],{"class":257},[229,735,24],{"class":254},[229,737,674],{"class":257},[229,739,27],{"class":254},[229,741,292],{"class":257},[14,743,744,747,748,598,751,754,755,758,759,762,763,765,766,393],{},[40,745,746],{},"Mechanism",": ",[18,749,750],{},"x NOT IN (a, b, c)",[18,752,753],{},"x \u003C> a AND x \u003C> b AND x \u003C> c",". If any element is NULL, ",[18,756,757],{},"x \u003C> NULL"," is UNKNOWN. ",[18,760,761],{},"TRUE AND UNKNOWN = UNKNOWN",". ",[18,764,20],{}," keeps only TRUE → ",[40,767,768],{},"one NULL poisons the entire NOT IN",[14,770,771],{},"The fixes:",[217,773,774],{"language":219},[221,775,777],{"className":223,"code":776,"language":219,"meta":225,"style":225},"-- Option 1: filter NULLs out of the subquery (works, but verbose)\nSELECT * FROM customers\nWHERE id NOT IN (\n  SELECT customer_id FROM orders WHERE customer_id IS NOT NULL\n);\n\n-- Option 2: NOT EXISTS — NULL-safe, planner uses hash anti-join, short-circuits.\n-- The subquery returns no rows for unmatched customers → NOT EXISTS = TRUE.\nSELECT * FROM customers c\nWHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);\n\n-- Option 3: IS DISTINCT FROM ALL — NULL-safe \"not equal to all\" (PostgreSQL)\nSELECT * FROM customers c\nWHERE c.id IS DISTINCT FROM ALL (SELECT customer_id FROM orders);\n",[18,778,779,784,794,806,823,827,831,836,841,852,891,895,900,910],{"__ignoreMap":225},[229,780,781],{"class":231,"line":232},[229,782,783],{"class":235},"-- Option 1: filter NULLs out of the subquery (works, but verbose)\n",[229,785,786,788,790,792],{"class":231,"line":239},[229,787,24],{"class":254},[229,789,503],{"class":254},[229,791,506],{"class":254},[229,793,659],{"class":257},[229,795,796,798,800,802,804],{"class":231,"line":245},[229,797,20],{"class":254},[229,799,582],{"class":257},[229,801,728],{"class":254},[229,803,731],{"class":254},[229,805,564],{"class":257},[229,807,808,810,812,814,816,818,820],{"class":231,"line":251},[229,809,569],{"class":254},[229,811,674],{"class":257},[229,813,27],{"class":254},[229,815,577],{"class":257},[229,817,20],{"class":254},[229,819,674],{"class":257},[229,821,822],{"class":254},"IS NOT NULL\n",[229,824,825],{"class":231,"line":261},[229,826,534],{"class":257},[229,828,829],{"class":231,"line":269},[229,830,299],{"emptyLinePlaceholder":298},[229,832,833],{"class":231,"line":295},[229,834,835],{"class":235},"-- Option 2: NOT EXISTS — NULL-safe, planner uses hash anti-join, short-circuits.\n",[229,837,838],{"class":231,"line":302},[229,839,840],{"class":235},"-- The subquery returns no rows for unmatched customers → NOT EXISTS = TRUE.\n",[229,842,843,845,847,849],{"class":231,"line":308},[229,844,24],{"class":254},[229,846,503],{"class":254},[229,848,506],{"class":254},[229,850,851],{"class":257}," customers c\n",[229,853,854,856,859,862,864,866,869,871,873,875,877,879,881,883,885,887,889],{"class":231,"line":314},[229,855,20],{"class":254},[229,857,858],{"class":254}," NOT",[229,860,861],{"class":254}," EXISTS",[229,863,158],{"class":257},[229,865,24],{"class":254},[229,867,868],{"class":283}," 1",[229,870,506],{"class":254},[229,872,423],{"class":257},[229,874,20],{"class":254},[229,876,428],{"class":283},[229,878,393],{"class":257},[229,880,433],{"class":283},[229,882,436],{"class":254},[229,884,390],{"class":283},[229,886,393],{"class":257},[229,888,443],{"class":283},[229,890,534],{"class":257},[229,892,893],{"class":231,"line":322},[229,894,299],{"emptyLinePlaceholder":298},[229,896,897],{"class":231,"line":328},[229,898,899],{"class":235},"-- Option 3: IS DISTINCT FROM ALL — NULL-safe \"not equal to all\" (PostgreSQL)\n",[229,901,902,904,906,908],{"class":231,"line":354},[229,903,24],{"class":254},[229,905,503],{"class":254},[229,907,506],{"class":254},[229,909,851],{"class":257},[229,911,912,914,916,918,920,923,926,928,931,933,935,937],{"class":231,"line":362},[229,913,20],{"class":254},[229,915,390],{"class":283},[229,917,393],{"class":257},[229,919,443],{"class":283},[229,921,922],{"class":254}," IS",[229,924,925],{"class":254}," DISTINCT",[229,927,506],{"class":254},[229,929,930],{"class":257}," ALL (",[229,932,24],{"class":254},[229,934,674],{"class":257},[229,936,27],{"class":254},[229,938,292],{"class":257},[14,940,941,947],{},[40,942,943,944,393],{},"Prefer ",[18,945,946],{},"NOT EXISTS"," It's NULL-safe and the planner can use a hash anti-join.",[623,949,951],{"id":950},"any-all-quantified-comparison","ANY \u002F ALL — Quantified Comparison",[14,953,954,956,957,960,961,964,965,967,968,971],{},[18,955,164],{}," (a.k.a. ",[18,958,959],{},"SOME",") — true if the comparison holds for ",[40,962,963],{},"at least one"," subquery row. ",[18,966,167],{}," — true for ",[40,969,970],{},"every"," row.",[217,973,974],{"language":219},[221,975,977],{"className":223,"code":976,"language":219,"meta":225,"style":225},"-- Orders larger than at least one order from customer 1.\n-- Equivalent to: amount > (SELECT MIN(amount) FROM orders WHERE customer_id = 1)\nSELECT * FROM orders\nWHERE amount > ANY (SELECT amount FROM orders WHERE customer_id = 1);\n\n-- Orders larger than every order from customer 1 (i.e., larger than the max).\n-- Equivalent to: amount > (SELECT MAX(amount) FROM orders WHERE customer_id = 1)\nSELECT * FROM orders\nWHERE amount > ALL (SELECT amount FROM orders WHERE customer_id = 1);\n\n-- = ANY (subquery) is exactly equivalent to IN (subquery).\n-- IN is the more common spelling; ANY\u002FALL matter for non-equality comparisons.\nSELECT * FROM orders\nWHERE customer_id = ANY (SELECT id FROM customers WHERE status = 'active');\n-- Same as: WHERE customer_id IN (SELECT id FROM customers WHERE status = 'active');\n",[18,978,979,984,989,999,1028,1032,1037,1042,1052,1080,1084,1089,1094,1104,1136],{"__ignoreMap":225},[229,980,981],{"class":231,"line":232},[229,982,983],{"class":235},"-- Orders larger than at least one order from customer 1.\n",[229,985,986],{"class":231,"line":239},[229,987,988],{"class":235},"-- Equivalent to: amount > (SELECT MIN(amount) FROM orders WHERE customer_id = 1)\n",[229,990,991,993,995,997],{"class":231,"line":245},[229,992,24],{"class":254},[229,994,503],{"class":254},[229,996,506],{"class":254},[229,998,266],{"class":257},[229,1000,1001,1003,1005,1007,1010,1012,1014,1016,1018,1020,1022,1024,1026],{"class":231,"line":251},[229,1002,20],{"class":254},[229,1004,274],{"class":257},[229,1006,111],{"class":254},[229,1008,1009],{"class":257}," ANY (",[229,1011,24],{"class":254},[229,1013,274],{"class":257},[229,1015,27],{"class":254},[229,1017,577],{"class":257},[229,1019,20],{"class":254},[229,1021,674],{"class":257},[229,1023,102],{"class":254},[229,1025,868],{"class":283},[229,1027,534],{"class":257},[229,1029,1030],{"class":231,"line":261},[229,1031,299],{"emptyLinePlaceholder":298},[229,1033,1034],{"class":231,"line":269},[229,1035,1036],{"class":235},"-- Orders larger than every order from customer 1 (i.e., larger than the max).\n",[229,1038,1039],{"class":231,"line":295},[229,1040,1041],{"class":235},"-- Equivalent to: amount > (SELECT MAX(amount) FROM orders WHERE customer_id = 1)\n",[229,1043,1044,1046,1048,1050],{"class":231,"line":302},[229,1045,24],{"class":254},[229,1047,503],{"class":254},[229,1049,506],{"class":254},[229,1051,266],{"class":257},[229,1053,1054,1056,1058,1060,1062,1064,1066,1068,1070,1072,1074,1076,1078],{"class":231,"line":308},[229,1055,20],{"class":254},[229,1057,274],{"class":257},[229,1059,111],{"class":254},[229,1061,930],{"class":257},[229,1063,24],{"class":254},[229,1065,274],{"class":257},[229,1067,27],{"class":254},[229,1069,577],{"class":257},[229,1071,20],{"class":254},[229,1073,674],{"class":257},[229,1075,102],{"class":254},[229,1077,868],{"class":283},[229,1079,534],{"class":257},[229,1081,1082],{"class":231,"line":314},[229,1083,299],{"emptyLinePlaceholder":298},[229,1085,1086],{"class":231,"line":322},[229,1087,1088],{"class":235},"-- = ANY (subquery) is exactly equivalent to IN (subquery).\n",[229,1090,1091],{"class":231,"line":328},[229,1092,1093],{"class":235},"-- IN is the more common spelling; ANY\u002FALL matter for non-equality comparisons.\n",[229,1095,1096,1098,1100,1102],{"class":231,"line":354},[229,1097,24],{"class":254},[229,1099,503],{"class":254},[229,1101,506],{"class":254},[229,1103,266],{"class":257},[229,1105,1106,1108,1110,1112,1114,1116,1118,1120,1123,1125,1128,1130,1134],{"class":231,"line":362},[229,1107,20],{"class":254},[229,1109,674],{"class":257},[229,1111,102],{"class":254},[229,1113,1009],{"class":257},[229,1115,24],{"class":254},[229,1117,582],{"class":257},[229,1119,27],{"class":254},[229,1121,1122],{"class":257}," customers ",[229,1124,20],{"class":254},[229,1126,1127],{"class":254}," status",[229,1129,436],{"class":254},[229,1131,1133],{"class":1132},"sJ6F3"," 'active'",[229,1135,534],{"class":257},[229,1137,1138],{"class":231,"line":367},[229,1139,1140],{"class":235},"-- Same as: WHERE customer_id IN (SELECT id FROM customers WHERE status = 'active');\n",[14,1142,1143,1146],{},[40,1144,1145],{},"Empty subquery with ANY\u002FALL",":",[194,1148,1149,1159],{},[197,1150,1151,1154,1155,1158],{},[18,1152,1153],{},"x > ALL (empty set)"," → ",[40,1156,1157],{},"TRUE"," (vacuously true — \"greater than every element of nothing\").",[197,1160,1161,1154,1164,1167],{},[18,1162,1163],{},"x > ANY (empty set)",[40,1165,1166],{},"FALSE"," (no element satisfies the condition).",[33,1169,1171],{"id":1170},"exists-semi-join-and-anti-join","EXISTS — Semi-Join and Anti-Join",[14,1173,1174,1176,1177,1180,1181,1184],{},[18,1175,170],{}," is true if the subquery returns ",[40,1178,1179],{},"at least one row"," — it doesn't care what the rows contain. By convention, ",[18,1182,1183],{},"SELECT 1"," is used (the value is irrelevant).",[217,1186,1187],{"language":219},[221,1188,1190],{"className":223,"code":1189,"language":219,"meta":225,"style":225},"-- Semi-join: customers who have placed at least one order.\n-- EXISTS short-circuits on the FIRST matching row — doesn't collect all matches.\n-- The planner can use an index on orders.customer_id to find the first match fast.\nSELECT * FROM customers c\nWHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);\n\n-- Anti-join: customers who have NOT placed any order.\n-- NOT EXISTS = TRUE when the subquery is empty (no matching rows).\nSELECT * FROM customers c\nWHERE NOT EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id);\n",[18,1191,1192,1197,1202,1207,1217,1251,1255,1260,1265,1275],{"__ignoreMap":225},[229,1193,1194],{"class":231,"line":232},[229,1195,1196],{"class":235},"-- Semi-join: customers who have placed at least one order.\n",[229,1198,1199],{"class":231,"line":239},[229,1200,1201],{"class":235},"-- EXISTS short-circuits on the FIRST matching row — doesn't collect all matches.\n",[229,1203,1204],{"class":231,"line":245},[229,1205,1206],{"class":235},"-- The planner can use an index on orders.customer_id to find the first match fast.\n",[229,1208,1209,1211,1213,1215],{"class":231,"line":251},[229,1210,24],{"class":254},[229,1212,503],{"class":254},[229,1214,506],{"class":254},[229,1216,851],{"class":257},[229,1218,1219,1221,1223,1225,1227,1229,1231,1233,1235,1237,1239,1241,1243,1245,1247,1249],{"class":231,"line":261},[229,1220,20],{"class":254},[229,1222,861],{"class":254},[229,1224,158],{"class":257},[229,1226,24],{"class":254},[229,1228,868],{"class":283},[229,1230,506],{"class":254},[229,1232,423],{"class":257},[229,1234,20],{"class":254},[229,1236,428],{"class":283},[229,1238,393],{"class":257},[229,1240,433],{"class":283},[229,1242,436],{"class":254},[229,1244,390],{"class":283},[229,1246,393],{"class":257},[229,1248,443],{"class":283},[229,1250,534],{"class":257},[229,1252,1253],{"class":231,"line":269},[229,1254,299],{"emptyLinePlaceholder":298},[229,1256,1257],{"class":231,"line":295},[229,1258,1259],{"class":235},"-- Anti-join: customers who have NOT placed any order.\n",[229,1261,1262],{"class":231,"line":302},[229,1263,1264],{"class":235},"-- NOT EXISTS = TRUE when the subquery is empty (no matching rows).\n",[229,1266,1267,1269,1271,1273],{"class":231,"line":308},[229,1268,24],{"class":254},[229,1270,503],{"class":254},[229,1272,506],{"class":254},[229,1274,851],{"class":257},[229,1276,1277,1279,1281,1283,1285,1287,1289,1291,1293,1295,1297,1299,1301,1303,1305,1307,1309],{"class":231,"line":314},[229,1278,20],{"class":254},[229,1280,858],{"class":254},[229,1282,861],{"class":254},[229,1284,158],{"class":257},[229,1286,24],{"class":254},[229,1288,868],{"class":283},[229,1290,506],{"class":254},[229,1292,423],{"class":257},[229,1294,20],{"class":254},[229,1296,428],{"class":283},[229,1298,393],{"class":257},[229,1300,433],{"class":283},[229,1302,436],{"class":254},[229,1304,390],{"class":283},[229,1306,393],{"class":257},[229,1308,443],{"class":283},[229,1310,534],{"class":257},[14,1312,1313,1315,1316,1319,1320,1323,1324,1326],{},[18,1314,170],{}," is a ",[40,1317,1318],{},"correlated"," subquery — it references ",[18,1321,1322],{},"c.id"," from the outer query. The planner typically implements it as a semi-join (or anti-join for ",[18,1325,946],{},"), which can short-circuit on the first match.",[623,1328,1330],{"id":1329},"exists-vs-in-decision-matrix","EXISTS vs IN — Decision Matrix",[45,1332,1333,1348],{},[48,1334,1335],{},[51,1336,1337,1340,1344],{},[54,1338,1339],{},"Criterion",[54,1341,943,1342],{},[18,1343,170],{},[54,1345,943,1346],{},[18,1347,138],{},[70,1349,1350,1364,1375,1386,1397],{},[51,1351,1352,1355,1358],{},[75,1353,1354],{},"Subquery can produce NULLs",[75,1356,1357],{},"✅ (NULL-safe)",[75,1359,1360,1361,1363],{},"❌ (use ",[18,1362,946],{},")",[51,1365,1366,1369,1372],{},[75,1367,1368],{},"Uncorrelated, small list",[75,1370,1371],{},"—",[75,1373,1374],{},"✅ (simpler syntax)",[51,1376,1377,1380,1383],{},[75,1378,1379],{},"Correlated (depends on outer row)",[75,1381,1382],{},"✅ (idiomatic)",[75,1384,1385],{},"works but less clear",[51,1387,1388,1391,1394],{},[75,1389,1390],{},"Large subquery result",[75,1392,1393],{},"✅ (semi-join, short-circuits)",[75,1395,1396],{},"may materialize full list",[51,1398,1399,1405,1407],{},[75,1400,1401,1402],{},"Literal list ",[18,1403,1404],{},"IN (1, 2, 3)",[75,1406,1371],{},[75,1408,1409],{},"✅ (the only good use of IN)",[33,1411,1413],{"id":1412},"subqueries-in-where-select-from-having","Subqueries in WHERE, SELECT, FROM, HAVING",[623,1415,1417],{"id":1416},"where-filter","WHERE — Filter",[217,1419,1420],{"language":219},[221,1421,1423],{"className":223,"code":1422,"language":219,"meta":225,"style":225},"-- Scalar comparison in WHERE\nSELECT * FROM orders\nWHERE amount > (SELECT AVG(amount) FROM orders WHERE customer_id = 1);\n\n-- HAVING with a scalar subquery — groups whose total exceeds the grand average\nSELECT customer_id, SUM(amount) AS total\nFROM orders\nGROUP BY customer_id\nHAVING SUM(amount) > (SELECT AVG(total) FROM (\n  SELECT SUM(amount) AS total FROM orders GROUP BY customer_id\n) avg_per_customer);\n",[18,1424,1425,1430,1440,1470,1474,1479,1496,1502,1510,1534,1555],{"__ignoreMap":225},[229,1426,1427],{"class":231,"line":232},[229,1428,1429],{"class":235},"-- Scalar comparison in WHERE\n",[229,1431,1432,1434,1436,1438],{"class":231,"line":239},[229,1433,24],{"class":254},[229,1435,503],{"class":254},[229,1437,506],{"class":254},[229,1439,266],{"class":257},[229,1441,1442,1444,1446,1448,1450,1452,1454,1456,1458,1460,1462,1464,1466,1468],{"class":231,"line":245},[229,1443,20],{"class":254},[229,1445,274],{"class":257},[229,1447,111],{"class":254},[229,1449,158],{"class":257},[229,1451,24],{"class":254},[229,1453,284],{"class":283},[229,1455,287],{"class":257},[229,1457,27],{"class":254},[229,1459,577],{"class":257},[229,1461,20],{"class":254},[229,1463,674],{"class":257},[229,1465,102],{"class":254},[229,1467,868],{"class":283},[229,1469,534],{"class":257},[229,1471,1472],{"class":231,"line":251},[229,1473,299],{"emptyLinePlaceholder":298},[229,1475,1476],{"class":231,"line":261},[229,1477,1478],{"class":235},"-- HAVING with a scalar subquery — groups whose total exceeds the grand average\n",[229,1480,1481,1483,1486,1489,1491,1493],{"class":231,"line":269},[229,1482,24],{"class":254},[229,1484,1485],{"class":257}," customer_id, ",[229,1487,1488],{"class":283},"SUM",[229,1490,287],{"class":257},[229,1492,345],{"class":254},[229,1494,1495],{"class":257}," total\n",[229,1497,1498,1500],{"class":231,"line":295},[229,1499,27],{"class":254},[229,1501,266],{"class":257},[229,1503,1504,1507],{"class":231,"line":302},[229,1505,1506],{"class":254},"GROUP BY",[229,1508,1509],{"class":257}," customer_id\n",[229,1511,1512,1514,1517,1519,1521,1523,1525,1527,1530,1532],{"class":231,"line":308},[229,1513,30],{"class":254},[229,1515,1516],{"class":283}," SUM",[229,1518,287],{"class":257},[229,1520,111],{"class":254},[229,1522,158],{"class":257},[229,1524,24],{"class":254},[229,1526,284],{"class":283},[229,1528,1529],{"class":257},"(total) ",[229,1531,27],{"class":254},[229,1533,564],{"class":257},[229,1535,1536,1538,1540,1542,1544,1547,1549,1551,1553],{"class":231,"line":314},[229,1537,569],{"class":254},[229,1539,1516],{"class":283},[229,1541,287],{"class":257},[229,1543,345],{"class":254},[229,1545,1546],{"class":257}," total ",[229,1548,27],{"class":254},[229,1550,577],{"class":257},[229,1552,1506],{"class":254},[229,1554,1509],{"class":257},[229,1556,1557],{"class":231,"line":322},[229,1558,1559],{"class":257},") avg_per_customer);\n",[623,1561,1563],{"id":1562},"select-computed-column","SELECT — Computed Column",[217,1565,1566],{"language":219},[221,1567,1569],{"className":223,"code":1568,"language":219,"meta":225,"style":225},"-- Correlated subquery in SELECT: per-customer most recent order date.\n-- Re-executes per outer row. For millions of rows, rewrite as a join + GROUP BY.\nSELECT c.name,\n       (SELECT MAX(ordered_on) FROM orders o WHERE o.customer_id = c.id) AS last_order\nFROM customers c;\n",[18,1570,1571,1576,1581,1593,1632],{"__ignoreMap":225},[229,1572,1573],{"class":231,"line":232},[229,1574,1575],{"class":235},"-- Correlated subquery in SELECT: per-customer most recent order date.\n",[229,1577,1578],{"class":231,"line":239},[229,1579,1580],{"class":235},"-- Re-executes per outer row. For millions of rows, rewrite as a join + GROUP BY.\n",[229,1582,1583,1585,1587,1589,1591],{"class":231,"line":245},[229,1584,24],{"class":254},[229,1586,390],{"class":283},[229,1588,393],{"class":257},[229,1590,396],{"class":283},[229,1592,399],{"class":257},[229,1594,1595,1597,1599,1602,1605,1607,1609,1611,1613,1615,1617,1619,1621,1623,1625,1627,1629],{"class":231,"line":251},[229,1596,331],{"class":257},[229,1598,24],{"class":254},[229,1600,1601],{"class":283}," MAX",[229,1603,1604],{"class":257},"(ordered_on) ",[229,1606,27],{"class":254},[229,1608,423],{"class":257},[229,1610,20],{"class":254},[229,1612,428],{"class":283},[229,1614,393],{"class":257},[229,1616,433],{"class":283},[229,1618,436],{"class":254},[229,1620,390],{"class":283},[229,1622,393],{"class":257},[229,1624,443],{"class":283},[229,1626,418],{"class":257},[229,1628,345],{"class":254},[229,1630,1631],{"class":257}," last_order\n",[229,1633,1634,1636],{"class":231,"line":261},[229,1635,27],{"class":254},[229,1637,458],{"class":257},[623,1639,1641],{"id":1640},"from-derived-table","FROM — Derived Table",[217,1643,1644],{"language":219},[221,1645,1647],{"className":223,"code":1646,"language":219,"meta":225,"style":225},"-- Derived table: aggregate per customer, then aggregate per city.\n-- SQL forbids nested aggregates (AVG(SUM(x)) is illegal), so we stage the inner SUM.\n-- A derived table MUST be aliased (per_customer) — syntax error without it.\nSELECT city, AVG(customer_total) AS avg_customer_spend\nFROM (\n  SELECT c.city, c.id, SUM(o.amount) AS customer_total\n  FROM customers c\n  JOIN orders o ON c.id = o.customer_id\n  GROUP BY c.city, c.id\n) per_customer\nGROUP BY city\nHAVING AVG(customer_total) > 100;\n",[18,1648,1649,1654,1659,1664,1682,1688,1729,1736,1761,1781,1786,1793],{"__ignoreMap":225},[229,1650,1651],{"class":231,"line":232},[229,1652,1653],{"class":235},"-- Derived table: aggregate per customer, then aggregate per city.\n",[229,1655,1656],{"class":231,"line":239},[229,1657,1658],{"class":235},"-- SQL forbids nested aggregates (AVG(SUM(x)) is illegal), so we stage the inner SUM.\n",[229,1660,1661],{"class":231,"line":245},[229,1662,1663],{"class":235},"-- A derived table MUST be aliased (per_customer) — syntax error without it.\n",[229,1665,1666,1668,1671,1674,1677,1679],{"class":231,"line":251},[229,1667,24],{"class":254},[229,1669,1670],{"class":257}," city, ",[229,1672,1673],{"class":283},"AVG",[229,1675,1676],{"class":257},"(customer_total) ",[229,1678,345],{"class":254},[229,1680,1681],{"class":257}," avg_customer_spend\n",[229,1683,1684,1686],{"class":231,"line":261},[229,1685,27],{"class":254},[229,1687,564],{"class":257},[229,1689,1690,1692,1694,1696,1699,1701,1704,1706,1708,1710,1712,1714,1717,1719,1722,1724,1726],{"class":231,"line":269},[229,1691,569],{"class":254},[229,1693,390],{"class":283},[229,1695,393],{"class":257},[229,1697,1698],{"class":283},"city",[229,1700,21],{"class":257},[229,1702,1703],{"class":283},"c",[229,1705,393],{"class":257},[229,1707,443],{"class":283},[229,1709,21],{"class":257},[229,1711,1488],{"class":283},[229,1713,412],{"class":257},[229,1715,1716],{"class":283},"o",[229,1718,393],{"class":257},[229,1720,1721],{"class":283},"amount",[229,1723,418],{"class":257},[229,1725,345],{"class":254},[229,1727,1728],{"class":257}," customer_total\n",[229,1730,1731,1734],{"class":231,"line":295},[229,1732,1733],{"class":254},"  FROM",[229,1735,851],{"class":257},[229,1737,1738,1741,1743,1746,1748,1750,1752,1754,1756,1758],{"class":231,"line":302},[229,1739,1740],{"class":254},"  JOIN",[229,1742,423],{"class":257},[229,1744,1745],{"class":254},"ON",[229,1747,390],{"class":283},[229,1749,393],{"class":257},[229,1751,443],{"class":283},[229,1753,436],{"class":254},[229,1755,428],{"class":283},[229,1757,393],{"class":257},[229,1759,1760],{"class":283},"customer_id\n",[229,1762,1763,1766,1768,1770,1772,1774,1776,1778],{"class":231,"line":308},[229,1764,1765],{"class":254},"  GROUP BY",[229,1767,390],{"class":283},[229,1769,393],{"class":257},[229,1771,1698],{"class":283},[229,1773,21],{"class":257},[229,1775,1703],{"class":283},[229,1777,393],{"class":257},[229,1779,1780],{"class":283},"id\n",[229,1782,1783],{"class":231,"line":314},[229,1784,1785],{"class":257},") per_customer\n",[229,1787,1788,1790],{"class":231,"line":322},[229,1789,1506],{"class":254},[229,1791,1792],{"class":257}," city\n",[229,1794,1795,1797,1799,1801,1803,1806],{"class":231,"line":328},[229,1796,30],{"class":254},[229,1798,284],{"class":283},[229,1800,1676],{"class":257},[229,1802,111],{"class":254},[229,1804,1805],{"class":283}," 100",[229,1807,1808],{"class":257},";\n",[33,1810,1812],{"id":1811},"lateral-correlated-joins-postgresql-mysql-80-sqlite-339","LATERAL — Correlated Joins (PostgreSQL, MySQL 8.0+, SQLite 3.39+)",[14,1814,1815,1816,1819,1820,1823],{},"A ",[18,1817,1818],{},"LATERAL"," subquery in a join can reference columns from tables ",[40,1821,1822],{},"earlier in the FROM clause"," — like a correlated subquery, but in join position.",[217,1825,1826],{"language":219},[221,1827,1829],{"className":223,"code":1828,"language":219,"meta":225,"style":225},"-- Top 3 orders per customer by amount.\n-- The LATERAL subquery re-evaluates per customer row, with LIMIT bounding each lookup.\n-- LEFT JOIN LATERAL + ON true ensures customers with zero orders still appear (with NULLs).\nSELECT c.name, top.id AS order_id, top.amount\nFROM customers c\nLEFT JOIN LATERAL (\n  SELECT id, amount\n  FROM orders o\n  WHERE o.customer_id = c.id    -- references c.id from the outer (left) table\n  ORDER BY amount DESC\n  LIMIT 3                        -- bounded per-row: planner can use index on customer_id + amount\n) top ON true;\n",[18,1830,1831,1836,1841,1846,1878,1884,1892,1898,1905,1927,1937,1948],{"__ignoreMap":225},[229,1832,1833],{"class":231,"line":232},[229,1834,1835],{"class":235},"-- Top 3 orders per customer by amount.\n",[229,1837,1838],{"class":231,"line":239},[229,1839,1840],{"class":235},"-- The LATERAL subquery re-evaluates per customer row, with LIMIT bounding each lookup.\n",[229,1842,1843],{"class":231,"line":245},[229,1844,1845],{"class":235},"-- LEFT JOIN LATERAL + ON true ensures customers with zero orders still appear (with NULLs).\n",[229,1847,1848,1850,1852,1854,1856,1858,1861,1863,1865,1868,1871,1873,1875],{"class":231,"line":251},[229,1849,24],{"class":254},[229,1851,390],{"class":283},[229,1853,393],{"class":257},[229,1855,396],{"class":283},[229,1857,21],{"class":257},[229,1859,1860],{"class":283},"top",[229,1862,393],{"class":257},[229,1864,443],{"class":283},[229,1866,1867],{"class":254}," AS",[229,1869,1870],{"class":257}," order_id, ",[229,1872,1860],{"class":283},[229,1874,393],{"class":257},[229,1876,1877],{"class":283},"amount\n",[229,1879,1880,1882],{"class":231,"line":261},[229,1881,27],{"class":254},[229,1883,851],{"class":257},[229,1885,1886,1889],{"class":231,"line":269},[229,1887,1888],{"class":254},"LEFT JOIN",[229,1890,1891],{"class":257}," LATERAL (\n",[229,1893,1894,1896],{"class":231,"line":295},[229,1895,569],{"class":254},[229,1897,258],{"class":257},[229,1899,1900,1902],{"class":231,"line":302},[229,1901,1733],{"class":254},[229,1903,1904],{"class":257}," orders o\n",[229,1906,1907,1910,1912,1914,1916,1918,1920,1922,1924],{"class":231,"line":308},[229,1908,1909],{"class":254},"  WHERE",[229,1911,428],{"class":283},[229,1913,393],{"class":257},[229,1915,433],{"class":283},[229,1917,436],{"class":254},[229,1919,390],{"class":283},[229,1921,393],{"class":257},[229,1923,443],{"class":283},[229,1925,1926],{"class":235},"    -- references c.id from the outer (left) table\n",[229,1928,1929,1932,1934],{"class":231,"line":314},[229,1930,1931],{"class":254},"  ORDER BY",[229,1933,274],{"class":257},[229,1935,1936],{"class":254},"DESC\n",[229,1938,1939,1942,1945],{"class":231,"line":322},[229,1940,1941],{"class":254},"  LIMIT",[229,1943,1944],{"class":283}," 3",[229,1946,1947],{"class":235},"                        -- bounded per-row: planner can use index on customer_id + amount\n",[229,1949,1950,1952,1954,1957],{"class":231,"line":328},[229,1951,418],{"class":257},[229,1953,1860],{"class":254},[229,1955,1956],{"class":254}," ON",[229,1958,1959],{"class":257}," true;\n",[14,1961,1962,1964,1965,1968,1969,1972],{},[18,1963,1818],{}," is the clean way to do \"top-N per group\" without window functions. The ",[18,1966,1967],{},"LIMIT"," inside makes it a bounded per-row lookup — with an index on ",[18,1970,1971],{},"(customer_id, amount DESC)",", the planner can do an index scan per customer, fetching only 3 rows each.",[33,1974,1976],{"id":1975},"complex-implementation-per-customer-metrics-with-exists-lateral","Complex Implementation: Per-Customer Metrics with EXISTS + LATERAL",[217,1978,1979],{"language":219},[221,1980,1982],{"className":223,"code":1981,"language":219,"meta":225,"style":225},"-- Production query: per-customer analytics dashboard.\n-- Combines:\n--   1. EXISTS semi-join (has recent orders?)\n--   2. Correlated scalar subquery (lifetime value)\n--   3. LATERAL join (top 3 most recent orders)\n--   4. NOT EXISTS anti-join (no returned orders)\nSELECT\n  c.id,\n  c.name,\n  c.email,\n  -- Correlated scalar: lifetime value. Re-executes per customer.\n  (SELECT SUM(o.amount) FROM orders o WHERE o.customer_id = c.id) AS lifetime_value,\n  -- Semi-join: has the customer ordered in the last 30 days?\n  EXISTS (\n    SELECT 1 FROM orders o\n    WHERE o.customer_id = c.id\n      AND o.ordered_on >= CURRENT_DATE - INTERVAL '30 days'\n  ) AS active_recently,\n  -- Anti-join: no returned orders (status = 'returned').\n  -- NOT EXISTS is NULL-safe — doesn't poison like NOT IN.\n  NOT EXISTS (\n    SELECT 1 FROM orders o\n    WHERE o.customer_id = c.id\n      AND o.status = 'returned'\n  ) AS has_no_returns,\n  -- LATERAL: top 3 most recent orders, bounded per-row.\n  top.recent_order_ids,\n  top.recent_amounts\nFROM customers c\nLEFT JOIN LATERAL (\n  SELECT\n    array_agg(o.id ORDER BY o.ordered_on DESC) AS recent_order_ids,\n    array_agg(o.amount ORDER BY o.ordered_on DESC) AS recent_amounts\n  FROM (\n    SELECT id, amount, ordered_on\n    FROM orders o\n    WHERE o.customer_id = c.id\n    ORDER BY ordered_on DESC\n    LIMIT 3                                  -- only fetch 3 rows per customer\n  ) o\n) top ON true\nWHERE c.status = 'active'\nORDER BY lifetime_value DESC NULLS LAST      -- NULL lifetime_value (no orders) sorts last\nLIMIT 100;\n",[18,1983,1984,1989,1994,1999,2004,2009,2014,2019,2030,2040,2051,2056,2102,2107,2114,2125,2144,2171,2181,2186,2191,2201,2212,2231,2248,2258,2264,2277,2287,2294,2301,2307,2338,2366,2373,2381,2389,2408,2419,2430,2436,2448,2464,2485],{"__ignoreMap":225},[229,1985,1986],{"class":231,"line":232},[229,1987,1988],{"class":235},"-- Production query: per-customer analytics dashboard.\n",[229,1990,1991],{"class":231,"line":239},[229,1992,1993],{"class":235},"-- Combines:\n",[229,1995,1996],{"class":231,"line":245},[229,1997,1998],{"class":235},"--   1. EXISTS semi-join (has recent orders?)\n",[229,2000,2001],{"class":231,"line":251},[229,2002,2003],{"class":235},"--   2. Correlated scalar subquery (lifetime value)\n",[229,2005,2006],{"class":231,"line":261},[229,2007,2008],{"class":235},"--   3. LATERAL join (top 3 most recent orders)\n",[229,2010,2011],{"class":231,"line":269},[229,2012,2013],{"class":235},"--   4. NOT EXISTS anti-join (no returned orders)\n",[229,2015,2016],{"class":231,"line":295},[229,2017,2018],{"class":254},"SELECT\n",[229,2020,2021,2024,2026,2028],{"class":231,"line":302},[229,2022,2023],{"class":283},"  c",[229,2025,393],{"class":257},[229,2027,443],{"class":283},[229,2029,399],{"class":257},[229,2031,2032,2034,2036,2038],{"class":231,"line":308},[229,2033,2023],{"class":283},[229,2035,393],{"class":257},[229,2037,396],{"class":283},[229,2039,399],{"class":257},[229,2041,2042,2044,2046,2049],{"class":231,"line":314},[229,2043,2023],{"class":283},[229,2045,393],{"class":257},[229,2047,2048],{"class":283},"email",[229,2050,399],{"class":257},[229,2052,2053],{"class":231,"line":322},[229,2054,2055],{"class":235},"  -- Correlated scalar: lifetime value. Re-executes per customer.\n",[229,2057,2058,2061,2063,2065,2067,2069,2071,2073,2075,2077,2079,2081,2083,2085,2087,2089,2091,2093,2095,2097,2099],{"class":231,"line":328},[229,2059,2060],{"class":257},"  (",[229,2062,24],{"class":254},[229,2064,1516],{"class":283},[229,2066,412],{"class":257},[229,2068,1716],{"class":283},[229,2070,393],{"class":257},[229,2072,1721],{"class":283},[229,2074,418],{"class":257},[229,2076,27],{"class":254},[229,2078,423],{"class":257},[229,2080,20],{"class":254},[229,2082,428],{"class":283},[229,2084,393],{"class":257},[229,2086,433],{"class":283},[229,2088,436],{"class":254},[229,2090,390],{"class":283},[229,2092,393],{"class":257},[229,2094,443],{"class":283},[229,2096,418],{"class":257},[229,2098,345],{"class":254},[229,2100,2101],{"class":257}," lifetime_value,\n",[229,2103,2104],{"class":231,"line":354},[229,2105,2106],{"class":235},"  -- Semi-join: has the customer ordered in the last 30 days?\n",[229,2108,2109,2112],{"class":231,"line":362},[229,2110,2111],{"class":254},"  EXISTS",[229,2113,564],{"class":257},[229,2115,2116,2119,2121,2123],{"class":231,"line":367},[229,2117,2118],{"class":254},"    SELECT",[229,2120,868],{"class":283},[229,2122,506],{"class":254},[229,2124,1904],{"class":257},[229,2126,2127,2130,2132,2134,2136,2138,2140,2142],{"class":231,"line":373},[229,2128,2129],{"class":254},"    WHERE",[229,2131,428],{"class":283},[229,2133,393],{"class":257},[229,2135,433],{"class":283},[229,2137,436],{"class":254},[229,2139,390],{"class":283},[229,2141,393],{"class":257},[229,2143,1780],{"class":283},[229,2145,2146,2149,2151,2153,2156,2159,2162,2165,2168],{"class":231,"line":379},[229,2147,2148],{"class":254},"      AND",[229,2150,428],{"class":283},[229,2152,393],{"class":257},[229,2154,2155],{"class":283},"ordered_on",[229,2157,2158],{"class":254}," >=",[229,2160,2161],{"class":257}," CURRENT_DATE ",[229,2163,2164],{"class":254},"-",[229,2166,2167],{"class":257}," INTERVAL ",[229,2169,2170],{"class":1132},"'30 days'\n",[229,2172,2173,2176,2178],{"class":231,"line":385},[229,2174,2175],{"class":257},"  ) ",[229,2177,345],{"class":254},[229,2179,2180],{"class":257}," active_recently,\n",[229,2182,2183],{"class":231,"line":402},[229,2184,2185],{"class":235},"  -- Anti-join: no returned orders (status = 'returned').\n",[229,2187,2188],{"class":231,"line":453},[229,2189,2190],{"class":235},"  -- NOT EXISTS is NULL-safe — doesn't poison like NOT IN.\n",[229,2192,2194,2197,2199],{"class":231,"line":2193},21,[229,2195,2196],{"class":254},"  NOT",[229,2198,861],{"class":254},[229,2200,564],{"class":257},[229,2202,2204,2206,2208,2210],{"class":231,"line":2203},22,[229,2205,2118],{"class":254},[229,2207,868],{"class":283},[229,2209,506],{"class":254},[229,2211,1904],{"class":257},[229,2213,2215,2217,2219,2221,2223,2225,2227,2229],{"class":231,"line":2214},23,[229,2216,2129],{"class":254},[229,2218,428],{"class":283},[229,2220,393],{"class":257},[229,2222,433],{"class":283},[229,2224,436],{"class":254},[229,2226,390],{"class":283},[229,2228,393],{"class":257},[229,2230,1780],{"class":283},[229,2232,2234,2236,2238,2240,2243,2245],{"class":231,"line":2233},24,[229,2235,2148],{"class":254},[229,2237,428],{"class":283},[229,2239,393],{"class":257},[229,2241,2242],{"class":283},"status",[229,2244,436],{"class":254},[229,2246,2247],{"class":1132}," 'returned'\n",[229,2249,2251,2253,2255],{"class":231,"line":2250},25,[229,2252,2175],{"class":257},[229,2254,345],{"class":254},[229,2256,2257],{"class":257}," has_no_returns,\n",[229,2259,2261],{"class":231,"line":2260},26,[229,2262,2263],{"class":235},"  -- LATERAL: top 3 most recent orders, bounded per-row.\n",[229,2265,2267,2270,2272,2275],{"class":231,"line":2266},27,[229,2268,2269],{"class":283},"  top",[229,2271,393],{"class":257},[229,2273,2274],{"class":283},"recent_order_ids",[229,2276,399],{"class":257},[229,2278,2280,2282,2284],{"class":231,"line":2279},28,[229,2281,2269],{"class":283},[229,2283,393],{"class":257},[229,2285,2286],{"class":283},"recent_amounts\n",[229,2288,2290,2292],{"class":231,"line":2289},29,[229,2291,27],{"class":254},[229,2293,851],{"class":257},[229,2295,2297,2299],{"class":231,"line":2296},30,[229,2298,1888],{"class":254},[229,2300,1891],{"class":257},[229,2302,2304],{"class":231,"line":2303},31,[229,2305,2306],{"class":254},"  SELECT\n",[229,2308,2310,2313,2315,2317,2319,2322,2324,2326,2328,2331,2333,2335],{"class":231,"line":2309},32,[229,2311,2312],{"class":257},"    array_agg(",[229,2314,1716],{"class":283},[229,2316,393],{"class":257},[229,2318,443],{"class":283},[229,2320,2321],{"class":254}," ORDER BY",[229,2323,428],{"class":283},[229,2325,393],{"class":257},[229,2327,2155],{"class":283},[229,2329,2330],{"class":254}," DESC",[229,2332,418],{"class":257},[229,2334,345],{"class":254},[229,2336,2337],{"class":257}," recent_order_ids,\n",[229,2339,2341,2343,2345,2347,2349,2351,2353,2355,2357,2359,2361,2363],{"class":231,"line":2340},33,[229,2342,2312],{"class":257},[229,2344,1716],{"class":283},[229,2346,393],{"class":257},[229,2348,1721],{"class":283},[229,2350,2321],{"class":254},[229,2352,428],{"class":283},[229,2354,393],{"class":257},[229,2356,2155],{"class":283},[229,2358,2330],{"class":254},[229,2360,418],{"class":257},[229,2362,345],{"class":254},[229,2364,2365],{"class":257}," recent_amounts\n",[229,2367,2369,2371],{"class":231,"line":2368},34,[229,2370,1733],{"class":254},[229,2372,564],{"class":257},[229,2374,2376,2378],{"class":231,"line":2375},35,[229,2377,2118],{"class":254},[229,2379,2380],{"class":257}," id, amount, ordered_on\n",[229,2382,2384,2387],{"class":231,"line":2383},36,[229,2385,2386],{"class":254},"    FROM",[229,2388,1904],{"class":257},[229,2390,2392,2394,2396,2398,2400,2402,2404,2406],{"class":231,"line":2391},37,[229,2393,2129],{"class":254},[229,2395,428],{"class":283},[229,2397,393],{"class":257},[229,2399,433],{"class":283},[229,2401,436],{"class":254},[229,2403,390],{"class":283},[229,2405,393],{"class":257},[229,2407,1780],{"class":283},[229,2409,2411,2414,2417],{"class":231,"line":2410},38,[229,2412,2413],{"class":254},"    ORDER BY",[229,2415,2416],{"class":257}," ordered_on ",[229,2418,1936],{"class":254},[229,2420,2422,2425,2427],{"class":231,"line":2421},39,[229,2423,2424],{"class":254},"    LIMIT",[229,2426,1944],{"class":283},[229,2428,2429],{"class":235},"                                  -- only fetch 3 rows per customer\n",[229,2431,2433],{"class":231,"line":2432},40,[229,2434,2435],{"class":257},"  ) o\n",[229,2437,2439,2441,2443,2445],{"class":231,"line":2438},41,[229,2440,418],{"class":257},[229,2442,1860],{"class":254},[229,2444,1956],{"class":254},[229,2446,2447],{"class":257}," true\n",[229,2449,2451,2453,2455,2457,2459,2461],{"class":231,"line":2450},42,[229,2452,20],{"class":254},[229,2454,390],{"class":283},[229,2456,393],{"class":257},[229,2458,2242],{"class":283},[229,2460,436],{"class":254},[229,2462,2463],{"class":1132}," 'active'\n",[229,2465,2467,2470,2473,2476,2479,2482],{"class":231,"line":2466},43,[229,2468,2469],{"class":254},"ORDER BY",[229,2471,2472],{"class":257}," lifetime_value ",[229,2474,2475],{"class":254},"DESC",[229,2477,2478],{"class":254}," NULLS",[229,2480,2481],{"class":254}," LAST",[229,2483,2484],{"class":235},"      -- NULL lifetime_value (no orders) sorts last\n",[229,2486,2488,2490,2492],{"class":231,"line":2487},44,[229,2489,1967],{"class":254},[229,2491,1805],{"class":283},[229,2493,1808],{"class":257},[33,2495,2497],{"id":2496},"anti-pattern-not-in-with-null-subquery-null-poison","Anti-Pattern: NOT IN with NULL Subquery (NULL Poison)",[623,2499,2501],{"id":2500},"wrong-way","❌ Wrong Way",[217,2503,2504],{"language":219},[221,2505,2507],{"className":223,"code":2506,"language":219,"meta":225,"style":225},"-- Intent: find customers who never ordered product #5.\n-- Bug: if orders.customer_id is NULL for ANY matched row (e.g., guest checkout),\n-- the subquery returns NULL among its results → NOT IN poisons → ZERO rows returned.\nSELECT * FROM customers c\nWHERE c.id NOT IN (\n  SELECT o.customer_id\n  FROM orders o\n  JOIN order_items oi ON oi.order_id = o.id\n  WHERE oi.product_id = 5\n);\n",[18,2508,2509,2514,2519,2524,2534,2550,2560,2566,2591,2607],{"__ignoreMap":225},[229,2510,2511],{"class":231,"line":232},[229,2512,2513],{"class":235},"-- Intent: find customers who never ordered product #5.\n",[229,2515,2516],{"class":231,"line":239},[229,2517,2518],{"class":235},"-- Bug: if orders.customer_id is NULL for ANY matched row (e.g., guest checkout),\n",[229,2520,2521],{"class":231,"line":245},[229,2522,2523],{"class":235},"-- the subquery returns NULL among its results → NOT IN poisons → ZERO rows returned.\n",[229,2525,2526,2528,2530,2532],{"class":231,"line":251},[229,2527,24],{"class":254},[229,2529,503],{"class":254},[229,2531,506],{"class":254},[229,2533,851],{"class":257},[229,2535,2536,2538,2540,2542,2544,2546,2548],{"class":231,"line":261},[229,2537,20],{"class":254},[229,2539,390],{"class":283},[229,2541,393],{"class":257},[229,2543,443],{"class":283},[229,2545,858],{"class":254},[229,2547,731],{"class":254},[229,2549,564],{"class":257},[229,2551,2552,2554,2556,2558],{"class":231,"line":269},[229,2553,569],{"class":254},[229,2555,428],{"class":283},[229,2557,393],{"class":257},[229,2559,1760],{"class":283},[229,2561,2562,2564],{"class":231,"line":295},[229,2563,1733],{"class":254},[229,2565,1904],{"class":257},[229,2567,2568,2570,2573,2575,2578,2580,2583,2585,2587,2589],{"class":231,"line":302},[229,2569,1740],{"class":254},[229,2571,2572],{"class":257}," order_items oi ",[229,2574,1745],{"class":254},[229,2576,2577],{"class":283}," oi",[229,2579,393],{"class":257},[229,2581,2582],{"class":283},"order_id",[229,2584,436],{"class":254},[229,2586,428],{"class":283},[229,2588,393],{"class":257},[229,2590,1780],{"class":283},[229,2592,2593,2595,2597,2599,2602,2604],{"class":231,"line":308},[229,2594,1909],{"class":254},[229,2596,2577],{"class":283},[229,2598,393],{"class":257},[229,2600,2601],{"class":283},"product_id",[229,2603,436],{"class":254},[229,2605,2606],{"class":283}," 5\n",[229,2608,2609],{"class":231,"line":314},[229,2610,534],{"class":257},[623,2612,2614],{"id":2613},"right-way","✅ Right Way",[217,2616,2617],{"language":219},[221,2618,2620],{"className":223,"code":2619,"language":219,"meta":225,"style":225},"-- NOT EXISTS: NULL-safe anti-join. The subquery returning empty = TRUE.\n-- The planner can use a hash anti-join on (customer_id, product_id).\nSELECT * FROM customers c\nWHERE NOT EXISTS (\n  SELECT 1\n  FROM orders o\n  JOIN order_items oi ON oi.order_id = o.id\n  WHERE o.customer_id = c.id     -- correlated: references outer c.id\n    AND oi.product_id = 5\n);\n",[18,2621,2622,2627,2632,2642,2652,2658,2664,2686,2707,2722],{"__ignoreMap":225},[229,2623,2624],{"class":231,"line":232},[229,2625,2626],{"class":235},"-- NOT EXISTS: NULL-safe anti-join. The subquery returning empty = TRUE.\n",[229,2628,2629],{"class":231,"line":239},[229,2630,2631],{"class":235},"-- The planner can use a hash anti-join on (customer_id, product_id).\n",[229,2633,2634,2636,2638,2640],{"class":231,"line":245},[229,2635,24],{"class":254},[229,2637,503],{"class":254},[229,2639,506],{"class":254},[229,2641,851],{"class":257},[229,2643,2644,2646,2648,2650],{"class":231,"line":251},[229,2645,20],{"class":254},[229,2647,858],{"class":254},[229,2649,861],{"class":254},[229,2651,564],{"class":257},[229,2653,2654,2656],{"class":231,"line":261},[229,2655,569],{"class":254},[229,2657,587],{"class":283},[229,2659,2660,2662],{"class":231,"line":269},[229,2661,1733],{"class":254},[229,2663,1904],{"class":257},[229,2665,2666,2668,2670,2672,2674,2676,2678,2680,2682,2684],{"class":231,"line":295},[229,2667,1740],{"class":254},[229,2669,2572],{"class":257},[229,2671,1745],{"class":254},[229,2673,2577],{"class":283},[229,2675,393],{"class":257},[229,2677,2582],{"class":283},[229,2679,436],{"class":254},[229,2681,428],{"class":283},[229,2683,393],{"class":257},[229,2685,1780],{"class":283},[229,2687,2688,2690,2692,2694,2696,2698,2700,2702,2704],{"class":231,"line":302},[229,2689,1909],{"class":254},[229,2691,428],{"class":283},[229,2693,393],{"class":257},[229,2695,433],{"class":283},[229,2697,436],{"class":254},[229,2699,390],{"class":283},[229,2701,393],{"class":257},[229,2703,443],{"class":283},[229,2705,2706],{"class":235},"     -- correlated: references outer c.id\n",[229,2708,2709,2712,2714,2716,2718,2720],{"class":231,"line":308},[229,2710,2711],{"class":254},"    AND",[229,2713,2577],{"class":283},[229,2715,393],{"class":257},[229,2717,2601],{"class":283},[229,2719,436],{"class":254},[229,2721,2606],{"class":283},[229,2723,2724],{"class":231,"line":314},[229,2725,534],{"class":257},[33,2727,2729],{"id":2728},"anti-pattern-correlated-subquery-in-select-for-large-tables","Anti-Pattern: Correlated Subquery in SELECT for Large Tables",[623,2731,2501],{"id":2732},"wrong-way-1",[217,2734,2735],{"language":219},[221,2736,2738],{"className":223,"code":2737,"language":219,"meta":225,"style":225},"-- Per-customer order count via correlated scalar subquery.\n-- For 1M customers: 1M subquery executions. The planner may not decorrelate it\n-- (especially if the subquery has LIMIT, DISTINCT, or volatile functions).\nSELECT c.name,\n       (SELECT COUNT(*) FROM orders o WHERE o.customer_id = c.id) AS order_count\nFROM customers c;\n",[18,2739,2740,2745,2750,2755,2767,2807],{"__ignoreMap":225},[229,2741,2742],{"class":231,"line":232},[229,2743,2744],{"class":235},"-- Per-customer order count via correlated scalar subquery.\n",[229,2746,2747],{"class":231,"line":239},[229,2748,2749],{"class":235},"-- For 1M customers: 1M subquery executions. The planner may not decorrelate it\n",[229,2751,2752],{"class":231,"line":245},[229,2753,2754],{"class":235},"-- (especially if the subquery has LIMIT, DISTINCT, or volatile functions).\n",[229,2756,2757,2759,2761,2763,2765],{"class":231,"line":251},[229,2758,24],{"class":254},[229,2760,390],{"class":283},[229,2762,393],{"class":257},[229,2764,396],{"class":283},[229,2766,399],{"class":257},[229,2768,2769,2771,2773,2775,2777,2779,2781,2783,2785,2787,2789,2791,2793,2795,2797,2799,2801,2803,2805],{"class":231,"line":261},[229,2770,331],{"class":257},[229,2772,24],{"class":254},[229,2774,409],{"class":283},[229,2776,412],{"class":257},[229,2778,415],{"class":254},[229,2780,418],{"class":257},[229,2782,27],{"class":254},[229,2784,423],{"class":257},[229,2786,20],{"class":254},[229,2788,428],{"class":283},[229,2790,393],{"class":257},[229,2792,433],{"class":283},[229,2794,436],{"class":254},[229,2796,390],{"class":283},[229,2798,393],{"class":257},[229,2800,443],{"class":283},[229,2802,418],{"class":257},[229,2804,345],{"class":254},[229,2806,450],{"class":257},[229,2808,2809,2811],{"class":231,"line":269},[229,2810,27],{"class":254},[229,2812,458],{"class":257},[623,2814,2614],{"id":2815},"right-way-1",[217,2817,2818],{"language":219},[221,2819,2821],{"className":223,"code":2820,"language":219,"meta":225,"style":225},"-- LEFT JOIN + GROUP BY: single pass over orders, hash-aggregated by customer_id.\n-- The planner does ONE scan of orders + ONE hash aggregation — O(N), not O(N*M).\nSELECT c.name,\n       COALESCE(o.order_count, 0) AS order_count   -- NULL for customers with zero orders\nFROM customers c\nLEFT JOIN (\n  SELECT customer_id, COUNT(*) AS order_count\n  FROM orders\n  GROUP BY customer_id\n) o ON o.customer_id = c.id;\n",[18,2822,2823,2828,2833,2845,2874,2880,2886,2905,2911,2917],{"__ignoreMap":225},[229,2824,2825],{"class":231,"line":232},[229,2826,2827],{"class":235},"-- LEFT JOIN + GROUP BY: single pass over orders, hash-aggregated by customer_id.\n",[229,2829,2830],{"class":231,"line":239},[229,2831,2832],{"class":235},"-- The planner does ONE scan of orders + ONE hash aggregation — O(N), not O(N*M).\n",[229,2834,2835,2837,2839,2841,2843],{"class":231,"line":245},[229,2836,24],{"class":254},[229,2838,390],{"class":283},[229,2840,393],{"class":257},[229,2842,396],{"class":283},[229,2844,399],{"class":257},[229,2846,2847,2850,2852,2854,2856,2859,2861,2864,2866,2868,2871],{"class":231,"line":251},[229,2848,2849],{"class":283},"       COALESCE",[229,2851,412],{"class":257},[229,2853,1716],{"class":283},[229,2855,393],{"class":257},[229,2857,2858],{"class":283},"order_count",[229,2860,21],{"class":257},[229,2862,2863],{"class":283},"0",[229,2865,418],{"class":257},[229,2867,345],{"class":254},[229,2869,2870],{"class":257}," order_count   ",[229,2872,2873],{"class":235},"-- NULL for customers with zero orders\n",[229,2875,2876,2878],{"class":231,"line":261},[229,2877,27],{"class":254},[229,2879,851],{"class":257},[229,2881,2882,2884],{"class":231,"line":269},[229,2883,1888],{"class":254},[229,2885,564],{"class":257},[229,2887,2888,2890,2892,2895,2897,2899,2901,2903],{"class":231,"line":295},[229,2889,569],{"class":254},[229,2891,1485],{"class":257},[229,2893,2894],{"class":283},"COUNT",[229,2896,412],{"class":257},[229,2898,415],{"class":254},[229,2900,418],{"class":257},[229,2902,345],{"class":254},[229,2904,450],{"class":257},[229,2906,2907,2909],{"class":231,"line":302},[229,2908,1733],{"class":254},[229,2910,266],{"class":257},[229,2912,2913,2915],{"class":231,"line":308},[229,2914,1765],{"class":254},[229,2916,1509],{"class":257},[229,2918,2919,2922,2924,2926,2928,2930,2932,2934,2936,2938],{"class":231,"line":314},[229,2920,2921],{"class":257},") o ",[229,2923,1745],{"class":254},[229,2925,428],{"class":283},[229,2927,393],{"class":257},[229,2929,433],{"class":283},[229,2931,436],{"class":254},[229,2933,390],{"class":283},[229,2935,393],{"class":257},[229,2937,443],{"class":283},[229,2939,1808],{"class":257},[33,2941,2943],{"id":2942},"tips-tricks","💡 Tips & Tricks",[194,2945,2946,2971,2992,3010,3023,3038],{},[197,2947,2948,2951,2952,161,2954,2956,2957,161,2959,2961,2962,2964,2965,2967,2968,2970],{},[40,2949,2950],{},"Idiom"," — prefer ",[18,2953,170],{},[18,2955,946],{}," over ",[18,2958,138],{},[18,2960,180],{}," for subqueries. ",[18,2963,170],{}," is NULL-safe (no poisoning), short-circuits on the first match, and the planner can use hash semi\u002Fanti-joins. Reserve ",[18,2966,138],{}," for literal lists (",[18,2969,1404],{},") where it's unambiguous.",[197,2972,2973,2976,2977,2979,2980,2984,2985,2988,2989,2991],{},[40,2974,2975],{},"Performance"," — a correlated subquery in the ",[18,2978,24],{}," list that can't be decorrelated runs ",[2981,2982,2983],"em",{},"per outer row",". For a million-row outer table, that's a million subquery executions. Check ",[18,2986,2987],{},"EXPLAIN"," for \"InitPlan\"\u002F\"SubPlan\" nodes; if you see per-row execution, rewrite as a join + ",[18,2990,1506],{}," or a window function.",[197,2993,2994,2996,2997,2999,3000,3002,3003,3006,3007,3009],{},[40,2995,2950],{}," — ",[18,2998,1183],{}," in ",[18,3001,170],{}," is convention, not requirement. ",[18,3004,3005],{},"SELECT *"," works too, but ",[18,3008,1183],{}," signals \"I don't care about the columns\" and avoids column-resolution surprises.",[197,3011,3012,3015,3016,3018,3019,3022],{},[40,3013,3014],{},"Debug"," — when ",[18,3017,180],{}," returns no rows unexpectedly, run the subquery alone and check for NULLs: ",[18,3020,3021],{},"SELECT customer_id FROM orders WHERE customer_id IS NULL",". One NULL is all it takes.",[197,3024,3025,3027,3028,3030,3031,3034,3035,3037],{},[40,3026,2950],{}," — use ",[18,3029,1818],{}," for \"top-N per group\" queries. It's more readable than ",[18,3032,3033],{},"ROW_NUMBER() OVER (...) WHERE rn \u003C= N"," and lets the planner use an indexed per-group lookup with ",[18,3036,1967],{},", which can be much faster than sorting the whole partition.",[197,3039,3040,2996,3042,3045,3046,3049,3050,3053,3054,3057,3058,161,3061,3064],{},[40,3041,2975],{},[18,3043,3044],{},"> ANY (subquery)"," is equivalent to ",[18,3047,3048],{},"> (SELECT MIN(...) ...)"," and ",[18,3051,3052],{},"> ALL (subquery)"," to ",[18,3055,3056],{},"> (SELECT MAX(...) ...)",". The scalar ",[18,3059,3060],{},"MIN",[18,3062,3063],{},"MAX"," form is often clearer and lets the planner cache a single value.",[33,3066,3068],{"id":3067},"️-edge-cases-gotchas","⚠️ Edge Cases & Gotchas",[194,3070,3071,3086,3105,3118,3130,3154,3166,3184,3203],{},[197,3072,3073,3078,3079,3082,3083,3085],{},[40,3074,3075,3077],{},[18,3076,180],{}," + NULL = zero rows"," — the most dangerous subquery gotcha. ",[18,3080,3081],{},"x NOT IN (1, NULL)"," returns no rows for the entire query. Always use ",[18,3084,946],{}," or filter NULLs.",[197,3087,3088,2996,3091,3094,3095,3098,3099,161,3102,393],{},[40,3089,3090],{},"Scalar subquery returning multiple rows",[18,3092,3093],{},"WHERE amount > (SELECT amount FROM orders WHERE customer_id = 1)"," is a runtime error if customer 1 has more than one order. Aggregate it (",[18,3096,3097],{},"MAX(amount)",") or use ",[18,3100,3101],{},"> ALL(...)",[18,3103,3104],{},"> ANY(...)",[197,3106,3107,3110,3111,3113,3114,3117],{},[40,3108,3109],{},"Correlated subquery re-execution cost"," — a correlated subquery logically runs once per outer row. The planner may decorrelate it into a join, but if it can't (e.g., the subquery has a ",[18,3112,1967],{}," or volatile function like ",[18,3115,3116],{},"random()","), it's a per-row performance trap.",[197,3119,3120,3126,3127,3129],{},[40,3121,3122,3123,3125],{},"Subquery in ",[18,3124,24],{}," list performance"," — each correlated scalar subquery in the ",[18,3128,24],{}," list adds a per-row execution. Multiple such subqueries compound: 5 correlated subqueries × 1M rows = 5M subquery calls.",[197,3131,3132,2996,3139,598,3141,3143,3144,598,3146,3148,3149,3151,3152,393],{},[40,3133,3134,161,3136,3138],{},[18,3135,164],{},[18,3137,167],{}," with empty subquery",[18,3140,1153],{},[40,3142,1157],{}," (vacuously), ",[18,3145,1163],{},[40,3147,1166],{},". This surprises: \"no orders exceed the max of an empty set\" returns all rows for ",[18,3150,167],{},", none for ",[18,3153,164],{},[197,3155,3156,2996,3159,3162,3163,393],{},[40,3157,3158],{},"Derived table requires an alias",[18,3160,3161],{},"SELECT * FROM (SELECT ...)"," is a syntax error. You must alias it: ",[18,3164,3165],{},"SELECT * FROM (SELECT ...) t",[197,3167,3168,2996,3174,3177,3178,3180,3181,3183],{},[40,3169,3170,3049,3172],{},[18,3171,170],{},[18,3173,3005],{},[18,3175,3176],{},"EXISTS (SELECT * FROM ...)"," works (columns are ignored), but if ",[18,3179,415],{}," references an unresolvable column, it errors. ",[18,3182,1183],{}," avoids any column-resolution surprises.",[197,3185,3186,2996,3192,3195,3196,3199,3200,3202],{},[40,3187,3188,3189,3191],{},"Duplicate rows from ",[18,3190,138],{}," with a join",[18,3193,3194],{},"SELECT c.* FROM customers c JOIN orders o ON c.id IN (...)"," can multiply rows if ",[18,3197,3198],{},"orders"," has multiple matches. ",[18,3201,138],{}," in a join condition is a filter, not a deduplication.",[197,3204,3205,2996,3210,3213,3214,3216],{},[40,3206,3207,3209],{},[18,3208,1818],{}," requires the keyword",[18,3211,3212],{},"JOIN (subquery referencing outer)"," without ",[18,3215,1818],{}," is a syntax error in PostgreSQL and MySQL 8.0+. SQLite 3.39+ is more permissive about implicit correlation.",[33,3218,3220],{"id":3219},"spot-the-bug","🧠 Spot the Bug",[14,3222,3223,3224,3227],{},"This query intends to find customers who have ",[40,3225,3226],{},"not"," ordered product #5. It returns zero rows even though several customers haven't ordered product #5. Why?",[217,3229,3230],{"language":219},[221,3231,3233],{"className":223,"code":3232,"language":219,"meta":225,"style":225},"SELECT * FROM customers c\nWHERE c.id NOT IN (\n  SELECT customer_id FROM orders o\n  JOIN order_items oi ON oi.order_id = o.id\n  WHERE oi.product_id = 5\n);\n",[18,3234,3235,3245,3261,3271,3293,3307],{"__ignoreMap":225},[229,3236,3237,3239,3241,3243],{"class":231,"line":232},[229,3238,24],{"class":254},[229,3240,503],{"class":254},[229,3242,506],{"class":254},[229,3244,851],{"class":257},[229,3246,3247,3249,3251,3253,3255,3257,3259],{"class":231,"line":239},[229,3248,20],{"class":254},[229,3250,390],{"class":283},[229,3252,393],{"class":257},[229,3254,443],{"class":283},[229,3256,858],{"class":254},[229,3258,731],{"class":254},[229,3260,564],{"class":257},[229,3262,3263,3265,3267,3269],{"class":231,"line":245},[229,3264,569],{"class":254},[229,3266,674],{"class":257},[229,3268,27],{"class":254},[229,3270,1904],{"class":257},[229,3272,3273,3275,3277,3279,3281,3283,3285,3287,3289,3291],{"class":231,"line":251},[229,3274,1740],{"class":254},[229,3276,2572],{"class":257},[229,3278,1745],{"class":254},[229,3280,2577],{"class":283},[229,3282,393],{"class":257},[229,3284,2582],{"class":283},[229,3286,436],{"class":254},[229,3288,428],{"class":283},[229,3290,393],{"class":257},[229,3292,1780],{"class":283},[229,3294,3295,3297,3299,3301,3303,3305],{"class":231,"line":261},[229,3296,1909],{"class":254},[229,3298,2577],{"class":283},[229,3300,393],{"class":257},[229,3302,2601],{"class":283},[229,3304,436],{"class":254},[229,3306,2606],{"class":283},[229,3308,3309],{"class":231,"line":269},[229,3310,534],{"class":257},[3312,3313,3314,3318,3336,3342,3439,3444],"details",{},[3315,3316,3317],"summary",{},"Answer",[14,3319,3320,3321,3324,3325,3328,3329,3332,3333,3335],{},"If any order row joins to an ",[18,3322,3323],{},"order_items"," row with ",[18,3326,3327],{},"product_id = 5"," but ",[18,3330,3331],{},"customer_id IS NULL"," (e.g., a guest checkout), the subquery returns a NULL among its results — and ",[18,3334,180],{}," with a NULL in the list returns zero rows for the entire outer query (the NULL-poisoning trap).",[14,3337,3338,3339,3341],{},"The fix — use ",[18,3340,946],{},", which is NULL-safe:",[217,3343,3344],{"language":219},[221,3345,3347],{"className":223,"code":3346,"language":219,"meta":225,"style":225},"SELECT * FROM customers c\nWHERE NOT EXISTS (\n  SELECT 1\n  FROM orders o\n  JOIN order_items oi ON oi.order_id = o.id\n  WHERE o.customer_id = c.id\n    AND oi.product_id = 5\n);\n",[18,3348,3349,3359,3369,3375,3381,3403,3421,3435],{"__ignoreMap":225},[229,3350,3351,3353,3355,3357],{"class":231,"line":232},[229,3352,24],{"class":254},[229,3354,503],{"class":254},[229,3356,506],{"class":254},[229,3358,851],{"class":257},[229,3360,3361,3363,3365,3367],{"class":231,"line":239},[229,3362,20],{"class":254},[229,3364,858],{"class":254},[229,3366,861],{"class":254},[229,3368,564],{"class":257},[229,3370,3371,3373],{"class":231,"line":245},[229,3372,569],{"class":254},[229,3374,587],{"class":283},[229,3376,3377,3379],{"class":231,"line":251},[229,3378,1733],{"class":254},[229,3380,1904],{"class":257},[229,3382,3383,3385,3387,3389,3391,3393,3395,3397,3399,3401],{"class":231,"line":261},[229,3384,1740],{"class":254},[229,3386,2572],{"class":257},[229,3388,1745],{"class":254},[229,3390,2577],{"class":283},[229,3392,393],{"class":257},[229,3394,2582],{"class":283},[229,3396,436],{"class":254},[229,3398,428],{"class":283},[229,3400,393],{"class":257},[229,3402,1780],{"class":283},[229,3404,3405,3407,3409,3411,3413,3415,3417,3419],{"class":231,"line":269},[229,3406,1909],{"class":254},[229,3408,428],{"class":283},[229,3410,393],{"class":257},[229,3412,433],{"class":283},[229,3414,436],{"class":254},[229,3416,390],{"class":283},[229,3418,393],{"class":257},[229,3420,1780],{"class":283},[229,3422,3423,3425,3427,3429,3431,3433],{"class":231,"line":295},[229,3424,2711],{"class":254},[229,3426,2577],{"class":283},[229,3428,393],{"class":257},[229,3430,2601],{"class":283},[229,3432,436],{"class":254},[229,3434,2606],{"class":283},[229,3436,3437],{"class":231,"line":302},[229,3438,534],{"class":257},[14,3440,3441,3443],{},[18,3442,946],{}," checks \"no row exists matching this customer and product 5\" — it returns TRUE when the subquery is empty, regardless of NULLs anywhere. It also lets the planner use a hash anti-join.",[14,3445,3446,747,3449,3451,3452,3454],{},[40,3447,3448],{},"The lesson",[18,3450,180],{}," with a subquery over nullable columns is a latent bug — it works until a NULL appears, then silently returns nothing. Use ",[18,3453,946],{}," for \"not in another table\" semantics.",[33,3456,3457],{"id":3315},"Summary",[14,3459,3460,3461,161,3463,161,3465,161,3467,161,3469,3471,3472,3474],{},"You can now classify subqueries by return shape (scalar\u002Frow\u002Ftable) and correlation; use ",[18,3462,138],{},[18,3464,180],{},[18,3466,170],{},[18,3468,164],{},[18,3470,167],{}," safely (avoiding the NULL-poisoning trap); build derived tables and ",[18,3473,1818],{}," joins; and choose between subqueries and joins based on readability, NULL-safety, and performance. Next: CTEs, the cleaner way to compose multi-stage queries.",[3476,3477,3478],"style",{},"html pre.shiki code .sdCPZ, html code.shiki .sdCPZ{--shiki-default:#6A737D;--shiki-github-dark:#6A737D}html pre.shiki code .svdQ7, html code.shiki .svdQ7{--shiki-default:#D73A49;--shiki-github-dark:#F97583}html pre.shiki code .ssxIu, html code.shiki .ssxIu{--shiki-default:#24292E;--shiki-github-dark:#E1E4E8}html pre.shiki code .snvgF, html code.shiki .snvgF{--shiki-default:#005CC5;--shiki-github-dark:#79B8FF}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 .sJ6F3, html code.shiki .sJ6F3{--shiki-default:#032F62;--shiki-github-dark:#9ECBFF}",{"title":225,"searchDepth":239,"depth":239,"links":3480},[3481,3482,3483,3484,3489,3492,3497,3498,3499,3503,3507,3508,3509,3510],{"id":35,"depth":239,"text":36},{"id":211,"depth":239,"text":212},{"id":470,"depth":239,"text":471},{"id":605,"depth":239,"text":606,"children":3485},[3486,3487,3488],{"id":625,"depth":245,"text":626},{"id":681,"depth":245,"text":682},{"id":950,"depth":245,"text":951},{"id":1170,"depth":239,"text":1171,"children":3490},[3491],{"id":1329,"depth":245,"text":1330},{"id":1412,"depth":239,"text":1413,"children":3493},[3494,3495,3496],{"id":1416,"depth":245,"text":1417},{"id":1562,"depth":245,"text":1563},{"id":1640,"depth":245,"text":1641},{"id":1811,"depth":239,"text":1812},{"id":1975,"depth":239,"text":1976},{"id":2496,"depth":239,"text":2497,"children":3500},[3501,3502],{"id":2500,"depth":245,"text":2501},{"id":2613,"depth":245,"text":2614},{"id":2728,"depth":239,"text":2729,"children":3504},[3505,3506],{"id":2732,"depth":245,"text":2501},{"id":2815,"depth":245,"text":2614},{"id":2942,"depth":239,"text":2943},{"id":3067,"depth":239,"text":3068},{"id":3219,"depth":239,"text":3220},{"id":3315,"depth":239,"text":3457},"A subquery is a query nested inside another query. Subqueries appear in WHERE, SELECT, FROM, HAVING, and join clauses. They compose queries before reaching for CTEs (chapter 07) or joins.","md",{},"\u002Fsql\u002F06-subqueries",{"title":5,"description":3511},"sql\u002F06-subqueries","nDfTaI-FUINUUWmKUzkyDBgIHhNl8XYdPVTIE1_PsS0",1789924654350]