💡 데이터 모델링의 이해
✔️ 속성 a, b, c, d, e로 구성된 릴레이션에서 아래와 같은 함수 종속성이 존재할 때,
이 릴레이션의 후보 키로 가장 적절하지 않은 것은?
ab -> cde
e -> b
d -> ab
- d
- ab
- ac
- ae
- 주어진 함수 종속성:
- ab -> cde
- e -> b
- d -> ab
- 각 속성 집합의 클로저(closure)를 계산해 봅시다: d의 클로저: d -> ab (주어진 함수 종속성) ab -> cde (주어진 함수 종속성) 따라서 d+ = {a, b, c, d, e} ab의 클로저: ab -> cde (주어진 함수 종속성) 따라서 ab+ = {a, b, c, d, e} ac의 클로저: ac+ = {a, c} ae의 클로저: e -> b (주어진 함수 종속성) 따라서 ae+ = {a, b, e}
- 후보 키의 조건:
- 유일성: 릴레이션의 모든 속성을 결정할 수 있어야 함
- 최소성: 더 이상 줄일 수 없는 최소한의 속성 집합이어야 함
- 각 선택지 분석:
- d: 모든 속성을 결정할 수 있으며 최소성을 만족함
- ab: 모든 속성을 결정할 수 있으며 최소성을 만족함
- ac: 모든 속성을 결정할 수 없음
- ae: 모든 속성을 결정할 수 없음
따라서, 이 릴레이션의 후보 키로 가장 적절하지 않은 것은 ac와 ae입니다.
그러나 문제에서 하나만 선택하라고 했으므로, 둘 중 하나를 선택해야 합니다.
ac와 ae 중에서 ae가 b를 결정할 수 있어 더 많은 속성을 결정할 수 있으므로, 후보 키로는 ac가 ae보다 더 부적절합니다.
결론적으로, 이 릴레이션의 후보 키로 가장 적절하지 않은 것은 ac입니다.
✔️ NULL 값에 대한 설명으로 가장 적절한 것은?
- NULL값에 어떠한 숫자를 더해도 결과는 항상 NULL이다.
- NULL값과 어떠한 숫자의 크기를 비교해도 결과는 항상 NULL이다.
- 'NULL = NULL' 연산의 결과는 TRUE이다.
- 집계 함수를 계산할 때 NULL 값은 0으로 처리된다.
NULL 값과 어떤 숫자를 비교한 결과는 항상 unknown 이다.
NULL = NULL 연산의 결과는 FALSE 또는 unknown 이다.
집계함수를 계산할 때 NULL 값은 0이 아니라 계산에서 제외된다.
💡 SQL 기본
✔️ 아래에 대한 설명으로 가장 적절한 것은?
CREATE TABLE 서비스
(
서비스번호 VARCHAR2(10) PRIMARY KEY,
서비스명 VARCHAR2(100) NULL,
개시일자 DATE NOT NULL
);
[SQL]
(ㄱ) SELECT * FROM 서비스 WHERE 서비스번호 = 1;
(ㄴ) INSERT INTO 서비스 VALUES ('999', '', '2015-11-11');
(ㄷ) SELECT * FROM 서비스 WHERE 서비스명 = '';
(ㄹ) SELECT * FROM 서비스 WHERE 서비스명 IS NULL;
- 서비스 번호 칼럼의 레코드 중 '001'과 같은 숫자 형식으로 된 레코드가 하나라도 입력되어 있다면 (ㄱ)은 오류 없이 실행된다.
- 오라클에서 (ㄴ)과 같이 데이터를 입력하였을 때, 서비스명 칼럼에 공백 문자 데이터가 입력된다.
- 오라클에서 (ㄴ)과 같이 데이터를 입력하고 (ㄷ)과 같이 조회하였을 때, 데이터는 조회된다.
- SQL Server에서 (ㄴ)과 같이 데이터를 입력하고 (ㄹ)과 같이 조회하였을 때, 데이터는 조회되지 않는다.
Oracle은 빈 문자열을 NULL로 처리합니다. 이로 인해 특히 문자열 비교나 삽입 시 예상치 못한 동작이 발생할 수 있습니다.
반면, SQL Server는 NULL과 빈 문자열을 별개의 것으로 처리합니다.
이는 빈 문자열을 누락된 데이터와 구별해야 하는 시나리오에서 더 직관적입니다.
✔️ 아래 SQL을 순서대로 실행했을 때 최종적으로 반영되는 SQL을 모두 고른 것은?
(가)
INSERT INTO emp (empno, ename, deptno) VALUES (999, 'Smith' 10);
SAVEPOINT a;
(나)
DELETE emp WHERE empno = 202;
SAVEPOINT b;
(다)
UPDATE emp SET ename = 'Clark';
ROLLBACK TO SAVEPOINT a;
(라)
INSERT INTO emp (empno, ename, deptno) VALUES (300, 'Thomas', 30);
SAVEPOINT c;
(마)
DELETE emp WHERE deptno = 20;
COMMIT;
(가) INSERT 문은 실행되고 SAVEPOINT a가 생성됩니다.
(나) DELETE 문이 실행되고 SAVEPOINT b가 생성됩니다.
(다) UPDATE 문이 실행되지만, 바로 ROLLBACK TO SAVEPOINT a로 인해 (나)와 (다)의 작업이 취소됩니다.
(라) INSERT 문이 실행되고 SAVEPOINT c가 생성됩니다.
(마) DELETE 문이 실행되고 최종적으로 COMMIT이 됩니다.
따라서 최종적으로 반영되는 SQL은:
- (가)의 INSERT 문 INSERT INTO emp (empno, ename, deptno) VALUES (999, 'Smith' 10);
- (라)의 INSERT 문 INSERT INTO emp (empno, ename, deptno) VALUES (300, 'Thomas', 30);
- (마)의 DELETE 문 DELETE emp WHERE deptno = 20;
이 세 개의 SQL 문이 최종적으로 데이터베이스에 반영됩니다.
(나)의 DELETE 문과 (다)의 UPDATE 문은 ROLLBACK으로 인해 취소되었습니다.
결론적으로, (가), (라), (마)의 SQL 문이 최종 반영됩니다.
✔️ 오류가 발생하는 SQL은?
1. SELECT 지역, SUM(매출 금액) AS 매출 금액
FROM 지역별 매출
GROUP BY 지역
ORDER BY 매출금액 DESC;
2. SELECT 지역, 매출 금액
FROM 지역별 매출
ORDER BY 년 ASC;
3. SELECT 지역, SUM(매출 금액) AS 매출 금액
FROM 지역별 매출
GROUP BY 지역
ORDER BY 년 DESC;
4. SELECT 지역, SUM(매출 금액) AS 매출 금액
FROM 지역별 매출
GROUP BY 지역
HAVING SUM(매출 금액) > 1000
ORDER BY COUNT(*) ASC;
SQL 실행 순서에 의하면 SELECT절 이후 ORDER BY절이 수행되기 때문에 SELECT 절에 기술되지 않은 '년' 칼럼으로
정렬하는 것은 옳지 않습니다. 하지만 Oracle은 행 기반 DATABASE이므로 데이터를 액세스 할 때 행 전체 칼럼을 메모리에 로드합니다. 이와 같은 특성으로 SELECT 절에 기술되지 않은 칼럼으로도 정렬을 할 수 있습니다.
3번의 경우 GROUP BY를 사용할 경우 GROUP BY 표현식이 아닌 값은 기술될 수 없습니다.
다시 말해 '년' 컬럼이 SELECT 목록이나 GROUP BY 절에 포함되어 있지 않아 오류가 발생할 수 있습니다.
대부분의 SQL 구현에서는 GROUP BY 절에 포함되지 않은 컬럼으로 정렬하는 것을 허용하지 않습니다.
✔️ SQL 실행 결과로 가장 적절한 것은?
SELECT TO_CHAR('2019.02.25', 'YYYY.MM.DD') + 1/12/(60/30), 'YYYY.MM.DD HH24:MI:SS')
FROM DUAL;
- 2019.02.25 02:00:00
- 2019.02.25 01:30:00
- 2019.02.25 01:00:00
- 2019.02.25 00:30:00
오라클에서 날짜 연산은 숫자의 연산과 같습니다.
1/24/60 은 1분을 의미합니다. 따라서 1/12는 2시간 (하루의 1/24), 30/60 = 2, 1/12/2는 1/24 즉, 1시간을 의미합니다.
1/12/(60/30) 은 1시간과 같습니다. (1/1440은 1분을 의미합니다)
따라서 2019년 2월 25일 00시에 1시간을 더한 값과 같습니다.
✔️ 아래 참고할 때 SQL 실행 결과로 가장 적절한 것은?
SELECT A.고객번호, A.고객명, B.단말기ID, B.단말기명, C.OSID, C.OS명
FROM 고객 A LEFT OUTER JOIN 단말기 B
ON (A.고객번호 IN (11000, 12000) AND A.단말기ID = B.단말기ID)
LEFT OUTER JOIN OS C
ON (B.OSID = C.OSID)
ORDER BY A.고객번호;
고객번호 | 고객명 | 단말기ID | 단말기명 | OSID | OS명 |
11000 | 홍길동 | 1000 | A1000 | 100 | Android |
12000 | 강감찬 | <NULL> | <NULL> | <NULL> | <NULL> |
13000 | 이순신 | <NULL> | <NULL> | <NULL> | <NULL> |
14000 | 안중근 | <NULL> | <NULL> | <NULL> | <NULL> |
15000 | 고길동 | <NULL> | <NULL> | <NULL> | <NULL> |
16000 | 이대로 | <NULL> | <NULL> | <NULL> | <NULL> |
WHERE절에 A.고객번호 IN (11000, 12000) 조건을 넣었다면 정답은 11000, 12000 인스턴스만 존재하는 테이블이 되었을 것입니다. 하지만 ON 절에 해당 조건을 넣었기 때문에 모든 고객에 대해 출력 하되 JOIN 대상 데이터가 고객번호 11000, 12000으로 제한되어 다음과 같이 테이블이 만들어집니다.
✔️ 아래 EMP 테이블과 DEPT 테이블을 LEFT, FULL, RIGHT 외부 조인 (OUTER JOIN)하면 생성되는 결과 건수로 가장 적절한 것은?
[EMP]
A B C 1 b w 3 d w 5 y y
[DEPT]
C D E w 1 10 z 4 11 v 2 22
- LEFT OUTER JOIN
A | B | C | D | E |
1 | b | w | 1 | 10 |
3 | d | w | 1 | 10 |
5 | y | y |
- FULL OUTER JOIN
A | B | C | D | E |
1 | b | w | 1 | 10 |
3 | d | w | 1 | 10 |
5 | y | y | ||
z | 4 | 11 | ||
v | 2 | 22 |
- RIGHT OUTER JOIN
A | B | C | D | E |
1 | b | w | 1 | 10 |
3 | d | w | 1 | 10 |
z | 4 | 11 | ||
v | 2 | 22 |
✔️ 아래에 대한 설명으로 가장 적절한 것은? (단, 칼럼 타입은 NUMBER이다.)
COL1 COL2 COL3 10 20 <NULL> 15 <NULL> <NULL> 50 70 20
- SELECT SUM(COL2) FROM TAB1 결과는 NULL이다
- SELECT SUM(COL1 + COL2 + COL3) FROM TAB1의 결과는 185이다.
- SELECT SUM(COL2 + COL3) FROM TAB1 결과는 90이다.
- SELECT SUM(COL2) + SUM(COL3) FROM TAB1 의 결과는 90이다.
1번은 SUM 함수는 NULL을 무시하므로 20 + NULL + 70 = 90 이 될 수 있습니다.
2번 같은 경우 NULL 이 아닌 경우인 50 + 70 + 20 = 140이 존재하므로 185는 틀린 값입니다.
3번은 70 + 20 = 90 인 경우가 존재하므로 정답이 될 수 있습니다.
4번은 각각의 합인 (20+NULL+70) + (NULL+NULL+20) = 110 이 되므로 틀린 값입니다.
💡 SQL 활용
✔️ 아래를 참고할 때 SQL 실행 결과로 가장 적절한 것은?
SELECT COL1, COL2, COUNT(*) AS CNT
FROM (SELECT COL1, COL2
FROM TBL1
UNION ALL
SELECT COL1, COL2
FROM TBL2
UNION
SELECT COL1, COL2
FROM TBL1)
GROUP BY COL1, COL2);
[TBL1]
COL1 | COL2 |
AA | A1 |
AB | A2 |
[TBL2]
COL1 | COL2 |
AA | A1 |
AB | A2 |
AC | A3 |
AD | A4 |
집합 연산자는 SQL 위에서 정의된 연산자가 먼저 수행됩니다.
따라서 UNION이 가장 나중에 수행되므로 결과적으로 중복 데이터가 모두 제거됩니다.
만약 UNION ALL이 나중에 수행되었다면 AA, AB에 대한 CNT가 2개로 찍히게 됩니다.
COL1 | COL2 | CNT |
AA | A1 | 1 |
AB | A2 | 1 |
AC | A3 | 1 |
AD | A4 | 1 |
✔️ 아래 SQL을 수행할 경우 정렬 순서 상 3번째 표시될 값은?
C1 C2 C3 1 <NULL> A 2 1 B 3 1 C 4 2 D SELECT C3 FROM TAB1 START WITH C2 IS NULL CONNECT BY PRIOR C1 = C2 ORDER SIBILINGS BY C3 DESC
- START WITH C2 IS NULL: C2가 NULL인 행에서 시작합니다. 이는 첫 번째 행(C1=1, C2=NULL, C3=A)입니다.
- CONNECT BY PRIOR C1 = C2: 이전 행의 C1 값과 현재 행의 C2 값이 같은 경우 연결합니다.
1 -> 2, 3 (C2가 1인 행들) 2 -> 없음 3 -> 없음 4 -> 없음 (C2가 2이므로 연결되지 않음) - ORDER SIBLINGS BY C3 DESC: 같은 레벨의 행들을 C3 기준으로 내림차순 정렬합니다.
결과적으로 다음과 같은 순서로 정렬됩니다:
- A (시작점)
- C (C2=1인 행 중 C3가 더 큰 값)
- B (C2=1인 행 중 C3가 더 작은 값)
따라서 정렬 순서 상 3번째로 표시될 값은 "B"입니다.
✔️ 아래 SQL과 동일한 결과를 출력하는 SQL로 가장 적절하지 않은 것은?
SELECT A.회원번호, A.회원명
FROM 회원 A, 동의항목 B
WHERE A.회원번호 = B.회원번호
GROUP BY A.회원번호, A.회원명
HAVING COUNT(CASE WHEN B.동의여부 = 'N' THEN 0 ELSE NULL END) >= 1
ORDER BY A.회원번호
[1번]
SELECT A.회원번호, A.회원명
FROM 회원 A
WHERE EXISTS (SELECT 1
FROM 동의항목 B
WHERE A.회원번호 = B.회원번호
AND B.동의여부 = 'N')
ORDER BY A.회원번호
[2번]
SELECT A.회원번호, A.회원명
FROM 회원 A
WHERE A.회원번호 IN (SELECT B.회원번호
FROM 동의항목 B
WHERE B.동의여부 = 'N')
ORDER BY A.회원번호
[3번]
SELECT A.회원번호, A.회원명
FROM 회원 A
WHERE 0 < (SELECT COUNT(*)
FROM 동의항목 B
WHERE B.동의여부 = 'N')
ORDER BY A.회원번호
[4번]
SELECT A.회원번호, A.회원명
FROM 회원 A, 동의항목 B
WHERE A.회원번호 = B.회원번호 AND B.동의여부 = 'N'
GROUP BY A.회원번호, A.회원명
ORDER BY A.회원번호
원본 SQL의 의도: 회원 중에서 동의하지 않은 항목이 하나 이상 있는 회원을 찾습니다.
1번 SQL: EXISTS를 사용하여 동의하지 않은 항목이 있는 회원을 찾습니다. 원본 SQL과 동일한 결과를 생성합니다.
2번 SQL: IN 서브쿼리를 사용하여 동의하지 않은 항목이 있는 회원을 찾습니다. 원본 SQL과 동일한 결과를 생성합니다.
3번 SQL: 이 SQL은 문제가 있습니다. 서브쿼리가 전체 동의항목 테이블에서 동의하지 않은 항목의 수를 세고 있어,
특정 회원과 관련이 없습니다. 모든 회원 또는 아무도 선택되지 않을 수 있어 원본 SQL과 다른 결과를 생성할 가능성이 높습니다.
4번 SQL: JOIN과 WHERE 조건을 사용하여 동의하지 않은 항목이 있는 회원을 찾습니다. 원본 SQL과 동일한 결과를 생성합니다.
✔️ 아래 SQL에 대한 설명으로 가장 적절하지 않은 것은?
SELECT B.사원번호, B.사원명, A.부서번호, A.부서명
, (SELECT COUNT(*)
FROM 부양가족 Y
WHERE Y.사원번호 = B.사원번호) AS 부양가족수
FROM 부서 A, (SELECT *
FROM 사원
WHERE 입사년도 = '2014') B
WHERE A.부서번호 = B.부서번호
AND EXIST (SELECT 1
FROM 사원 X
WHERE X.부서번호 = A.부서번호);
- 위 SQL에는 다중 행 연관 서브쿼리, 단일 행 연관 서브쿼리, 인라인 뷰가 사용되었다.
- SELECT절에 사용된 서브쿼리는 스칼라 서브쿼리라고 하며, 이러한 형태의 서브쿼리는 JOIN으로 동일 결과를 추출할 수도 있다.
- WHERE절 서브쿼리에 사원 테이블 검색 조건 입사년도 조건 FROM 절 서브쿼리와 동일하게 추가해야 원하는 결과를 추출할 수 있다.
- FROM절 서브쿼리는 동적 뷰라고도 하며, SQL문장 중 테이블 명이 올 수 있는 곳에서 사용할 수 있다.
WHERE 절의 EXIST 서브쿼리에 입사년도 조건을 추가하는 것은 불필요하며, 오히려 원래의 쿼리 의도를 변경시킬 수 있습니다.
현재 쿼리는 2014년에 입사한 사원이 있는 부서의 정보를 조회하는 것이며,
EXIST 절은 단순히 해당 부서에 사원이 존재하는지만 확인합니다.
💡 관리 구문
✔️ SQL에서 사용되는 표준 데이터 타입으로 가장 적절하지 않은 것은? (단, DBMS는 오라클로 가정)
- Text
- Char
- Varchar2
- Number
Text 형태의 데이터 타입은 Char, VarChar2 타입을 사용합니다.
✔️ 관계형 DB에서 자식 테이블 FK 데이터 생성 시 부모 테이블에 PK가 없는 경우,
자식 테이블 데이터 입력을 허용하지 않는 참조 동작(Referential Action)은?
- CASCADE
- RESTRICT
- AUTOMATIC
- DEPENDENT
- Delete(Modify) Action : (부서 - 사원)
1) Cascade : Master 삭제 시 Child 같이 삭제
2) Set Null : Master 삭제 시 Child 해당 필드 Null
3) Set Default : Master 삭제 시 Child 해당 필드 Default 값으로 설정
4) Restrict : Child 테이블에 PK 값이 없는 경우만 Master 삭제 허용
5) No Action : 참조 무결성 위반하는 삭제/수정 액션을 취하지 않음
- Insert Action (부서 - 사원)
1) Automatic : Master 테이블에 PK가 없는 경우 Master PK 생성 후 Child 입력
2) Set Null : Master 테이블에 PK가 없는 경우 Child 외부 키를 Null 값으로 처리
3) Set Default : Master 테이블에 PK가 없는 경우 Child 외부 키를 지정된 기본 값으로 처리
4) Dependant : Master 테이블에 PK가 존재할 때만 Child 입력 허용
5) No Action : 참조 무결성 위반하는 입력 액션을 취하지 않음