DB

SQL 레벨업 5장 반복문 (2)

Bryan Lee 2022. 4. 25. 15:39

 

16강 SQL에서는 반복을 어떻게 표현할까?

 

 

1. 포인트는 CASE 식과 윈도우 함수

- SQL에서 반복을 대신하는 수단은 바로 CASE 식과 윈도우 함수임 

-> 정확하게 말하면 CASE 식은 절차 지향형 언어에서 말하는 IF-THEN-ELSE 구문에 대응하는 기능임

-> SQL에서 CASE 식과 윈도우 함수를 함께 사용하면 다음과 같음

INSERT INTO Sales2,
SELECT company,
       year,
       sale,
       CASE SIGN(sale-MAX(sale)
                          OVER (PARTITION BY company)
                          ORDER BY year
                          ROWS BETWEEN 1 PRECEDING
                                   AND 1 PRECEDING))
       WHEN 0 THEN '='
       WHEN 1 THEN '+'
       WHEN -1 THEN '-'
       ELSE NULL END AS var
 FROM Sales;

- SELECT 구문의 실행 계획을 살펴보면, 

  일단 Sales 테이블을 풀스캔하고, 윈도우 함수를 정렬로 실행합니다. 

 

- 윈도우 함수로 '직전 회사명'과 '직전 매상'도 검색할 수 있습니다. 

SELECT company,
       year,
       sale,
       MAX(company)
           OVER (PARTITION BY company
                     ORDER BY year
                 ROWS BETWEEN 1 PRECEDING
                          AND 1 PRECEDING) AS pre_company,
       MAX(sale)
           OVER (PARTITION BY company 
                     ORDER BY year
                 ROWS BETWEEN 1 PRECEDING
                          AND 1 PRECEDING) AS pre_sale
FROM Sales;

 

2. 최대 반복 횟수가 정해진 경우

- '인접한 우편 번호 찾기'라는 문제를 풀어볼 수 있습니다.

   우선, 우편 번호를 관리하는 테이블을 만들고, 우편 번호들을 입력합니다. 

CREATE TABLE PostalCode
(pcode CHAR(7),
 district_name VARCHAR(256),
      CONSTRAINT pk_pcode PRIMARY KEY(pcode));
      
INSERT INTO PostalCode VALUES ('4130001', '시즈오카 아타미 이즈미');
INSERT INTO PostalCode VALUES ('4130002', '시즈오카 아타미 이즈산');
INSERT INTO PostalCode VALUES ('4130003', '시즈오카 아타미 아지로');
INSERT INTO PostalCode VALUES ('4130041', '시즈오카 아타미 아오바초');
INSERT INTO PostalCode VALUES ('4380824', '시즈오카 이와타 아카');

- 기본적인 방법은 다음과 같습니다.

-> 일단 우편번호 '4130033'이 테이블에 있는지를 찾습니다.

-> 그런데 테이블에 없다면 이어서 '413003*'가 있는지를 찾습니다.

-> 마찬가지로 없다면 이어서 '41300**'가 있는지를 찾습니다. 

-> 3개의 우편번호가 일치하므로, 이것을 출력하면 됩니다. 

 

- 이 문제의 포인트는 순위입니다. 가장 가까운 우편번호 순위를 0, 가장 먼 우편 번호 순위를 6으로 나타낸다면

  다음과 같습니다. 

SELECT pcode,
       district_name,
       CASE WHEN pcode='4130033' THEN 0
       WHEN pcode LIKE '413003%' THEN 1
       WHEN pcode LIKE '41300%' THEN 2
       WHEN pcode LIKE '4130%' THEN 3
       WHEN pcode LIKE '413%' THEN 4
       WHEN pcode LIKE '41%' THEN 5
       WHEN pcode LIKE '4%' THEN 6
       ELSE NULL END AS rank
FROM PostalCode;

 

- 결론적으로, 가까운 우편번호를 구하는 쿼리는 다음과 같습니다.

SELECT pcode,
       district_name,
