JOIN

JOIN

1. JOIN κ°œλ…

JOIN은 두 개 μ΄μƒμ˜ ν…Œμ΄λΈ”μ„ μ—°κ²°ν•˜μ—¬ 데이터λ₯Ό μ‘°νšŒν•˜λŠ” λ°©λ²•μž…λ‹ˆλ‹€.

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚     users       β”‚     β”‚     orders      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€     β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ id β”‚ name       β”‚     β”‚ id β”‚ user_id    β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€     β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1  β”‚ κΉ€μ² μˆ˜     │◄────│ 1  β”‚ 1          β”‚
β”‚ 2  β”‚ 이영희     │◄────│ 2  β”‚ 1          β”‚
β”‚ 3  β”‚ λ°•λ―Όμˆ˜     β”‚     β”‚ 3  β”‚ 2          β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
         ↑ users.id = orders.user_id

2. μ‹€μŠ΅ ν…Œμ΄λΈ” μ€€λΉ„

-- μ‚¬μš©μž ν…Œμ΄λΈ”
CREATE TABLE users (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(255)
);

-- μ£Όλ¬Έ ν…Œμ΄λΈ”
CREATE TABLE orders (
    id SERIAL PRIMARY KEY,
    user_id INTEGER REFERENCES users(id),
    product_name VARCHAR(200),
    amount NUMERIC(10, 2),
    order_date DATE DEFAULT CURRENT_DATE
);

-- μƒ˜ν”Œ 데이터
INSERT INTO users (name, email) VALUES
('κΉ€μ² μˆ˜', 'kim@email.com'),
('이영희', 'lee@email.com'),
('λ°•λ―Όμˆ˜', 'park@email.com'),
('μ΅œμ§€μ˜', 'choi@email.com');  -- μ£Όλ¬Έ μ—†λŠ” μ‚¬μš©μž

INSERT INTO orders (user_id, product_name, amount) VALUES
(1, 'λ…ΈνŠΈλΆ', 1500000),
(1, '마우슀', 50000),
(2, 'ν‚€λ³΄λ“œ', 100000),
(2, 'λͺ¨λ‹ˆν„°', 300000),
(3, 'ν—€λ“œμ…‹', 150000),
(NULL, 'μ„ λ¬Όμ„ΈνŠΈ', 80000);  -- νšŒμ› μ•„λ‹Œ μ£Όλ¬Έ

3. INNER JOIN

μ–‘μͺ½ ν…Œμ΄λΈ” λͺ¨λ‘μ— μΌμΉ˜ν•˜λŠ” λ°μ΄ν„°λ§Œ λ°˜ν™˜ν•©λ‹ˆλ‹€.

-- κΈ°λ³Έ 문법
SELECT columns
FROM table1
INNER JOIN table2 ON table1.column = table2.column;

-- μ‚¬μš©μžμ™€ μ£Όλ¬Έ 정보 쑰회
SELECT
    users.name,
    users.email,
    orders.product_name,
    orders.amount
FROM users
INNER JOIN orders ON users.id = orders.user_id;

κ²°κ³Ό:

  name  β”‚      email       β”‚ product_name β”‚  amount
────────┼──────────────────┼──────────────┼──────────
 κΉ€μ² μˆ˜ β”‚ kim@email.com    β”‚ λ…ΈνŠΈλΆ       β”‚ 1500000
 κΉ€μ² μˆ˜ β”‚ kim@email.com    β”‚ 마우슀       β”‚   50000
 이영희 β”‚ lee@email.com    β”‚ ν‚€λ³΄λ“œ       β”‚  100000
 이영희 β”‚ lee@email.com    β”‚ λͺ¨λ‹ˆν„°       β”‚  300000
 λ°•λ―Όμˆ˜ β”‚ park@email.com   β”‚ ν—€λ“œμ…‹       β”‚  150000

ν…Œμ΄λΈ” 별칭 μ‚¬μš©

SELECT u.name, o.product_name, o.amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id;

JOIN만 μ“°λ©΄ INNER JOIN

-- INNER μƒλž΅ κ°€λŠ₯
SELECT u.name, o.product_name
FROM users u
JOIN orders o ON u.id = o.user_id;

