본문으로 건너뛰기

서브쿼리

정보처리기사 목차SQL 고급레슨 96 / 233

목차

서브쿼리(subquery)는 SQL 문 안에 괄호로 넣은 SELECT 문입니다. 안쪽의 서브쿼리가 먼저 실행되고, 그 결과가 바깥 쿼리의 조건 값 자리에 들어갑니다.

'운영체제를 들은 학생의 이름'을 구하려면 표 셋을 차례로 봐야 합니다. 과목 표에서 운영체제의 과목코드를 찾고, 수강 표에서 그 과목을 들은 학번을 찾고, 학생 표에서 그 학번의 이름을 찾습니다. 쿼리를 세 번 나눠 돌리고 앞 결과를 손으로 옮겨 적을 수도 있습니다. 서브쿼리를 쓰면 앞 단계의 답을 한 문장 안에서 다음 단계의 조건으로 넘깁니다. 서브쿼리를 품은 바깥쪽 쿼리는 메인 쿼리(main query)라고 부릅니다. 실기에는 서브쿼리를 세 겹으로 겹친 문장의 결과를 쓰는 문제가 나온 적이 있습니다.

4단계 중 1단계. 가장 안쪽 서브쿼리가 먼저 실행되어 운영체제의 과목코드 OS 하나를 돌려줍니다.

1안쪽부터 풀리는 차례

그림도 안쪽 괄호부터 한 겹씩 풀어 보입니다. 가장 안쪽 서브쿼리가 먼저 실행됩니다. 과목명이 '운영체제'인 행에서 과목코드 OS 하나를 돌려줍니다. OS가 괄호 자리에 들어가 가운데 쿼리의 조건은 과목코드 = 'OS'가 됩니다. 운영체제를 들은 학번 101·102·104가 나옵니다. 값이 셋이라 바깥 조건에는 여러 값을 받는 IN을 씁니다. 조건이 학번 IN (101, 102, 104)⁠가 되어 김민수·이서준·박서연 3행이 나옵니다.

2돌려주는 행 수로 갈리는 두 가지

서브쿼리는 돌려주는 행이 하나인지 여럿인지에 따라 받는 연산자가 달라집니다. 그림의 마지막 장면처럼 바깥의 IN을 =로 바꾸면 오류가 납니다. =는 값 하나만 받는데 서브쿼리가 세 값을 돌려주기 때문입니다.

가르는 기준단일행 서브쿼리다중행 서브쿼리
돌려주는 행한 행여러 행일 수 있다
받는 연산자=·>·< 같은 비교 연산자IN·ANY·ALL·EXISTS
비교 연산자로 받으면된다ORA-01427 오류

3여러 값을 받는 연산자

ANY는 서브쿼리가 돌려준 값 가운데 하나라도 조건을 만족하면 참입니다. ALL은 돌려준 값 모두에 대해 조건을 만족해야 참입니다. EXISTS는 서브쿼리 결과가 한 행이라도 있으면 참이고, NOT EXISTS는 한 행도 없으면 참입니다. 어떤 값이 나오는지는 보지 않고 행이 있는지만 봅니다.

연산자참이 되는 때
IN돌려준 값 가운데 하나와 같다
> ANY돌려준 값 가운데 하나보다 크다 — 가장 작은 값보다 크면 참
> ALL돌려준 값 모두보다 크다 — 가장 큰 값보다 크면 참
EXISTS돌려준 행이 하나라도 있다

예제

결과 표는 Oracle에서 돌린 결과입니다.

세 겹 서브쿼리

그림의 쿼리입니다. 괄호 안쪽부터 한 겹씩 풀면 됩니다.

SELECT 이름
FROM 학생
WHERE 학번 IN (
  SELECT 학번
  FROM 수강
  WHERE 과목코드 = (
    SELECT 과목코드
    FROM 과목
    WHERE 과목명 = '운영체제'
  )
)
ORDER BY 학번;
이름
김민수
이서준
박서연

단일행 서브쿼리와 다중행 서브쿼리

값 하나만 돌려주는 서브쿼리를 단일행 서브쿼리라고 합니다. =·>·< 같은 비교 연산자로 받습니다. 아래 서브쿼리는 전체 평균 점수 75 하나를 돌려주고, 바깥 조건은 점수 > 75가 됩니다.

SELECT 학번,
  과목코드,
  점수
FROM 수강
WHERE 점수 > (
  SELECT AVG(점수)
  FROM 수강
)
ORDER BY 점수 DESC;
학번과목코드점수
105DS100
104NW95
105NW90
101DS85
101NW80

여러 행을 돌려줄 수 있는 서브쿼리는 다중행 서브쿼리입니다. IN·ANY·ALL·EXISTS 같은 연산자로 받습니다. 같은 자리에 =를 쓰면 Oracle은 ORA-01427(single-row subquery returns more than one row) 오류를 냅니다.

SELECT 이름
FROM 학생
WHERE 학번 = (
  SELECT 학번
  FROM 수강
  WHERE 과목코드 = 'OS'
);

ANY와 ALL

아래 두 쿼리는 자료구조(DS) 점수를 운영체제(OS) 점수 75·75·60과 견줍니다. > ALL은 가장 큰 값 75보다 커야 하므로 2행입니다.

SELECT 학번,
  점수
FROM 수강
WHERE 과목코드 = 'DS'
  AND 점수 > ALL (
    SELECT 점수
    FROM 수강
    WHERE 과목코드 = 'OS'
  )
