Section 2. 쿼리 와 조인

1. 비상관/ 상관 서브 쿼리

우선 서브 쿼리란 () 으로 쿼리 안에 쿼리를 부르는 기능이다.

1
2
3
4
5
6
7
SELECT
CategoryID, CategoryName, Description
FROM Categories
WHERE
CategoryID = ANY
(SELECT CategoryID FROM Products
WHERE Price > 50);

이 예제에 IN, ALL, ANY 연산자가 있는데 ‘= ANY’ 은 IN 와 같은 효과 이고
서브 쿼리에 자주 쓰인는 연산자 이다.

한개 테이블에 대해서 쿼리 할때 비상관 이라 하고 안과 밖에 쿼리가 서로 관계 해서 쿼리 하는 걸 상관 쿼리 라고 한다. 아래 예제 처럼 table 2개를 연관 하는 경우다.

1
2
3
4
5
6
7
SELECT
ProductID, ProductName,
(
SELECT CategoryName FROM Categories C
WHERE C.CategoryID = P.CategoryID
) AS CategoryName
FROM Products P;

다수의 예제다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
SELECT
CategoryID, CategoryName,
(
SELECT MAX(Price) FROM Products P
WHERE P.CategoryID = C.CategoryID
) AS MaximumPrice,
(
SELECT AVG(Price) FROM Products P
WHERE P.CategoryID = C.CategoryID
) AS AveragePrice
FROM Categories C;

SELECT
ProductID, ProductName, CategoryID, Price
-- ,(SELECT AVG(Price) FROM Products P2
-- WHERE P2.CategoryID = P1.CategoryID)
FROM Products P1
WHERE Price < (
SELECT AVG(Price) FROM Products P2
WHERE P2.CategoryID = P1.CategoryID
);

SELECT
CategoryID, CategoryName
-- ,(SELECT MAX(P.Price) FROM Products P
-- WHERE P.CategoryID = C.CategoryID
-- ) AS MaxPrice
FROM Categories C
WHERE EXISTS (
SELECT * FROM Products P
WHERE P.CategoryID = C.CategoryID
AND P.Price > 80
);

2. JOIN - 여러 테이블 조립하기

2개 테이블을 연관 해서 가져온다. 구문이 JOIN, ON 들어간다.
제일 기본이 Inner Join이라고 하고 양쪽 모두에 값이 있는 행만 반환 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
SELECT C.CategoryID, C.CategoryName, P.ProductName
FROM Categories C
JOIN Products P
ON C.CategoryID = P.CategoryID;
-- ambiguous 주의!

SELECT
CONCAT(
P.ProductName, ' by ', S.SupplierName
) AS Product,
S.Phone, P.Price
FROM Products P
JOIN Suppliers S
ON P.SupplierID = S.SupplierID
WHERE Price > 50
ORDER BY ProductName;

여러 테이블을 조인 할수 있고 GROUP BY 도 가능하다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
SELECT 
C.CategoryID, C.CategoryName,
P.ProductName,
O.OrderDate,
D.Quantity
FROM Categories C
JOIN Products P
ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
ON P.ProductID = D.ProductID
JOIN Orders O
ON O.OrderID = D.OrderID;

SELECT
C.CategoryName, P.ProductName,
MIN(O.OrderDate) AS FirstOrder,
MAX(O.OrderDate) AS LastOrder,
SUM(D.Quantity) AS TotalQuantity
FROM Categories C
JOIN Products P
ON C.CategoryID = P.CategoryID
JOIN OrderDetails D
ON P.ProductID = D.ProductID
JOIN Orders O
ON O.OrderID = D.OrderID
GROUP BY C.CategoryID, P.ProductID;

SELF JOIN 이라고 한개의 테이블로도 가능 하다

1
2
3
4
5
6
7
8
SELECT
E1.EmployeeID, CONCAT_WS(' ', E1.FirstName, E1.LastName) AS Employee,
E2.EmployeeID, CONCAT_WS(' ', E2.FirstName, E2.LastName) AS NextEmployee
FROM Employees E1
JOIN Employees E2
ON E1.EmployeeID + 1 = E2.EmployeeID;

-- 1번의 전, 마지막 번호의 다음은?

값이 없는 행을 가져 오고 싶을 때는 쓰는 것이 외부 조인이고 LEFT/RIGHT OUTER JOIN
왼쪽 NULL 값도 보여줄때 LEFT, 오른쪽 은 RIGHT 이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
SELECT
C.CustomerName, S.SupplierName,
C.City, C.Country
FROM Customers C
LEFT JOIN Suppliers S
ON C.City = S.City AND C.Country = S.Country;
-- LEFT를 RIGHT로 바꿔서도 실행해 볼 것

SELECT
IFNULL(C.CustomerName, '-- NO CUSTOMER --'),
IFNULL(S.SupplierName, '-- NO SUPPLIER --'),
IFNULL(C.City, S.City),
IFNULL(C.Country, S.Country)
FROM Customers C
LEFT JOIN Suppliers S
ON C.City = S.City AND C.Country = S.Country;

-- LEFT를 RIGHT로 바꿔서도 실행해 볼 것

IFNULL 활용해서 없는 값에 원하는 구문 넣을수 있다.

조건 없이 모든 조합을 해 보는 것을 CROSS JOIN 이라고 한다.

1
2
3
4
5
SELECT
E1.LastName, E2.FirstName
FROM Employees E1
CROSS JOIN Employees E2
ORDER BY E1.EmployeeID;

3. UNION - 집합으로 다루기

합집합: UNION, UNION ALL

1
2
3
4
5
6
7
8
9
10
11
12
13
14
SELECT CustomerName AS Name, City, Country, 'CUSTOMER'
FROM Customers
UNION
SELECT SupplierName AS Name, City, Country, 'SUPPLIER'
FROM Suppliers
ORDER BY Name;

SELECT CategoryID AS ID FROM Categories
WHERE CategoryID > 4
UNION
SELECT EmployeeID AS ID FROM Employees
WHERE EmployeeID % 2 = 0;

-- UNION ALL로 바꿔볼 것

교집합

1
2
3
4
5
6
SELECT CategoryID AS ID
FROM Categories C, Employees E
WHERE
C.CategoryID > 4
AND E.EmployeeID % 2 = 0
AND C.CategoryID = E.EmployeeID;

차집합

1
2
3
4
5
6
7
8
9
SELECT CategoryID AS ID
FROM Categories
WHERE
CategoryID > 4
AND CategoryID NOT IN (
SELECT EmployeeID
FROM Employees
WHERE EmployeeID % 2 = 0
);

대칭차집합: 교집합만 제외

1
2
3
4
5
6
7
8
SELECT ID FROM (
SELECT CategoryID AS ID FROM Categories
WHERE CategoryID > 4
UNION ALL
SELECT EmployeeID AS ID FROM Employees
WHERE EmployeeID % 2 = 0
) AS Temp
GROUP BY ID HAVING COUNT(*) = 1;