WHERE  CASE WHEN pcode='4130033' THEN 0
       		WHEN pcode LIKE '413003%' THEN 1
       	    WHEN pcode LIKE '41300%' THEN 2
            WHEN pcode LIKE '4130%' THEN 3
            WHEN pcode LIKE '413%' THEN 4
            WHEN pcode LIKE '41%' THEN 5
            WHEN pcode LIKE '4%' THEN 6
       ELSE NULL END =
       	   (SELECT MIN(CASE WHEN pcode = '4130033' THEN 0
                            WHEN pcode LIKE '413003%' THEN 1
                            WHEN pcode LIKE '41300%' THEN 2
                            WHEN pcode LIKE '4130%' THEN 3
                            WHEN pcode LIKE '413%' THEN 4
                            WHEN pcode LIKE '41%' THEN 5
                            WHEN pcode LIKE '4%' THEN 6
                            ELSE NULL END)
		   FROM PostalCode);


- 위의 쿼리는 테이블 스캔이 2회 발생합니다.

-> 왜냐하면 바로 순위의 최솟값을 서브쿼리에서 찾기 때문입니다.

-> 고전적인 방법이지만 다음과 같이 윈도우 함수를 사용하면 스캔 횟수를 줄일 수 있습니다. 

SELECT pcode,
       district_name
FROM (SELECT pcode,
             district_name,
             CASE WHEN pcode = '4130033' THEN 0
                  WHEN pcode LIKE '413003%' THEN 1
                  WHEN pcode LIKE '41300%' THEN 2
                  WHEN pcode LIKE '4130%' THEN 3
                  WHEN pcode LIKE '413%' THEN 4
                  WHEN pcode LIKE '41%' THEN 5
                  WHEN pcode LIKE '4%' THEN 6
                  ELSE NULL END AS hit_code,
             MIN(CASE WHEN pcode='4130033' THEN 0
                      WHEN pcode LIKE '413003%' THEN 1
                      WHEN pcode LIKE '41300%' THEN 2
                      WHEN pcode LIKE '4130%' THEN 3
                      WHEN pcode LIKE '413%' THEN 4
                      WHEN pcode LIKE '41%' THEN 5
                      WHEN pcode LIKE '4%' THEN 6
                      ELSE NULL END)
             OVER(ORDER BY CASE WHEN pcode = '4130033' THEN 0
                                WHEN pcode LIKE '413003%' THEN 1
                                WHEN pcode LIKE '41300%' THEN 2
                                WHEN pcode LIKE '4130%' THEN 3
                                WHEN pcode LIKE '413%' THEN 4
                                WHEN pcode LIKE '41%' THEN 5
                                WHEN pcode LIKE '4%' THEN 6
                                ELSE NULL END) AS min_code
             FROM PostalCode) Foo
 WHERE hit_code = min_code;

 

17장 바이어스의 공죄

- 누구나 적어도 한 번 이상은 반복에 의존하는 병에 걸립니다. 

-> 이는 절차 지향형 언어와 파일 시스템의 심리 모델이기 때문입니다. 

-> 프로그래밍을 공부할 때, 다들 절차 지향형 언어로 공부를 시작합니다.

-> 절차 지향형 언어는 대부분 파일을 열고, 레코드를 한 줄씩 읽고 

    비즈니스 로직을 처리하고, 최종적으로 파일을 닫습니다.

-> 이러한 처리 모델이 절차 지향형 프로그래밍의 기본입니다. 

 

- SQL은 서두에 소개한 Codd의 말처럼 '절차 지향형에서의 탈출'을 

  목표로 설계된 언어입니다. 

  DBMS도 내부적으로는 절차 지향형 언어로 만들어져,

  실제로 물리 데이터 접근 등은 모두 절차 지향적인 방법으로 수행됩니다. 

-> 이러한 절차적 계층을 은폐하는 것이 SQL의 이념입니다. 

 

참고

- SQL 레벨업

'DB' 카테고리의 다른 글

SQL 레벨업 6장 결합  (0) 2022.04.25
SQL 레벨업 5장 반복문 (1)  (0) 2022.04.18
SQL 레벨업 4장 집약과 자르기  (1) 2022.04.18
SQL 레벨업 3장 SQL의 조건 분기  (0) 2022.04.11
SQL 레벨업 1장 DBMS 아키텍쳐  (2) 2022.04.04