[{"data":1,"prerenderedAt":917},["ShallowReactive",2],{"blog-\u002Fblog\u002Fsql-set-operations-union-intersect-except":3},{"id":4,"title":5,"body":6,"description":903,"difficulty":904,"extension":905,"framework":906,"frameworkSlug":81,"meta":907,"navigation":152,"order":149,"path":908,"qaPath":909,"seo":910,"stem":911,"subtopic":912,"topic":913,"topicSlug":914,"updated":915,"__hash__":916},"blog\u002Fblog\u002Fsql-set-operations-union-intersect-except.md","SQL Set Operations — UNION, INTERSECT, and EXCEPT Explained",{"type":7,"value":8,"toc":893},"minimark",[9,14,40,55,59,76,300,309,313,322,376,388,392,408,505,512,581,585,588,680,684,696,774,778,784,855,861,865,889],[10,11,13],"h2",{"id":12},"what-set-operations-do","What set operations do",[15,16,17,18,22,23,27,28,31,32,35,36,39],"p",{},"Joins combine columns from multiple tables into wider rows. Set operations combine\nthe ",[19,20,21],"strong",{},"rows"," of multiple queries into a single result set — like stacking spreadsheets\nvertically. The three operators are ",[24,25,26],"code",{},"UNION",", ",[24,29,30],{},"INTERSECT",", and ",[24,33,34],{},"EXCEPT"," (called\n",[24,37,38],{},"MINUS"," in Oracle).",[15,41,42,43,46,47,50,51,54],{},"The rules for all three: the queries must return the ",[19,44,45],{},"same number of columns",", and\ncorresponding columns must have ",[19,48,49],{},"compatible types",". Column names come from the\nfirst query. ",[24,52,53],{},"ORDER BY"," goes at the very end and applies to the combined result.",[10,56,58],{"id":57},"union-combining-result-sets","UNION — combining result sets",[15,60,61,63,64,67,68,71,72,75],{},[24,62,26],{}," stacks two query results and ",[19,65,66],{},"removes duplicates"," (implicit ",[24,69,70],{},"DISTINCT",").\n",[24,73,74],{},"UNION ALL"," stacks them without deduplication and is faster.",[77,78,83],"pre",{"className":79,"code":80,"language":81,"meta":82,"style":82},"language-sql shiki shiki-themes github-light github-dark","-- All email addresses in the system (customers + staff), deduplicated\nSELECT email, 'customer' AS source FROM customers\nUNION\nSELECT email, 'staff'    AS source FROM staff_accounts;\n\n-- Transaction history: credit card charges and bank transfers in one timeline\nSELECT\n    id,\n    amount,\n    created_at,\n    'card'     AS payment_type\nFROM card_transactions\nWHERE customer_id = 4821\n\nUNION ALL\n\nSELECT\n    id,\n    amount,\n    created_at,\n    'bank'\nFROM bank_transfers\nWHERE customer_id = 4821\n\nORDER BY created_at DESC;\n","sql","",[24,84,85,94,121,127,147,154,160,166,172,178,184,196,204,220,225,231,236,241,246,251,256,262,270,281,286],{"__ignoreMap":82},[86,87,90],"span",{"class":88,"line":89},"line",1,[86,91,93],{"class":92},"sJ8bj","-- All email addresses in the system (customers + staff), deduplicated\n",[86,95,97,101,105,109,112,115,118],{"class":88,"line":96},2,[86,98,100],{"class":99},"szBVR","SELECT",[86,102,104],{"class":103},"sVt8B"," email, ",[86,106,108],{"class":107},"sZZnC","'customer'",[86,110,111],{"class":99}," AS",[86,113,114],{"class":103}," source ",[86,116,117],{"class":99},"FROM",[86,119,120],{"class":103}," customers\n",[86,122,124],{"class":88,"line":123},3,[86,125,126],{"class":99},"UNION\n",[86,128,130,132,134,137,140,142,144],{"class":88,"line":129},4,[86,131,100],{"class":99},[86,133,104],{"class":103},[86,135,136],{"class":107},"'staff'",[86,138,139],{"class":99},"    AS",[86,141,114],{"class":103},[86,143,117],{"class":99},[86,145,146],{"class":103}," staff_accounts;\n",[86,148,150],{"class":88,"line":149},5,[86,151,153],{"emptyLinePlaceholder":152},true,"\n",[86,155,157],{"class":88,"line":156},6,[86,158,159],{"class":92},"-- Transaction history: credit card charges and bank transfers in one timeline\n",[86,161,163],{"class":88,"line":162},7,[86,164,165],{"class":99},"SELECT\n",[86,167,169],{"class":88,"line":168},8,[86,170,171],{"class":103},"    id,\n",[86,173,175],{"class":88,"line":174},9,[86,176,177],{"class":103},"    amount,\n",[86,179,181],{"class":88,"line":180},10,[86,182,183],{"class":103},"    created_at,\n",[86,185,187,190,193],{"class":88,"line":186},11,[86,188,189],{"class":107},"    'card'",[86,191,192],{"class":99},"     AS",[86,194,195],{"class":103}," payment_type\n",[86,197,199,201],{"class":88,"line":198},12,[86,200,117],{"class":99},[86,202,203],{"class":103}," card_transactions\n",[86,205,207,210,213,216],{"class":88,"line":206},13,[86,208,209],{"class":99},"WHERE",[86,211,212],{"class":103}," customer_id ",[86,214,215],{"class":99},"=",[86,217,219],{"class":218},"sj4cs"," 4821\n",[86,221,223],{"class":88,"line":222},14,[86,224,153],{"emptyLinePlaceholder":152},[86,226,228],{"class":88,"line":227},15,[86,229,230],{"class":99},"UNION ALL\n",[86,232,234],{"class":88,"line":233},16,[86,235,153],{"emptyLinePlaceholder":152},[86,237,239],{"class":88,"line":238},17,[86,240,165],{"class":99},[86,242,244],{"class":88,"line":243},18,[86,245,171],{"class":103},[86,247,249],{"class":88,"line":248},19,[86,250,177],{"class":103},[86,252,254],{"class":88,"line":253},20,[86,255,183],{"class":103},[86,257,259],{"class":88,"line":258},21,[86,260,261],{"class":107},"    'bank'\n",[86,263,265,267],{"class":88,"line":264},22,[86,266,117],{"class":99},[86,268,269],{"class":103}," bank_transfers\n",[86,271,273,275,277,279],{"class":88,"line":272},23,[86,274,209],{"class":99},[86,276,212],{"class":103},[86,278,215],{"class":99},[86,280,219],{"class":218},[86,282,284],{"class":88,"line":283},24,[86,285,153],{"emptyLinePlaceholder":152},[86,287,289,291,294,297],{"class":88,"line":288},25,[86,290,53],{"class":99},[86,292,293],{"class":103}," created_at ",[86,295,296],{"class":99},"DESC",[86,298,299],{"class":103},";\n",[15,301,302,303,305,306,308],{},"Use ",[24,304,74],{}," by default — deduplication requires sorting or hashing the\nentire result, which is expensive. Only use ",[24,307,26],{}," when you genuinely need\nduplicates removed.",[10,310,312],{"id":311},"intersect-rows-that-exist-in-both-queries","INTERSECT — rows that exist in both queries",[15,314,315,317,318,321],{},[24,316,30],{}," returns only rows that appear in ",[19,319,320],{},"both"," result sets.",[77,323,325],{"className":79,"code":324,"language":81,"meta":82,"style":82},"-- Customers who have both placed an order AND submitted a support ticket\n-- (i.e., paying customers with problems — a useful cohort for churn analysis)\nSELECT customer_id FROM orders\nINTERSECT\nSELECT customer_id FROM support_tickets WHERE created_at >= '2026-01-01';\n",[24,326,327,332,337,348,353],{"__ignoreMap":82},[86,328,329],{"class":88,"line":89},[86,330,331],{"class":92},"-- Customers who have both placed an order AND submitted a support ticket\n",[86,333,334],{"class":88,"line":96},[86,335,336],{"class":92},"-- (i.e., paying customers with problems — a useful cohort for churn analysis)\n",[86,338,339,341,343,345],{"class":88,"line":123},[86,340,100],{"class":99},[86,342,212],{"class":103},[86,344,117],{"class":99},[86,346,347],{"class":103}," orders\n",[86,349,350],{"class":88,"line":129},[86,351,352],{"class":99},"INTERSECT\n",[86,354,355,357,359,361,364,366,368,371,374],{"class":88,"line":149},[86,356,100],{"class":99},[86,358,212],{"class":103},[86,360,117],{"class":99},[86,362,363],{"class":103}," support_tickets ",[86,365,209],{"class":99},[86,367,293],{"class":103},[86,369,370],{"class":99},">=",[86,372,373],{"class":107}," '2026-01-01'",[86,375,299],{"class":103},[15,377,378,380,381,384,385,387],{},[24,379,30],{}," is equivalent to a semi-join with ",[24,382,383],{},"EXISTS",", but more readable when\nyou are comparing two already-aggregated sets. Note: MySQL added ",[24,386,30],{},"\nsupport only in version 8.0.31.",[10,389,391],{"id":390},"except-rows-in-one-set-but-not-the-other","EXCEPT — rows in one set but not the other",[15,393,394,396,397,399,400,403,404,407],{},[24,395,34],{}," (Oracle: ",[24,398,38],{},") returns rows from the first query that do not appear\nin the second. Order matters: ",[24,401,402],{},"A EXCEPT B"," ≠ ",[24,405,406],{},"B EXCEPT A",".",[77,409,411],{"className":79,"code":410,"language":81,"meta":82,"style":82},"-- Products that exist in the catalogue but have never been ordered\nSELECT id FROM products\nEXCEPT\nSELECT DISTINCT product_id FROM order_items;\n\n-- Users registered before 2026 who have NOT logged in this year\nSELECT user_id FROM users WHERE created_at \u003C '2026-01-01'\nEXCEPT\nSELECT DISTINCT user_id FROM login_events WHERE occurred_at >= '2026-01-01';\n",[24,412,413,418,430,435,448,452,457,479,483],{"__ignoreMap":82},[86,414,415],{"class":88,"line":89},[86,416,417],{"class":92},"-- Products that exist in the catalogue but have never been ordered\n",[86,419,420,422,425,427],{"class":88,"line":96},[86,421,100],{"class":99},[86,423,424],{"class":103}," id ",[86,426,117],{"class":99},[86,428,429],{"class":103}," products\n",[86,431,432],{"class":88,"line":123},[86,433,434],{"class":99},"EXCEPT\n",[86,436,437,440,443,445],{"class":88,"line":129},[86,438,439],{"class":99},"SELECT DISTINCT",[86,441,442],{"class":103}," product_id ",[86,444,117],{"class":99},[86,446,447],{"class":103}," order_items;\n",[86,449,450],{"class":88,"line":149},[86,451,153],{"emptyLinePlaceholder":152},[86,453,454],{"class":88,"line":156},[86,455,456],{"class":92},"-- Users registered before 2026 who have NOT logged in this year\n",[86,458,459,461,464,466,469,471,473,476],{"class":88,"line":162},[86,460,100],{"class":99},[86,462,463],{"class":103}," user_id ",[86,465,117],{"class":99},[86,467,468],{"class":103}," users ",[86,470,209],{"class":99},[86,472,293],{"class":103},[86,474,475],{"class":99},"\u003C",[86,477,478],{"class":107}," '2026-01-01'\n",[86,480,481],{"class":88,"line":168},[86,482,434],{"class":99},[86,484,485,487,489,491,494,496,499,501,503],{"class":88,"line":174},[86,486,439],{"class":99},[86,488,463],{"class":103},[86,490,117],{"class":99},[86,492,493],{"class":103}," login_events ",[86,495,209],{"class":99},[86,497,498],{"class":103}," occurred_at ",[86,500,370],{"class":99},[86,502,373],{"class":107},[86,504,299],{"class":103},[15,506,507,508,511],{},"The alternative with ",[24,509,510],{},"NOT EXISTS"," is identical in result and often faster because\nit can use an index on the subquery:",[77,513,515],{"className":79,"code":514,"language":81,"meta":82,"style":82},"SELECT id FROM products p\nWHERE NOT EXISTS (\n    SELECT 1 FROM order_items i WHERE i.product_id = p.id\n);\n",[24,516,517,528,541,576],{"__ignoreMap":82},[86,518,519,521,523,525],{"class":88,"line":89},[86,520,100],{"class":99},[86,522,424],{"class":103},[86,524,117],{"class":99},[86,526,527],{"class":103}," products p\n",[86,529,530,532,535,538],{"class":88,"line":96},[86,531,209],{"class":99},[86,533,534],{"class":99}," NOT",[86,536,537],{"class":99}," EXISTS",[86,539,540],{"class":103}," (\n",[86,542,543,546,549,552,555,557,560,562,565,568,571,573],{"class":88,"line":123},[86,544,545],{"class":99},"    SELECT",[86,547,548],{"class":218}," 1",[86,550,551],{"class":99}," FROM",[86,553,554],{"class":103}," order_items i ",[86,556,209],{"class":99},[86,558,559],{"class":218}," i",[86,561,407],{"class":103},[86,563,564],{"class":218},"product_id",[86,566,567],{"class":99}," =",[86,569,570],{"class":218}," p",[86,572,407],{"class":103},[86,574,575],{"class":218},"id\n",[86,577,578],{"class":88,"line":129},[86,579,580],{"class":103},");\n",[10,582,584],{"id":583},"chaining-multiple-set-operations","Chaining multiple set operations",[15,586,587],{},"You can chain several set operations. Without parentheses they are evaluated\nleft to right; use parentheses to control precedence.",[77,589,591],{"className":79,"code":590,"language":81,"meta":82,"style":82},"-- All premium or trial users, minus those who are currently suspended\n(\n    SELECT user_id FROM subscriptions WHERE plan = 'premium'\n    UNION\n    SELECT user_id FROM subscriptions WHERE plan = 'trial'\n)\nEXCEPT\nSELECT user_id FROM user_flags WHERE flag = 'suspended';\n",[24,592,593,598,603,624,629,648,653,657],{"__ignoreMap":82},[86,594,595],{"class":88,"line":89},[86,596,597],{"class":92},"-- All premium or trial users, minus those who are currently suspended\n",[86,599,600],{"class":88,"line":96},[86,601,602],{"class":103},"(\n",[86,604,605,607,609,611,614,616,619,621],{"class":88,"line":123},[86,606,545],{"class":99},[86,608,463],{"class":103},[86,610,117],{"class":99},[86,612,613],{"class":103}," subscriptions ",[86,615,209],{"class":99},[86,617,618],{"class":103}," plan ",[86,620,215],{"class":99},[86,622,623],{"class":107}," 'premium'\n",[86,625,626],{"class":88,"line":129},[86,627,628],{"class":99},"    UNION\n",[86,630,631,633,635,637,639,641,643,645],{"class":88,"line":149},[86,632,545],{"class":99},[86,634,463],{"class":103},[86,636,117],{"class":99},[86,638,613],{"class":103},[86,640,209],{"class":99},[86,642,618],{"class":103},[86,644,215],{"class":99},[86,646,647],{"class":107}," 'trial'\n",[86,649,650],{"class":88,"line":156},[86,651,652],{"class":103},")\n",[86,654,655],{"class":88,"line":162},[86,656,434],{"class":99},[86,658,659,661,663,665,668,670,673,675,678],{"class":88,"line":168},[86,660,100],{"class":99},[86,662,463],{"class":103},[86,664,117],{"class":99},[86,666,667],{"class":103}," user_flags ",[86,669,209],{"class":99},[86,671,672],{"class":103}," flag ",[86,674,215],{"class":99},[86,676,677],{"class":107}," 'suspended'",[86,679,299],{"class":103},[10,681,683],{"id":682},"using-set-operations-for-data-quality-checks","Using set operations for data quality checks",[15,685,686,687,691,692,695],{},"Set operations are excellent for comparing what ",[688,689,690],"em",{},"should"," be in a table versus\nwhat ",[688,693,694],{},"is"," in it:",[77,697,699],{"className":79,"code":698,"language":81,"meta":82,"style":82},"-- Find order IDs that exist in the payments table but not in the orders table\n-- (orphaned payments — a data integrity problem)\nSELECT order_id FROM payments\nEXCEPT\nSELECT id FROM orders;\n\n-- Find products in the warehouse inventory not in the product catalogue\nSELECT sku FROM warehouse_stock\nEXCEPT\nSELECT sku FROM products;\n",[24,700,701,706,711,723,727,738,742,747,759,763],{"__ignoreMap":82},[86,702,703],{"class":88,"line":89},[86,704,705],{"class":92},"-- Find order IDs that exist in the payments table but not in the orders table\n",[86,707,708],{"class":88,"line":96},[86,709,710],{"class":92},"-- (orphaned payments — a data integrity problem)\n",[86,712,713,715,718,720],{"class":88,"line":123},[86,714,100],{"class":99},[86,716,717],{"class":103}," order_id ",[86,719,117],{"class":99},[86,721,722],{"class":103}," payments\n",[86,724,725],{"class":88,"line":129},[86,726,434],{"class":99},[86,728,729,731,733,735],{"class":88,"line":149},[86,730,100],{"class":99},[86,732,424],{"class":103},[86,734,117],{"class":99},[86,736,737],{"class":103}," orders;\n",[86,739,740],{"class":88,"line":156},[86,741,153],{"emptyLinePlaceholder":152},[86,743,744],{"class":88,"line":162},[86,745,746],{"class":92},"-- Find products in the warehouse inventory not in the product catalogue\n",[86,748,749,751,754,756],{"class":88,"line":168},[86,750,100],{"class":99},[86,752,753],{"class":103}," sku ",[86,755,117],{"class":99},[86,757,758],{"class":103}," warehouse_stock\n",[86,760,761],{"class":88,"line":174},[86,762,434],{"class":99},[86,764,765,767,769,771],{"class":88,"line":180},[86,766,100],{"class":99},[86,768,753],{"class":103},[86,770,117],{"class":99},[86,772,773],{"class":103}," products;\n",[10,775,777],{"id":776},"union-all-for-combining-partitioned-data","UNION ALL for combining partitioned data",[15,779,780,781,783],{},"When data is split across multiple tables (e.g., by year), ",[24,782,74],{}," assembles\nthem without a schema change:",[77,785,787],{"className":79,"code":786,"language":81,"meta":82,"style":82},"-- Query across three years of archived order data\nSELECT id, customer_id, total_amount, created_at FROM orders_2024\nUNION ALL\nSELECT id, customer_id, total_amount, created_at FROM orders_2025\nUNION ALL\nSELECT id, customer_id, total_amount, created_at FROM orders_2026\nORDER BY created_at DESC\nLIMIT 100;\n",[24,788,789,794,806,810,821,825,836,845],{"__ignoreMap":82},[86,790,791],{"class":88,"line":89},[86,792,793],{"class":92},"-- Query across three years of archived order data\n",[86,795,796,798,801,803],{"class":88,"line":96},[86,797,100],{"class":99},[86,799,800],{"class":103}," id, customer_id, total_amount, created_at ",[86,802,117],{"class":99},[86,804,805],{"class":103}," orders_2024\n",[86,807,808],{"class":88,"line":123},[86,809,230],{"class":99},[86,811,812,814,816,818],{"class":88,"line":129},[86,813,100],{"class":99},[86,815,800],{"class":103},[86,817,117],{"class":99},[86,819,820],{"class":103}," orders_2025\n",[86,822,823],{"class":88,"line":149},[86,824,230],{"class":99},[86,826,827,829,831,833],{"class":88,"line":156},[86,828,100],{"class":99},[86,830,800],{"class":103},[86,832,117],{"class":99},[86,834,835],{"class":103}," orders_2026\n",[86,837,838,840,842],{"class":88,"line":162},[86,839,53],{"class":99},[86,841,293],{"class":103},[86,843,844],{"class":99},"DESC\n",[86,846,847,850,853],{"class":88,"line":168},[86,848,849],{"class":99},"LIMIT",[86,851,852],{"class":218}," 100",[86,854,299],{"class":103},[15,856,857,858,860],{},"Modern databases handle this more elegantly with table partitioning, but ",[24,859,74],{}," views are a pragmatic workaround when partitioning is not available.",[10,862,864],{"id":863},"recap","Recap",[15,866,867,869,870,872,873,875,876,878,879,882,883,885,886,888],{},[24,868,74],{}," is the workhorse — use it whenever you need to stack rows from\nmultiple queries and do not need deduplication. ",[24,871,26],{}," (with dedup) is for\nwhen duplicates are genuinely unwanted and the cost of sorting is acceptable.\n",[24,874,30],{}," finds the common set; ",[24,877,34],{}," finds the difference. All three\nrequire matching column counts and compatible types. For set membership tests\n(",[24,880,881],{},"NOT IN"," \u002F ",[24,884,510],{},"), the join-based form is usually faster — but ",[24,887,34],{},"\nis more readable for comparing two large, pre-computed result sets.",[890,891,892],"style",{},"html pre.shiki code .sJ8bj, html code.shiki .sJ8bj{--shiki-default:#6A737D;--shiki-dark:#6A737D}html pre.shiki code .szBVR, html code.shiki .szBVR{--shiki-default:#D73A49;--shiki-dark:#F97583}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sZZnC, html code.shiki .sZZnC{--shiki-default:#032F62;--shiki-dark:#9ECBFF}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-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 .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":82,"searchDepth":96,"depth":96,"links":894},[895,896,897,898,899,900,901,902],{"id":12,"depth":96,"text":13},{"id":57,"depth":96,"text":58},{"id":311,"depth":96,"text":312},{"id":390,"depth":96,"text":391},{"id":583,"depth":96,"text":584},{"id":682,"depth":96,"text":683},{"id":776,"depth":96,"text":777},{"id":863,"depth":96,"text":864},"SQL UNION, UNION ALL, INTERSECT, and EXCEPT with real examples — stacking result sets, deduplication costs, and when to use each operator.","medium","md","SQL",{},"\u002Fblog\u002Fsql-set-operations-union-intersect-except","\u002Fsql\u002Fbasics\u002Fset-operations",{"title":5,"description":903},"blog\u002Fsql-set-operations-union-intersect-except","Set Operations","Query Basics","basics","2026-06-20","o_ZbObr3UiHGQDPfpiBR9PHvE7rjOvsnH2GudV0mx-0",1782244089327]