9. SQL - UNION

Jasonยท2026๋…„ 1์›” 12์ผ

SQL

๋ชฉ๋ก ๋ณด๊ธฐ
9/47


SQL UNION & ์ง‘ํ•ฉ ์—ฐ์‚ฐ โ€” ๋ฐ์ดํ„ฐ ํ•ฉ์น˜๊ธฐ

๐Ÿ’ก ์—ฌ๋Ÿฌ ์ฟผ๋ฆฌ ๊ฒฐ๊ณผ๋ฅผ ํ•˜๋‚˜๋กœ ํ•ฉ์น˜๋Š” ์ง‘ํ•ฉ ์—ฐ์‚ฐ์„ ์ •๋ฆฌํ•œ๋‹ค.


๐Ÿค” ์ง‘ํ•ฉ ์—ฐ์‚ฐ์ด ๋ญ์•ผ?

๋‘ ๊ฐœ ์ด์ƒ์˜ SELECT ๊ฒฐ๊ณผ๋ฅผ ํ•ฉ์น˜๋Š” ์—ฐ์‚ฐ.

์—ฐ์‚ฐ์„ค๋ช…์ˆ˜ํ•™
UNIONํ•ฉ์ง‘ํ•ฉ (์ค‘๋ณต ์ œ๊ฑฐ)A โˆช B
UNION ALLํ•ฉ์ง‘ํ•ฉ (์ค‘๋ณต ํฌํ•จ)A + B
INTERSECT๊ต์ง‘ํ•ฉA โˆฉ B
EXCEPT์ฐจ์ง‘ํ•ฉA - B

๐Ÿ“Š ์˜ˆ์ œ ํ…Œ์ด๋ธ”

[employees_kr] (ํ•œ๊ตญ ์ง์›)
| id | name | dept |
|----|------|------|
| 1 | ๊น€์ฒ ์ˆ˜ | ๊ฐœ๋ฐœ |
| 2 | ์ด์˜ํฌ | ๋งˆ์ผ€ํŒ… |
| 3 | ๋ฐ•๋ฏผ์ˆ˜ | ๊ฐœ๋ฐœ |

[employees_jp] (์ผ๋ณธ ์ง์›)
| id | name | dept |
|----|------|------|
| 101 | ็”ฐไธญ | ๊ฐœ๋ฐœ |
| 102 | ้ˆดๆœจ | ์˜์—… |
| 2 | ์ด์˜ํฌ | ๋งˆ์ผ€ํŒ… |


1๏ธโƒฃ UNION โ€” ํ•ฉ์ง‘ํ•ฉ (์ค‘๋ณต ์ œ๊ฑฐ)

SELECT name, dept FROM employees_kr
UNION
SELECT name, dept FROM employees_jp;
namedept
๊น€์ฒ ์ˆ˜๊ฐœ๋ฐœ
์ด์˜ํฌ๋งˆ์ผ€ํŒ…
๋ฐ•๋ฏผ์ˆ˜๊ฐœ๋ฐœ
็”ฐไธญ๊ฐœ๋ฐœ
้ˆดๆœจ์˜์—…

ํฌ์ธํŠธ: ์ด์˜ํฌ๋Š” ์–‘์ชฝ์— ์žˆ์ง€๋งŒ ํ•œ ๋ฒˆ๋งŒ ๋‚˜์˜ด!


2๏ธโƒฃ UNION ALL โ€” ํ•ฉ์ง‘ํ•ฉ (์ค‘๋ณต ํฌํ•จ)

SELECT name, dept FROM employees_kr
UNION ALL
SELECT name, dept FROM employees_jp;
namedept
๊น€์ฒ ์ˆ˜๊ฐœ๋ฐœ
์ด์˜ํฌ๋งˆ์ผ€ํŒ…
๋ฐ•๋ฏผ์ˆ˜๊ฐœ๋ฐœ
็”ฐไธญ๊ฐœ๋ฐœ
้ˆดๆœจ์˜์—…
์ด์˜ํฌ๋งˆ์ผ€ํŒ…

ํฌ์ธํŠธ: ์ด์˜ํฌ๊ฐ€ ๋‘ ๋ฒˆ ๋‚˜์˜ด!

UNION vs UNION ALL

๋น„๊ตUNIONUNION ALL
์ค‘๋ณต์ œ๊ฑฐ์œ ์ง€
์„ฑ๋Šฅ๋А๋ฆผ (์ •๋ ฌ ํ•„์š”)๋น ๋ฆ„
์‚ฌ์šฉ ์‹œ์ ์ค‘๋ณต ์ œ๊ฑฐ ํ•„์š”์„ฑ๋Šฅ ์ค‘์š”

โ†’ ๋Œ€๋ถ€๋ถ„ UNION ALL์ด ๋” ๋น ๋ฆ„! ์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ UNION ์‚ฌ์šฉ.