4. LEFT (OUTER) JOIN

μ™Όμͺ½ ν…Œμ΄λΈ”μ˜ λͺ¨λ“  ν–‰ + 였λ₯Έμͺ½μ—μ„œ μΌμΉ˜ν•˜λŠ” 행을 λ°˜ν™˜ν•©λ‹ˆλ‹€. μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ NULL둜 μ±„μ›Œμ§‘λ‹ˆλ‹€.

SELECT
    u.name,
    o.product_name,
    o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id;

κ²°κ³Ό:

  name  β”‚ product_name β”‚  amount
────────┼──────────────┼──────────
 κΉ€μ² μˆ˜ β”‚ λ…ΈνŠΈλΆ       β”‚ 1500000
 κΉ€μ² μˆ˜ β”‚ 마우슀       β”‚   50000
 이영희 β”‚ ν‚€λ³΄λ“œ       β”‚  100000
 이영희 β”‚ λͺ¨λ‹ˆν„°       β”‚  300000
 λ°•λ―Όμˆ˜ β”‚ ν—€λ“œμ…‹       β”‚  150000
 μ΅œμ§€μ˜ β”‚ NULL         β”‚ NULL      ← μ£Όλ¬Έ μ—†λŠ” μ‚¬μš©μžλ„ 포함

μ£Όλ¬Έ μ—†λŠ” μ‚¬μš©μžλ§Œ μ°ΎκΈ°

SELECT u.name, u.email
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;

5. RIGHT (OUTER) JOIN

였λ₯Έμͺ½ ν…Œμ΄λΈ”μ˜ λͺ¨λ“  ν–‰ + μ™Όμͺ½μ—μ„œ μΌμΉ˜ν•˜λŠ” 행을 λ°˜ν™˜ν•©λ‹ˆλ‹€.

SELECT
    u.name,
    o.product_name,
    o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id;

κ²°κ³Ό:

  name  β”‚ product_name β”‚  amount
────────┼──────────────┼──────────
 κΉ€μ² μˆ˜ β”‚ λ…ΈνŠΈλΆ       β”‚ 1500000
 κΉ€μ² μˆ˜ β”‚ 마우슀       β”‚   50000
 이영희 β”‚ ν‚€λ³΄λ“œ       β”‚  100000
 이영희 β”‚ λͺ¨λ‹ˆν„°       β”‚  300000
 λ°•λ―Όμˆ˜ β”‚ ν—€λ“œμ…‹       β”‚  150000
 NULL   β”‚ μ„ λ¬Όμ„ΈνŠΈ     β”‚   80000   ← νšŒμ› μ—†λŠ” 주문도 포함

6. FULL (OUTER) JOIN

μ–‘μͺ½ ν…Œμ΄λΈ”μ˜ λͺ¨λ“  행을 λ°˜ν™˜ν•©λ‹ˆλ‹€. μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ NULL둜 μ±„μ›Œμ§‘λ‹ˆλ‹€.

SELECT
    u.name,
    o.product_name,
    o.amount
FROM users u
FULL JOIN orders o ON u.id = o.user_id;

κ²°κ³Ό:

  name  β”‚ product_name β”‚  amount
────────┼──────────────┼──────────
 κΉ€μ² μˆ˜ β”‚ λ…ΈνŠΈλΆ       β”‚ 1500000
 κΉ€μ² μˆ˜ β”‚ 마우슀       β”‚   50000
 이영희 β”‚ ν‚€λ³΄λ“œ       β”‚  100000
 이영희 β”‚ λͺ¨λ‹ˆν„°       β”‚  300000
 λ°•λ―Όμˆ˜ β”‚ ν—€λ“œμ…‹       β”‚  150000
 μ΅œμ§€μ˜ β”‚ NULL         β”‚ NULL      ← μ£Όλ¬Έ μ—†λŠ” μ‚¬μš©μž
 NULL   β”‚ μ„ λ¬Όμ„ΈνŠΈ     β”‚   80000   ← νšŒμ› μ—†λŠ” μ£Όλ¬Έ

7. CROSS JOIN

