문서 읽는 데 110분 · 3회 · 50문항

SQLD 3회 기출복원 풀이

목차 52
전체 5회 중 3회 · 기출문제풀이

1과목 데이터 모델링의 이해 (문 1~10)

문 1. 아래에서 설명하는 스키마 구조로 가장 적절한 것은?

모든 사용자 관점의 데이터베이스 스키마를 통합하여 데이터베이스 전체의 논리적 구조와 데이터 간의 관계를 정의한다.

  • ① 내부 스키마(Internal Schema)
  • ② 논리 스키마(Logical Schema)
  • ③ 개념 스키마(Conceptual Schema)
  • ④ 외부 스키마(External Schema)
정답 및 해설 보기

정답 ③

개념 스키마는 외부 스키마에서 요구한 데이터를 통합하여 데이터베이스 전체의 논리적 구조와 데이터 간의 관계를 정의한다. 조직 전체 관점이라 물리적 저장 구조나 특정 응용 프로그램에 의존하지 않는다. 지문의 '통합'·'전체'·'논리적 구조'가 곧 개념 스키마의 핵심 표지다.

🔑 암기 3단계 스키마 — 외부(사용자/응용 관점) · 개념(조직 전체 통합 관점) · 내부(물리적 저장 관점).

문 2. 발생 시점에 따라 구분할 수 있는 엔터티의 유형으로 가장 적절하지 않은 것은?

  • ① 행위 엔터티
  • ② 중심 엔터티
  • ③ 기본 엔터티
  • ④ 사건 엔터티
정답 및 해설 보기

정답 ④

발생 시점에 따라 구분하는 엔터티 유형은 기본 엔터티·중심 엔터티·행위 엔터티 세 가지다. '사건 엔터티'는 발생 시점이 아니라 물리적 형태의 존재 여부(유형/개념/사건)에 따른 다른 분류 축에 속하므로, 발생 시점 분류로는 적절하지 않다. 비슷한 이름으로 헷갈리게 만든 보기다.

🔑 암기 발생 시점 분류 = 기본 · 중심 · 행위.

문 3. 아래 ERD에 대한 설명으로 가장 적절하지 않은 것은?