3๏ธโƒฃ INTERSECT โ€” ๊ต์ง‘ํ•ฉ

SELECT name, dept FROM employees_kr
INTERSECT
SELECT name, dept FROM employees_jp;
namedept
์ด์˜ํฌ๋งˆ์ผ€ํŒ…

ํฌ์ธํŠธ: ์–‘์ชฝ ๋ชจ๋‘์— ์žˆ๋Š” ๊ฒƒ๋งŒ!

์ฃผ์˜: MySQL์€ INTERSECT ๋ฏธ์ง€์›! ๋Œ€์ฒด ๋ฐฉ๋ฒ•:

-- MySQL ๋Œ€์ฒด: INNER JOIN
SELECT DISTINCT a.name, a.dept
FROM employees_kr a
INNER JOIN employees_jp b 
    ON a.name = b.name AND a.dept = b.dept;

-- ๋˜๋Š” IN
SELECT name, dept FROM employees_kr
WHERE (name, dept) IN (SELECT name, dept FROM employees_jp);

4๏ธโƒฃ EXCEPT (MINUS) โ€” ์ฐจ์ง‘ํ•ฉ

-- PostgreSQL, SQL Server
SELECT name, dept FROM employees_kr
EXCEPT
SELECT name, dept FROM employees_jp;

-- Oracle
SELECT name, dept FROM employees_kr
MINUS
SELECT name, dept FROM employees_jp;
namedept
๊น€์ฒ ์ˆ˜๊ฐœ๋ฐœ
๋ฐ•๋ฏผ์ˆ˜๊ฐœ๋ฐœ

ํฌ์ธํŠธ: A์—๋งŒ ์žˆ๊ณ  B์—๋Š” ์—†๋Š” ๊ฒƒ!

MySQL ๋Œ€์ฒด:

SELECT name, dept FROM employees_kr
WHERE (name, dept) NOT IN (SELECT name, dept FROM employees_jp);

-- ๋˜๋Š” LEFT JOIN
SELECT a.name, a.dept
FROM employees_kr a
LEFT JOIN employees_jp b 
    ON a.name = b.name AND a.dept = b.dept
WHERE b.name IS NULL;

5๏ธโƒฃ ์ง‘ํ•ฉ ์—ฐ์‚ฐ ๊ทœ์น™

๊ทœ์น™ 1: ์ปฌ๋Ÿผ ์ˆ˜๊ฐ€ ๊ฐ™์•„์•ผ ํ•จ

-- โŒ ์—๋Ÿฌ: ์ปฌ๋Ÿผ ์ˆ˜ ๋‹ค๋ฆ„
SELECT id, name, dept FROM employees_kr
UNION
SELECT name, dept FROM employees_jp;

-- โœ… ์ปฌ๋Ÿผ ์ˆ˜ ๋งž์ถค
SELECT name, dept FROM employees_kr
UNION
SELECT name, dept FROM employees_jp;

๊ทœ์น™ 2: ์ปฌ๋Ÿผ ํƒ€์ž…์ด ํ˜ธํ™˜๋˜์–ด์•ผ ํ•จ

-- โŒ ์—๋Ÿฌ ๊ฐ€๋Šฅ: ํƒ€์ž… ๋ถˆ์ผ์น˜
SELECT id, name FROM employees_kr   -- id: INT
UNION
SELECT name, dept FROM employees_jp;  -- name: VARCHAR

-- โœ… ํƒ€์ž… ๋งž์ถค
SELECT name, dept FROM employees_kr
UNION
SELECT name, dept FROM employees_jp;

๊ทœ์น™ 3: ์ปฌ๋Ÿผ๋ช…์€ ์ฒซ ๋ฒˆ์งธ SELECT ๊ธฐ์ค€

SELECT name AS ์ด๋ฆ„, dept AS ๋ถ€์„œ FROM employees_kr
UNION
SELECT name AS n, dept AS d FROM employees_jp;
-- ๊ฒฐ๊ณผ ์ปฌ๋Ÿผ๋ช…: ์ด๋ฆ„, ๋ถ€์„œ (์ฒซ ๋ฒˆ์งธ ๊ธฐ์ค€)

6๏ธโƒฃ ORDER BY๋Š” ๋งˆ์ง€๋ง‰์—

-- ORDER BY๋Š” ์ „์ฒด ๊ฒฐ๊ณผ์— ์ ์šฉ
SELECT name, dept FROM employees_kr
UNION ALL
SELECT name, dept FROM employees_jp
ORDER BY name;  -- ๋งˆ์ง€๋ง‰์— ํ•œ ๋ฒˆ๋งŒ!
-- โŒ ์—๋Ÿฌ: ๊ฐ SELECT์— ORDER BY
SELECT name FROM employees_kr ORDER BY name
UNION
SELECT name FROM employees_jp;