λͺ¨λ“  κ°€λŠ₯ν•œ 쑰합을 λ°˜ν™˜ν•©λ‹ˆλ‹€ (μΉ΄ν‹°μ…˜ κ³±).

-- 색상과 μ‚¬μ΄μ¦ˆ ν…Œμ΄λΈ”
CREATE TABLE colors (name VARCHAR(20));
CREATE TABLE sizes (name VARCHAR(10));

INSERT INTO colors VALUES ('λΉ¨κ°•'), ('νŒŒλž‘'), ('κ²€μ •');
INSERT INTO sizes VALUES ('S'), ('M'), ('L');

-- λͺ¨λ“  μ‘°ν•©
SELECT c.name AS color, s.name AS size
FROM colors c
CROSS JOIN sizes s;

κ²°κ³Ό:

 color β”‚ size
───────┼──────
 λΉ¨κ°•  β”‚ S
 λΉ¨κ°•  β”‚ M
 λΉ¨κ°•  β”‚ L
 νŒŒλž‘  β”‚ S
 νŒŒλž‘  β”‚ M
 νŒŒλž‘  β”‚ L
 κ²€μ •  β”‚ S
 κ²€μ •  β”‚ M
 κ²€μ •  β”‚ L

8. SELF JOIN (자기 쑰인)

같은 ν…Œμ΄λΈ”μ„ 자기 μžμ‹ κ³Ό μ‘°μΈν•©λ‹ˆλ‹€.

-- 직원-κ΄€λ¦¬μž 관계
CREATE TABLE employees (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100),
    manager_id INTEGER REFERENCES employees(id)
);

INSERT INTO employees (name, manager_id) VALUES
('λŒ€ν‘œμ΄μ‚¬', NULL),
('λΆ€μž₯', 1),
('κ³Όμž₯A', 2),
('κ³Όμž₯B', 2),
('사원', 3);

-- 직원과 κ΄€λ¦¬μž 이름 쑰회
SELECT
    e.name AS 직원,
    m.name AS κ΄€λ¦¬μž
FROM employees e
LEFT JOIN employees m ON e.manager_id = m.id;

κ²°κ³Ό:

  직원   β”‚ κ΄€λ¦¬μž
─────────┼─────────
 λŒ€ν‘œμ΄μ‚¬ β”‚ NULL
 λΆ€μž₯     β”‚ λŒ€ν‘œμ΄μ‚¬
 κ³Όμž₯A    β”‚ λΆ€μž₯
 κ³Όμž₯B    β”‚ λΆ€μž₯
 사원     β”‚ κ³Όμž₯A

9. 닀쀑 ν…Œμ΄λΈ” JOIN

3개 μ΄μƒμ˜ ν…Œμ΄λΈ”μ„ μ—°κ²°ν•©λ‹ˆλ‹€.

-- μΉ΄ν…Œκ³ λ¦¬ ν…Œμ΄λΈ” μΆ”κ°€
CREATE TABLE categories (
    id SERIAL PRIMARY KEY,
    name VARCHAR(100)
);

-- μƒν’ˆ ν…Œμ΄λΈ”
CREATE TABLE products (
    id SERIAL PRIMARY KEY,
    category_id INTEGER REFERENCES categories(id),
    name VARCHAR(200),
    price NUMERIC(10, 2)
);

-- μ£Όλ¬Έ 상세 ν…Œμ΄λΈ”
CREATE TABLE order_items (
    id SERIAL PRIMARY KEY,
    order_id INTEGER REFERENCES orders(id),
    product_id INTEGER REFERENCES products(id),
    quantity INTEGER
);

-- 3개 ν…Œμ΄λΈ” JOIN
SELECT
    u.name AS user_name,
    p.name AS product_name,
    c.name AS category_name,
    oi.quantity
FROM users u
JOIN orders o ON u.id = o.user_id
JOIN order_items oi ON o.id = oi.order_id
JOIN products p ON oi.product_id = p.id
JOIN categories c ON p.category_id = c.id;

10. JOIN 쑰건과 WHERE

ON vs WHERE

-- ON: ν…Œμ΄λΈ” μ—°κ²° 쑰건
-- WHERE: κ²°κ³Ό 필터링

