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
- 윈도우 함수는 인덱스 스캔 1회, 상관 서브쿼리는 테이블 풀 스캔 2회
- 윈도우 함수를 사용하는 것이 성능 측면에서 좋음
기본 키가 여러 개의 필드로 구성되는 경우
- 윈도우 함수를 사용
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
- 다중 필드 비교 사용
- 암묵적인 자료형 변환이 발생하지 않으므로 기본 키 인덱스도 사용 가능
- 필드가 3개 이상일 때도 간단하게 확장 가능
그룹마다 순번을 붙이는 경우
- 윈도우 함수를 사용
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) >= COUNT(*) /2
AND SUM(CASE WHEN W2.weight <= W1.weight THEN 1 ELSE 0 END) >= COUNT(*) /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
ROW_NUMBER 함수로 구한 순번을 2배하고 거기서 COUNT(*)를 빼서 diff를 구함- 중간값에 해당하는
diff는 0부터 2 사이의 값을 가짐 - 이전 방법에 비해 정렬이 1회 감소하므로 성능 향상
순번을 사용한 테이블 분할
- 문제. 단절 구간 찾기
- 집합 지향적 방법 - 집합의 경계선
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이 지원하지 않음
시퀀스 객체
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 설계 특성상 이러한 물리적인 계층의 접근 패턴을 사용자가 바꿀 수 없음
- 순번을 키로 사용할 때의 성능 문제에 대처
- DBMS 내부에서 변화를 주어 제대로 분산할 수 있는 구조(일종의 해시)를 사용
INSERT 구문 자체는 빨라지지만 SELECT 구문의 성능이 나빠질 위험이 있음
- 인덱스에 복잡한 필드를 추가하여 데이터의 분산도를 높임
- 인덱스에 불필요한 필드를 키로 추가하는 것은 좋은 설계가 아님
IDENTITY 필드
- 테이블의 필드로 정의하고, 테이블에
INSERT가 발생할 때마다 자동으로 순번을 붙여주는 기능 - 테이블에 종속적이므로
CACHE나 NOORDER를 지정할 수 없어 성능상 이점 없음
채번 테이블
- 시퀀스 객체나 IDENTITY 필드를 모두 지원하지 않을 경우 사용
- 개선 방법이 없으므로 사용할 이유가 없음