문3 ERD — 계약(#계약번호, #고객번호(FK), #서비스번호(FK), #납부자번호(FK), *계약기간, *계약금액)을 중심으로 고객(#고객번호, *고객명)·서비스(#서비스번호, *서비스명)·납부자(#납부자번호, *납부자명)가 각각 비식별 관계(점선)로 연결된 ERD

  • ① [계약] 테이블은 고객번호, 서비스번호, 납부자번호를 모두 외래 키로 가진다.
  • ② 한 명의 고객은 여러 건의 계약을 가질 수 있다.
  • ③ 각 계약은 하나의 서비스와만 연결된다.
  • ④ [계약] 테이블은 고객과 납부자 간의 M:N 관계를 해소하는 역할을 한다.
정답 및 해설 보기

정답 ④

[계약]은 고객과 납부자 간의 M:N 관계를 푸는 단순 연결 테이블이 아니다. 고객과 납부자는 서로 직접적인 다대다 관계로 모델링되지 않았고, [계약]은 계약기간·계약금액 같은 핵심 정보를 관리하는 행위 엔터티다. 따라서 [계약]을 관계 해소용 교차 테이블로 보는 설명은 본질을 축소한 것이다.

오답 ① 고객번호·서비스번호·납부자번호가 모두 외래 키로 들어 있다. ② 고객과 계약은 1:N이라 한 고객이 여러 계약을 가진다. ③ 계약은 서비스번호(FK)를 단일 속성으로 가져 하나의 서비스와 연결된다.

🔑 암기 행위(사건) 엔터티 = 업무 행위가 일어날 때마다 발생, 자신만의 핵심 속성을 가진다(교차 엔터티와 구분).

문 4. 다른 속성을 이용해 값을 계산하거나 도출할 수 있는 속성은?

  • ① 기본 속성
  • ② 파생 속성
  • ③ 설계 속성
  • ④ 일반 속성
정답 및 해설 보기

정답 ②

파생 속성은 다른 속성들로부터 유도된 속성으로, 기존 속성을 이용해 값을 계산하거나 도출한다. 합계·평균·총금액 같은 통계/연산 결과가 대표적이다.

🔑 암기 속성 분류 — 기본 속성(원래 업무 데이터) · 설계 속성(설계 과정에서 만든 코드/일련번호) · 파생 속성(계산·도출 값).

문 5. 아래 ERD에 대한 설명으로 가장 적절하지 않은 것은?

문5 ERD — 상품(상품번호 PK, 상품명·상품분류코드·재고수량)과 주문(주문번호 PK, 상품번호(FK), 주문일시·주문금액). 상품 쪽 필수(│), 주문 쪽 까마귀 발(다중)로 상품:주문 = 1:N. 주문이 상품번호를 외래 키로 직접 포함

  • ① 하나의 상품은 여러 개의 주문과 연결될 수 있다.
  • ② 한 주문은 오직 한 개의 상품과 연결되도록 설계되어 있다.
  • ③ 상품과 주문의 관계는 1:N이다.
  • ④ 하나의 주문은 여러 개의 상품을 포함할 수 있다.
정답 및 해설 보기

정답 ④

상품과 주문은 1:N 관계이며, [주문] 테이블이 상품번호(FK)를 직접 포함하므로 한 건의 주문은 오직 한 상품만 참조한다. 따라서 하나의 주문이 여러 상품을 포함할 수는 없다. 한 주문에 여러 상품을 담으려면 다대다 관계이거나 별도의 주문상세 엔터티가 있어야 한다.

오답 ①③ 상품:주문 = 1:N이라 한 상품이 여러 주문에 연결된다. ② 주문이 상품번호(FK) 하나만 가지므로 한 주문은 한 상품과 연결된다.

⚠️ 함정 "장바구니에 여러 상품"이라는 현실 경험을 끌어들이면 안 된다. ERD의 관계선과 외래 키 구성만으로 판단한다.

문 6. 주식별자에 대한 설명으로 가장 적절하지 않은 것은?

  • ① 주식별자를 구성하는 속성은 유일성을 만족하는 최소한의 속성 집합이어야 한다.
  • ② 주식별자는 NULL 값을 허용한다.
  • ③ 주식별자는 엔터티 내 모든 인스턴스를 유일하게 구별할 수 있어야 한다.
  • ④ 주식별자의 값은 일반적으로 자주 변경되지 않는 안정적인 속성이어야 한다.
정답 및 해설 보기

정답 ②

주식별자는 개체 무결성 제약에 따라 NULL을 가질 수 없다(존재성). NULL을 허용하면 인스턴스를 유일하게 식별할 수 없으니 주식별자의 역할 자체가 무너진다. 나머지는 유일성·최소성(①), 유일성(③), 불변성(④)을 정확히 설명한다.

🔑 암기 주식별자 4대 특징 — 유일성 · 최소성 · 불변성 · 존재성(NOT NULL).

문 7. 아래 ERD에 대한 설명으로 가장 적절한 것은? 🎯 고난도

문7 ERD — 부서(부서번호 PK, 부서명)와 사원(사원번호 PK, 부서번호(FK), 주민등록번호·성명·입사일). 부서 쪽 필수(│), 사원 쪽 까마귀 발(다중)의 비식별 관계(점선). 외래 키 부서번호는 사원의 주식별자에 포함되지 않는다

  • ① 사원은 부서와 식별 관계로 연결되어 있으며, 부서가 삭제되면 사원도 함께 삭제된다.
  • ② 부서번호는 사원의 일반 속성으로만 존재하며, 관계 제약은 적용되지 않는다.
  • ③ 주민등록번호는 본질식별자이지만, 데이터 관리의 편의성을 고려해 별도의 식별자가 주식별자로 채택되었다.
  • ④ 부서와 사원은 비식별 관계로, 사원은 부서 없이도 존재할 수 있다.
정답 및 해설 보기

정답 ③

주민등록번호는 업무상 이미 존재하는 속성으로 구성된 본질식별자다. 하지만 개인정보 보호와 조인 효율 같은 관리 편의성 때문에, 설계 과정에서 만든 사원번호가 주식별자로 채택되었다. 이때 주민등록번호는 일반 속성으로 남는다. 본질식별자가 있어도 인조식별자를 주식별자로 쓰는 전형적인 모델링이다.

오답 ① 식별 관계는 부모의 주식별자가 자식의 주식별자에 포함될 때 성립하는데, 여기서 부서번호는 사원의 주식별자가 아니므로 비식별 관계다. ② 부서번호는 외래 키라 참조 무결성 제약이 적용된다. ④ 부서와 사원은 1:N이며 사원이 부서에 필수로 참여하므로 부서 없이 존재할 수 없다.

🔑 암기 본질식별자(업무상 의미: 주민번호) vs 인조식별자(관리용으로 새로 생성: 사원번호). 편의를 위해 인조식별자를 주식별자로 채택 가능.

문 8. 아래 테이블에서 제1정규형(1NF)을 만족하기 위한 방법으로 가장 적절한 것은?

텍스트
[프로젝트]
프로젝트ID    담당자
P101         김철수, 이영희
P102         박민수, 최수정, 이준호
P103         강지우
  • ① 프로젝트별 담당자를 한 행에 하나씩 저장할 수 있도록 테이블을 새로 만들어 다중값 속성을 분리한다.
  • ② 담당자 다중값 속성을 그대로 두고, 값들을 쉼표(,)로 구분하여 하나의 칼럼에 저장한다.
  • ③ 담당자 속성을 담당자1, 담당자2 등 별도의 칼럼으로 나누어 저장한다.
  • ④ 프로젝트당 최대 담당자 수를 미리 정하고, 그 수에 맞춰 고정된 개수의 칼럼을 생성하여 저장한다.
정답 및 해설 보기

정답 ①

제1정규형은 모든 속성이 원자값(하나의 값)을 가져야 한다는 원칙이다. 담당자 칼럼에 여러 명이 쉼표로 들어간 다중값 속성은 1NF 위반이다. 이를 해결하려면 프로젝트ID와 담당자 한 쌍이 한 행을 이루도록 분리해야 한다.

오답 ② 쉼표로 묶는 것은 여전히 한 칼럼에 여러 값이 들어가 1NF 위반이다. ③④ 담당자1·담당자2처럼 칼럼을 늘리는 방식은 반복 그룹을 만들어 1NF 위반이며, 담당자 수 변동에도 취약하다.

🔑 암기 1NF = 모든 속성이 원자값. 다중값은 행으로 분리(별도 엔터티)한다.

문 9. 아래 ERD에 대한 설명으로 가장 적절한 것은?

문9 ERD — 학생(학번 PK, 이름·전공)과 수강신청(신청ID·학번(FK) PK, 과목코드·과목명). 학생 쪽 필수(│), 수강신청 쪽 까마귀 발(다중)의 식별 관계로 학생:수강신청 = 1:N. 학번이 수강신청의 주식별자에 포함됨

  • ① 한 학생은 여러 과목을 수강신청할 수 있다.
  • ② 학생 인스턴스를 생성할 때 반드시 같은 학번을 참조하는 수강신청 인스턴스도 함께 생성해야 한다.
  • ③ 수강신청 인스턴스를 삭제하면 해당 학생 인스턴스도 함께 삭제해야 한다.
  • ④ 수강신청 인스턴스의 학번(FK)은 NULL을 허용하므로, 학생 인스턴스 없이도 수강신청 인스턴스를 생성할 수 있다.
정답 및 해설 보기

정답 ①

학생과 수강신청은 1:N 관계다. 한 학생 인스턴스는 여러 수강신청 인스턴스를 가질 수 있고, 각 수강신청은 반드시 하나의 학생에 속한다.

오답 ② 수강신청은 선택적(0..N)이라 학생을 추가할 때 수강신청을 꼭 만들 필요는 없다. ③ 자식(수강신청)을 지워도 부모(학생)는 삭제되지 않는다. ④ 학번이 수강신청의 주식별자에 포함된 식별 관계라 NULL을 허용하지 않으며, 참조 무결성에 따라 그 학번은 학생 테이블에 반드시 존재해야 한다.

🔑 암기 1:N 해석 — 기준 엔터티 1건당 참조 엔터티 여러 건. 식별 관계의 외래 키는 NULL 불가.

문 10. NULL에 대한 설명으로 가장 적절한 것은?

  • ① NULL과의 비교 연산 결과는 항상 FALSE이다.
  • ② 집계 함수(SUM, AVG 등)는 NULL 값을 제외하고 계산한다.
  • ③ NULL은 숫자 0과 동일한 의미를 가진다.
  • ④ IE 표기법에서는 속성명 앞에 동그라미(O)로 NULL 허용 여부를 표시한다.
정답 및 해설 보기

정답 ②

SUM·AVG·MAX·MIN 같은 집계 함수는 입력값 중 NULL을 자동으로 제외하고 계산한다.

오답 ① NULL과의 비교 결과는 FALSE가 아니라 UNKNOWN이다. ③ 0은 숫자 값이고 NULL은 값이 없는 상태라 서로 다르다. ④ 속성명 앞에 동그라미(O)로 NULL 허용을 표시하는 것은 Barker 표기법이며, IE 표기법은 별도의 기호로 표시하지 않는다.

⚠️ 함정 NULL = '값 없음(Unknown)'. 비교 연산은 UNKNOWN, 집계는 제외, 0이나 공백과 다르다.


2과목 SQL 기본 및 활용 (문 11~50)

문 11. SQL의 실행 결과가 아래와 같을 때, 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은? 🎯 고난도

FIRST_NAME LAST_NAME
Jason Park
Mason Choi
Emily Lee
Eric Kim
Wilson Jung
SQL
SELECT *
FROM CUSTOMER
WHERE     ㉠      ;

[실행 결과]

FIRST_NAME LAST_NAME
Jason Park
Mason Choi
Wilson Jung

SQL
RTRIM(FIRST_NAME, 'n') = FIRST_NAME

SQL
INSTR(LOWER(FIRST_NAME), 'son') = 3

SQL
REGEXP_LIKE(FIRST_NAME, 'son$', 'i')

SQL
FIRST_NAME LIKE '_son'
정답 및 해설 보기

정답 ③

실행 결과는 'son'으로 끝나는 Jason·Mason·Wilson 세 건이다. REGEXP_LIKE(FIRST_NAME, 'son$', 'i')는 끝($)이 'son'인 문자열을 대소문자 무시(i)로 찾으므로 실행하면 정확히 이 세 행을 반환한다.

오답 ① RTRIM(FIRST_NAME, 'n')은 오른쪽 'n'을 제거하므로, 끝이 n인 이름은 원본과 달라져 제외되고 Emily·Eric만 남는다. ② INSTR(LOWER(...), 'son') = 3은 'son'이 3번째에서 시작해야 하는데 Jason·Mason만 3번째이고 Wilson은 4번째라 Wilson이 누락된다. ④ LIKE '_son'은 언더바가 정확히 한 글자라 'son'으로 끝나는 4글자만 찾으므로 한 건도 없다.

🔑 암기 정규식 — $(문자열 끝) · i 옵션(대소문자 무시) · LIKE '_'(정확히 한 글자).

문 12. 아래 SQL의 실행 결과가 다른 하나는? (단, DBMS는 오라클로 가정함)

SQL
SELECT SUBSTR(
        'AI ENGINEER', 4, 2
       )
  FROM DUAL;

SQL
SELECT SUBSTR(
        'AI ENGINEER', INSTR('AI ENGINEER', 'EN'), 2
       )
  FROM DUAL;

SQL
SELECT SUBSTR(
        'AI ENGINEER', 7, 2
       )
  FROM DUAL;

SQL
SELECT SUBSTR(
        'AI ENGINEER', -8, 2
       )
  FROM DUAL;
정답 및 해설 보기

정답 ③

오라클의 문자 위치는 1부터 시작하고, 공백도 한 글자로 센다. 'AI ENGINEER'에서 A(1) I(2) 공백(3) E(4) N(5) G(6) I(7)이다.

  • SUBSTR(..., 4, 2) = 4번째 'E'부터 2글자 → EN
  • INSTR('AI ENGINEER', 'EN') = 'EN'의 시작 위치 4 → SUBSTR(..., 4, 2)EN
  • SUBSTR(..., 7, 2) = 7번째 'I'부터 2글자 → IN
  • SUBSTR(..., -8, 2) = 오른쪽에서 8번째 'E'부터 2글자 → EN

실행하면 ①②④는 EN, ③만 IN을 반환해 ③이 다르다.

🔑 암기 SUBSTR(문자열, 시작, 길이) — 시작이 양수면 왼쪽부터, 음수면 오른쪽에서부터 센다.

문 13. 아래 SQL의 빈칸 ㉠에 들어갔을 때 오류가 발생하는 것은? (단, DBMS는 오라클로 가정함)

SQL
SELECT       ㉠     , COUNT(STUDENT_ID)
FROM STUDENT
GROUP BY MAJOR, YEAR;
  • ① MIN(STUDENT_ID)
  • ② MAJOR
  • ③ YEAR
  • ④ GPA
정답 및 해설 보기

정답 ④

GROUP BY를 쓰면 SELECT 절에는 그룹화 기준 칼럼이나 집계 함수가 적용된 표현식만 올 수 있다. MAJOR·YEAR(②③)는 그룹화 기준이라 그대로 쓸 수 있고, MIN(STUDENT_ID)(①)는 집계 함수라 통과한다. 그러나 GPA(④)는 그룹화에도 없고 집계 함수로 묶이지도 않아, 실행하면 ORA-00979: not a GROUP BY expression 오류가 난다.

🔑 암기 GROUP BY 황금 규칙 — SELECT에는 그룹 기준 칼럼 또는 집계 함수만.

문 14. 아래 SQL의 실행 결과는? (단, 현재 시각은 2025년 6월 15일 14시 35분 26초이며, DBMS는 오라클로 가정함)

SQL
SELECT
    TO_CHAR(
            SYSDATE,
            'YY-MM-DD HH24:MI:SS') AS NRM,
    TO_CHAR(
            TRUNC(SYSDATE),
            'YY-MM-DD HH24:MI:SS') AS TRC,
    TO_CHAR(
            ROUND(SYSDATE),
            'YY-MM-DD HH24:MI:SS') AS RND
FROM DUAL;

NRM TRC RND
25-06-15 14:35:26 25-06-15 14:35:26 25-06-16 00:00:00

NRM TRC RND
25-06-15 14:35:26 25-06-15 00:00:00 25-06-16 00:00:00

NRM TRC RND
25-06-15 00:00:00 25-06-15 00:00:00 25-06-16 00:00:00

NRM TRC RND
25-06-15 14:35:26 25-06-15 00:00:00 25-06-15 00:00:00
정답 및 해설 보기

정답 ②

  • TO_CHAR(SYSDATE, ...) = 현재 시각 그대로 → 25-06-15 14:35:26
  • TRUNC(SYSDATE) = 시·분·초를 잘라 자정으로 → 25-06-15 00:00:00
  • ROUND(SYSDATE) = 정오(12:00:00)를 기준으로 반올림. 현재 14시 35분은 정오 이후라 다음 날 자정으로 → 25-06-16 00:00:00

세 값을 정확히 담은 것은 ②다. 고정 시각으로 실행해 검산한 결과와 일치한다.

🔑 암기 날짜 TRUNC = 시간 잘라 자정 / ROUND = 정오 기준 반올림(이전이면 오늘 자정, 이후면 다음 날 자정).

문 15. [주문] 테이블이 있을 때, 2024년 5월부터 2024년 8월까지의 총주문금액을 구하는 SQL로 가장 적절한 것은? (단, DBMS는 오라클로 가정함)

SQL
SELECT SUM(주문금액)
  FROM 주문
  WHERE EXTRACT(YEAR FROM 주문일) = 2024
  AND 
      EXTRACT(MONTH FROM 주문일)
     IN (5, 6, 7, 8);

SQL
SELECT SUM(주문금액)
  FROM 주문
  WHERE 주문일 BETWEEN DATE '2024-05-01'
  AND DATE '2024-08-31';

SQL
SELECT SUM(주문금액)
  FROM 주문
  WHERE TO_CHAR(주문일, 'MM')
       IN ('5', '6', '7', '8');

SQL
SELECT SUM(주문금액)
  FROM 주문
  WHERE EXTRACT(YEAR FROM 주문일) = 2024
  AND 
      EXTRACT(MONTH FROM 주문일)
     IN (5, 6, 7, 8)
  AND EXTRACT(DAY FROM 주문일) <= 30;
정답 및 해설 보기

정답 ①

①은 연도를 2024로 고정하고 월을 5·6·7·8로 제한하므로, 시간 정보에 구애받지 않고 해당 기간을 정확히 추출한다.

오답 ② DATE '2024-08-31'은 시간이 생략되어 8월 31일 00:00:00을 뜻하고, BETWEEN은 양 끝값을 포함하므로 8월 31일 00시 이후에 들어온 주문이 누락될 수 있다. ③ TO_CHAR(주문일, 'MM')은 '05'~'08'을 반환하는데 비교값이 '5'~'8'이라 안 맞고, 연도 조건도 없어 다른 해의 5~8월까지 포함된다. ④ DAY <= 30 조건 때문에 31일 주문이 빠져 전체 기간을 담지 못한다.

⚠️ 함정 날짜 상수 DATE '...'는 시간이 00:00:00이라, BETWEEN 마지막 날의 그 이후 시각 데이터가 누락된다.

문 16. 아래 SQL을 실행할 때 오류가 발생하는 것의 개수로 가장 적절한 것은? 🎯 고난도

SQL
CREATE TABLE MOVIE (
     TITLE    VARCHAR2(30),
     RATING NUMBER
);
TITLE RATING
Avatar 9.0
Titanic 8.5
Inception 9.3
Matrix 8.7
Interstellar 9.1
SQL
(가) SELECT SUM(RATING)
       FROM MOVIE
       WHERE SUM(RATING) > 25;

(나) SELECT (
            SELECT RATING
            FROM MOVIE
            WHERE RATING >= 9) AS TOP_R
       FROM MOVIE;

(다) SELECT TITLE, SUM(RATING)
       FROM MOVIE
       GROUP BY RATING;

(라) SELECT TITLE, RATING
       FROM MOVIE
       GROUP BY RATING;
  • ① 1개
  • ② 2개
  • ③ 3개
  • ④ 4개
정답 및 해설 보기

정답 ④

네 쿼리 모두 오류가 난다. 실행하면 각각 다음 오류를 던진다.

  • (가) WHERE 절에는 집계 함수를 쓸 수 없다 → ORA-00934. 집계 조건은 HAVING으로.
  • (나) 스칼라 서브쿼리가 단일 행을 반환해야 하는데 RATING >= 9인 영화가 Avatar·Inception·Interstellar 세 건이라 → ORA-01427 (다중 행 반환).
  • (다) GROUP BY RATING인데 TITLE이 그룹 기준에도, 집계 함수에도 없다 → ORA-00979.
  • (라) (다)와 같은 이유로 TITLE이 GROUP BY에 없어 → ORA-00979.

🔑 암기 WHERE에 집계 함수 금지 / 스칼라 서브쿼리는 단일 행·단일 칼럼 / SELECT 비집계 칼럼은 GROUP BY 필수.

문 17. 아래 SQL을 실행했을 때 ID가 3인 행의 CNT_3의 값은?

ID VAL
1 2
2 3
3 3
4 1
5 3
SQL
SELECT ID, VAL,
     COUNT(CASE
                WHEN VAL = 3 THEN 1 END)
     OVER(
          ORDER BY ID
          ROWS BETWEEN UNBOUNDED
          PRECEDING AND CURRENT ROW)
     AS CNT_3
FROM NUMS
ORDER BY ID;
  • ① 1
  • ② 2
  • ③ 3
  • ④ 4
정답 및 해설 보기

정답 ②

ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW는 ID 순으로 첫 행부터 현재 행까지를 누적 범위로 잡는다. COUNT(CASE WHEN VAL = 3 THEN 1 END)에서 CASE는 VAL이 3이면 1, 아니면 NULL을 내고 COUNT는 NULL을 세지 않으므로, 누적 범위 안의 VAL=3 개수만 집계된다. ID 3까지의 VAL은 2·3·3이라 3이 두 번 등장하므로 CNT_3은 2다. 실행하면 ID 3 행의 CNT_3은 2로 나온다.

🔑 암기 누적 프레임 UNBOUNDED PRECEDING ~ CURRENT ROW + COUNT(CASE ...) = 조건 만족 건수의 누적.

문 18. 아래 테이블을 참고할 때 SQL의 실행 결과가 다른 하나는?

PROD_ID SALES REGION
1 900 A
2 900 B
3 850 C
4 800 D
5 800 D
6 750 E
7 750 E
8 700 F

SQL
SELECT SALES
  FROM (
        SELECT SALES, RANK() OVER (ORDER BY SALES DESC) AS RNK
         FROM TBL_SALES
        )
  WHERE RNK <= 3;

SQL
SELECT SALES
  FROM (
        SELECT SALES, DENSE_RANK() OVER (ORDER BY SALES DESC) AS RNK
         FROM TBL_SALES
        )
  WHERE RNK <= 2;

SQL
SELECT *
  FROM (
        SELECT DISTINCT SALES
         FROM TBL_SALES
         ORDER BY SALES DESC
        )
  WHERE ROWNUM <= 3;

SQL
SELECT SALES
  FROM (
        SELECT SALES
         FROM TBL_SALES
         ORDER BY SALES DESC
        )
  WHERE ROWNUM <= 3;
정답 및 해설 보기

정답 ③

  • RANK()는 공동 순위 뒤를 건너뛴다(1,1,3...). RNK <= 3이면 900·900·850 세 행.
  • DENSE_RANK()는 건너뛰지 않는다(1,1,2...). RNK <= 2면 900·900·850 세 행.
  • ④ 내림차순 정렬 후 ROWNUM <= 3 → 900·900·850.
  • ③ 인라인 뷰에서 DISTINCT SALES로 중복을 먼저 없애 900·850·800·750·700이 되고, 상위 3개는 900·850·800.

실행하면 ①②④는 {900, 900, 850}, ③만 {900, 850, 800}이라 ③이 다르다.

⚠️ 함정 DISTINCT가 중복 값(900) 자체를 하나로 합쳐 결과 건수와 구성을 바꿔 버린다.

문 19. 아래 테이블을 참고할 때 SQL의 실행 결과가 다른 하나는?

PNAME PRICE
Pen 100
Book 200
Note 100
Pen NULL
Ruler 200
NULL 300
Book NULL
Eraser 400

SQL
SELECT COUNT(DISTINCT PNAME)
  FROM PRODUCT;

SQL
SELECT COUNT(*)
  FROM (SELECT DISTINCT PNAME
          FROM PRODUCT);

SQL
SELECT COUNT(PNAME)
  FROM (SELECT DISTINCT PNAME
          FROM PRODUCT);

SQL
SELECT COUNT(*)
  FROM (SELECT DISTINCT PNAME
          FROM PRODUCT
          WHERE PNAME IS NOT NULL);
정답 및 해설 보기

정답 ②

SELECT DISTINCT PNAME의 고유값은 Pen·Book·Note·Ruler·Eraser·NULL로 NULL을 포함해 6개다.

  • COUNT(*)는 NULL 여부와 무관하게 행을 세므로 NULL 행까지 포함해 6.
  • COUNT(DISTINCT PNAME)은 NULL을 제외한 서로 다른 값만 세어 5.
  • ③ DISTINCT 결과에 NULL이 있어도 COUNT(PNAME)이 NULL을 빼므로 5.
  • ④ 처음부터 WHERE PNAME IS NOT NULL로 NULL을 제외하므로 5.

실행하면 ①③④는 5, ②만 6이라 ②가 다르다.

⚠️ 함정 COUNT(*)는 NULL 행도 세지만 COUNT(칼럼)은 NULL을 뺀다. DISTINCT는 NULL을 하나의 고유값으로 남긴다.

문 20. 아래 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은?

상품ID 카테고리 판매연도 매출
101 식품 2023 500
102 식품 2022 600
103 의류 2022 800
104 식품 2023 900
105 의류 2023 400

[실행 결과]

카테고리 판매연도 합계매출
식품 2022 600
식품 2023 1400
식품 NULL 2000
의류 2022 800
의류 2023 400
의류 NULL 1200
NULL NULL 3200
SQL
SELECT 카테고리, 판매연도,
         SUM(매출) AS 합계매출
FROM 상품
GROUP BY     ㉠      (카테고리, 판매연도)
ORDER BY 카테고리, 판매연도;
  • ① CUBE
  • ② ROLLUP
  • ③ GROUPING SETS
  • ④ GROUPING
정답 및 해설 보기

정답 ②

결과를 보면 (카테고리, 판매연도)별 상세, 카테고리별 소계(판매연도 NULL), 전체 합계(둘 다 NULL)가 순서대로 나온다. 이렇게 나열한 칼럼의 오른쪽에서 왼쪽으로 단계적 소계와 전체 총계를 만드는 것은 ROLLUP이다. 실행하면 GROUP BY ROLLUP(카테고리, 판매연도)가 이 결과를 그대로 만든다.

오답 ① CUBE라면 판매연도만의 소계(카테고리 NULL)까지 나와 행이 더 늘어난다. ④ GROUPING은 소계/총계 행을 판별하는 함수일 뿐 그룹화 연산자가 아니다.

🔑 암기 ROLLUP(A, B) = 상세 → A별 소계 → 전체 총계(한 방향). CUBE는 모든 조합.

문 21. 아래 SQL의 실행 시 출력되는 행의 개수로 가장 적절한 것은? 🎯 고난도

A_ID NAME
A001 Kim
A002 Lee
A003 Park
A004 Choi
A005 Han
A006 Jung
BOOK_NO A_ID
B100 A001
B101 A002
B102 A003
B103 A004
B104 A005
SQL
SELECT *
FROM AUTHOR A LEFT JOIN BOOK B
ON A.A_ID = B.A_ID
UNION ALL
SELECT *
FROM AUTHOR A RIGHT JOIN BOOK B
ON A.A_ID = B.A_ID
UNION ALL
SELECT *
FROM AUTHOR A INNER JOIN BOOK B
ON A.A_ID <> B.A_ID;
  • ① 34
  • ② 35
  • ③ 36
  • ④ 37
정답 및 해설 보기

정답 ③

세 쿼리를 UNION ALL로 합치므로 행 수는 단순 합산된다.

  • LEFT JOIN: AUTHOR 6행 중 A001~A005는 매칭되고 A006은 NULL로 붙어 6행.
  • RIGHT JOIN: BOOK 5행이 모두 AUTHOR에 있어 5행.
  • INNER JOIN ON A.A_ID <> B.A_ID: 6 × 5 = 30 조합에서 같은 A_ID로 매칭되는 5건을 뺀 25행.

6 + 5 + 25 = 36. 실행하면 36행을 반환한다.

🔑 암기 비등가 조인(<>) 건수 = (카티션 곱) − (일치 건수). UNION ALL은 중복 제거 없이 합산.

문 22. 아래 SQL의 실행 결과는? 🎯 고난도

고객번호 고객이름
C001 김수연
C002 박민호
C003 이영숙
고객번호 전화번호
C001 02-123-4567
C001 010-1234-4567
C002 031-987-6543
C003 010-9865-1234
C003 010-9876-5432
SQL
SELECT 고객이름
FROM 고객
WHERE EXISTS (
     SELECT 1
     FROM 고객정보
     WHERE 고객정보.고객번호 = 고객.고객번호
            AND 전화번호
            NOT BETWEEN '010-0000-0000'
            AND '010-9999-9999')
AND NOT EXISTS (
     SELECT 1
     FROM 고객정보
     WHERE 고객정보.고객번호 = 고객.고객번호
            AND 전화번호
            BETWEEN '010-0000-0000'
            AND '010-9999-9999');

고객이름
김수연

고객이름
박민호

고객이름
김수연
박민호

고객이름
이영숙
정답 및 해설 보기

정답 ②

첫 EXISTS는 '010이 아닌 전화번호가 하나라도 있는' 고객, 둘째 NOT EXISTS는 '010 범위 전화번호가 하나도 없는' 고객을 뜻한다. 두 조건을 모두 만족하려면 010을 전혀 쓰지 않고 다른 번호만 쓰는 고객이어야 한다.

  • 김수연(C001): 02 번호도 있지만 010 번호도 있어 탈락.
  • 박민호(C002): 031 번호만 있고 010은 없어 통과.
  • 이영숙(C003): 010 번호만 있어 탈락.

실행하면 박민호 한 건을 반환한다.

🔑 암기 EXISTS = 조건 만족 행이 하나라도 있는지 / NOT EXISTS = 조건 만족 행이 하나도 없는지.

문 23. 다음 두 테이블에 대해 LEFT JOIN, RIGHT JOIN, FULL OUTER JOIN, INNER JOIN을 각각 실행하여 생성되는 결과의 레코드 수를 구한 뒤, 이 값을 모두 합한 결과는?

A_KEY
A
C
E
G
B_KEY
B
D
F
G
SQL
(가) SELECT A_KEY, B_KEY
       FROM A LEFT JOIN B
       ON A_KEY = B_KEY;
(나) SELECT A_KEY, B_KEY
       FROM A RIGHT JOIN B
       ON A_KEY = B_KEY;
(다) SELECT A_KEY, B_KEY
       FROM A FULL OUTER JOIN B
       ON A_KEY = B_KEY;
(라) SELECT A_KEY, B_KEY
       FROM A INNER JOIN B
       ON A_KEY = B_KEY;
  • ① 13
  • ② 14
  • ③ 15
  • ④ 16
정답 및 해설 보기

정답 ④

두 테이블의 공통 값은 G 하나뿐이다.

  • LEFT JOIN: A의 4행 모두 출력(매칭 안 되면 NULL) → 4행.
  • RIGHT JOIN: B의 4행 모두 출력 → 4행.
  • FULL OUTER JOIN: 양쪽 합집합(4 + 4 − 공통 1) → 7행.
  • INNER JOIN: 공통 값만 → 1행.

4 + 4 + 7 + 1 = 16. 실행해도 16으로 나온다.

🔑 암기 중복 키가 없을 때 L + R + F + I = 2 × (N + M). 여기선 N = M = 4라 16.

문 24. 두 테이블 간 동일한 이름을 가진 칼럼의 동등 조인(EQUI JOIN)을 수행하는 방식의 JOIN은?

  • ① INNER JOIN
  • ② CROSS JOIN
  • ③ OUTER JOIN
  • ④ NATURAL JOIN
정답 및 해설 보기

정답 ④

NATURAL JOIN은 두 테이블에서 이름이 같은 모든 칼럼을 자동으로 찾아 동등(=) 조인한다. 조건을 명시하지 않아도 동일 칼럼명을 기준으로 EQUI JOIN이 수행된다.

오답 ① INNER JOIN도 EQUI JOIN을 할 수 있지만 조건을 직접 지정해야 한다. ② CROSS JOIN은 조건 없는 카티션 곱이다. ③ OUTER JOIN은 매칭되지 않는 행도 NULL로 포함한다.

🔑 암기 NATURAL JOIN = 같은 이름 칼럼 자동 EQUI 조인 / USING = 지정한 같은 이름 칼럼으로 조인.

문 25. 아래 테이블을 참고할 때 SQL의 실행 결과가 다른 하나는? (단, DBMS는 오라클로 가정함)

T1.VAL
A
B
C
D
T2.VAL
A
B

SQL
SELECT VAL
  FROM T1
  MINUS
SELECT VAL
  FROM T2
  ORDER BY 1;

SQL
SELECT A.VAL
  FROM T1 A LEFT JOIN T2 B
  ON A.VAL = B.VAL
  WHERE B.VAL IS NULL
  ORDER BY 1;

SQL
SELECT A.VAL
  FROM T1 A, T2 B
  WHERE A.VAL <> B.VAL
  ORDER BY 1;

SQL
SELECT A.VAL
  FROM T1 A
  WHERE A.VAL NOT IN (SELECT VAL FROM T2)
  ORDER BY 1;
정답 및 해설 보기

정답 ③

T1에서 T2를 뺀 차집합은 C·D 둘이다. ①(MINUS), ②(LEFT JOIN 후 IS NULL), ④(NOT IN)는 모두 C·D를 반환한다.

③은 차집합이 아니라 카티션 곱이다. T1 × T2의 8개 조합에서 값이 서로 다른 행만 남긴다. C는 T2의 A·B 모두와 다르니 두 번, D도 A·B와 달라 두 번, A는 B와만 달라 한 번, B는 A와만 달라 한 번 나온다. 실행하면 A·B·C·C·D·D로 6행이 되어 혼자 다르다.

🔑 암기 차집합 = MINUS = LEFT JOIN + IS NULL = NOT IN = NOT EXISTS. A.VAL <> B.VAL은 차집합이 아니다.

문 26. 집합 연산자 INTERSECT에 대한 설명으로 가장 적절한 것은?

  • ① 두 결과 집합의 교집합을 구하며, 중복된 행을 하나로 축소해 반환한다.
  • ② 두 결과 집합의 교집합을 구하며, 중복된 행도 모두 반환한다.
  • ③ 두 결과 집합의 합집합을 구하며, 중복된 행을 하나로 축소해 반환한다.
  • ④ 두 결과 집합의 합집합을 구하며, 중복된 행도 모두 반환한다.
정답 및 해설 보기

정답 ①

INTERSECT는 두 결과에 공통으로 존재하는 행만 반환하는 교집합 연산자이며, 집합 연산자(UNION·INTERSECT·MINUS)는 뒤에 ALL을 붙이지 않는 한 기본으로 중복을 하나로 제거한다.

🔑 암기 UNION/INTERSECT/MINUS = 기본은 중복 제거. UNION ALL만 중복 보존.

문 27. 아래 테이블과 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은? 🎯 고난도

M_ID NAME
101 LEE
103 KIM
104 CHOI
105 LEE
107 KIM

[실행 결과]

M_ID NAME
104 CHOI
105 LEE
107 KIM
SQL
SELECT A.M_ID, A.NAME
FROM MEMBER A LEFT JOIN MEMBER B
ON A.NAME = B.NAME
AND (     ㉠      )
WHERE B.M_ID IS NULL
ORDER BY A.M_ID;

SQL
A.M_ID <= B.M_ID

SQL
A.M_ID < B.M_ID

SQL
A.M_ID >= B.M_ID

SQL
A.M_ID > B.M_ID
정답 및 해설 보기

정답 ②

실행 결과는 NAME별 가장 큰 M_ID만 남은 모습이다. 빈칸에 A.M_ID < B.M_ID를 넣으면, 같은 이름에서 자신보다 큰 M_ID를 가진 B와 조인된다. 그런 B가 있으면 매칭되어 B.M_ID IS NULL에서 걸러지고, 없으면(=내가 그 이름의 최댓값) B가 NULL이 되어 살아남는다. 실행하면 104·105·107이 남아 결과와 일치한다.

오답 ① ③ <=·>=는 자기 자신(같은 행)까지 매칭되어 항상 B가 존재하므로 한 건도 안 남는다. ④ >는 자신보다 작은 값이 없는 행, 즉 최솟값을 골라 결과와 다르다.

🔑 암기 최댓값 셀프 조인 — A < B로 LEFT JOIN 후 B IS NULL(나보다 큰 동명이인이 없음 = 최댓값).

문 28. 부서별로 사원수가 가장 많은 부서를 찾기 위한 SQL을 수행하고자 한다. 빈칸 ㉠, ㉡에 들어갈 내용으로 가장 적절한 것은?

SQL
SELECT 부서
FROM 직원
GROUP BY 부서
    ㉠     >=      ㉡      (
                SELECT COUNT(*)
                FROM 직원
                GROUP BY 부서);
  • ① HAVING COUNT(*), ANY
  • ② HAVING COUNT(*), ALL
  • ③ ORDER BY COUNT(*), ANY
  • ④ ORDER BY COUNT(*), ALL
정답 및 해설 보기

정답 ②

그룹별 집계 결과를 조건으로 거르므로 HAVING이 맞고, '가장 많은' 부서는 모든 부서 인원수와 비교해 크거나 같아야 하니 >= ALL이다. >= ALL(...)은 서브쿼리의 모든 값보다 크거나 같다는 뜻이라 결국 최댓값과 같은 부서만 남는다. 실행해 보면 ALL은 최다 부서만, ANY는 최소 이상이라 거의 모든 부서가 나온다.

🔑 암기 >= ALL = 최댓값 이상(=최댓값) / >= ANY = 최솟값 이상.

문 29. 아래 SQL에 대한 설명으로 가장 적절하지 않은 것은?

문29 데이터 모델 — 회원(회원번호 PK, 회원명·가입일)·메일발송(메일ID PK, 회원번호(FK)·이벤트번호(FK), 발송일자·발송상태)·이벤트(이벤트번호 PK, 이벤트명·이벤트기간). 회원:메일발송 = 1:N, 이벤트:메일발송 = 1:N

SQL
SELECT A.회원번호, A.회원명
FROM 회원 A
WHERE NOT EXISTS (
     SELECT 1 ---------------------- ㉠
     FROM 메일발송 M JOIN 이벤트 E
     ON M.이벤트번호 = E.이벤트번호
     WHERE M.회원번호 = A.회원번호
     AND M.발송일자 < DATE '2020-01-20'
);
  • ① 2020-01-20 이전에 메일 발송 기록이 전혀 없는 회원을 조회한다.
  • ② ㉠ 위치에는 EXISTS 절의 특성상 실제 칼럼명 대신 상수를 사용해도 결과에는 영향이 없다.
  • ③ 서브쿼리의 FROM 절에 여러 테이블을 나열할 때 조인 조건이 없어도 NOT EXISTS의 결과는 변하지 않는다.
  • ④ M.회원번호 = A.회원번호 조건은 서브쿼리에서 외부 쿼리의 A.회원번호 값을 참조하여 비교하는 역할을 한다.
정답 및 해설 보기

정답 ③

M.이벤트번호 = E.이벤트번호는 필수 조인 조건이다. 이를 누락하면 메일발송과 이벤트가 카티션 곱으로 결합해, 이벤트 일치 여부와 무관하게 행이 존재하는 것으로 판단된다. 그러면 NOT EXISTS의 판정이 뒤집혀 결과가 달라질 수 있으므로 "조인 조건이 없어도 결과는 변하지 않는다"는 ③이 틀렸다.

오답 ① NOT EXISTS로 발송 이력이 없는 회원을 찾는다. ② EXISTS는 행의 존재 여부만 보므로 SELECT 절에 1·'X' 같은 상수를 써도 무방하다. ④ M.회원번호 = A.회원번호는 외부 쿼리 값을 참조하는 상관(Correlated) 서브쿼리의 핵심 조건이다.

⚠️ 함정 여러 테이블을 나열하고 조인 조건을 빼면 카티션 곱이 생겨 EXISTS/NOT EXISTS 판정이 뒤집힐 수 있다.

문 30. 학생 테이블에서 학과별로 점수가 가장 높은 학생을 조회하는 SQL로 가장 적절하지 않은 것은?

SQL
SELECT 학과, 이름, 점수
FROM 학생 S1
WHERE NOT EXISTS (
        SELECT 1
        FROM 학생 S2
        WHERE S2.학과 = S1.학과
        AND S2.점수 > S1.점수
  );

SQL
SELECT S1.학과, S1.이름, S1.점수
FROM 학생 S1
JOIN (
        SELECT 학과, MAX(점수) AS 최고점수
        FROM 학생
        GROUP BY 학과
     ) S2
ON S1.학과 = S2.학과
AND S1.점수 = S2.최고점수;

SQL
SELECT 학과, 이름, 점수
FROM (
        SELECT 학과, 이름, 점수,
            RANK() OVER (PARTITION BY 학과
                ORDER BY 점수 DESC) AS RK
        FROM 학생
     ) T
WHERE T.RK = 1;

SQL
SELECT 학과, 이름, 점수
FROM 학생 S1
WHERE S1.점수 >= ANY (
        SELECT 점수
        FROM 학생 S2
        WHERE S2.학과 = S1.학과
  );
정답 및 해설 보기

정답 ④

>= ANY는 비교 집합 중 하나라도 만족하면 참이라, '점수 >= ANY(같은 학과 점수들)'은 학과 최저점 이상이면 모두 통과한다. 결국 학과 내 최저점 학생까지 포함되어 '가장 높은 학생'을 찾는 목적과 어긋난다. 최고점만 구하려면 >= ALL이어야 한다.

오답 ① 나보다 점수 높은 같은 학과 학생이 없는 사람(NOT EXISTS) = 1등. ② 서브쿼리로 학과별 MAX를 구해 조인. ③ RANK() = 1로 학과별 1등. 모두 올바른 패턴이다.

🔑 암기 그룹 최댓값 = NOT EXISTS(더 큰 값 없음) · MAX 조인 · RANK() = 1 · >= ALL. >= ANY는 최솟값 이상이라 오답.

문 31. SQL 명령어의 종류가 올바르게 연결되지 않은 것은?

  • ① DDL – CREATE
  • ② DCL – GRANT
  • ③ DML – DROP
  • ④ TCL – ROLLBACK
정답 및 해설 보기

정답 ③

DROP은 객체 구조를 다루는 데이터 정의어(DDL)다. DML은 데이터를 다루는 SELECT·INSERT·UPDATE·DELETE·MERGE이므로 DROP과 묶인 ③이 잘못된 연결이다.

🔑 암기 DDL = CREATE·ALTER·DROP·RENAME(TRUNCATE) / DML = SELECT·INSERT·UPDATE·DELETE·MERGE / DCL = GRANT·REVOKE / TCL = COMMIT·ROLLBACK·SAVEPOINT.

문 32. 정규 표현식 메타 문자에 대한 설명으로 가장 적절하지 않은 것은?

  • ① . : 공백 문자를 제외한 임의의 한 문자와 일치
  • ② $ : 입력 문자열의 끝 위치와 일치
  • ③ ^ : 입력 문자열의 시작 위치와 일치
  • ④ \w : 영문자, 숫자, 밑줄( _ ) 중 하나와 일치
정답 및 해설 보기

정답 ①

점(.)은 개행 문자(\n)를 제외한 임의의 한 문자와 일치한다. 스페이스·탭 같은 공백 문자는 개행이 아니므로 점에 포함된다. 따라서 "공백 문자를 제외"라고 한 ①이 틀렸다.

🔑 암기 . = 개행만 제외한 임의의 한 문자(공백 포함) / ^ 시작 · $ 끝 / \w = 영문자·숫자·밑줄.

문 33. 아래 실행 결과를 출력하는 SQL로 가장 적절한 것은? (단, DBMS는 오라클로 가정함)

PRODUCT_ID PERIOD AMOUNT
P1 1월 100
P1 2월 200
P2 1월 150
P2 2월 250

[실행 결과]

PRODUCT_ID PERIOD AMOUNT
P1 1월 100
P1 2월 200
P1 소계 300
P2 1월 150
P2 2월 250
P2 소계 400

SQL
SELECT PRODUCT_ID,
       PERIOD,
       SUM(AMOUNT) AS AMOUNT
  FROM SALES_RESULTS
  GROUP BY PERIOD, ROLLUP (PRODUCT_ID)
  ORDER BY PRODUCT_ID, PERIOD;

SQL
SELECT NVL(PRODUCT_ID, '전체') AS PRODUCT_ID,
       NVL(PERIOD, '소계') AS PERIOD,
       SUM(AMOUNT) AS AMOUNT
  FROM SALES_RESULTS
  GROUP BY CUBE (PRODUCT_ID, PERIOD)
  ORDER BY PRODUCT_ID, PERIOD;

SQL
SELECT NVL(PRODUCT_ID, '전체') AS PRODUCT_ID,
       NVL(PERIOD, '소계') AS PERIOD,
       SUM(AMOUNT) AS AMOUNT
  FROM SALES_RESULTS
  GROUP BY GROUPING SETS (
       (PRODUCT_ID),
       (PERIOD)
  )
  ORDER BY PRODUCT_ID, PERIOD;

SQL
SELECT NVL(PRODUCT_ID, '전체') AS PRODUCT_ID,
       NVL(PERIOD, '소계') AS PERIOD,
       SUM(AMOUNT) AS AMOUNT
  FROM SALES_RESULTS
  GROUP BY GROUPING SETS (
       (PRODUCT_ID, PERIOD),
       (PRODUCT_ID)
  )
  ORDER BY PRODUCT_ID, PERIOD;
정답 및 해설 보기

정답 ④

결과는 상품별 월별 상세와 상품별 소계만 있고 전체 총계는 없다. 이렇게 원하는 집계 조합만 골라 만드는 것은 GROUPING SETS이며, (PRODUCT_ID, PERIOD) 상세와 (PRODUCT_ID) 소계 두 조합을 지정한 ④가 정확하다. 실행하면 소계 행의 PERIOD가 NULL이라 NVL(PERIOD, '소계')로 '소계'가 찍힌다.

오답 ① GROUP BY PERIOD, ROLLUP(PRODUCT_ID)는 기간 기준이라 요구한 상품별 소계가 안 나온다. ② CUBE는 기간별 소계·전체 총계까지 추가로 만든다. ③ (PRODUCT_ID), (PERIOD)만 묶어 상품별 월별 상세가 빠진다.

🔑 암기 GROUPING SETS는 괄호로 지정한 그룹 조합만 개별 집계(방향성·총계 강제 없음).

문 34. 아래 SQL의 실행 결과는?

직원ID 부서코드 직원명 급여
001 100 김수연 2500
002 100 박민호 3000
003 200 이영숙 4500
004 200 오지훈 3000
005 200 한진아 2500
006 300 장민호 4500
007 300 최영희 3000
SQL
SELECT Y.직원ID, Y.부서코드, Y.직원명, Y.급여
FROM (SELECT 직원ID,
          MAX(급여) OVER (PARTITION BY 부서코드)
          AS 최고급여
     FROM 직원
) X
JOIN 직원 Y ON X.직원ID = Y.직원ID
        AND X.최고급여 = Y.급여;

직원ID 부서코드 직원명 급여
003 200 이영숙 4500
006 300 장민호 4500

직원ID 부서코드 직원명 급여
002 100 박민호 3000
003 200 이영숙 4500
006 300 장민호 4500
007 300 최영희 3000

직원ID 부서코드 직원명 급여
001 100 김수연 2500
005 200 한진아 2500
007 300 최영희 3000

직원ID 부서코드 직원명 급여
002 100 박민호 3000
003 200 이영숙 4500
006 300 장민호 4500
정답 및 해설 보기

정답 ④

서브쿼리에서 MAX(급여) OVER (PARTITION BY 부서코드)로 부서별 최고급여를 각 직원에 붙인다(100=3000, 200=4500, 300=4500). 메인쿼리에서 X.최고급여 = Y.급여로 자기 부서 최고급여와 같은 직원만 남긴다. 실행하면 박민호(002)·이영숙(003)·장민호(006) 세 명이 나와 ④와 일치한다.

🔑 암기 MAX() OVER (PARTITION BY ...)로 그룹 최댓값을 각 행에 확장한 뒤, 그 값과 같은 행만 골라 그룹별 최고 행을 추출한다.

문 35. 아래 SQL의 실행 결과는? (단, DBMS는 오라클로 가정함)

SQL
SELECT NVL(COUNT(*), -1)
FROM DUAL
WHERE 1 = 2;
  • ① -1
  • ② 0
  • ③ NULL
  • ④ 1
정답 및 해설 보기

정답 ②

WHERE 1 = 2라 입력 행이 0건이다. 그래도 집계 함수는 입력이 없어도 결과 행을 1개 반환하고, 이때 COUNT(*)는 행을 세는 함수라 0을 돌려준다. 따라서 NVL(0, -1)이 되는데, NVL은 첫 인자가 NULL일 때만 두 번째 값으로 바꾸므로 0은 그대로 0이 된다. 실행하면 0이 나온다.

⚠️ 함정 COUNT(*)는 데이터가 없으면 NULL이 아니라 0을 반환한다(MAX·MIN·SUM은 NULL). 그래서 NVL이 작동하지 않는다.

문 36. 아래에서 설명하는 서브쿼리의 종류로 가장 적절한 것은?

메인쿼리의 조건부에서 두 개 이상의 칼럼을 한꺼번에 조회하고 비교해야 할 때 활용된다. 서브쿼리의 실행 결과로 여러 개의 칼럼을 반환한다. 서브쿼리와 메인쿼리에서 비교하려는 칼럼들의 개수 및 위치가 반드시 일치해야 한다.

  • ① 다중행 서브쿼리
  • ② 다중칼럼 서브쿼리
  • ③ 단일칼럼 서브쿼리
  • ④ 단일행 서브쿼리
정답 및 해설 보기

정답 ②

둘 이상의 칼럼을 하나의 조합으로 묶어 비교하고, 메인쿼리와 서브쿼리의 칼럼 개수·위치가 일치해야 하는 것은 다중칼럼 서브쿼리다. (부서코드, 급여) IN (SELECT 부서코드, MAX(급여) ...)처럼 쓴다.

🔑 암기 다중칼럼 서브쿼리 = 칼럼 조합 단위 비교, 메인·서브 칼럼 개수·위치 일치 필수.

문 37. 아래 SQL의 실행 결과는?

T1.ID T1.NAME
10 John
20 Mary
T2.ID T2.NAME
10 Jack
20 James
30 Luke
40 Laura
SQL
MERGE INTO T1
USING T2
ON (T1.ID = T2.ID)
WHEN MATCHED THEN
    UPDATE SET T1.NAME = T2.NAME
    WHERE T1.ID <= 10
WHEN NOT MATCHED THEN
    INSERT (ID, NAME) VALUES (T2.ID, T2.NAME)
    WHERE T2.ID >= 40;

SELECT * FROM T1;

ID NAME
10 John
20 Mary

ID NAME
10 Jack

ID NAME
20 James
40 Laura

ID NAME
10 Jack
20 Mary
40 Laura
정답 및 해설 보기

정답 ④

WHEN MATCHED는 ID가 일치하는 10·20 중 T1.ID <= 10인 10만 갱신해 John이 Jack이 되고, 20은 Mary 그대로 남는다. WHEN NOT MATCHED는 T1에 없는 30·40 중 T2.ID >= 40인 40(Laura)만 삽입하고 30은 제외한다. 실행 후 T1은 10/Jack, 20/Mary, 40/Laura가 되어 ④와 일치한다.

🔑 암기 MERGE — MATCHED는 UPDATE, NOT MATCHED는 INSERT. 각 절의 WHERE 조건을 만족하는 행만 작업한다.

문 38. 트랜잭션에서 데이터를 수정하고 아직 COMMIT을 수행하지 않았다. 이에 관한 설명으로 가장 적절하지 않은 것은? (단, DBMS는 오라클로 가정함)

  • ① 다른 세션은 COMMIT 전이라도 수정된 내용을 조회로 확인할 수 있다.
  • ② 다른 세션은 현재 수정 중인 동일한 행을 동시에 수정할 수 없다.
  • ③ 같은 세션에서는 아직 COMMIT하지 않았어도 본인이 수정한 내용을 조회할 수 있다.
  • ④ 같은 세션에서 ROLLBACK을 실행하면 이번 트랜잭션의 모든 변경사항이 취소된다.
정답 및 해설 보기

정답 ①

오라클은 COMMIT되지 않은 데이터를 다른 세션이 읽지 못하게 막는다(읽기 일관성). 즉 더티 리드(Dirty Read)를 허용하지 않으므로, 다른 세션은 COMMIT 이후에야 변경 내용을 볼 수 있다. 따라서 "COMMIT 전이라도 조회로 확인할 수 있다"는 ①이 틀렸다.

오답 ② 수정 중인 행은 잠금 때문에 다른 세션이 동시에 수정할 수 없다. ③ 같은 세션은 자기 변경을 바로 본다. ④ ROLLBACK은 트랜잭션의 모든 변경을 취소한다.

🔑 암기 오라클은 더티 리드 불허 — 미커밋 변경은 본인 세션만 조회 가능.

문 39. 아래 SQL의 실행 결과는? (단, DBMS는 오라클로 가정함)

SQL
CREATE TABLE PAIRS (
     VAL1 CHAR(50),
     VAL2 CHAR(50)
);

INSERT INTO PAIRS (VAL1, VAL2)
VALUES ('cat', 'cat');
INSERT INTO PAIRS (VAL1, VAL2)
VALUES ('dog', '      dog');
INSERT INTO PAIRS (VAL1, VAL2)
VALUES ('fish', 'fish      ');
INSERT INTO PAIRS (VAL1, VAL2)
VALUES ('bird', '      bird      ');

SELECT COUNT(*)
FROM PAIRS
WHERE VAL1 = VAL2;
  • ① 1
  • ② 2
  • ③ 3
  • ④ 4
정답 및 해설 보기

정답 ②

CHAR는 고정 길이라 빈자리에 공백이 채워지고, 비교할 때 뒤쪽 공백은 무시하지만 앞쪽 공백은 글자로 본다.

  • 'cat' = 'cat' → 같음
  • 'dog' vs ' dog' → 앞쪽 공백이 있어 다름
  • 'fish' vs 'fish ' → 뒤쪽 공백은 무시되어 같음
  • 'bird' vs ' bird ' → 앞쪽 공백이 있어 다름

같은 행은 'cat'·'fish' 두 건이라 실행하면 2가 나온다.

⚠️ 함정 CHAR 비교 — 뒤쪽 공백은 무시(blank-padded), 앞쪽 공백은 비교에 포함된다.

문 40. 아래 SQL의 실행 결과를 순서대로 나열한 것은? 🎯 고난도

PROD_ID PRICE
P001 500
P001 700
P001 NULL
P002 900
P002 1100
SQL
(가) SELECT SUM(PRICE)/COUNT(PRICE)
      FROM SALES_INFO
      WHERE PROD_ID = 'P001';

(나) SELECT SUM(PRICE)/COUNT(*)
      FROM SALES_INFO
      WHERE PRICE > (
                    SELECT MIN(PRICE)
                       FROM SALES_INFO
                       WHERE PROD_ID = 'P002');
  • ① 400, 1100
  • ② 400, 600
  • ③ 600, 1100
  • ④ 600, 600
정답 및 해설 보기

정답 ③

  • (가) P001의 PRICE는 500·700·NULL이다. SUM(PRICE) = 1200, COUNT(PRICE)는 NULL을 빼고 2 → 1200 / 2 = 600.
  • (나) 서브쿼리 MIN(PRICE) WHERE PROD_ID = 'P002' = 900이라 조건은 PRICE > 900. 전체에서 900보다 큰 값은 P002의 1100뿐이다. SUM(PRICE) = 1100, COUNT(*) = 1 → 1100 / 1 = 1100.

순서대로 600, 1100이라 ③이다. 실행해도 같다.

🔑 암기 COUNT(칼럼)은 NULL 제외, COUNT(*)는 전체 행. 분모가 무엇이냐로 평균이 갈린다.

문 41. 오라클 계층형 질의에 대한 설명으로 가장 적절하지 않은 것은?

SQL
SELECT ...
FROM 테이블명
WHERE 조건식 AND 조건식
START WITH 조건식
CONECT BY[NOCYCLE] 조건식 AND 조건식
ORDER SINGLES BY 칼럼, 칼럼 ...;
  • ① START WITH 절은 계층형 질의의 루트 노드를 지정하는 데 사용한다.
  • ② CONNECT BY 절은 자식 노드들의 계층 단계(LEVEL)를 지정하는 데 사용한다.
  • ③ ORDER SIBLINGS BY 절은 같은 부모를 가진 형제 노드들 간의 정렬 순서를 지정하는 데 사용한다.
  • ④ WHERE 절은 계층형 질의의 전체 결과 집합에 대해 조건을 적용하는 데 사용한다.
정답 및 해설 보기

정답 ②

CONNECT BY 절은 부모와 자식 간의 관계와 전개 방향을 정의한다(PRIOR 부모키 = 자식키 형태). 계층 단계인 LEVEL은 개발자가 지정하는 것이 아니라, 전개되면서 오라클이 자동으로 부여하는 의사 칼럼(Pseudo Column)이다. 따라서 "CONNECT BY가 LEVEL을 지정한다"는 ②가 틀렸다.

오답 ① START WITH는 루트 노드 지정. ③ ORDER SIBLINGS BY는 형제 노드 정렬. ④ WHERE는 전개 후 최종 결과 집합에 조건 적용.

🔑 암기 START WITH(시작점) · CONNECT BY(부모-자식 관계·방향) · LEVEL(자동 부여 의사 칼럼) · ORDER SIBLINGS BY(형제 정렬).

문 42. 아래 테이블과 실행 결과를 참고할 때 SQL의 빈칸 ㉠에 들어갈 내용으로 가장 적절한 것은?

SQL
CREATE TABLE 일자별매출 (
     일자      NUMBER,
     상품코드 VARCHAR2(2),
     매출금액 NUMBER
);
일자 상품코드 매출금액
20250101 A 150
20250102 B 200
20250102 A 250
20250103 B 500
20250104 C 450
20250105 D 150
SQL
SELECT A.일자, A.상품코드,
          ㉠     매출금액
FROM 일자별매출 A
ORDER BY A.일자, A.상품코드;

[실행 결과]

일자 상품코드 매출금액
20250101 A 150
20250102 A 600
20250102 B 600
20250103 B 1100
20250104 C 1550
20250105 D 1700

SQL
SUM(매출금액) OVER(PARTITION BY 상품코드
  ORDER BY 일자)

SQL
SUM(매출금액) OVER(ORDER BY 일자 ROWS
  BETWEEN UNBOUNDED PRECEDING AND CURRENT
  ROW)

SQL
(SELECT SUM(B.매출금액) FROM 일자별매출 B
  WHERE B.일자 <= A.일자)

SQL
SUM(매출금액) OVER(PARTITION BY 상품코드
  ORDER BY 일자 ROWS BETWEEN UNBOUNDED
  PRECEDING AND CURRENT ROW)
정답 및 해설 보기

정답 ③

실행 결과는 같은 일자에 속한 모든 행이 동일한 누적값을 가진다(20250102의 A·B가 모두 600). 이는 '현재 행의 일자보다 작거나 같은 모든 매출의 합'을 논리적으로 구한 것이다. ③의 상관 서브쿼리 WHERE B.일자 <= A.일자가 정확히 이 값을 만든다. 실행하면 결과표와 일치한다.

오답 ① ④ PARTITION BY 상품코드라 상품별로 누적해 같은 일자라도 값이 갈린다. ② ROWS는 물리적 행 단위 누적이라 같은 일자의 위·아래 행 값이 달라진다(행 기준이라 600·600이 안 나옴). 같은 결과를 윈도우 함수로 내려면 RANGE를 써야 한다.

🔑 암기 ROWS(물리적 행 누적) vs RANGE·상관 서브쿼리 <=(논리적 값 누적, 동점은 같은 값).

문 43. 아래 SQL의 실행 결과는?

PLAYER_ID SCORE
P01 10
P02 20
P03 20
P04 30
P05 100
SQL
SELECT TOTAL_PLAYERS
FROM (
     SELECT P.*,
         COUNT(*) OVER (
            ORDER BY SCORE
            RANGE BETWEEN UNBOUNDED
            PRECEDING AND CURRENT ROW)
         AS TOTAL_PLAYERS
     FROM PLAYERS P
) T
WHERE T.PLAYER_ID = 'P05';
  • ① 1
  • ② 2
  • ③ 4
  • ④ 5
정답 및 해설 보기

정답 ④

COUNT(*) OVER (ORDER BY SCORE RANGE BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)는 점수 기준으로 처음부터 현재 행까지 누적 개수를 센다. RANGE라 현재 행의 SCORE 이하(동점 포함)인 모든 행이 대상이다. P05의 SCORE는 100으로 전체 최고점이라 SCORE <= 100인 5개 행이 모두 집계된다. 실행하면 5가 나온다.

🔑 암기 마지막 순위의 RANGE 누적은 결국 전체 건수와 같다.

문 44. 아래 SQL의 실행 결과는? (단, DBMS는 오라클로 가정함)

NAME APR MAY JUN
North 120 180 130
South 200 210 190
West 150 170 160
SQL
SELECT NAME,
            SALES_MONTH,
            SALES_VOLUME
FROM SALES
UNPIVOT (
     SALES_VOLUME
     FOR SALES_MONTH IN (APR, MAY, JUN)
     )
ORDER BY NAME, SALES_MONTH;

NAME SALES_MONTH SALES_VOLUME
North APR 120
North JUN 130
North MAY 180
South APR 200
South JUN 190
South MAY 210
West APR 150
West JUN 160
West MAY 170

NAME SALES_MONTH SALES_VOLUME
North APR 120
North MAY 180
North JUN 130
South APR 200
South MAY 210
South JUN 190
West APR 150
West MAY 170
West JUN 160

NAME SALES_MONTH SALES_VOLUME
North APR 470
South MAY 560
West JUN 480

NAME SALES_MONTH SALES_VOLUME
North APR 120
South APR 200
West APR 150
North MAY 180
South MAY 210
West MAY 170
North JUN 130
South JUN 190
West JUN 160
정답 및 해설 보기

정답 ①

UNPIVOT으로 APR·MAY·JUN 열이 행으로 펼쳐지면 SALES_MONTH 칼럼에는 'APR'·'MAY'·'JUN'이라는 문자열이 들어간다. ORDER BY NAME, SALES_MONTH는 이 문자열을 사전순으로 정렬하므로 APR → JUN → MAY 순서가 된다. 실행하면 지역별로 APR·JUN·MAY 순으로 9행이 나와 ①과 일치한다.

오답 ② 시간순(APR·MAY·JUN)으로 정렬한 함정. ③ 값이 합쳐진 잘못된 결과. ④ NAME이 아니라 월 우선으로 정렬한 형태.

⚠️ 함정 UNPIVOT으로 만들어진 기준 칼럼 값은 문자열이라, ORDER BY가 사전순(APR·JUN·MAY)으로 정렬한다(시간순 아님).

문 45. RENAME COLUMN 명령어에 대한 설명으로 가장 적절하지 않은 것은? (단, DBMS는 오라클로 가정함)

  • ① 칼럼은 제약 조건이 설정되어 있어도 이름을 변경할 수 있다.
  • ② RENAME COLUMN 명령어는 칼럼명뿐만 아니라 데이터 타입도 변경할 수 있다.
  • ③ 기본값(DEFAULT)이 지정된 칼럼도 RENAME COLUMN으로 이름을 변경할 수 있다.
  • ④ RENAME COLUMN으로 칼럼명을 변경해도 기존 칼럼의 데이터 타입과 속성은 그대로 유지된다.
정답 및 해설 보기

정답 ②

RENAME COLUMN은 칼럼 이름만 바꾼다. 데이터 타입·크기·속성을 바꾸려면 ALTER TABLE ... MODIFY를 써야 한다. 따라서 "데이터 타입도 변경할 수 있다"는 ②가 틀렸다.

🔑 암기 ALTER TABLE — RENAME COLUMN(이름) · MODIFY(타입/크기/속성) · ADD(추가) · DROP COLUMN(삭제).

문 46. 아래 SQL에 대한 적절한 설명을 모두 고른 것은? 🎯 고난도

SQL
SELECT 제품명, 가격
FROM 제품
ORDER BY 가격 DESC NULLS LAST
OFFSET 3 ROWS
FETCH FIRST 5 ROWS ONLY;
텍스트
(가) FETCH FIRST 5 ROWS ONLY는 OFFSET 이후의 행 중 최대 5개까지만 출력한다.
(나) ORDER BY 절에 의해 가격이 높은 순으로 정렬되고, NULL 값은 항상 결과의 마지막에 위치한다.
(다) OFFSET과 FETCH는 ORDER BY보다 먼저 적용되므로, 정렬되기 전 순서 기준으로 3개 행을 건너뛰고 5개 행을 가져온다.
  • ① (나)
  • ② (가), (나)
  • ③ (가), (다)
  • ④ (가), (나), (다)
정답 및 해설 보기

정답 ②

(가) OFFSET 3 ROWS로 3개를 건너뛴 뒤 FETCH FIRST 5 ROWS ONLY가 그다음부터 최대 5개를 가져오므로 맞다. (나) ORDER BY 가격 DESC NULLS LAST로 내림차순 정렬하고 NULL은 끝에 두므로 맞다.

(다)는 틀렸다. 실제로는 ORDER BY가 먼저 실행되어 정렬이 끝난 뒤, 그 결과에 OFFSET과 FETCH가 적용된다. 정렬 전에 건너뛴다는 설명은 잘못이다. 따라서 (가)·(나)만 옳다.

🔑 암기 정렬(ORDER BY) → 건너뛰기(OFFSET) → 추출(FETCH) 순서.

문 47. 제약 조건에 대한 설명으로 가장 적절하지 않은 것은?

  • ① 제약 조건은 지정된 테이블의 칼럼에 적용된다.
  • ② PRIMARY KEY는 고유키 제약과 NOT NULL 제약을 포함하는 제약이다.
  • ③ CHECK는 조건식의 결과가 TRUE일 때 데이터 입력을 허용하고, FALSE이면 거부한다.
  • ④ UNIQUE는 속성을 고유하게 식별해야 하므로 여러 개의 NULL을 가질 수 없다.
정답 및 해설 보기

정답 ④

UNIQUE는 값의 중복을 막지만, NULL은 서로 다른 값으로 간주해 비교 대상에서 제외한다. 그래서 한 칼럼에 NULL을 여러 개 넣을 수 있다. "여러 개의 NULL을 가질 수 없다"는 ④가 틀렸다.

오답 ① 제약은 테이블 칼럼에 적용된다. ② PRIMARY KEY = UNIQUE + NOT NULL. ③ CHECK는 조건이 TRUE일 때만 허용한다.

⚠️ 함정 UNIQUE 칼럼에도 NULL은 여러 개 허용된다(NULL끼리는 같다고 판단하지 않음).

문 48. 아래 데이터 모델에서 카테고리ID가 10인 카테고리를 기준으로 해당 카테고리의 모든 하위 카테고리를 최하위까지 전개하기 위한 SQL로 가장 적절한 것은?

문48 데이터 모델 — 카테고리(카테고리ID PK, 카테고리명·상위카테고리ID(FK))의 자기 참조 관계. 상위카테고리ID(FK)가 같은 테이블의 카테고리ID를 가리켜 계층 구조를 형성

SQL
SELECT *
  FROM 카테고리
  START WITH 카테고리ID = 10
  CONNECT BY PRIOR 카테고리ID = 상위카테고리ID;

SQL
SELECT *
  FROM 카테고리
  START WITH 카테고리ID = 10
  CONNECT BY PRIOR 상위카테고리ID = 카테고리ID;

SQL
SELECT *
  FROM 카테고리
  START WITH 상위카테고리ID IS NULL
  CONNECT BY PRIOR 카테고리ID = 상위카테고리ID;

SQL
SELECT *
  FROM 카테고리
  START WITH 상위카테고리ID IS NULL
  CONNECT BY PRIOR 상위카테고리ID = 카테고리ID;
정답 및 해설 보기

정답 ①

카테고리ID 10을 시작점으로 하므로 START WITH 카테고리ID = 10이어야 한다. 그리고 부모의 카테고리ID가 자식의 상위카테고리ID와 일치하는 경로를 따라 내려가는 순방향(하향) 전개라 CONNECT BY PRIOR 카테고리ID = 상위카테고리ID가 맞다. 이 조합이 10번 아래 모든 하위 카테고리를 최하위까지 조회한다.

오답 ② 시작점은 맞지만 PRIOR 상위카테고리ID = 카테고리ID는 자식에서 부모로 가는 상향 전개라 하위 조회가 안 된다. ③ ④ 시작점이 루트(상위카테고리ID IS NULL)라 10 기준 전개가 아니다.

🔑 암기 순방향(하향) 전개 = CONNECT BY PRIOR 부모키(카테고리ID) = 자식키(상위카테고리ID).

문 49. 아래 SQL의 실행 결과와 동일한 것은?

SQL
SELECT TOP 5 주문번호, 고객명, 주문일자, 주문금액
FROM 주문
ORDER BY 주문일자 DESC;

SQL
SELECT 주문번호, 고객명, 주문일자, 주문금액
FROM 주문
WHERE ROWNUM <= 5
ORDER BY 주문일자 DESC;

SQL
SELECT 주문번호, 고객명, 주문일자, 주문금액
FROM (
        SELECT 주문번호, 고객명, 주문일자, 주문금액,
               ROW_NUMBER() OVER (ORDER BY 주문일자 ASC) AS RN
        FROM 주문
        )
WHERE RN <= 5;

SQL
SELECT 주문번호, 고객명, 주문일자, 주문금액
FROM (
        SELECT 주문번호, 고객명, 주문일자, 주문금액,
               RANK() OVER (ORDER BY 주문일자 DESC) AS RN
        FROM 주문
        )
WHERE RN <= 5;

SQL
SELECT 주문번호, 고객명, 주문일자, 주문금액
FROM (
        SELECT 주문번호, 고객명, 주문일자, 주문금액,
               ROW_NUMBER() OVER (ORDER BY 주문일자 DESC) AS RN
        FROM 주문
        )
WHERE RN <= 5;
정답 및 해설 보기

정답 ④

TOP 5 ... ORDER BY 주문일자 DESC는 최신순으로 정렬한 뒤 상위 5개를 가져온다. ④는 ROW_NUMBER() OVER (ORDER BY 주문일자 DESC)로 정렬 순위를 매긴 다음 RN <= 5로 잘라 동일하게 동작한다.

오답 ① WHERE ROWNUM <= 5가 ORDER BY보다 먼저 적용되어, 정렬 전 임의의 5개가 잘린 뒤 정렬되므로 최신 5개가 아니다. ② ORDER BY 주문일자 ASC라 가장 오래된 5개가 나온다. ③ RANK()는 주문일자가 같으면 같은 순위를 주고 다음 순위를 건너뛰어, 동일 일자가 많으면 결과가 5개를 넘을 수 있다.

🔑 암기 정렬 후 추출을 보장하려면 인라인 뷰 정렬 후 ROWNUM, 또는 ROW_NUMBER() OVER (ORDER BY ...)를 쓴다.

문 50. 아래 SQL의 실행 결과는? (단, DBMS는 오라클로 가정함) 🎯 고난도

CITY
London
Lisbon
Larcon
Lyon
Luton
SQL
SELECT CITY
FROM CITIES
WHERE REGEXP_LIKE(
   CITY, '^L[^aeiou][A-Za-z]*on$', 'i'
);
  • ① London, Larcon, Luton
  • ② London, Lisbon, Larcon
  • ③ Lyon
  • ④ Lisbon, Luton
정답 및 해설 보기

정답 ③

패턴 ^L[^aeiou][A-Za-z]*on$을 분해하면 — ^L(L로 시작) · [^aeiou](두 번째 글자가 모음이 아님) · [A-Za-z]*(영문자 0개 이상) · on$(on으로 끝남)이다. 두 번째 글자가 모음인 London(o)·Lisbon(i)·Larcon(a)·Luton(u)은 모두 탈락하고, Lyon만 두 번째가 'y'(모음 아님)이면서 'on'으로 끝나 통과한다. 실행하면 Lyon 한 건을 반환한다.

🔑 암기 ^(시작) · [^...](지정 문자 제외) · $(끝). [^aeiou]는 모음이 아닌 한 문자.


전체 목록 기출문제풀이

합격까지

SQLD, 약점 유형이 보이나요?

초개인화 학습앱 Klue로 틀린 유형을 집중 공략하고, 에듀윌 온라인강의로 개념까지 정리하세요.