-- LEFT JOIN + WHERE
SELECT u.name, o.product_name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.amount > 100000;  -- NULL ν–‰ 제거됨

-- LEFT JOIN + ON에 μΆ”κ°€ 쑰건
SELECT u.name, o.product_name, o.amount
FROM users u
LEFT JOIN orders o ON u.id = o.user_id AND o.amount > 100000;
-- λͺ¨λ“  μ‚¬μš©μž μœ μ§€, 쑰건 λ§žλŠ” 주문만 μ—°κ²°

볡합 JOIN 쑰건

SELECT *
FROM table1 t1
JOIN table2 t2 ON t1.col1 = t2.col1 AND t1.col2 = t2.col2;

11. USING 절

λ™μΌν•œ 컬럼λͺ…μœΌλ‘œ 쑰인할 λ•Œ κ°„λ‹¨ν•˜κ²Œ ν‘œν˜„ν•©λ‹ˆλ‹€.

-- ON μ‚¬μš©
SELECT * FROM orders o
JOIN users u ON o.user_id = u.id;

-- USING μ‚¬μš© (컬럼λͺ…이 같을 λ•Œ)
-- orders.user_id와 users.user_idκ°€ κ°™λ‹€λ©΄:
SELECT * FROM orders
JOIN users USING (user_id);

12. NATURAL JOIN

λ™μΌν•œ μ΄λ¦„μ˜ λͺ¨λ“  컬럼으둜 μžλ™ μ‘°μΈν•©λ‹ˆλ‹€. (ꢌμž₯ν•˜μ§€ μ•ŠμŒ)

-- 같은 μ΄λ¦„μ˜ λͺ¨λ“  컬럼으둜 쑰인
SELECT * FROM orders
NATURAL JOIN users;

-- μ˜λ„μΉ˜ μ•Šμ€ κ²°κ³Όκ°€ λ‚˜μ˜¬ 수 μžˆμ–΄ λͺ…μ‹œμ  ON ꢌμž₯

13. JOIN μ‹œκ°ν™”

INNER JOIN:         LEFT JOIN:          RIGHT JOIN:         FULL JOIN:
    β”Œβ”€β”€β”€β”              β”Œβ”€β”€β”€β”              β”Œβ”€β”€β”€β”              β”Œβ”€β”€β”€β”
   β”Œβ”Όβ”€β”€β”€β”Όβ”            β”Œβ”Όβ”€β”€β”€β”Όβ”            β”Œβ”Όβ”€β”€β”€β”Όβ”            β”Œβ”Όβ”€β”€β”€β”Όβ”
  β”Œβ”Όβ”‚β–ˆβ–ˆβ–ˆβ”‚β”Όβ”          β”Œβ”Όβ”‚β–ˆβ–ˆβ–ˆβ”‚ β”‚          β”‚ β”‚β–ˆβ–ˆβ–ˆβ”‚β”Όβ”          β”Œβ”Όβ”‚β–ˆβ–ˆβ–ˆβ”‚β”Όβ”
  β”‚ β”‚β–ˆβ–ˆβ–ˆβ”‚ β”‚          β”‚β”‚β–ˆβ–ˆβ–ˆβ–ˆβ”‚ β”‚          β”‚ β”‚β–ˆβ–ˆβ–ˆβ–ˆβ”‚β”‚          β”‚β”‚β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ”‚β”‚
  β””β”Όβ”‚β–ˆβ–ˆβ–ˆβ”‚β”Όβ”˜          β””β”Όβ”‚β–ˆβ–ˆβ–ˆβ”‚ β”‚          β”‚ β”‚β–ˆβ–ˆβ–ˆβ”‚β”Όβ”˜          β””β”Όβ”‚β–ˆβ–ˆβ–ˆβ”‚β”Όβ”˜
   β””β”Όβ”€β”€β”€β”Όβ”˜            β””β”Όβ”€β”€β”€β”˜ β”‚          β”‚ β””β”€β”€β”€β”Όβ”˜            β””β”€β”€β”€β”€β”€β”Όβ”˜
    β””β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”˜          β””β”€β”€β”€β”€β”€β”˜              β””β”€β”€β”€β”€β”€β”˜
   A ∩ B               A 전체            B 전체            A βˆͺ B

