16단원 '절차형 SQL'의 다섯 레슨에서 6문제, 앞 단원(13·14)에서 2문제를 섞었고, 끝에 실기 꼴 3문제가 있습니다. 앞 단원 문제가 어느 레슨인지는 정답을 펼치면 보입니다. 문제의 블록과 SQL은 아래 네 표로 Oracle에서 돌린다고 보고 풀어 보세요. 실기 꼴은 종이에 답을 끝까지 쓴 뒤 펼치세요. 띄어쓰기만 다르면 같은 답입니다.
학과
| 학과코드 | 학과명 | 건물 |
|---|---|---|
| CS | 컴퓨터 | 공학관 |
| EE | 전자 | 공학관 |
| BA | 경영 | 경영관 |
학생
| 학번 | 이름 | 학년 | 학과코드 |
|---|---|---|---|
| 101 | 김민수 | 3 | CS |
| 102 | 이서준 | 2 | CS |
| 103 | 김지민 | 2 | CS |
| 104 | 박서연 | 1 | EE |
| 105 | 최민준 | 3 | EE |
| 106 | 정하윤 | 3 | EE |
| 107 | 강도현 | 1 | NULL |
과목
| 과목코드 | 과목명 | 학점 | 선수과목 |
|---|---|---|---|
| DS | 자료구조 | 3 | NULL |
| OS | 운영체제 | 3 | DS |
| NW | 네트워크 | 2 | OS |
수강
| 학번 | 과목코드 | 점수 |
|---|---|---|
| 101 | DS | 85 |
| 101 | OS | 75 |
| 101 | NW | 80 |
| 102 | DS | 70 |
| 102 | OS | 60 |
| 103 | DS | 65 |
| 103 | NW | 55 |
| 104 | OS | 75 |
| 104 | NW | 95 |
| 105 | DS | 100 |
| 105 | NW | 90 |
| 107 | NW | 50 |
문제 1
다음 블록은 컴퓨터학과(CS) 학생 수를 세어 변수에 담고, 그 값에 1을 더해 다시 담습니다. (가)·(나)에 들어갈 것으로 알맞은 것을 고르세요.
DECLARE
v_인원 NUMBER;
BEGIN
SELECT COUNT(*) (가) v_인원
FROM 학생
WHERE 학과코드 = 'CS';
v_인원 (나) v_인원 + 1;
END;
- ① (가) AS · (나)
= - ② (가) AS · (나)
:= - ③ (가) INTO · (나)
= - ④ (가) INTO · (나)
:=
정답
④ — SELECT 결과를 변수에 담는 절은 INTO이고, 변수에 값을 담는 기호는 :=입니다. 등호 하나(=)는 값이 같은지 묻는 비교라서 담는 자리에 쓰지 않습니다.
- ① 두 자리를 모두 쿼리에서 쓰는 꼴로 봤습니다. AS는 결과 열에 별칭을 붙이는 말이고,
=는 비교 기호입니다. - ② (나)는 맞게 봤지만 (가)를 별칭으로 봤습니다. 별칭은 결과 열의 이름만 바꾸고, 변수에 담지는 못합니다.
- ③ (가)는 맞게 봤지만 (나)를 비교 기호로 봤습니다. 블록에서 값을 담을 때는 콜론을 붙인
:=를 씁니다.
다시 볼 레슨: 절차형 SQL의 구조
문제 2
학년을 받아 그 학년 학생이 몇 명인지 세어 돌려주는 프로시저를 다음처럼 만들어 두었습니다. 3학년 인원을 받아 보는 문장으로 알맞은 것을 고르세요. 받을 칸은 VARIABLE 결과 NUMBER로 미리 만들어 두었습니다.
CREATE PROCEDURE 학년인원(
p_학년 IN NUMBER,
p_건수 OUT NUMBER
) IS
BEGIN
SELECT COUNT(*) INTO p_건수
FROM 학생
WHERE 학년 = p_학년;
END;
- ①
EXECUTE 학년인원(3); - ②
EXECUTE 학년인원(3, :결과); - ③
EXECUTE 학년인원(3, 0); - ④
SELECT 학년인원(3) FROM DUAL;
정답
② — 괄호 안의 값은 적은 차례대로 매개변수에 담기고, OUT 자리에는 값 대신 돌려받을 변수를 적습니다. VARIABLE로 만든 칸은 :결과처럼 앞에 콜론을 붙여 가리킵니다.
- ① OUT 자리를 비워도 된다고 봤습니다. 매개변수가 둘인데 값을 하나만 넘기면 매개변수의 수가 맞지 않아 호출 자체가 막힙니다.
- ③ OUT 자리에 값을 그대로 적었습니다. 프로시저가 담아 줄 자리가 필요하므로 값이 아니라 변수를 적습니다.
- ④ 프로시저를 값이 올 자리에서 부를 수 있다고 봤습니다. 쿼리 안에서 부르는 것은 사용자 정의 함수이고, 프로시저 이름을 적으면 데이터베이스가 알아보지 못합니다.
다시 볼 레슨: 저장 프로시저
문제 3
점수를 받아 80점 이상이면 '우수'를 돌려주는 함수를 만들고, 네트워크(NW) 수강 행에 붙여 조회했습니다. 일어나는 일로 알맞은 것을 고르세요.
CREATE FUNCTION 평가(
p_점수 NUMBER
) RETURN VARCHAR2 IS
BEGIN
IF p_점수 >= 80 THEN
RETURN '우수';
END IF;
END;
/
SELECT 학번,
평가(점수) AS 평가
FROM 수강
WHERE 과목코드 = 'NW'
ORDER BY 학번;
- ① 함수는 만들어지고, 80점 미만인 행을 만나면 오류가 난다
- ② 함수가 만들어지지 않아 쿼리도 돌지 않는다
- ③ 함수는 만들어지고, 80점 미만인 행은 NULL로 나온다
- ④ 함수는 만들어지고, 80점 미만인 행만 결과에서 빠진다
정답
① — 몸의 RETURN은 값을 들고 그 자리에서 함수를 끝냅니다. 80점 미만인 점수는 IF 안으로 들어가지 못해 RETURN을 한 번도 지나지 않고 END에 닿고, 그런 행에서 ORA-06503 오류가 납니다. NW 점수는 101이 80, 103이 55이므로 55를 받은 행에서 오류가 나고, 쿼리는 결과를 내놓지 못합니다.
- ② 갈림길마다 RETURN이 없으면 만들 때 막힌다고 봤습니다. 머리에 돌려줄 자료형이 있어 함수는 만들어지고, 값을 들고 나오지 못하는 것은 부를 때 드러납니다.
- ③ 돌려준 값이 없으면 NULL이 된다고 봤습니다. 돌려주는 통로를 지나지 않은 것은 빈 값이 아니라 오류입니다.
- ④ 값을 못 받은 행만 조용히 빠진다고 봤습니다. 한 행에서 난 오류가 쿼리 전체를 멈춥니다.
다시 볼 레슨: 사용자 정의 함수
문제 4
학생 표의 학년이 바뀔 때마다 기록 표에 한 줄을 남기려고 다음 트리거를 만들고, 2학년의 학년을 1씩 올렸습니다. 기록 표에 쌓이는 줄 수가 (가)입니다. 같은 몸으로 FOR EACH ROW만 뺀 트리거를 만들어 같은 UPDATE를 돌리면 (나)입니다. 알맞은 것을 고르세요.
CREATE TABLE 변경기록 (
메모 VARCHAR2(20 CHAR)
);
CREATE TRIGGER 학년_변경기록
AFTER UPDATE ON 학생
FOR EACH ROW
BEGIN
INSERT INTO 변경기록
VALUES ('학년 바뀜');
END;
/
UPDATE 학생
SET 학년 = 학년 + 1
WHERE 학년 = 2;
SELECT COUNT(*) AS 줄수
FROM 변경기록;
- ① (가) 1줄 · (나) 1줄
- ② (가) 1줄 · (나) 2줄
- ③ (가) 2줄 · (나) 1줄
- ④ (가) 2줄 · (나) 2줄
정답
③ — FOR EACH ROW가 있으면 바뀐 행마다 한 번씩 깨어납니다. 2학년은 102·103 둘이라 UPDATE 한 문장에 두 줄이 쌓입니다. FOR EACH ROW를 빼면 문장 트리거가 되어 문장 하나에 한 번만 깨어나므로 한 줄입니다.
- ① (가)를 문장 트리거처럼 봤습니다. 행 트리거는 바뀐 행 수만큼 돕니다.
- ② 두 자리를 거꾸로 봤습니다. 행마다 도는 쪽이
FOR EACH ROW가 있는 트리거입니다. - ④ (나)에서도 행마다 돈다고 봤습니다.
FOR EACH ROW가 없으면 몇 행이 바뀌어도 한 번입니다.
| 줄수 |
|---|
| 2 |
다시 볼 레슨: 트리거와 이벤트
문제 5
어느 쇼핑몰 데이터베이스에서 (가)·(나) 두 가지 일을 저절로 일어나게 하려 합니다. 각각 무엇으로 만들어야 하는지 알맞은 것을 고르세요.
-
(가) 주문 표에 주문 한 건이 들어가는 순간 재고 기록 표에 한 줄을 남깁니다.
-
(나) 데이터가 바뀌지 않아도 날마다 밤 3시가 되면 지난해 접속 기록을 지웁니다.
-
① (가) 트리거 · (나) 트리거
-
② (가) 트리거 · (나) 이벤트
-
③ (가) 이벤트 · (나) 트리거
-
④ (가) 이벤트 · (나) 이벤트
정답
② — 둘을 가르는 것은 깨우는 쪽입니다. (가)는 주문 표의 데이터가 들어가는 것이 깨우므로 트리거이고, (나)는 데이터가 그대로여도 정해진 시각이 깨우므로 이벤트입니다.
- ① (나)도 데이터 변경이 깨운다고 봤습니다. 접속 기록을 지우는 일은 아무 표도 바뀌지 않아도 때가 되면 돌아야 합니다.
- ③ 두 자리를 거꾸로 봤습니다. 표에 매여 변경을 기다리는 쪽이 트리거, 데이터베이스에 걸려 시각을 기다리는 쪽이 이벤트입니다.
- ④ (가)도 시각이 깨운다고 봤습니다. 주문이 몇 시에 들어올지 정해 둘 수 없고, 들어가는 순간에 맞춰 돌아야 합니다.
다시 볼 레슨: 트리거와 이벤트
문제 6
학생 표를 만들 때 학년 열에 CHECK (학년 BETWEEN 1 AND 4) 제약 조건을 걸어 두었습니다. (가) 학년을 5로 적은 INSERT가 어떻게 되는지, (나) 5로 들어오는 값을 막지 않고 4로 바꿔 저장하려면 무엇을 두어야 하는지 알맞은 것을 고르세요.
- ① (가) 행이 들어가고 학년은 NULL이 된다 · (나) CHECK 제약 조건
- ② (가) 행이 들어가고 학년은 NULL이 된다 · (나) BEFORE 트리거
- ③ (가) 오류가 나고 행이 들어가지 않는다 · (나) CHECK 제약 조건
- ④ (가) 오류가 나고 행이 들어가지 않는다 · (나) BEFORE 트리거
정답
④ — 제약 조건은 어긋나는 값을 받지 않고 그 문장을 오류로 되돌립니다. 행은 들어가지 않고 표는 그대로입니다. 값을 바꿔 저장하려면 저장되기 전에 끼어드는 자리가 필요하고, 그 자리가 BEFORE 행 트리거입니다. :NEW.학년에 4를 담으면 담은 값이 저장됩니다.
- ① 제약 조건이 어긋나는 값을 비워서 넣는다고 보고, (나)도 거절하는 쪽을 골랐습니다. 제약 조건은 값을 고치지 않습니다.
- ② (나)는 맞게 봤지만 (가)에서 값이 비워진다고 봤습니다. 제약 조건을 어기면 그 INSERT 문이 거절됩니다.
- ③ (가)는 맞게 봤지만 (나)도 제약 조건으로 봤습니다. 제약 조건에는 값을 다듬는 자리가 없어, 걸리면 거절뿐입니다.
다시 볼 레슨: DDL: CREATE·ALTER·DROP · 트리거와 이벤트
문제 7
다음 블록을 돌렸습니다. 찍히는 줄이 (가)이고, 블록이 끝나는 모습이 (나)입니다. 알맞은 것을 고르세요.
SET SERVEROUTPUT ON
DECLARE
미달 EXCEPTION;
v_평균 NUMBER;
BEGIN
SELECT AVG(점수) INTO v_평균
FROM 수강
WHERE 학번 = 103;
IF v_평균 < 70 THEN
RAISE 미달;
END IF;
DBMS_OUTPUT.PUT_LINE(
'통과');
EXCEPTION
WHEN 미달 THEN
DBMS_OUTPUT.PUT_LINE(
'미달');
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE(
'오류');
END;
/
- ① (가)
미달· (나) 정상으로 끝난다 - ② (가)
미달· (나) 오류 번호가 나온다 - ③ (가)
통과· (나) 정상으로 끝난다 - ④ (가)
통과· (나) 오류 번호가 나온다
정답
① — 103번의 점수는 65·55라 평균이 60이고, 70 미만이므로 RAISE 미달;을 지납니다. 그 순간 흐름이 예외 처리부로 뛰어 아래의 '통과' 줄을 건너뛰고, 이름이 맞는 WHEN 미달만 실행됩니다. 예외를 받아 처리하면 블록은 정상으로 끝납니다.
- ② 받은 예외도 밖으로 나간다고 봤습니다. 받아 처리한 예외는 오류 번호를 내지 않습니다.
- ③ 평균을 70 이상으로 셈했거나, RAISE가 흐름을 바꾸지 않는다고 봤습니다. 65와 55의 평균은 60입니다.
- ④ 두 자리를 모두 어긋나게 봤습니다. RAISE를 지나면
'통과'는 찍히지 않고, 받은 예외는 오류로 끝나지 않습니다.
미달
다시 볼 레슨: 절차형 SQL의 오류 처리
문제 8
다음 쿼리를 돌리면 어떻게 되는지 고르세요.
SELECT 이름
FROM 학생
WHERE 학과코드 = (
SELECT 학과코드
FROM 학생
WHERE 학년 = 3
);
- ① 3학년이 속한 학과의 학생 이름이 모두 나온다
- ② 한 행도 나오지 않는다
- ③ 서브쿼리가 여러 행을 돌려줘 오류가 난다
- ④ 서브쿼리가 돌려준 첫 행의 학과만 쓰여 그 학과 학생 이름이 나온다
정답
③ — =는 값 하나와 견주는 비교 연산자라서 서브쿼리가 돌려주는 행이 하나여야 합니다. 3학년은 101·105·106 셋이고 학과코드가 CS·EE·EE 세 행으로 나오므로 ORA-01427 오류가 납니다. 여러 값을 받으려면 IN이나 = ANY를 씁니다.
- ①
=자리에 값이 여럿 와도 그 가운데 하나만 맞으면 참이 된다고 봤습니다. 그 뜻으로 쓰는 연산자가IN입니다. - ② 조건이 참이 되지 못해 결과가 빈다고 봤습니다. 거짓이 되는 것이 아니라 쿼리 자체가 멈춥니다.
- ④ 여러 행 가운데 첫 행만 쓰인다고 봤습니다. 비교 연산자는 행을 골라 쓰지 않습니다.
다시 볼 레슨: 서브쿼리
문제 9 (실기)
다음 (가)·(나)에서 블록이 받게 되는 예외의 이름을 영문으로, 대문자와 밑줄까지 그대로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
- (가) 수강 표에서 점수 하나를 변수에 담으려고 학번 101만 조건으로 주었습니다. 101번은 세 과목을 듣고 있습니다.
- (나) 반 평균을 내려고 점수 합계를 학생 수로 셈하는 블록을 돌렸습니다. 조건에 맞는 학생이 한 명도 없어 학생 수 자리에 0이 담겼습니다.
정답
받는 답: (가) TOO_MANY_ROWS · (나) ZERO_DIVIDE
(가)의 실마리는 'SELECT INTO가 담을 행이 한 행을 넘는다', (나)는 '셈하는 값 가운데 아래쪽이 0이다'입니다.
- 같은 이름의 다른 꼴: 오류 번호로 쓴 꼴(ORA-01422·ORA-01476) — 번호가 아니라 예외의 이름을 묻습니다.
- 틀리는 꼴 NO_DATA_FOUND — (가)에: SELECT INTO가 담을 행을 찾지 못했을 때 나는 이름입니다. (가)는 찾은 행이 많아서 난 오류입니다.
- 틀리는 꼴 DUP_VAL_ON_INDEX — (나)에: 겹치면 안 되는 칸에 같은 값을 넣었을 때 나는 이름입니다. 셈하다 난 오류가 아닙니다.
다시 볼 레슨: 절차형 SQL의 오류 처리
문제 10 (실기)
어느 회사가 접속 기록 표를 비웁니다. (가)·(나)에서 쓸 명령어를 SQL 키워드 그대로 대문자로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
- (가) 지난달에 쌓인 기록만 조건으로 골라 지우고, 잘못 지웠으면 ROLLBACK으로 되돌릴 수 있어야 합니다.
- (나) 행을 전부 지워 비우되 표의 틀은 남깁니다. 되돌릴 일은 없고, 시험 자료는 이 명령어를 DDL로 봅니다.
정답
받는 답: (가) DELETE · (나) TRUNCATE
(가)의 실마리는 '조건으로 골라 지운다'와 '되돌릴 수 있다', (나)는 '행은 다 지우고 틀은 남는다'와 'DDL'입니다.
- 같은 이름의 다른 꼴:
DELETE FROM·TRUNCATE TABLE처럼 뒤따르는 낱말까지 쓴 꼴 — 명령어가 들어 있으니 함께 받습니다. - 틀리는 꼴 TRUNCATE — (가)에: 조건을 줄 수 없고 행을 전부 지우며, 되돌릴 수도 없습니다.
- 틀리는 꼴 DROP — (나)에: 행과 함께 표의 틀까지 없앱니다. (나)는 틀을 남깁니다.
다시 볼 레슨: DML: INSERT·UPDATE·DELETE
문제 11 (실기)
다음을 차례로 돌렸습니다. 마지막 쿼리의 결과가 몇 행이고 각 행의 값이 무엇인지 위에서부터 차례로 쓰세요. 답을 끝까지 쓴 뒤 정답을 펼치세요.
CREATE FUNCTION 구간(
p_점수 NUMBER
) RETURN VARCHAR2 IS
BEGIN
IF p_점수 >= 90 THEN
RETURN '상';
ELSIF p_점수 >= 70 THEN
RETURN '중';
END IF;
RETURN '하';
END;
/
SELECT 구간(점수) AS 구간,
COUNT(*) AS 인원,
MAX(점수) AS 최고
FROM 수강
WHERE 과목코드 <> 'DS'
GROUP BY 구간(점수)
HAVING COUNT(*) >= 3
ORDER BY 구간(점수);
정답
받는 답: 2행 · 중 3 80 · 하 3 60
함수는 행마다 한 번씩 불려 점수를 구간으로 바꾸고, GROUP BY가 그 값으로 묶습니다. 자료구조(DS)를 뺀 여덟 행의 구간은 95·90이 상, 80·75·75가 중, 60·55·50이 하입니다. HAVING이 인원 3 미만인 묶음을 떨어뜨려 상은 남지 않고, 남은 두 묶음의 가장 높은 점수는 80과 60입니다.
- 틀리는 꼴 3행 · 상 2 95 · 중 3 80 · 하 3 60: HAVING을 지나쳤습니다. 인원이 3 미만인 묶음은 결과에 남지 않습니다.
- 틀리는 꼴 3행 · 상 3 100 · 중 5 85 · 하 4 65: WHERE의
과목코드 <> 'DS'를 빼고 열두 행으로 셈했습니다. - 틀리는 꼴 2행 · 중 4 90 · 하 3 60: 90점을 중으로 봤습니다. 머리의 IF가 90 이상을 먼저 걸러 상으로 보냅니다.
| 구간 | 인원 | 최고 |
|---|---|---|
| 중 | 3 | 80 |
| 하 | 3 | 60 |
다시 볼 레슨: 사용자 정의 함수 · 그룹과 집계: GROUP BY·HAVING