SELECT 문의 여섯 절, 작성 순서와 실행 순서(FWGHSO), DISTINCT·별칭·DUAL을 익힙니다.
빈출기출 11회분 중 37문항 · 많이 나온 주제: DISTINCT(11)
학습 목표
SELECT 문의 여섯 절, 작성 순서와 실행 순서(FWGHSO), DISTINCT·별칭·DUAL을 익힙니다.
01
SELECT 문 한눈에 보기
SELECT 문은 데이터베이스에서 원하는 데이터를 골라 조회하는 명령어입니다.
어느 테이블에서(FROM) · 어떤 행만(WHERE) · 무엇별로 묶어(GROUP BY) · 묶은 뒤 어떤 그룹만(HAVING) · 어떤 열을(SELECT) · 어떤 순서로(ORDER BY) 볼지를 정합니다. 모든 절이 필요한 것은 아니고, 가장 단순한 조회는 SELECT ... FROM ...입니다.
SELECT 열1, 열2,...-- ⑤ 어떤 열을 보여 줄까?FROM 테이블 -- ① 어디에서 읽을까?WHERE 행 조건 -- ② 어떤 행만 남길까?GROUPBY 묶을 기준 -- ③ 무엇별로 묶을까?HAVING 그룹 조건 -- ④ 어떤 그룹만 남길까?ORDERBY 정렬 기준;-- ⑥ 어떤 순서로 볼까?
주석의 번호(①~⑥)가 실행 순서입니다. 절마다 자세한 내용은 WHERE 절, GROUP BY·HAVING 절, ORDER BY 절, 함수, 조인 단원에서 다룹니다.
02
작성 순서와 실행 순서 (FWGHSO)
SQL은 SELECT부터 작성하지만, DB는 FROM부터 처리합니다. 이 차이가 시험에 가장 자주 나옵니다.
SELECT 문의 두 가지 순서
작성 순서
SELECT→
FROM→
WHERE→
GROUP BY→
HAVING→
ORDER BY
논리적 실행 순서
FROM→
WHERE→
GROUP BY→
HAVING→
SELECT→
ORDER BY
실행 순서는 앞 글자를 따서 F·W·G·H·S·O로 외웁니다. SELECT가 다섯 번째라서 SELECT 별칭은 WHERE·GROUP BY·HAVING에서 일반적으로 쓸 수 없고, ORDER BY에서는 쓸 수 있습니다.
실행 순서로 판단하는 별칭 사용
SQL
판단
이유
SELECT FEE * 2 AS DBL ... WHERE DBL > 100000
오류
WHERE(②)가 SELECT(⑤)보다 먼저
SELECT FEE * 2 AS DBL ... ORDER BY DBL
가능
ORDER BY(⑥)는 SELECT 다음
SELECT LEARNER_NAME, FEE ... ORDER BY 2
가능
ORDER BY는 SELECT 목록 순번도 가능
03
SELECT 절의 다양한 표현
SELECT 절에 쓸 수 있는 것
형태
의미
예시 결과
SELECT *
모든 열
신청번호·이름·강좌·수강료·상태
SELECT 열1, 열2
특정 열만
201, 서유진
SELECT DISTINCT 열
중복 제거
도예·사진
SELECT 열 AS 별칭
결과 열에 이름 붙이기
DOUBLE_FEE
SELECT FEE * 2
산술 계산
100000
SELECT 이름 || '님'
문자열 연결 (Oracle)
서유진님
SELECT 1 + 1 FROM DUAL
테이블 없이 계산
2
문자 값(리터럴)은 작은따옴표로 감쌉니다. 문자 안에 작은따옴표 자체를 넣으려면 작은따옴표를 두 번 씁니다. 큰따옴표는 문자 값이 아니라 별칭에 씁니다. 문자열 연결은 Oracle이 ||, SQL Server가 +입니다(참고).
작은따옴표 리터럴 읽기
SELECT 식
출력
'SQL'
SQL
'I''m'
I'm
'''OK'''
'OK'
'A' || 'B' (Oracle)
AB
'''OK'''는 바깥 따옴표 한 쌍을 벗기면 ''OK''가 남고, ''가 따옴표 하나가 되어 'OK'가 출력됩니다.
NULL이 들어간 식은 두 가지로 나뉩니다. 산술 연산(+ - * /)은 결과가 NULL 값이고, 비교(=, > 등)는 결과가 참도 거짓도 아닌 UNKNOWN이라 WHERE에서 그 행이 빠집니다.
NULL이 섞인 식의 결과
식
결과
성격
NULL + 1
NULL
산술 → NULL 값
NULL * NULL
NULL
산술 → NULL 값
WHERE 1 > NULL
UNKNOWN
비교 → 행이 남지 않음
'X'
X
상수 → 그대로
04
DISTINCT: 열 조합 기준 중복 제거
DISTINCT는 SELECT 바로 뒤에 한 번 쓰고, 나열한 열 조합 전체가 같은 행을 하나로 합칩니다. 아무것도 안 쓰면 기본값 ALL이라 중복이 그대로 나옵니다. 원본 테이블은 바뀌지 않습니다.
TABLE예시 데이터: 문화센터 신청 (서유진이 두 강좌를 신청)5행
ENROLL_ID
LEARNER_NAME
COURSE_TYPE
201
서유진
도예
202
하진우
사진
203
서유진
사진
204
정서윤
도예
205
유민호
도예
같은 데이터, 다른 SELECT
쿼리
결과
이유
SELECT LEARNER_NAME
5행
중복 그대로 (ALL)
SELECT DISTINCT LEARNER_NAME
4행
서유진이 한 줄로 합쳐짐
SELECT DISTINCT LEARNER_NAME, COURSE_TYPE
5행
이름+강좌 조합은 모두 다름
DISTINCT LEARNER_NAME
LEARNER_NAME, COURSE_TYPE 5행
LEARNER_NAME
COURSE_TYPE
서유진
도예
하진우
사진
서유진
사진
정서윤
도예
유민호
도예
DISTINCT→같은 이름을 한 행으로
SELECT DISTINCT LEARNER_NAME → 4행
LEARNER_NAME
서유진
하진우
정서윤
유민호
이름만 고르면 서유진 두 행이 한 행이 됩니다. 강좌까지 함께 고르면 (서유진, 도예)와 (서유진, 사진)이 서로 달라 5행 그대로입니다. 결과 순서는 ORDER BY 없이는 보장되지 않습니다.
NULL도 중복으로 봅니다. 열에 NULL이 여러 개 있으면 SELECT DISTINCT 결과에는 NULL 한 행이 나옵니다. 반면 COUNT(DISTINCT 열)은 NULL을 세지 않습니다(GROUP BY 단원). DISTINCT는 SELECT 바로 뒤에만 쓸 수 있어서 SELECT LEARNER_NAME, DISTINCT COURSE_TYPE은 오류입니다.
05
별칭 규칙과 DUAL
별칭(Alias) 규칙
규칙
예
AS는 생략 가능
SELECT LEARNER_NAME 수강생
공백·특수문자·대소문자 유지는 큰따옴표
AS "수강생 이름"
숫자로 시작하거나 예약어인 별칭도 큰따옴표
AS "2026년"
테이블 별칭에는 AS를 쓰지 않음 (Oracle)
FROM CULTURE_ENROLLMENT E
WHERE·GROUP BY·HAVING에서 SELECT 별칭
불가 (SELECT보다 먼저 실행되므로)
ORDER BY에서 SELECT 별칭
가능 (SELECT 다음에 실행되므로)
DUAL은 Oracle이 기본으로 제공하는 1행·1열짜리 가짜 테이블입니다. 테이블 없이 계산이나 함수 결과를 확인할 때 씁니다.
SELECT1+1FROMDUAL;-- 2SELECTSYSDATEFROMDUAL;-- 현재 날짜·시각SELECT USER FROMDUAL;-- 접속한 계정
보충 실습여기부터는 테이블을 직접 만들어 보는 긴 SQL 실습입니다. 처음 공부할 때는 건너뛰고, 위 내용을 이해한 뒤 돌아와도 됩니다.
06
SQL 실전: 문화센터 신청 데이터로 결과 검증하기
보충 실습
학습용 테이블을 처음부터 만들고, 같은 데이터에 여러 종류의 SELECT를 적용해 봅니다. 독립된 실습 스키마에서 아래 코드를 순서대로 실행한다고 가정합니다. ENROLL_ID는 기본키이고, STATUS는 유효 신청 Y와 취소 신청 N으로 구분합니다. 수강료가 아직 정해지지 않은 건은 NULL로 저장할 수 있습니다.
-- 1. 실행용 데이터 준비 (Oracle)CREATETABLECULTURE_ENROLLMENT(
ENROLL_ID NUMBER(4)PRIMARYKEY,
LEARNER_NAME VARCHAR2(40CHAR)NOTNULL,
COURSE_TYPE VARCHAR2(20CHAR)NOTNULL,
FEE NUMBER(8),
STATUS CHAR(1)NOTNULL,CONSTRAINT CK_CE_STATUS CHECK(STATUS IN('Y','N')));INSERTINTO CULTURE_ENROLLMENT VALUES(201,'서유진','도예',50000,'Y');INSERTINTO CULTURE_ENROLLMENT VALUES(202,'하진우','사진',60000,'Y');INSERTINTO CULTURE_ENROLLMENT VALUES(203,'서유진','사진',60000,'N');INSERTINTO CULTURE_ENROLLMENT VALUES(204,'정서윤','도예',50000,'Y');INSERTINTO CULTURE_ENROLLMENT VALUES(205,'유민호','도예',NULL,'Y');COMMIT;-- 2. WHERE: 유효 신청만 선택 (201, 202, 204, 205)SELECT ENROLL_ID, LEARNER_NAME
FROM CULTURE_ENROLLMENT
WHERE STATUS ='Y'ORDERBY ENROLL_ID;-- 3. GROUP BY + HAVING: 유효 신청이 2건 이상인 강좌SELECT COURSE_TYPE,COUNT(*)AS ENROLL_CNT
FROM CULTURE_ENROLLMENT
WHERE STATUS ='Y'GROUPBY COURSE_TYPE
HAVINGCOUNT(*)>=2ORDERBY COURSE_TYPE;-- 4. DISTINCT: 강좌 종류만 두 행으로 조회SELECTDISTINCT COURSE_TYPE
FROM CULTURE_ENROLLMENT
ORDERBY COURSE_TYPE;-- 5. 연산 결과: NULL 수강료는 계산 결과도 NULLSELECT ENROLL_ID, FEE *2AS DOUBLE_FEE
FROM CULTURE_ENROLLMENT
ORDERBY ENROLL_ID;
이 흐름을 머릿속으로 추적하면 실행 순서 문제를 쉽게 풀 수 있습니다. 실습 5는 같은 데이터에 다른 표현식을 적용하는 것뿐이므로 원래 FEE를 수정하지 않습니다. SELECT는 데이터를 읽는 명령어이며, 단순 조회만으로 원본 행이 바뀌지는 않습니다.
07
SQL 실전: 박물관 예약 테이블 생성부터 SELECT까지
보충 실습
방문객(VISITOR)과 전시 예약(EXHIBIT_RESERVATION) 테이블을 Oracle SQL로 만들어 봅니다. DDL로 테이블과 제약조건을 만들고, DML로 예시 데이터를 넣은 뒤, TCL인 COMMIT으로 확정합니다. 마지막에는 두 테이블을 조인해 취소되지 않은 예약만 SELECT합니다.
-- 1. DDL: 방문객과 전시 예약 테이블 생성CREATETABLEVISITOR(
VISITOR_ID NUMBER(6)PRIMARYKEY,
VISITOR_NAME VARCHAR2(40CHAR)NOTNULL,
EMAIL VARCHAR2(100CHAR)UNIQUE);CREATETABLEEXHIBIT_RESERVATION(
RESERVATION_ID NUMBER(8)PRIMARYKEY,
VISITOR_ID NUMBER(6)NOTNULL,
VISIT_DATE DATENOTNULL,
TICKET_COUNT NUMBER(3)NOTNULL,
STATUS CHAR(1)DEFAULT'R'NOTNULL,
CREATED_AT TIMESTAMPDEFAULT SYSTIMESTAMP NOTNULL,CONSTRAINT FK_ER_VISITOR FOREIGNKEY(VISITOR_ID)REFERENCESVISITOR(VISITOR_ID),CONSTRAINT CK_ER_TICKET CHECK(TICKET_COUNT >=1),CONSTRAINT CK_ER_STATUS CHECK(STATUS IN('R','C')));-- 선택적으로 생성하는 조회용 인덱스CREATEINDEX IDX_ER_VISITOR
ONEXHIBIT_RESERVATION(VISITOR_ID);-- 2. DML: 방문객과 예약 데이터 입력INSERTINTO VISITOR VALUES(101,'윤가은','gaeun@example.test');INSERTINTO VISITOR VALUES(102,'최민서','minseo@example.test');INSERTINTO VISITOR VALUES(103,'하태원','taewon@example.test');INSERTINTOEXHIBIT_RESERVATION(RESERVATION_ID, VISITOR_ID, VISIT_DATE, TICKET_COUNT, STATUS)VALUES(9001,101,DATE'2026-11-03',2,'R');INSERTINTOEXHIBIT_RESERVATION(RESERVATION_ID, VISITOR_ID, VISIT_DATE, TICKET_COUNT, STATUS)VALUES(9002,101,DATE'2026-11-04',1,'R');INSERTINTOEXHIBIT_RESERVATION(RESERVATION_ID, VISITOR_ID, VISIT_DATE, TICKET_COUNT, STATUS)VALUES(9003,102,DATE'2026-11-05',3,'C');-- 3. TCL: 데이터 변경 확정COMMIT;-- 4. DML(조회): 방문 예정인 예약만 확인SELECT R.RESERVATION_ID,
V.VISITOR_NAME,TO_CHAR(R.VISIT_DATE,'YYYY-MM-DD')AS VISIT_DATE,
R.TICKET_COUNT
FROM EXHIBIT_RESERVATION R
JOIN VISITOR V ON V.VISITOR_ID = R.VISITOR_ID
WHERE R.STATUS ='R'ORDERBY R.RESERVATION_ID;
TABLE예약 조회 예상 결과2행
RESERVATION_ID
VISITOR_NAME
VISIT_DATE
TICKET_COUNT
9001
윤가은
2026-11-03
2
9002
윤가은
2026-11-04
1
SELECT는 열을 고르고, FROM과 JOIN은 읽을 테이블과 연결 조건을 지정하며, WHERE는 조건에 맞는 행만 남깁니다. ORDER BY는 예약번호순으로 결과를 정렬합니다. 예약번호 9003은 취소 상태 C라서 제외되고, 방문객번호 103은 예약이 없어 INNER JOIN 결과에 나오지 않습니다. 이 예제는 새로 만든 빈 스키마에서 위 문장들을 순서대로 한 번 실행한 결과를 전제로 합니다.
모델링 전(Before)에는 예약을 파일 한 줄에 9001,윤가은,2026-11-03,2,R처럼 저장할 수 있습니다. 하지만 이 방식만으로는 이름 중복 구분, 방문객과 예약의 참조 관계, 여러 작업의 일괄 확정·취소, 접근 권한 관리가 자동으로 보장되지 않습니다. 모델링 후(After)에는 방문객과 예약을 분리하고 PK·FK·CHECK와 트랜잭션을 활용합니다. 다만 DBMS를 쓴다고 모든 조회가 자동으로 빠른 것은 아니며, 인덱스 효과와 성능은 데이터 양·조건·실행 계획에 따라 달라집니다.