-- โœ… ์„œ๋ธŒ์ฟผ๋ฆฌ๋กœ ๊ฐ์‹ธ๋ฉด ๊ฐ€๋Šฅ
(SELECT name FROM employees_kr ORDER BY name LIMIT 10)
UNION
(SELECT name FROM employees_jp ORDER BY name LIMIT 10);

7๏ธโƒฃ ์‹ค์ „ ํ™œ์šฉ ํŒจํ„ด

ํŒจํ„ด 1: ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ” ํ•ฉ์น˜๊ธฐ

-- ์—ฌ๋Ÿฌ ์ง€์—ญ ๋ฐ์ดํ„ฐ ํ†ตํ•ฉ
SELECT 'ํ•œ๊ตญ' AS region, * FROM sales_kr
UNION ALL
SELECT '์ผ๋ณธ' AS region, * FROM sales_jp
UNION ALL
SELECT '๋ฏธ๊ตญ' AS region, * FROM sales_us;

ํŒจํ„ด 2: ์กฐ๊ฑด๋ณ„ ๋ฐ์ดํ„ฐ ํ•ฉ์น˜๊ธฐ

-- ๊ธˆ์•ก ๊ตฌ๊ฐ„๋ณ„๋กœ ๋ผ๋ฒจ ๋ถ™์—ฌ์„œ ํ•ฉ์น˜๊ธฐ
SELECT 'VIP' AS tier, user_id, total_amount
FROM orders
WHERE total_amount >= 1000000

UNION ALL

SELECT '์ผ๋ฐ˜' AS tier, user_id, total_amount
FROM orders
WHERE total_amount < 1000000;

ํŒจํ„ด 3: ์—†๋Š” ๋ฐ์ดํ„ฐ ์ฑ„์šฐ๊ธฐ

-- ์‹ค์ œ ๋ฐ์ดํ„ฐ + ๋นˆ ๋‚ ์งœ ์ฑ„์šฐ๊ธฐ
SELECT date, COUNT(*) as cnt
FROM orders
GROUP BY date

UNION ALL

SELECT date, 0 as cnt
FROM calendar  -- ๋‚ ์งœ ํ…Œ์ด๋ธ”
WHERE date NOT IN (SELECT DISTINCT date FROM orders);

ํŒจํ„ด 4: ํ”ผ๋ฒ— ๋Œ€์‹  ์‚ฌ์šฉ

-- ํ–‰์„ ์—ด๋กœ ๋ณ€ํ™˜ํ•˜๋Š” ํšจ๊ณผ
SELECT 'A์ƒํ’ˆ ๋งค์ถœ' AS metric, SUM(amount) AS value
FROM orders WHERE product = 'A'

UNION ALL

SELECT 'B์ƒํ’ˆ ๋งค์ถœ' AS metric, SUM(amount) AS value
FROM orders WHERE product = 'B';

8๏ธโƒฃ ์„ฑ๋Šฅ ๊ณ ๋ ค

UNION vs UNION ALL ์„ฑ๋Šฅ

-- ๋А๋ฆผ: ์ค‘๋ณต ์ œ๊ฑฐ ์œ„ํ•ด ์ •๋ ฌ ํ•„์š”
SELECT * FROM table_a
UNION
SELECT * FROM table_b;

-- ๋น ๋ฆ„: ๊ทธ๋ƒฅ ๋ถ™์ด๊ธฐ๋งŒ
SELECT * FROM table_a
UNION ALL
SELECT * FROM table_b;

์ธ๋ฑ์Šค ํ™œ์šฉ

-- ๊ฐ SELECT๊ฐ€ ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋จ
-- ๊ฐ๊ฐ์— ๋งž๋Š” ์ธ๋ฑ์Šค๊ฐ€ ์žˆ์œผ๋ฉด ์ข‹์Œ
SELECT * FROM orders_2023 WHERE user_id = 1
UNION ALL
SELECT * FROM orders_2024 WHERE user_id = 1;

๐ŸŽฏ ์‹ค์ „ ๋ฌธ์ œ

๋ฌธ์ œ 1: ๊ธฐ๋ณธ UNION

ํ•œ๊ตญ๊ณผ ์ผ๋ณธ ์ง์› ์ „์ฒด ๋ช…๋‹จ์„ ์ค‘๋ณต ์—†์ด ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
SELECT name, dept FROM employees_kr
UNION
SELECT name, dept FROM employees_jp;

๋ฌธ์ œ 2: ์ถœ์ฒ˜ ํ‘œ์‹œ

์ „์ฒด ์ง์› ๋ช…๋‹จ๊ณผ ํ•จ๊ป˜ ์–ด๋А ์ง€์‚ฌ ์†Œ์†์ธ์ง€ ํ‘œ์‹œํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
SELECT 'KR' AS country, name, dept FROM employees_kr
UNION ALL
SELECT 'JP' AS country, name, dept FROM employees_jp;

