SQL의 순서
레코드에 순번 붙이기
기본 키가 한 개의 필드일 경우
- 윈도우 함수를 사용
SELECT student_id, ROW_NUMBER() OVER (ORDER BY student_id) AS seq FROM Weights
- 상관 서브쿼리를 사용
SELECT student_id, (SELECT COUNT(*) FROM Weights W2 WHERE W2.student_id <= W1.student_id) AS seq FROM Weights W1
- 윈도우 함수를 사용
SELECT student_id, ROW_NUMBER() OVER (ORDER BY class, student_id) AS seq FROM Weights2
- 상관 서브쿼리를 사용
SELECT class, student_id, (SELECT COUNT(*) FROM Weights2 W2 WHERE (W2.class, W2.student_id) <= (W1.class, W1.student_id) ) AS seq FROM Weights2 W1
- 윈도우 함수를 사용
SELECT class, student_id, ROW_NUMBER() OVER (PARTITION BY class ORDER BY student_id) AS seq FROM Weights2
- 상관 서브쿼리를 사용
SELECT class, student_id, (SELECT COUNT(*) FROM Weights2 W2 WHERE W2.class = W1.class AND W2.student_id = W1.student_id) AS seq FROM Weights2 W1
순번과 갱신
- 테이블에 순번 필드(seq)를 생성
- 윈도우 함수를 사용
UPDATE Weights3 SET seq = (SELECT seq FROM (SELECT class, student_id, ROW_NUMBER() OVER (PARTITION BY class ORDER BY student_id) AS seq) FROM Weights3) SeqTbl WHERE Weights3.class = SeqTbl.class AND Weights3.student_id = SeqTbl.student_id)
- 상관 서브쿼리를 사용
UPDATE Weights3 SET seq = (SELECT COUNT(*) FROM Weights3 W2 WHERE W2.class = Weights3.class AND W2.student_id <= Weights3.student_id)
레코드에 순번 붙이기 응용
중앙값 구하기
- 집합 지향적 방법
SELECT AVG(weight) FROM (SELECT W1.weight FROM Weights W1, Weights W2 GROUP BY W1.weight HAVING SUM(CASE WHEN W2.weight >= W1.weight THEN 1 ELSE 0 END) >= COOUNT(*) /2 AND SUM(CASE WHEN W2.weight <= W1.weight THEN 1 ELSE 0 END) >= COOUNT(*) /2) TMP
CASE
식에 표현한 두 개의 특성 함수로 모집합Weights
를 상위 집합과 하위 집합으로 분할- 코드가 복잡해서 무엇을 하고 있는지 한 번에 이해하기 어려움
- 자기 결합을 수행하므로 성능이 나쁨
- 절차 지향적 방법 1 - 세계의 중심을 향해
SELECT AVG(weight) FROM (SELECT W1.weight ROW_NUMBER() OVER (ORDER BY weight ASC, student_id ASC) hi, ROW_NUMBER() OVER (ORDER BY weight DESC, student_id DESC) lo, FROM Weights) TMP WHERE hi IN (lo, lo+1, lo-1)
IN
구문으로 홀수인 경우, 짝수인 경우를 한꺼번에 처리- 비슷한 기능의
RANK
또는DENSE_RANK
는 중복이 발생할 수 있으므로 연속성과 유일성을 갖게 만들 수 없음 - Weights 테이블에 대한 접근이 1회로 감소, 정렬 2회
ORDER BY
의 정렬키에 기본 키인 student_id를 포함해야 함- 포함하지 않을 경우 체중이 같은 학생이 여러 명 있을 때
NULL
이 발생할 가능성이 있음
- 포함하지 않을 경우 체중이 같은 학생이 여러 명 있을 때
- 절차 지향적 방법 2 - 2 빼기 1은 1
SELECT AVG(weight) AS median FROM (SELECT weight, 2 * ROW_NUMBER() OVER (ORDER BY weight) - COUNT(*) OVER() AS diff FROM Weights) TMP WHERE diff BETWEEN 0 AND 2
- 문제. 단절 구간 찾기
- 집합 지향적 방법 - 집합의 경계선
SELECT (N1.num + 1) AS gap_start, '~', (MIN(N2.num) - 1) AS gap_end FROM Numbers N1 INNER JOIN Numbers N2 ON N2.num > N1.num GROUP BY N1.num HAVING (N1.num + 1) < MIN(N2.num)
- 간단하지만 자기 결합을 사용하여 비용이 높고 실행 계획이 불안정함
- 절차 지향적 방법 - ‘다음 레코드’와 비교
SELECT num + 1 AS gap_start, '~', (num + diff - 1) AS gap_end FROM (SELECT num, MAX(num) OVER(ORDER BY num ROWS BETWEEN 1 FOLLOWING AND 1 FOLLWING) - num) FROM numbers) TMP(num, diff) WHERE diff <> 1
- 윈도우 함수로 ‘현재 레코드의 다음 레코드’를 구하고 두 레코드의 숫자 차이를
diff
필드에 저장 - 결합을 사용하지 않으므로 성능이 안정적
- 윈도우 함수로 ‘현재 레코드의 다음 레코드’를 구하고 두 레코드의 숫자 차이를
- 집합 지향적 방법 - 집합의 경계선
테이블에 존재하는 시퀀스 구하기
- 집합 지향적 방법 - 다시, 집합의 경계선
SELECT MIN(num) AS low, '~', MAX(num) AS high FROM (SELECT N1.num, COUNT(N2.num) - N1.num FROM Numbers N1 INNER JOIN Numbers N2 ON N2.num <= N1.num GROUP BY N1.num) N(num, gp) GROUP BY gp
- 절차 지향적 방법 - 다시 ‘다음 레코드 하나’와 비교
SELECT low, high FROM (SELECT low, CASE WHEN high IS NULL THEN MIN(high) OVER (ORDER BY seq ROWS BETWEEN CURRENT ROW AND UNBOUNDED FOLLOWING) ELSE high END AS high FROM (SELECT CASE WHEN COALESCE(prev_diff, 0) <> 1 THEN num ELSE NULL END AS low, CASE WHEN COALESCE(next_diff, 0) <> 1 THEN num ELSE NULL END AS high, seq FROM (SELECT num, MAX(num) OVER(ORDER BY num ROWS BETWEEN 1 PRECEDING AND 1 PRECEDING) AS prev_diff, ROW_NUMBER() OVER (ORDER BY num) AS seq FROM Numbers) TMP1 ) TMP2) TMP3 WHERE low IS NOT NULL
- TMP1은 현재 레코드와 전후의 레코드에 있는 num 값의 차이를 구함
- max_diff는 다음 레코드의 num에서 현재 레코드의 num을 뺀 값이고, prev_diff는 현재 레코드의 num에서 이전 레코드의 num을 뺀 값
- next_diff나 prev_diff가 1보다 크다는 것은 단절된 부분이 있음을 의미
- TMP3에서 동일한 레코드에 low와 high 필드가 존재하지 않는 경우를 정리
WHERE low IS NOT NULL
로 불필요한 레코드 제거- 서브쿼리 스캔의 비용은 테이블의 크기에 비례하므로 성능 측면에서 집합 지향 쿼리에 비해 좋다고 단언할 수는 없음
시퀀스 객체, IDENTITY 필드, 채번 테이블
- 시퀀스 객체, IDENTITY 필드는 최대한 사용하지 않고 꼭 필요한 부분에만 사용
- IDENTITY 필드보다는 시퀀스 객체를 활용
- 시퀀스 객체는 MySQL이 지원하지 않고, IDENTITY 필드는 Orcale이 지원하지 않음
시퀀스 객체
```sql CREATE SEQUENCE testseq START WITH 1 INCREMENT BY 1 MAXVALUE 1000 MINVALUE 1 CYCLE
INSERT INTO HogeTbl VALUES(NEXT VALUE FOR nextval, ‘a’, ‘b’, …) ```
- 시퀀스 객체의 문제점
- 표준화가 늦어서 구현에 따라 구문이 달라 이식성이 없고, 사용할 수 없는 구현도 있음
- 시스템에서 자동으로 생성되는 값이므로 실제 엔티티 속성이 아님
- 성능적인 문제 발생
- 시퀀스 객체로 발생하는 성능 문제
- 시퀀스 객체의 로직에 의존하는 문제
- 시퀀스 객체는 유일성, 연속성, 순서성을 만족
- 동시 실행을 제어하기 위하여 락을 사용하므로 성능 저하 및 오버헤드
- 시퀀스 객체의 로직에 의존하는 문제
- 시퀀스 객체로 발생하는 성능 문제의 대처
CACHE
: 새로운 값이 필요할 때마다 메모리에 읽어들일 필요가 있는 값의 수를 설정- 시스템 장애 시 연속성 보장 불가능
NOORDER
: 순서성을 담보하지 않으므로 오버헤드 감소- 순서성이 필요한 경우 사용할 수 없음
- 순번을 키로 사용할 때의 성능 문제
- 순번처럼 비슷한 데이터를 연속적으로
INSERT
하면 물리적으로 같은 영역에 저장됨 - 저장소의 특정 물리적 블록에만 I/O 부하가 커지므로 성능 악화 발생 (Hot Spot, Hot Block)
- RDB 설계 특성상 이러한 물리적인 계층의 접근 패턴을 사용자가 바꿀 수 없음
- 순번처럼 비슷한 데이터를 연속적으로
- 순번을 키로 사용할 때의 성능 문제에 대처
- 테이블의 필드로 정의하고, 테이블에
INSERT
가 발생할 때마다 자동으로 순번을 붙여주는 기능 - 테이블에 종속적이므로
CACHE
나NOORDER
를 지정할 수 없어 성능상 이점 없음채번 테이블
- 시퀀스 객체나 IDENTITY 필드를 모두 지원하지 않을 경우 사용
- 개선 방법이 없으므로 사용할 이유가 없음