SQL첫걸음 24강 '상관 서브쿼리' 추가
This commit is contained in:
@@ -33,4 +33,5 @@
|
||||
- [20강. 행 개수 구하기 - COUNT](https://github.com/banjjoknim/TIL/blob/master/SQL%EC%B2%AB%EA%B1%B8%EC%9D%8C/Lecture20.md)
|
||||
- [21강. COUNT 이외의 집계함수](https://github.com/banjjoknim/TIL/blob/master/SQL%EC%B2%AB%EA%B1%B8%EC%9D%8C/Lecture21.md)
|
||||
- [22강. 그룹화 - GROUP BY](https://github.com/banjjoknim/TIL/blob/master/SQL%EC%B2%AB%EA%B1%B8%EC%9D%8C/Lecture22.md)
|
||||
- [23강. 서브쿼리](https://github.com/banjjoknim/TIL/blob/master/SQL%EC%B2%AB%EA%B1%B8%EC%9D%8C/Lecture23.md)
|
||||
- [23강. 서브쿼리](https://github.com/banjjoknim/TIL/blob/master/SQL%EC%B2%AB%EA%B1%B8%EC%9D%8C/Lecture23.md)
|
||||
- [24강. 상관 서브쿼리](https://github.com/banjjoknim/TIL/blob/master/SQL%EC%B2%AB%EA%B1%B8%EC%9D%8C/Lecture24.md)
|
||||
184
SQL첫걸음/Lecture24.md
Normal file
184
SQL첫걸음/Lecture24.md
Normal file
@@ -0,0 +1,184 @@
|
||||
# 24강. 상관 서브쿼리
|
||||
서브쿼리를 사용해 `DELETE` 명령과 `SELECT` 명령을 결합할 수 있었다. 스칼라 서브쿼리가 사용하기 쉬운 서브쿼리란 것도 알았다. 여기서는 서브쿼리의 일종인 `상관 서브쿼리`를 `EXISTS` 술어로 조합시켜서 서브쿼리를 사용하는 방법에 관해 알아본다.
|
||||
|
||||
**`EXISTS`**
|
||||
```
|
||||
EXISTS (SELECT 명령)
|
||||
```
|
||||
|
||||
- `EXISTS` 술어를 사용하면 서브쿼리가 반환하는 결괏값이 있는지를 조사할 수 있다.
|
||||
- 특히 `EXISTS`를 사용하는 경우에는 서브쿼리가 반드시 스칼라 값을 반환할 필요는 없다.
|
||||
- `EXISTS`는 단지 반환된 행이 있는지를 확인해보고 값이 있으면 참, 없으면 거짓을 반환하므로 어떤 패턴이라도 상관없다.
|
||||
|
||||
---
|
||||
|
||||
## 1. EXISTS
|
||||
- 서브쿼리를 사용해 검색할 때 `데이터가 존재하는지 아닌지` 판별하기 위해 조건을 지정할 수도 있다.
|
||||
- 이런 경우 `EXISTS` 술어를 이용해 조사할 수 있다.
|
||||
|
||||
`SELECT * FROM sample551;`
|
||||
|no|a|
|
||||
|-|-|
|
||||
|1|NULL|
|
||||
|2|NULL|
|
||||
|3|NULL|
|
||||
|4|NULL|
|
||||
|5|NULL|
|
||||
|
||||
`SELECT * FROM sample552;`
|
||||
|no2|
|
||||
|-|
|
||||
|3|
|
||||
|5|
|
||||
|
||||
지금부터 sample552에 no 열의 값과 같은 행이 있다면 '있음'이라는 값으로, 행이 없으면 '없음'이라는 값으로 갱신하도록 하겠습니다. 몇 가지 갱신 방법이 있지만 여기서는 `WHERE` 구에 조건을 지정해 '있음'으로 갱신하는 경우와 '없음'으로 갱신하는 경우로 나누어 처리한다.
|
||||
|
||||
```
|
||||
UPDATE sample551 SET a = '있음' WHERE ...
|
||||
UPDATE sample551 SET a = '없음' WHERE ...
|
||||
```
|
||||
|
||||
- 앞의 명령에서 `WHERE` 부분을 살펴보자. 여기서 단순하게 `no = 1`처럼 지정하는 방식으로는 처리할 수 없다.
|
||||
- 서브쿼리를 사용해 sample552에 행이 있는지부터 조사해야 한다.
|
||||
- 그리고 '있음'인 경우, 행이 존재하는 경우에 대해 참으로 설정한다.
|
||||
- 즉, 다음과 같이 `EXISTS`를 사용하면 조건에 맞는 행을 갱신할 수 있다.
|
||||
|
||||
**`EXISTS를 사용해 '있음'으로 갱신하기`**
|
||||
```
|
||||
UPDATE sample551 SET a = '있음' WHERE
|
||||
EXISTS (SELECT * FROM sample552 WHERE no2 = no);
|
||||
```
|
||||
|
||||
`결과`
|
||||
|no|a|
|
||||
|-|-|
|
||||
|1|NULL|
|
||||
|2|NULL|
|
||||
|3|있음|
|
||||
|4|NULL|
|
||||
|5|있음|
|
||||
|
||||
- 서브쿼리 부분이 `UPDATE`의 `WHERE` 구로 행을 검색할 때마다 차례로 실행되는 느낌이다.
|
||||
- 서브쿼리의 `WHERE` 구는 `no2 = no`라는 조건식으로 되어 있다.
|
||||
- no2는 sample552의 열이고 no는 sample551의 열이다. 이때 no가 3과 5일 때만 서브쿼리가 행을 반환한다.
|
||||
- `EXISTS` 술어에 서브쿼리를 지정하면 서브쿼리가 행을 반환할 경우에 참을 돌려준다.
|
||||
- 결과가 한 줄이라도 그 이상이라도 참이 된다. 반면 반환되는 행이 없을 경우에는 거짓이 된다.
|
||||
|
||||
---
|
||||
|
||||
## 2. NOT EXISTS
|
||||
- '없음'의 경우, 행이 존재하지 않는 상태가 참이 되므로 이때는 `NOT EXISTS`를 사용한다.
|
||||
- `NOT`을 붙이는 것으로 값을 부정할 수 있다.
|
||||
|
||||
**`NOT EXISTS를 사용해 '없음'으로 갱신하기`**
|
||||
```
|
||||
UPDATE sample551 SET a = '없음' WHERE
|
||||
NOT EXISTS (SELECT * FROM sample552 WHERE no2 = no);
|
||||
```
|
||||
|
||||
- 이처럼 서브쿼리를 이용해 다른 테이블의 상황을 판단하고 `UPDATE`로 갱신할 수 있었다.
|
||||
- `SELECT` 명령이나 `DELETE` 명령으로도 서브쿼리를 사용할 수 있다.
|
||||
|
||||
---
|
||||
|
||||
## 3. 상관 서브쿼리
|
||||
- 서브쿼리에는 명령 안에 중첩구조로 된 `SELECT` 명령이 존재한다.
|
||||
- 지금부터 '있음'으로 갱신하는 `UPDATE` 명령을 다시 살펴본다.
|
||||
|
||||
```
|
||||
UPDATE sample551 SET a = '있음' WHERE
|
||||
EXISTS (SELECT * FROM sample552 WHERE no2 = no);
|
||||
```
|
||||
|
||||
- `UPDATE` 명령(부모)에서 `WHERE` 구에 괄호로 묶은 부분이 서브쿼리(자식)가 된다.
|
||||
- 부모 명령에서는 sample551를 갱신한다.
|
||||
- 자식 서브쿼리에서는 sample552 테이블의 no2 열 값이 부모의 no 열 값과 일치하는 행을 검색한다.
|
||||
- 이처럼 부모 명령과 자식인 서브쿼리가 특정 관계를 맺는 것을 `상관 서브쿼리`라 부른다.
|
||||
- 앞서 23강에서 설명한 `DELETE`의 경우에는 상관 서브쿼리가 아니다. 상관 서브쿼리가 아닌 단순한 서브쿼리는 단독 쿼리로 실행할 수 있다.
|
||||
|
||||
```
|
||||
DELETE FROM sample54 WHERE a = (SELECT MIN(a) FROM sample54);
|
||||
```
|
||||
|
||||
- 하지만 상관 서브쿼리에서는 부모 명령과 연관되어 처리되기 때문에 서브쿼리 부분만을 따로 떼어내어 실행시킬 수 없다.
|
||||
|
||||
```
|
||||
UPDATE sample551 SET a = '있음' WHERE
|
||||
EXISTS (SELECT * FROM sample552 WHERE no2 = no);
|
||||
|
||||
SELECT * FROM sample552 WHERE no2 = no;
|
||||
-> 에러: no2가 불명확하다.
|
||||
```
|
||||
|
||||
### 테이블명 붙이기
|
||||
- 지금은 sample551과 sample552는 각각 열이 no와 no2로 서로 다르기 때문에 no가 sample551의 열, no2가 sample552의 열인 것을 알 수 있다.
|
||||
- 하지만 만약 두 열이 모두 같은 이름을 가진다면? `WHERE no = no`라고 조건을 지정하면 제대로 동작할까?
|
||||
|
||||
- 사실은 양쪽 테이블 모두 no라는 열로 되어있다면 잘 동작하지 않는다(대부분은 열이 애매하다는 내용의 에러가 발생한다).
|
||||
- 다만 `MySQL`에서는 서브쿼리의 `WHERE no = no`는 `WHERE sample552.no = sample552.no`가 되어 조건식은 항상 참이 된다. 결과적으로 `sample551`의 모든 행은 a열 값이 '있다'로 갱신된다.
|
||||
- 방금 언급한 사례가 정상적으로 처리되도록 하려면 열이 어느 테이블의 것인지 명시적으로 나타낼 필요가 있다.
|
||||
- 테이블 지정은 간단하다. 열명 앞에 `테이블명.`을 붙이기만 하면 된다.
|
||||
- 예를 들어 no 열이 sample551의 것이라면 `sample551.no`라고 지정한다.
|
||||
- 마찬가지로 no2의 경우에는 `sample552.no2`로 지정한다.
|
||||
- 이것으로 sample551과 sample552가 열 이름이 같아도 제대로 구별되므로 문제 없이 실행할 수 있다.
|
||||
|
||||
**`열에 테이블명 붙이기`**
|
||||
```
|
||||
UPDATE sample551 SET a = '있음' WHERE
|
||||
EXISTS (SELECT * FROM sample552 WHERE sample552.no2 = sample551.no);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. IN
|
||||
- 스칼라 값끼리 비교할 때는 `=` 연산자를 사용한다. 다만 집합을 비교할 때는 사용할 수 없다.
|
||||
- `IN`을 사용하면 집합 안의 값이 존재하는지를 조사할 수 있다.
|
||||
- 서브쿼리를 사용할 때 `IN`을 통해 비교하는 경우도 많다.
|
||||
- sample552에는 3과 5라는 값이 존재하는데, 서브쿼리를 사용하지 않고 `WHERE` 구로 간단하게 처리한다면 다음과 같이 조건을 붙일 수 있다.
|
||||
- 이처럼 특정 열의 값이 `무엇 또는(OR) 무엇`이라는 조건식을 지정하는 경우 `IN`을 사용하면 간단하게 지정할 수 있다.
|
||||
|
||||
```
|
||||
WHERE no = 3 OR no = 5;
|
||||
```
|
||||
|
||||
**`IN`**
|
||||
```
|
||||
열명 IN(집합)
|
||||
```
|
||||
|
||||
- `IN`에서는 오른쪽에 집합을 지정한다.
|
||||
- 왼쪽에 지정된 값과 같은 값이 집합 안에 존재하면 참을 반환한다.
|
||||
- 집합은 상수 리스트를 괄호로 묶어 기술한다.
|
||||
- 앞의 `WHERE` 조건식을 `IN`을 사용하도록 수정하면 다음과 같다.
|
||||
- `IN`으로 지정한 값이 3과 5밖에 없어 `OR`로 기술했을 때와 별 차이가 없는 것 같지만, 값을 여러개 지정할 경우에는 조건식이 상당히 깔끔해진다.
|
||||
|
||||
**`IN을 사용해 조건식 기술`**
|
||||
```
|
||||
SELECT * FROM sample551 WHERE no IN (3, 5);
|
||||
```
|
||||
|
||||
- 한편, 집합 부분은 서브쿼리로도 지정할 수 있다. 상수 리스트 부분을 서브쿼리로 바꾸어 보면 다음과 같다.
|
||||
|
||||
**`IN의 오른쪽을 서브쿼리로 지정하기`**
|
||||
```
|
||||
SELECT * FROM sample551 WHERE no IN (SELECT no2 FROM sample552);
|
||||
```
|
||||
|
||||
- 이 같은 경우 서브쿼리는 스칼라 서브쿼리가 될 필요는 없다.
|
||||
- `IN`에는 집합을 지정할 수 있기 때문에 이전에 스칼라 값을 설명할 때 언급한 패턴을 들자면 1과 2의 패턴으로 지정할 필요가 있다.
|
||||
- 반면 3과 4의 패턴에서는 열이 복수로 지정되므로 비교할 수 없다. `IN`의 왼쪽에는 하나의 열이 지정되어 있기 때문이다.
|
||||
- `IN`은 집합 안에 값이 포함되어 있으면 참이 된다. 반면 `NOT IN`으로 지정하면 집합에 값이 포함되어 있지 않을 경우 참이 된다.
|
||||
|
||||
### IN과 NULL
|
||||
- 집계함수에서는 집합 안의 `NULL` 값을 무시하고 처리했다.
|
||||
- `IN`에서는 집합 안에 `NULL` 값이 있어도 무시하지는 않는다.
|
||||
- 다만 `NULL = NULL`을 제대로 계산할 수 없으므로 `IN`을 사용해도 `NULL` 값은 비교할 수 없다.
|
||||
- 즉, `NULL`을 비교할 때는 `IS NULL`을 사용해야 한다. 또한 `NOT IN`의 경우, 집합 안에 `NULL` 값이 있으면 설령 왼쪽 값이 집합 안에 포함되어 있지 않아도 참을 반환하지 않는다.
|
||||
- 그 결과는 `불명(UNKNOWN)`이 된다.
|
||||
- `MySQL`에서 집합에 `NULL`이 포함되어 있는 경우, 조건식 `IN`은 왼쪽 값이 집합에 포함되어 있으면 참을, 그렇지 않으면 `NULL`을 반환한다.
|
||||
- `NOT IN`은 왼쪽 값이 집합에 포함되어 있으면 거짓을, 그렇지 않으면 `NULL`을 반환한다.
|
||||
- 결국 `NOT IN`의 경우 집합에 `NULL`이 포함되어 있다면 그 결괏값은 0건이 된다.
|
||||
- `NULL`을 반환한다는 것은 비교할 수 없다는 것을 의미한다.
|
||||
- 왼쪽의 값이 `NULL`인 경우에도 오른쪽의 값과 관계없이 비교할 수 없으므로 조건식은 참 또는 거짓이 아닌 `NULL`을 반환한다.
|
||||
|
||||
---
|
||||
Reference in New Issue
Block a user