ORDER BY 학번;
학번점수
10185
105100

> ANY는 가장 작은 값 60보다 크기만 하면 되므로 DS 4행이 모두 나옵니다.

SELECT 학번,
  점수
FROM 수강
WHERE 과목코드 = 'DS'
  AND 점수 > ANY (
    SELECT 점수
    FROM 수강
    WHERE 과목코드 = 'OS'
  )
ORDER BY 학번;
학번점수
10185
10270
10365
105100

EXISTS

아래 서브쿼리는 바깥 학생 행의 S.학번을 받아, 학생 행마다 다시 실행됩니다. 수강 기록이 한 행도 없는 학생은 106 정하윤뿐입니다.

SELECT S.학번,
  S.이름
FROM 학생 S
WHERE NOT EXISTS (
  SELECT *
  FROM 수강 E
  WHERE E.학번 = S.학번
);
학번이름
106정하윤

시험 포인트

  • 필기: 서브쿼리는 가장 안쪽부터 풉니다. 안쪽 결과가 바깥 조건의 값 자리에 들어갑니다. 값 하나는 =·> 같은 비교 연산자로, 여러 값은 IN·ANY·ALL로 받습니다. = 자리에 여러 행이 오면 오류입니다. > ALL은 가장 큰 값보다 크다, > ANY는 가장 작은 값보다 크다는 뜻입니다. IN은 = ANY와 같습니다. EXISTS는 서브쿼리 결과에 행이 하나라도 있으면 참입니다.
  • 실기: 세 겹 서브쿼리의 결과를 안쪽부터 풀어 표로 씁니다. 이름은 서브쿼리(Subquery), 메인 쿼리(Main Query)로 씁니다.

확인 문제

1. 다음 네 서브쿼리 가운데 단일행 서브쿼리인 것을 고르세요.

  • ① SELECT 학과코드 FROM 학생 WHERE 학년 = 3
  • ② SELECT 점수 FROM 수강 WHERE 과목코드 = 'OS'
  • ③ SELECT 학점 FROM 과목 GROUP BY 학점
  • ④ SELECT COUNT(*) FROM 과목
정답

④입니다. 집계 함수만 적고 GROUP BY를 쓰지 않으면 표 전체가 한 묶음이 되어 값 하나가 나옵니다. 여기서는 과목 표의 행 수 3 하나입니다.

  • ① 조건이 하나면 한 행만 나온다고 봤습니다. 3학년은 101·105·106 셋이라 학과코드 3행이 나옵니다.
  • ② 과목코드로 과목 하나를 집었으니 한 행이라고 봤습니다. 집은 것은 과목이고, 운영체제를 들은 학생이 셋이라 점수 3행이 나옵니다.
  • ③ GROUP BY가 여러 행을 한 행으로 묶는다고 봤습니다. 묶음마다 한 행이 나오고, 학점 값은 2·3 둘이라 2행입니다.

2. 다음 (가)·(나)에 맞는 이름을 한글 이름과 영문으로 함께 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.

  • (가) 학과별 학생 수를 먼저 구해 종이에 적고 그 값을 다음 SELECT 문의 WHERE 절에 손으로 옮겨 적는 대신, 앞 SELECT 문을 괄호에 넣어 WHERE 절 안에 그대로 적었습니다. 괄호 안에 들어간 쪽의 이름입니다.
  • (나) 같은 문장에서 그 괄호를 품은 쪽, 곧 괄호의 결과를 조건 값으로 받아 최종 결과를 내는 쪽의 이름입니다.
정답

받는 답: (가) 서브쿼리(Subquery) · (나) 메인 쿼리(Main Query)

(가)의 실마리는 '괄호에 넣어 다른 SELECT 문 안에 적었다', (나)는 '괄호를 품고 최종 결과를 낸다'입니다.

  • 같은 이름의 다른 꼴: 한글 이름만 쓰거나 영문만 쓴 꼴 — 문제가 둘을 함께 쓰라고 했습니다.
  • 틀리는 꼴 메인 쿼리 — (가)에: 괄호를 품은 바깥쪽입니다. 안쪽과 바깥쪽을 거꾸로 쓰기 쉽습니다.
  • 틀리는 꼴 뷰(View) — (가)에: SELECT 문에 이름을 붙여 저장해 두고 표처럼 부르는 것입니다. 한 문장 안에 괄호로 넣은 것과 다릅니다.

3. 다음 쿼리의 결과를 표로 쓰세요.

SELECT 학번,
  점수
FROM 수강
WHERE 과목코드 = 'NW'
  AND 점수 >= ALL (
    SELECT AVG(점수)
    FROM 수강
    GROUP BY 과목코드
  )
ORDER BY 학번;
정답
학번점수
10180
10495
10590

서브쿼리는 과목별 평균 셋을 돌려줍니다. 자료구조 80 · 네트워크 74 · 운영체제 70입니다. >= ALL은 돌려받은 세 값 모두보다 크거나 같아야 참이므로, 가장 큰 80 이상이면 됩니다. 네트워크 점수 80·55·95·90·50 가운데 80 이상은 101의 80, 104의 95, 105의 90입니다. 101의 80은 80과 꼭 같지만 >=라서 남습니다. > ALL로 바꾸면 101이 빠져 2행이 됩니다.

더 자세히: SQLD의 서브쿼리

끝까지 내려오면 저절로 표시됩니다. 이 기기에만 저장합니다.

정보처리기사 목차로