14. μ‹€μŠ΅ 예제

μ‹€μŠ΅ 1: κΈ°λ³Έ JOIN

-- 1. μ£Όλ¬Έν•œ 적 μžˆλŠ” μ‚¬μš©μžμ™€ μ£Όλ¬Έ 정보
SELECT u.name, o.product_name, o.amount, o.order_date
FROM users u
INNER JOIN orders o ON u.id = o.user_id
ORDER BY o.order_date DESC;

-- 2. 각 μ‚¬μš©μžλ³„ 총 μ£Όλ¬Έ κΈˆμ•‘
SELECT u.name, SUM(o.amount) AS total_amount
FROM users u
INNER JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY total_amount DESC;

μ‹€μŠ΅ 2: OUTER JOIN

-- 1. λͺ¨λ“  μ‚¬μš©μž (μ£Όλ¬Έ μ—¬λΆ€ 관계없이)
SELECT
    u.name,
    COALESCE(SUM(o.amount), 0) AS total_amount,
    COUNT(o.id) AS order_count
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
GROUP BY u.id, u.name
ORDER BY total_amount DESC;

-- 2. μ£Όλ¬Έν•˜μ§€ μ•Šμ€ μ‚¬μš©μž μ°ΎκΈ°
SELECT u.name, u.email
FROM users u
LEFT JOIN orders o ON u.id = o.user_id
WHERE o.id IS NULL;

-- 3. νšŒμ›μ΄ μ•„λ‹Œ μ£Όλ¬Έ μ°ΎκΈ°
SELECT o.id, o.product_name, o.amount
FROM users u
RIGHT JOIN orders o ON u.id = o.user_id
WHERE u.id IS NULL;

μ‹€μŠ΅ 3: 볡합 쑰건 JOIN

-- 1. 100λ§Œμ› 이상 μ£Όλ¬Έν•œ μ‚¬μš©μž
SELECT DISTINCT u.name, u.email
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.amount >= 1000000;

-- 2. 졜근 30일 이내 μ£Όλ¬Έν•œ μ‚¬μš©μž
SELECT DISTINCT u.name
FROM users u
INNER JOIN orders o ON u.id = o.user_id
WHERE o.order_date >= CURRENT_DATE - INTERVAL '30 days';

μ‹€μŠ΅ 4: μ—¬λŸ¬ ν…Œμ΄λΈ” JOIN

-- μΉ΄ν…Œκ³ λ¦¬ β†’ μƒν’ˆ β†’ μ£Όλ¬Έ μ—°κ²°
SELECT
    c.name AS category,
    p.name AS product,
    u.name AS customer,
    oi.quantity,
    p.price * oi.quantity AS subtotal
FROM categories c
JOIN products p ON c.id = p.category_id
JOIN order_items oi ON p.id = oi.product_id
JOIN orders o ON oi.order_id = o.id
JOIN users u ON o.user_id = u.id
ORDER BY c.name, p.name;

15. μ„±λŠ₯ 고렀사항

인덱슀 ν™œμš©

-- μ™Έλž˜ν‚€ μ»¬λŸΌμ— 인덱슀 생성
CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_order_items_order_id ON order_items(order_id);
CREATE INDEX idx_order_items_product_id ON order_items(product_id);

ν•„μš”ν•œ 컬럼만 SELECT

-- λ‚˜μœ 예
SELECT * FROM users u JOIN orders o ON u.id = o.user_id;

-- 쒋은 예
SELECT u.name, o.product_name, o.amount
FROM users u JOIN orders o ON u.id = o.user_id;

EXPLAIN으둜 μ‹€ν–‰ κ³„νš 확인

EXPLAIN SELECT u.name, o.product_name
FROM users u
JOIN orders o ON u.id = o.user_id;

λ‹€μŒ 단계

07_Aggregation_and_Grouping.mdμ—μ„œ 집계 ν•¨μˆ˜μ™€ GROUP BYλ₯Ό λ°°μ›Œλ΄…μ‹œλ‹€!

to navigate between lessons