๋ฌธ์ œ 3: ์ฐจ์ง‘ํ•ฉ (MySQL)

ํ•œ๊ตญ์—๋งŒ ์žˆ๊ณ  ์ผ๋ณธ์—๋Š” ์—†๋Š” ์ง์›์„ ๊ตฌํ•˜์„ธ์š”. (MySQL)

์ •๋‹ต ๋ณด๊ธฐ
SELECT name, dept 
FROM employees_kr
WHERE (name, dept) NOT IN (
    SELECT name, dept FROM employees_jp
);

-- ๋˜๋Š” LEFT JOIN
SELECT a.name, a.dept
FROM employees_kr a
LEFT JOIN employees_jp b 
    ON a.name = b.name AND a.dept = b.dept
WHERE b.name IS NULL;

๋ฌธ์ œ 4: Top N์”ฉ ํ•ฉ์น˜๊ธฐ

ํ•œ๊ตญ ๋งค์ถœ ์ƒ์œ„ 3๊ฐœ์™€ ์ผ๋ณธ ๋งค์ถœ ์ƒ์œ„ 3๊ฐœ๋ฅผ ํ•ฉ์ณ์„œ ๊ตฌํ•˜์„ธ์š”.

์ •๋‹ต ๋ณด๊ธฐ
(SELECT 'KR' AS country, product, amount 
 FROM sales_kr 
 ORDER BY amount DESC 
 LIMIT 3)
UNION ALL
(SELECT 'JP' AS country, product, amount 
 FROM sales_jp 
 ORDER BY amount DESC 
 LIMIT 3);

๐Ÿ’ก ์ง‘ํ•ฉ ์—ฐ์‚ฐ ์„ ํƒ ๊ฐ€์ด๋“œ

Q: ์ค‘๋ณต ์ œ๊ฑฐ ํ•„์š”?
โ”œโ”€โ”€ Yes โ†’ UNION
โ””โ”€โ”€ No  โ†’ UNION ALL (๋” ๋น ๋ฆ„!)

Q: ์–‘์ชฝ ๋‹ค ์žˆ๋Š” ๊ฒƒ๋งŒ?
โ””โ”€โ”€ INTERSECT (MySQL: INNER JOIN)

Q: ํ•œ์ชฝ์—๋งŒ ์žˆ๋Š” ๊ฒƒ?
โ””โ”€โ”€ EXCEPT/MINUS (MySQL: NOT IN, LEFT JOIN)

๐Ÿ“ ํ•ต์‹ฌ ์ •๋ฆฌ

[ํ•ฉ์ง‘ํ•ฉ]
UNION: ์ค‘๋ณต ์ œ๊ฑฐ (๋А๋ฆผ)
UNION ALL: ์ค‘๋ณต ์œ ์ง€ (๋น ๋ฆ„)

[๊ต์ง‘ํ•ฉ]
INTERSECT (MySQL: JOIN์œผ๋กœ ๋Œ€์ฒด)

[์ฐจ์ง‘ํ•ฉ]
EXCEPT/MINUS (MySQL: NOT IN์œผ๋กœ ๋Œ€์ฒด)

[๊ทœ์น™]
- ์ปฌ๋Ÿผ ์ˆ˜ ๋™์ผ
- ํƒ€์ž… ํ˜ธํ™˜
- ์ปฌ๋Ÿผ๋ช…์€ ์ฒซ ๋ฒˆ์งธ SELECT ๊ธฐ์ค€
- ORDER BY๋Š” ๋งˆ์ง€๋ง‰์— ํ•œ ๋ฒˆ๋งŒ

๐Ÿ”— ๋‹ค์Œ ํฌ์ŠคํŠธ

[SQL ์‹œ๋ฆฌ์ฆˆ #10] BigQuery ๋ฌธ๋ฒ• โ€” ์ฑ„๋„ํ†ก์ด ์“ฐ๋Š” ๊ทธ DB


๐Ÿ“ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ UNION ALL์ด ์ •๋‹ต์ด๋‹ค.
์ค‘๋ณต ์ œ๊ฑฐ๊ฐ€ ์ •๋ง ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ UNION์„ ์“ฐ์ž!

profile
Data Analyst | Thoughts Become Things. ํ•  ์ˆ˜ ์žˆ๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•  ์ˆ˜ ์—†๋‹ค๊ณ  ์ƒ๊ฐํ•˜๋ฉด ํ•  ์ˆ˜ ์—†๋‹ค. | www.linkedin.com/in/๋ช…์ˆ˜-์ œ-7ab843200

0๊ฐœ์˜ ๋Œ“๊ธ€