SELECT 처리 순서·조건·NULL·집계·정렬
SELECT의 작성 순서와 논리적인 처리 순서는 다르다. WHERE는 개별 행을, HAVING은 집계된 그룹을 거르며 NULL 비교에는 UNKNOWN이 포함된다. COUNT·SUM·AVG의 NULL 처리와 GROUP BY·ROLLUP·CUBE의 집계 수준을 구분해야 한다. DISTINCT는 출력 열 조합의 중복을 제거하며 ORDER BY만이 결과 정렬을 요구한다.
SELECT는 쓰는 순서와 결과를 만드는 순서가 다르다
SELECT 문은 필요한 행을 고르고, 그룹을 만들고, 출력 열을 계산한 뒤 정렬하는 조회 문장이다. SQL을 적는 순서만 따라가면 WHERE와 HAVING, 출력 별칭의 사용 시점, 집계 결과가 만들어지는 위치를 혼동하기 쉽다.
기본적인 단일 SELECT 질의 블록은 보통 다음 순서로 작성한다. 아래 구조표에서 대괄호는 선택적으로 사용할 수 있는 절을 뜻하며, 실행용 SQL 문장이 아니라 절의 위치를 보여 주는 표기다.
SELECT [DISTINCT] 출력 식
FROM 원본
[WHERE 행 조건]
[GROUP BY 그룹 식]
[HAVING 그룹 조건]
[ORDER BY 정렬 식]
[OFFSET / FETCH / LIMIT 등 행 제한]
그러나 결과의 의미를 추적할 때는 다음 학습용 논리 순서를 사용한다.
FROM / JOIN
→ WHERE
→ GROUP BY와 집계
→ HAVING
→ SELECT 출력 식
→ DISTINCT
→ ORDER BY
→ OFFSET / FETCH / LIMIT 등 행 제한
| 단계 | 핵심 역할 | 단계가 끝난 뒤의 결과 | 시험에서 보는 단서 |
|---|---|---|---|
FROM·JOIN | 읽을 원본과 행의 결합 관계를 정한다. | 조회 대상 행 집합 | 테이블 별칭, 조인 조건 |
WHERE | 그룹화 전 각 행의 조건을 평가한다. | 조건이 TRUE인 행만 남음 | 일반 열 조건, IS NULL |
GROUP BY·집계 | 같은 그룹 키의 행을 묶고 집계값을 계산한다. | 그룹당 한 행의 후보 | COUNT, SUM, AVG |
HAVING | 만들어진 그룹의 조건을 평가한다. | 조건이 TRUE인 그룹만 남음 | 집계 결과 조건 |
SELECT | 출력할 열과 식을 계산하고 별칭을 만든다. | 결과 열 구성 | 계산식, 열 별칭 |
DISTINCT | 선택된 결과 행의 중복을 제거한다. | 고유한 결과 행 | 열 한 개가 아닌 출력 조합 |
ORDER BY | 최종 결과의 표시 순서를 정한다. | 정렬된 결과 | ASC, DESC, 다중 키 |
| 행 제한 | 정렬된 결과에서 일부 행만 반환한다. | 최종 행 범위 | FETCH, LIMIT, TOP |
이 순서는 개념과 이름 해석을 위한 모델이다. 옵티마이저는 같은 결과를 보장하는 범위에서 필터 위치, 조인 순서, 접근 경로 등을 바꿀 수 있으므로 논리 순서를 실제 물리 실행 순서로 단정하면 안 된다. WITH, 집합 연산, 윈도 함수가 포함된 복합 질의에는 추가 단계가 있지만, 여기서는 기본 단일 질의 블록에 집중한다.
출력 별칭을 WHERE에서 바로 쓰기 어려운 이유
SELECT의 출력 별칭은 WHERE보다 뒤에서 만들어진다. 따라서 같은 질의 블록의 WHERE에서 그 별칭을 사용하는 문장은 표준적이고 이식 가능한 SQL이 아니며 여러 DBMS에서 오류가 난다.
-- 이식성이 낮거나 오류가 나는 형태
SELECT salary * 12 AS annual_salary
FROM employee
WHERE annual_salary >= 60000;
-- 같은 질의 블록에서는 식을 직접 사용
SELECT salary * 12 AS annual_salary
FROM employee
WHERE salary * 12 >= 60000
ORDER BY annual_salary DESC;
출력 별칭은 논리적으로 뒤에 오는 ORDER BY에서 사용할 수 있는 경우가 일반적이다. GROUP BY·HAVING에서 별칭을 허용하는 범위는 DBMS마다 다르므로 시험이나 실무에서는 제품 전제를 확인한다.
SELECT 논리 처리 흐름
FROM/JOIN → WHERE → GROUP BY·집계 → HAVING → SELECT → DISTINCT → ORDER BY → 행 제한 순으로 결과를 해석한다. 윈도 함수는 행·그룹 필터 뒤에 계산하므로 같은 질의의 WHERE에서 직접 필터하지 않는다. 논리 순서가 물리 실행 순서를 고정하지는 않는다.
WHERE 조건: 행마다 TRUE·FALSE·UNKNOWN을 판정한다
WHERE는 FROM과 JOIN이 만든 각 행에 검색 조건을 적용한다. 조건 결과가 TRUE인 행만 다음 단계로 전달되고, FALSE와 UNKNOWN인 행은 제외된다.
| 조건 종류 | 대표 구문 | 의미 | 주의점 |
|---|---|---|---|
| 비교 | =, <>, <, <=, >, >= | 두 값을 비교 | NULL과 일반 비교하면 UNKNOWN |
| 범위 | BETWEEN a AND b | a 이상 b 이하 | 양 끝값을 모두 포함 |
| 목록 | IN (값1, 값2, ...) | 목록 중 하나와 일치 | NULL이 섞인 부정 조건에 주의 |
| 패턴 | LIKE | 문자열 패턴과 일치 | %는 0자 이상, _는 정확히 1자 |
| NULL 검사 | IS NULL, IS NOT NULL | 값의 부재 여부를 검사 | = NULL을 사용하지 않음 |
| 논리 결합 | NOT, AND, OR | 조건을 부정·결합 | 괄호로 업무 의도를 명시 |
논리 연산자의 우선순위
일반적인 논리 연산자 우선순위는 다음과 같다.
NOT → AND → OR
따라서 다음 조건은 AND가 먼저 결합된다.
WHERE status = 'ACTIVE'
AND salary >= 4500
OR department = '영업'
이는 다음과 같은 뜻이다.
WHERE (status = 'ACTIVE' AND salary >= 4500)
OR department = '영업'
영업 부서도 반드시 재직 상태여야 한다면 괄호의 위치를 바꿔야 한다.
WHERE status = 'ACTIVE'
AND (salary >= 4500 OR department = '영업')
우선순위를 암기했더라도 복합 조건에는 괄호를 사용해야 선택지의 의도와 실제 조건을 안정적으로 맞출 수 있다. 또한 우선순위는 식이 묶이는 규칙이고, 옵티마이저가 조건을 실제로 평가하는 시간 순서를 보장하는 규칙은 아니다.
BETWEEN과 날짜·시간 경계
x BETWEEN a AND b는 x >= a AND x <= b와 같은 포함 범위다. 숫자 범위에서는 편리하지만 시각 자료형에서 하루의 마지막 순간을 임의로 적으면 정밀도 차이로 행이 빠질 수 있다. 하루 전체를 조회할 때는 다음처럼 시작 이상, 다음 경계 미만의 반열린 구간을 사용하는 방식이 명확하다.
WHERE created_at >= :start_at
AND created_at < :next_day_at
매개변수 표기와 날짜 계산 방식은 DBMS·드라이버에 따라 달라진다.
LIKE의 와일드카드와 이스케이프
%: 문자가 없거나 여러 개인 부분과 일치한다._: 정확히 한 문자와 일치한다.- 와일드카드 문자 자체를 찾으려면
ESCAPE문자를 지정할 수 있다.
-- 문자열 안에 실제 밑줄(_)이 있는 행 검색
WHERE code LIKE '%#_%' ESCAPE '#'
대소문자 구분과 한글·영문 정렬·비교 방식은 DBMS의 자료형과 콜레이션 설정에 따라 달라질 수 있다.
NULL과 3값 논리
NULL은 값이 0이라는 뜻이 아니라 값을 알 수 없거나 해당 값이 존재하지 않음을 나타내는 표시다. 표준적인 개념에서는 빈 문자열과도 구분하지만, Oracle Database는 현재 길이 0인 문자 값을 NULL로 처리하는 제품 특성이 있으므로 DBMS 전제를 확인해야 한다.
NULL이 일반 비교나 논리식에 참여하면 TRUE와 FALSE 외에 UNKNOWN이 생길 수 있다.
| A | B | A AND B | A OR B |
|---|---|---|---|
| TRUE | TRUE | TRUE | TRUE |
| TRUE | FALSE | FALSE | TRUE |
| TRUE | UNKNOWN | UNKNOWN | TRUE |
| FALSE | TRUE | FALSE | TRUE |
| FALSE | FALSE | FALSE | FALSE |
| FALSE | UNKNOWN | FALSE | UNKNOWN |
| UNKNOWN | TRUE | UNKNOWN | TRUE |
| UNKNOWN | FALSE | FALSE | UNKNOWN |
| UNKNOWN | UNKNOWN | UNKNOWN | UNKNOWN |
NOT UNKNOWN도 UNKNOWN이다. WHERE와 HAVING은 TRUE만 통과시키므로 UNKNOWN은 FALSE처럼 결과에서 제외되지만, 이후 논리 연산에서 FALSE와 완전히 같은 값은 아니다.
NULL은 IS NULL로 검사한다
-- 올바른 NULL 검사
SELECT emp_id, emp_name
FROM employee
WHERE bonus IS NULL;
-- bonus가 NULL이어도 비교 결과는 TRUE가 아니라 UNKNOWN
SELECT emp_id, emp_name
FROM employee
WHERE bonus = NULL;
NULL = NULL과 NULL <> NULL도 UNKNOWN이다. 두 값의 NULL까지 포함한 동등성 비교가 필요한 경우에는 DBMS가 지원하는 IS [NOT] DISTINCT FROM 같은 구문을 사용할 수 있지만, 기본 시험 문제에서는 먼저 IS NULL과 IS NOT NULL을 구분한다.
NULL의 전파와 COALESCE
일반적인 산술 연산은 NULL을 만나면 NULL을 반환한다. 예를 들어 salary + NULL의 결과는 NULL이다. 많은 단일 행 함수도 NULL을 전파하지만, 구체적인 동작은 함수와 DBMS에 따라 예외가 있을 수 있다.
COALESCE(a, b, c)는 왼쪽부터 처음 만나는 NULL이 아닌 값을 반환하는 표준 함수다.
SELECT emp_name,
COALESCE(bonus, 0) AS displayed_bonus
FROM employee;
표시 목적의 NULL 대체와 원본 데이터의 의미 변경은 구분해야 한다. NULL을 0으로 표시했다고 해서 실제 저장값이 0으로 바뀌는 것은 아니다.
NOT IN에 NULL이 섞이면 생기는 함정
다음 조건은 값이 10이 아니더라도 TRUE가 되지 않을 수 있다.
WHERE value NOT IN (10, NULL)
개념적으로 value <> 10 AND value <> NULL과 연결되며, 두 번째 비교가 UNKNOWN이므로 10이 아닌 값도 전체 조건이 UNKNOWN이 될 수 있다. NULL 가능성이 있는 목록이나 서브쿼리의 부정 조건은 NOT EXISTS와 의미를 비교해야 한다.
단일 행 함수와 집계 함수는 처리 단위가 다르다
함수는 목적이 같아도 이름·인수·반환 자료형·날짜 계산 규칙이 DBMS마다 다를 수 있다. 시험에서는 먼저 한 행마다 계산하는 단일 행 함수와 여러 행을 하나의 값으로 줄이는 집계 함수를 구분한다.
| 구분 | 처리 단위 | 대표 예 | 결과 행 수 |
|---|---|---|---|
| 문자 함수 | 한 행의 문자값 | UPPER, LOWER, CHAR_LENGTH | 입력 행 수를 유지 |
| 숫자 함수 | 한 행의 숫자값 | ABS, ROUND | 입력 행 수를 유지 |
| 날짜·시간 함수 | 한 행 또는 현재 시점 | CURRENT_DATE, CURRENT_TIMESTAMP | 입력 행 수를 유지 |
| NULL 처리 함수 | 한 행의 값 후보 | COALESCE | 입력 행 수를 유지 |
| 집계 함수 | 전체 행 또는 그룹의 여러 행 | COUNT, SUM, AVG, MIN, MAX | 전체 또는 그룹당 한 행 |
SUBSTRING과 SUBSTR, CHAR_LENGTH와 LENGTH·LEN, 날짜 덧셈 구문, NULL 대체용 NVL·ISNULL 등은 제품별 차이가 있다. 범용 개념을 묻는 문제에서는 함수의 목적과 처리 단위를 먼저 보고, 특정 구문이 나오면 DBMS 전제를 확인한다.
GROUP BY와 집계: 여러 행을 그룹당 한 행으로 줄인다
GROUP BY는 같은 그룹 식의 값을 가진 행을 하나의 그룹으로 묶는다. 집계 함수는 각 그룹의 여러 행에서 하나의 요약값을 만든다. GROUP BY가 없고 집계 함수만 있으면 조건을 통과한 전체 행 집합을 하나의 그룹처럼 처리한다.
| 집계 함수 | 세는·계산하는 대상 | NULL 처리 | 빈 입력 집합의 일반적 결과 |
|---|---|---|---|
COUNT(*) | 행 자체 | 행의 열에 NULL이 있어도 그 행을 셈 | 0 |
COUNT(식) | 식이 NULL이 아닌 행 | NULL인 결과를 제외 | 0 |
COUNT(DISTINCT 식) | 서로 다른 NULL 아닌 값 | NULL 제외, 중복 제거 | 0 |
SUM(식) | NULL 아닌 수치의 합 | NULL 제외 | NULL |
AVG(식) | NULL 아닌 수치의 평균 | NULL 제외, 분모도 NULL 아닌 값의 수 | NULL |
MIN(식)·MAX(식) | NULL 아닌 값의 최솟값·최댓값 | NULL 제외 | NULL |
COUNT(*)는 “모든 열을 센다”가 아니라 행을 센다. COUNT(bonus)는 bonus가 NULL이 아닌 행만 세며, bonus가 0인 행은 NULL이 아니므로 포함한다.
GROUP BY가 있는 SELECT 목록의 기본 규칙
그룹 질의의 SELECT 목록에는 일반적으로 다음 두 종류만 둔다.
GROUP BY에 사용한 그룹 식- 집계 함수로 계산한 식
-- 부서별 평균 급여
SELECT department,
AVG(salary) AS avg_salary
FROM employee
GROUP BY department;
그룹에 여러 행이 있는데 그룹 기준도 아니고 집계하지도 않은 일반 열을 출력하면 어느 행의 값을 선택해야 하는지 정해지지 않는다. SQL 표준의 함수 종속 예외나 일부 DBMS 확장이 존재하지만, 문제에서는 일반 열은 그룹 기준에 포함하고 나머지는 집계한다는 기본 규칙으로 판별하는 것이 안전하다.
NULL인 그룹 키가 여러 행에 있으면 그룹화에서는 하나의 NULL 그룹으로 묶인다. 이는 일반 비교식에서 NULL = NULL이 UNKNOWN인 것과 다른 처리 문맥이다.
WHERE와 HAVING의 차이
| 구분 | 필터 대상 | 처리 시점 | 집계 함수 조건 |
|---|---|---|---|
WHERE | 원본의 개별 행 | 그룹화 전 | 같은 질의 블록에서 직접 사용하지 않음 |
HAVING | GROUP BY가 만든 그룹 | 그룹화·집계 후 | COUNT(*), AVG(salary) 등 사용 가능 |
SELECT department,
COUNT(*) AS employee_count
FROM employee
WHERE status = 'ACTIVE'
GROUP BY department
HAVING COUNT(*) >= 2;
WHERE status = 'ACTIVE'는 재직 중인 행만 그룹화 대상으로 남긴다. HAVING COUNT(*) >= 2는 그 결과로 만들어진 그룹 중 인원이 2명 이상인 부서만 남긴다. HAVING이 있으면 GROUP BY가 없더라도 선택된 전체 행을 하나의 그룹으로 취급하는 그룹 질의가 될 수 있다. 그러나 행 조건을 대신하는 절로 남용하면 의미가 흐려진다.
하나의 질의를 단계별로 추적하기
다음 employee 데이터를 사용한다.
| emp_id | department | emp_name | status | salary | bonus |
|---|---|---|---|---|---|
| 101 | 개발 | 김민수 | ACTIVE | 5000 | 500 |
| 102 | 개발 | 이지수 | ACTIVE | 4000 | NULL |
| 103 | 개발 | 박수현 | LEAVE | 3500 | 300 |
| 201 | 기획 | 최하늘 | ACTIVE | 4500 | NULL |
| 202 | 기획 | 정다온 | ACTIVE | 3000 | 0 |
| 301 | 영업 | 윤서준 | ACTIVE | 2800 | NULL |
| 302 | 영업 | 한유진 | LEAVE | 3200 | NULL |
재직자가 2명 이상인 부서의 인원수, 보너스 입력 인원수, 평균 급여를 평균 급여 내림차순으로 조회한다.
SELECT department,
COUNT(*) AS employee_count,
COUNT(bonus) AS bonus_count,
AVG(salary) AS avg_salary
FROM employee
WHERE status = 'ACTIVE'
GROUP BY department
HAVING COUNT(*) >= 2
ORDER BY avg_salary DESC, department ASC;
| 논리 단계 | 처리 내용 | 남는 결과 |
|---|---|---|
FROM | employee의 7행을 읽음 | 7행 |
WHERE | status = 'ACTIVE'가 TRUE인 행만 통과 | 5행 |
GROUP BY·집계 | 개발·기획·영업의 3개 그룹 생성 | 3그룹 |
HAVING | 재직자 수가 2명 이상인 그룹만 통과 | 개발·기획 2그룹 |
SELECT | 그룹별 COUNT와 AVG 결과를 출력 열로 구성 | 2행 |
ORDER BY | 평균 급여 내림차순, 동률이면 부서명 오름차순 | 개발 → 기획 |
최종 결과는 다음과 같다. 평균값의 표시 형식은 DBMS와 클라이언트에 따라 4500, 4500.0처럼 다를 수 있다.
| department | employee_count | bonus_count | avg_salary |
|---|---|---|---|
| 개발 | 2 | 1 | 4500 |
| 기획 | 2 | 1 | 3750 |
개발 부서의 보너스 값은 500, NULL이므로 COUNT(*)는 2이고 COUNT(bonus)는 1이다. 기획 부서의 0은 NULL이 아니므로 역시 COUNT(bonus)에 포함된다.
소계와 총계를 만드는 그룹 함수
일반 GROUP BY는 지정한 열 조합의 그룹별 집계만 만든다. ROLLUP, CUBE, GROUPING SETS는 서로 다른 수준의 집계를 한 질의에서 표현한다. ()는 전체 데이터를 하나로 묶는 총계를 나타낸다.
| 표현 | 생성하는 그룹 기준 |
|---|---|
GROUP BY a, b | (a,b) |
GROUP BY ROLLUP(a,b) | (a,b), (a), () |
GROUP BY CUBE(a,b) | (a,b), (a), (b), () |
GROUP BY GROUPING SETS ((a),(b)) | (a), (b)만 |
ROLLUP은 열의 순서에 따른 계층적 소계이므로 ROLLUP(a,b)와 ROLLUP(b,a)가 다를 수 있다. CUBE는 지정 열의 모든 부분집합에 대한 집계다. 서로 다른 열 n개에 대해 단순 ROLLUP은 n+1개, CUBE는 2ⁿ개의 그룹 기준 집합을 만들지만 이것이 실제 결과 행 수와 같다는 뜻은 아니다.
예를 들어 GROUP BY ROLLUP(department, job)은 부서·직무별 집계, 부서별 소계, 전체 총계를 만든다. GROUPING 함수는 해당 열이 소계·총계를 위해 생략되었으면 1, 실제 그룹 기준이면 0을 돌려준다. 집계 때문에 생긴 NULL과 원본 데이터의 NULL을 구분할 때 사용한다. 문법 지원 범위는 DBMS에 따라 다를 수 있다.
DISTINCT·ORDER BY·행 제한은 서로 다른 단계다
DISTINCT는 선택된 열 조합 전체에 적용된다
SELECT DISTINCT department, status
FROM employee;
이 문장은 department만이 아니라 department와 status의 조합이 같은 결과 행을 하나로 줄인다. SELECT DISTINCT department, status에서 첫 번째 열에만 DISTINCT가 적용되는 것은 아니다. 중복 판정에서는 NULL도 하나의 같은 값 묶음처럼 처리되므로, 선택된 열 조합이 모두 같은 NULL 결과 행 여러 개는 하나로 줄어들 수 있다.
DISTINCT와 GROUP BY가 같은 고유 키 목록을 만들 때도 있지만 역할은 다르다.
DISTINCT: 이미 계산된 결과 행의 중복을 제거한다.GROUP BY: 행을 그룹으로 묶어 집계하거나 그룹 단위 계산을 수행한다.
ORDER BY가 있어야 결과 순서를 요구할 수 있다
ORDER BY가 없으면 현재 실행에서 기본키나 입력 순서처럼 보이는 결과가 나오더라도 그 순서는 보장되지 않는다.
ORDER BY salary DESC, emp_id ASC
첫 번째 키인 salary를 내림차순으로 정렬하고, salary가 같을 때 emp_id를 오름차순으로 정렬한다. ASC는 일반적으로 기본 방향이지만 명시하면 의도가 더 분명하다.
문자 정렬은 콜레이션에 영향을 받고, NULL이 오름차순에서 앞에 오는지 뒤에 오는지는 DBMS마다 다르다. 일부 DBMS는 NULLS FIRST·NULLS LAST를 지원한다. 문제에 제품 전제가 없다면 NULL의 기본 정렬 위치를 하나로 단정하지 않는다.
상위 N행에는 결정적인 정렬 기준이 필요하다
SELECT emp_id, emp_name, salary
FROM employee
ORDER BY salary DESC, emp_id ASC
FETCH FIRST 3 ROWS ONLY;
급여가 같은 행이 있어도 고유한 emp_id를 보조 키로 사용하므로 동일한 데이터 상태에서는 반환 순서가 결정적이다. ORDER BY 없이 행 수만 제한하면 “어떤 3행”인지 보장되지 않는다.
행 제한 구문은 DBMS 방언이다.
- SQL 표준 계열:
OFFSET ... FETCH FIRST|NEXT ... ROWS ONLY - PostgreSQL·MySQL·SQLite 등:
LIMIT - SQL Server:
TOP또는OFFSET ... FETCH
문제에서는 구문 이름보다 정렬 후 행 제한인지, 동률을 가르는 보조 키가 있는지를 먼저 확인한다.
조건식·문자 함수·조건부 집계를 연결하기
검색형 CASE는 WHEN 조건을 위에서부터 확인하여 처음 TRUE인 분기의 결과를 선택한다. UNKNOWN은 TRUE가 아니므로 해당 분기를 선택하지 않으며, 일치한 분기가 없으면 ELSE를 사용한다. ELSE까지 없으면 결과는 NULL이다.
SUM(CASE WHEN status='DONE' THEN amount ELSE 0 END)는 DONE 행의 금액만 합계에 기여하게 한다. 조건부 집계에서는 CASE가 행별 값을 먼저 정하고, SUM이 그 값들을 모은다는 순서를 따른다.
NULLIF(a,b)는 두 값이 같으면 NULL, 그렇지 않으면 첫 값을 돌려준다. COALESCE는 처음으로 NULL이 아닌 인수를 고른다. 따라서 COALESCE(numerator / NULLIF(denominator,0),0)은 분모 0을 먼저 NULL로 바꾼 뒤 계산 결과의 NULL을 0으로 바꾸는 패턴이다. 정수 나눗셈·소수 나눗셈의 제품별 차이도 별도로 확인한다.
SQLite에서 ASCII 문자열을 사용할 때 SUBSTR(text,1,2)는 첫 문자부터 두 문자를, LENGTH(text)는 문자열의 문자 수를 구한다. TRIM은 기본적으로 양 끝 공백을 제거하고 UPPER는 해당 구현이 지원하는 대문자 변환을 수행한다. ||는 문자열 연결 연산자다. UPPER의 모든 언어 문자 처리까지 ASCII 예제로 일반화하지 않는다.
그룹별 평균만 다시 평균 내면 원래 행의 수가 다를 때 전체 평균과 달라진다. 전체 평균은 그룹 합계들의 합 ÷ 그룹의 NULL 아닌 측정값 개수들의 합으로 계산한다. 행 수가 다른 그룹의 평균을 동일한 비중으로 합치는 오류를 피한다.
ROLLUP·CUBE·GROUPING SETS의 실제 결과 계산
ROLLUP(a,b,c)가 만드는 집합은 (a,b,c), (a,b), (a), ()다. 앞에서부터의 계층만 만들기 때문에 (b,c) 소계는 없다. CUBE(a,b,c)는 이 집합뿐 아니라 (a,c), (b,c), (b), (c)도 포함하여 8개의 그룹 기준 집합을 만든다. GROUPING SETS는 명시한 집합만 만들며, ()를 적지 않았다면 총계가 자동으로 추가되는 것은 아니다.
아래 매출에서 ROLLUP(지역,상품)을 계산한다고 하자.
| 지역 | 상품 | 금액 |
|---|---|---|
| 동부 | A | 20 |
| 동부 | B | 30 |
| 서부 | A | 40 |
지역·상품 집계 3행, 지역별 소계 2행, 전체 총계 1행으로 결과는 6행이다. 소계는 동부 50·서부 40, 총계는 90이다. 그룹 기준 집합 3개와 출력 행 6개는 다른 수다. 결과 순서는 ORDER BY 없이는 보장하지 않는다.
집계에서 생략한 열은 NULL로 표시될 수 있다. 원본 값 자체가 NULL인 그룹과 구별하려면 GROUPING(열)을 사용한다. 그 열이 집계 기준에서 생략된 행은 1, 실제 그룹 키로 참여한 행은 값이 NULL이어도 0이다. SQLite는 이러한 그룹 확장 구문을 직접 지원하지 않으므로, 이 개념을 SQLite의 실행 결과라고 혼동하지 않는다.
ROLLUP과 CUBE의 그룹 집합
ROLLUP(A,B)는 (A,B), (A), ()이고 CUBE(A,B)는 여기에 (B)를 더한다. ()는 전체 합계다. 한 그룹 집합에서도 실제 키 값 수에 따라 여러 결과 행이 나온다. GROUPING은 집계 NULL과 실제 NULL을 구분한다.