SQLP 모의고사 5회

70문항 · 제한 시간 180분. 다 풀면 과목별 점수와 합격 여부, 문제마다 해설을 보여 줘요.

  1. 1. 온라인 게임 운영 시스템을 구축하면서 다음 (ㄱ)~(ㄹ) 작업을 수행하였다. 이 중 논리적 데이터 모델링 단계에 해당하는 작업만 모두 고른 것은?

    • (ㄱ) 계정·캐릭터·길드·아이템 등 핵심 엔터티와 그 사이의 관계를 도출하여 경영진 보고용 개괄 ERD를 작성하였다.
    • (ㄴ) 캐릭터 엔터티에서 서버명이 서버코드에 의해 결정되는 것을 발견하고 서버 엔터티로 분리하였다.
    • (ㄷ) 하루 2천만 건이 쌓이는 접속로그 테이블을 월 단위 Range 파티션으로 설계하고 테이블스페이스를 배정하였다.
    • (ㄹ) 아이템거래 엔터티의 주식별자를 확정하고, 판매 캐릭터·구매 캐릭터로부터 상속받는 외래식별자 속성을 정의하였다.
    1. (ㄱ), (ㄴ)
    2. (ㄴ), (ㄹ)
    3. (ㄱ), (ㄹ)
    4. (ㄴ), (ㄷ), (ㄹ)
  2. 2. 게임 운영 업무를 분석하여 계정, 아이템(아이템 마스터), 캐릭터, 접속로그, 아이템거래 엔터티를 도출하였다. 엔터티 분류에 대한 설명으로 가장 적절한 것은?

    1. 아이템은 게임 운영의 중심이 되는 정보이므로 발생 시점 기준으로 중심 엔터티에 해당한다.
    2. 접속로그는 계정이 없어도 독립적으로 생성될 수 있으므로 기본 엔터티에 해당한다.
    3. 기본 엔터티는 다른 엔터티와의 관계에 의해 생성되며, 일반적으로 자식 엔터티를 갖지 않는다.
    4. 아이템거래는 업무가 수행되면서 발생하는 행위 엔터티로, 여러 부모 엔터티로부터 주식별자를 상속받고 데이터가 지속적으로 많이 발생한다.
  3. 3. 캐릭터 엔터티에 다음 속성들이 정의되어 있다. 속성의 분류와 특성에 대한 설명으로 가장 적절하지 않은 것은?

    속성 설명
    캐릭터번호 캐릭터 생성 시 시스템이 부여하는 일련번호
    캐릭터명 사용자가 입력한 이름
    직업코드 10: 전사, 20: 마법사, 30: 궁수 (운영팀이 규칙화한 코드)
    누적결제금액 해당 캐릭터로 결제한 금액의 합계
    생성일시 캐릭터를 만든 일시
    1. 직업코드는 업무에 원래 존재하는 정보를 그대로 가져온 것이므로 기본 속성이다.
    2. 캐릭터번호처럼 유일성을 확보하기 위해 새로 만든 일련번호는 설계 속성이다.
    3. 누적결제금액은 결제 엔터티로부터 계산되는 파생 속성이므로, 정합성 유지를 위해 꼭 필요한 경우에만 정의하는 것이 좋다.
    4. 캐릭터명은 하나의 인스턴스에서 하나의 값만 가지므로 단일값 속성이다.
  4. 4. 게임 운영 업무 규칙이 다음과 같을 때, 계정과 캐릭터 사이의 관계를 IE 표기법으로 표현한 설명으로 가장 적절한 것은?

    • 계정은 캐릭터를 하나도 만들지 않을 수도 있고, 최대 6개까지 만들 수 있다.
    • 캐릭터는 반드시 하나의 계정에 속해야 한다.
    1. 계정 쪽 선 끝에는 원과 막대(o|)를 표기한다. 캐릭터는 계정 없이 생성될 수 있기 때문이다.
    2. 최대 6개라는 제한은 IE 표기법의 관계 차수 기호로 직접 표현된다.
    3. 캐릭터 쪽 선 끝에는 원과 까마귀 발(o{)을 표기하고, 최대 6개 제한은 별도의 업무 규칙(제약)으로 관리한다.
    4. 계정은 캐릭터 존재에 필수적으로 참여하므로 캐릭터 쪽 선 끝에는 막대와 까마귀 발(|{)을 표기한다.
  5. 5. 다음 중 부모 엔터티와 자식 엔터티를 비식별 관계로 설정하는 것이 가장 적절한 경우는?

    1. 계정과 계정보안설정은 1:1 관계이며, 보안설정은 계정 없이 존재할 수 없고 계정ID만으로 식별된다.
    2. 결제와 결제상세에서 결제상세는 결제번호와 상세순번으로 식별되며, 결제 단위로 함께 생성·삭제된다.
    3. 길드와 캐릭터에서 캐릭터는 길드에 가입하지 않을 수 있고, 활동 중 다른 길드로 옮길 수도 있다.
    4. 캐릭터와 캐릭터스킬에서 캐릭터스킬은 (캐릭터번호, 스킬코드)로 식별되며 캐릭터가 삭제되면 함께 삭제된다.
  6. 6. 다음 인벤토리 테이블의 기본키는 (캐릭터번호, 슬롯번호)이며, 업무 규칙상 함수 종속은 아래와 같다. 이 테이블이 만족하는 가장 높은 정규형과 그 판단 근거로 옳은 것은? (모든 속성은 원자값만 가진다.)

    [인벤토리]

    캐릭터번호 슬롯번호 캐릭터명 아이템코드 아이템명 수량
    1001 1 불꽃검사 I01 체력물약 20
    1001 2 불꽃검사 I07 강철검 1
    1002 1 달빛궁수 I01 체력물약 5
    • 캐릭터번호 → 캐릭터명
    • (캐릭터번호, 슬롯번호) → 아이템코드, 수량
    • 아이템코드 → 아이템명
    1. 제1정규형: 캐릭터명이 기본키의 일부인 캐릭터번호에 부분 함수 종속되어 제2정규형을 위반한다.
    2. 제2정규형: 아이템명이 아이템코드를 거쳐 기본키에 이행 함수 종속되어 제3정규형을 위반한다.
    3. 제3정규형: 결정자인 아이템코드가 후보키가 아니므로 BCNF를 위반한다.
    4. BCNF: 모든 결정자가 기본키에 포함되어 있다.
  7. 7. ACCT(계정)와 CHARS(캐릭터)는 1:M 관계이며, 계정은 캐릭터를 갖지 않을 수도 있다(선택 관계). 데이터가 다음과 같을 때 (가), (나) SQL의 실행 결과로 옳은 것은?

    [ACCT]

    ACCT_ID
    A1
    A2
    A3
    A4

    [CHARS]

    CHAR_ID ACCT_ID LV
    C1 A1 50
    C2 A1 10
    C3 A2 70
    C4 A3 20
    C5 A3 35
    -- (가)
    SELECT A.ACCT_ID, COUNT(C.CHAR_ID) AS CNT
    FROM   ACCT A LEFT OUTER JOIN CHARS C
           ON  C.ACCT_ID = A.ACCT_ID
           AND C.LV >= 30
    GROUP  BY A.ACCT_ID;
    
    -- (나)
    SELECT A.ACCT_ID, COUNT(C.CHAR_ID) AS CNT
    FROM   ACCT A LEFT OUTER JOIN CHARS C
           ON  C.ACCT_ID = A.ACCT_ID
    WHERE  C.LV >= 30
    GROUP  BY A.ACCT_ID;
    1. (가) 4행, CNT가 0인 행 0개 / (나) 4행
    2. (가) 4행, CNT가 0인 행 1개 / (나) 4행
    3. (가) 3행, CNT가 0인 행 0개 / (나) 3행
    4. (가) 4행, CNT가 0인 행 1개 / (나) 3행
  8. 8. 게임 내 거래소에서 아이템 1건이 판매되면 다음 다섯 작업이 수행된다. 아이템거래(거래이력)는 판매 캐릭터·구매 캐릭터와 필수 관계로 모델링되어 있다. 이 처리의 트랜잭션 설계에 대한 설명으로 가장 적절한 것은?

    1. 판매자 인벤토리에서 아이템 삭제(DELETE)
    2. 구매자 인벤토리에 아이템 추가(INSERT)
    3. 구매자 골드 차감(UPDATE)
    4. 판매자 골드 증가(UPDATE)
    5. 아이템거래 이력 기록(INSERT)
    1. 각 DML마다 커밋하더라도 중간에 장애가 나면 남은 작업을 재시도하면 되므로 원자성이 보장된다.
    2. 다섯 작업을 하나의 트랜잭션으로 묶어 모두 반영되거나 모두 취소되도록 해야 하며, 거래이력도 필수 관계이므로 같은 트랜잭션 범위에 포함해야 한다.
    3. 거래이력은 로그성 데이터이므로 먼저 별도 트랜잭션으로 INSERT·커밋한 뒤 나머지 작업을 수행해야 정합성이 높아진다.
    4. 같은 아이템에 대해 두 구매자가 동시에 구매를 요청해 둘 다 성공하는 문제는 ACID 중 지속성(Durability)이 보장되지 않아 생긴다.
  9. 9. CHARS 테이블의 데이터가 다음과 같을 때, SQL의 실행 결과로 옳은 것은? (Oracle 기준, 결과는 C1 / C2 / C3 / C4 순서)

    [CHARS]

    CHAR_ID GUILD_ID LV
    C1 G1 60
    C2 NULL 30
    C3 G1 NULL
    C4 G2 40
    C5 NULL NULL
    SELECT COUNT(*)          AS C1,
           COUNT(GUILD_ID)   AS C2,
           AVG(LV)           AS C3,
           AVG(NVL(LV, 0))   AS C4
    FROM   CHARS
    WHERE  GUILD_ID <> 'G2' OR LV IS NULL;
    1. 3 / 2 / 60 / 20
    2. 4 / 3 / 45 / 30
    3. 3 / 3 / 60 / 20
    4. 3 / 2 / 20 / 20
  10. 10. 접속로그 엔터티의 본질식별자는 (계정ID, 접속일시)이다. 설계자는 이 대신 시퀀스로 생성하는 로그ID를 PK로 사용하려 한다. 접속로그 조회는 대부분 WHERE 계정ID = :1 AND 접속일시 BETWEEN :2 AND :3 형태로 이루어진다. 이에 대한 설명으로 가장 적절한 것은?

    1. 로그ID를 PK로 두면 업무상 같은 (계정ID, 접속일시)가 중복 입력되는 것을 별도 조치 없이 막을 수 있다.
    2. 시퀀스로 만든 로그ID는 트랜잭션이 롤백되더라도 항상 빈틈 없이 연속된 값이 보장된다.
    3. 본질식별자 (계정ID, 접속일시)를 PK로 하면 계정ID만 조건으로 주는 조회에서는 PK 인덱스를 Range Scan할 수 없다.
    4. 로그ID를 PK로 사용하면 주요 조회를 위해 (계정ID, 접속일시) 인덱스를 별도로 만들어야 하므로, 인덱스 수가 늘어 INSERT 부하가 커질 수 있다.
  11. 11. CHARS 테이블에는 CHAR_ID, CHAR_NM, SERVER_ID, LV 컬럼이 있다. 다음 (ㄱ)~(ㄹ) 중 Oracle에서 오류 없이 실행되는 SQL의 개수는?

    -- (ㄱ)
    SELECT CHAR_NM AS "이름", LV FROM CHARS ORDER BY "이름";
    
    -- (ㄴ)
    SELECT CHAR_NM, LV * 2 AS LV2 FROM CHARS WHERE LV2 > 100;
    
    -- (ㄷ)
    SELECT DISTINCT SERVER_ID FROM CHARS ORDER BY LV;
    
    -- (ㄹ)
    SELECT C.*, LV FROM CHARS C;
    1. 0개
    2. 1개
    3. 2개
    4. 3개
  12. 12. 시즌 이벤트 일정을 계산하는 다음 SQL의 실행 결과로 옳은 것은? (Oracle, 결과는 A / B / C / D 순서)

    SELECT TO_CHAR(ROUND(TO_DATE('2026-03-17 13:00', 'YYYY-MM-DD HH24:MI')), 'MM-DD') AS A,
           TO_CHAR(ADD_MONTHS(DATE '2026-01-31', 1), 'MM-DD')                       AS B,
           MONTHS_BETWEEN(DATE '2026-03-31', DATE '2026-02-28')                     AS C,
           TO_CHAR(ROUND(DATE '2026-03-17', 'MM'), 'MM-DD')                         AS D
    FROM   DUAL;
    1. 03-17 / 02-28 / 1 / 03-01
    2. 03-18 / 02-28 / 1 / 04-01
    3. 03-18 / 03-03 / 1.0968 / 04-01
    4. 03-17 / 03-03 / 1 / 03-01
  13. 13. 다음 SQL의 실행 결과로 옳은 것은? (Oracle, 결과는 A / B / C / D 순서)

    SELECT INSTR('dragon_knight_01', '_', 1, 2)   AS A,
           SUBSTR('dragon_knight_01', -2)         AS B,
           LPAD('77', 5, '0')                     AS C,
           NVL2(NULLIF('A', 'A'), 'X', 'Y')       AS D
    FROM   DUAL;
    1. 7 / dr / 77000 / X
    2. 14 / 01 / 77000 / Y
    3. 7 / 01 / 00077 / X
    4. 14 / 01 / 00077 / Y
  14. 14. 다음 [ITEM] 테이블에 대해 (가)~(다) SQL을 실행했을 때 결과로 옳은 것은? (결과는 (가) / (나) / (다) 순서)

    [ITEM]

    ITEM_NM
    HP_포션
    HP포션(대)
    MP_포션
    %할인권
    HPX포션
    -- (가)
    SELECT COUNT(*) FROM ITEM WHERE ITEM_NM LIKE 'HP_%';
    
    -- (나)
    SELECT COUNT(*) FROM ITEM WHERE ITEM_NM LIKE 'HP\_%' ESCAPE '\';
    
    -- (다)
    SELECT COUNT(*) FROM ITEM WHERE ITEM_NM LIKE '%\%%' ESCAPE '\';
    1. 3 / 1 / 1
    2. 2 / 1 / 1
    3. 3 / 1 / 5
    4. 1 / 1 / 1
  15. 15. 다음 [PAY] 테이블에 SQL을 실행한 결과로 옳은 것은? (ACCT_ID(CNT, OK_AMT) 형식으로 표기)

    [PAY]

    PAY_NO ACCT_ID AMT STATUS
    1 A1 10000 OK
    2 A1 5000 CANCEL
    3 A2 30000 OK
    4 A2 NULL OK
    5 A3 20000 OK
    6 A3 20000 OK
    7 A4 50000 CANCEL
    SELECT ACCT_ID,
           COUNT(AMT) AS CNT,
           SUM(CASE WHEN STATUS = 'OK' THEN AMT END) AS OK_AMT
    FROM   PAY
    GROUP  BY ACCT_ID
    HAVING COUNT(*) >= 2
       AND SUM(CASE WHEN STATUS = 'OK' THEN AMT ELSE 0 END) >= 20000
    ORDER  BY ACCT_ID;
    1. A2(1, 30000), A3(2, 40000)
    2. A2(2, 30000), A3(2, 40000)
    3. A1(2, 10000), A2(1, 30000), A3(2, 40000)
    4. A3(2, 40000)
  16. 16. 다음 [CHARS] 테이블에 SQL을 실행한 결과의 출력 순서로 옳은 것은? (Oracle)

    [CHARS]

    CHAR_ID GUILD_ID LV
    C1 G1 50
    C2 NULL 70
    C3 G2 NULL
    C4 G1 50
    C5 NULL NULL
    C6 G2 80
    SELECT CHAR_ID
    FROM   CHARS
    ORDER  BY CASE WHEN GUILD_ID IS NULL THEN 1 ELSE 0 END,
              LV DESC NULLS LAST,
              CHAR_ID;
    1. C3, C6, C1, C4, C5, C2
    2. C6, C1, C4, C3, C5, C2
    3. C6, C1, C4, C3, C2, C5
    4. C2, C5, C6, C1, C4, C3
  17. 17. 캐릭터 레벨 구간별 등급 기준표가 다음과 같다. SQL을 실행했을 때 결과 행 수와 GRADE가 '고수'인 행의 수로 옳은 것은?

    [LV_GRADE]

    GRADE MIN_LV MAX_LV
    초보 1 29
    중수 30 59
    고수 55 99

    [CHARS]

    CHAR_ID LV
    C1 10
    C2 30
    C3 57
    C4 60
    C5 100
    SELECT C.CHAR_ID, G.GRADE
    FROM   CHARS C, LV_GRADE G
    WHERE  C.LV BETWEEN G.MIN_LV AND G.MAX_LV;
    1. 4행, 1개
    2. 5행, 2개
    3. 5행, 1개
    4. 6행, 2개
  18. 18. 다음 [ACCT], [PAY] 테이블에 대해 (가)~(다) SQL을 실행했을 때 결과로 옳은 것은? (결과는 (가) / (나) / (다) 순서)

    [ACCT]

    ACCT_ID
    A1
    A2
    A3

    [PAY]

    PAY_NO ACCT_ID AMT
    1 A1 5000
    2 A1 20000
    3 A4 30000
    -- (가)
    SELECT COUNT(*) FROM ACCT A FULL OUTER JOIN PAY P ON A.ACCT_ID = P.ACCT_ID;
    
    -- (나)
    SELECT COUNT(*) FROM ACCT A LEFT OUTER JOIN PAY P
           ON A.ACCT_ID = P.ACCT_ID AND P.AMT > 10000;
    
    -- (다)
    SELECT COUNT(*) FROM ACCT A RIGHT OUTER JOIN PAY P ON A.ACCT_ID = P.ACCT_ID
    WHERE  A.ACCT_ID IS NULL;
    1. 5 / 4 / 1
    2. 6 / 3 / 1
    3. 4 / 3 / 1
    4. 5 / 3 / 1
  19. 19. 다음 [CHARS], [EVT] 테이블에 대해 (가)~(라) SQL을 실행했을 때 결과로 옳은 것은? (CHARS에는 SERVER_ID가 'S9'인 행이 없다. 결과는 (가) / (나) / (다) / (라) 순서)

    [CHARS]

    CHAR_ID SERVER_ID LV
    C1 S1 10
    C2 S1 40
    C3 S2 70

    [EVT]

    EVT_LV
    30
    NULL
    -- (가)
    SELECT COUNT(*) FROM CHARS WHERE LV > ALL (SELECT LV FROM CHARS WHERE SERVER_ID = 'S9');
    -- (나)
    SELECT COUNT(*) FROM CHARS WHERE LV > ANY (SELECT LV FROM CHARS WHERE SERVER_ID = 'S9');
    -- (다)
    SELECT COUNT(*) FROM CHARS WHERE LV > ALL (SELECT EVT_LV FROM EVT);
    -- (라)
    SELECT COUNT(*) FROM CHARS WHERE LV > ANY (SELECT EVT_LV FROM EVT);
    1. 0 / 0 / 0 / 2
    2. 3 / 0 / 0 / 2
    3. 3 / 0 / 2 / 2
    4. 0 / 0 / 2 / 2
  20. 20. 서버별 최고 레벨 캐릭터를 조회하려고 다음 SQL을 작성하였다. [CHARS] 테이블이 아래와 같을 때 결과로 출력되는 CHAR_ID를 모두 나열한 것은?

    [CHARS]

    CHAR_ID SERVER_ID LV
    C1 S1 50
    C2 S1 70
    C3 S1 70
    C4 S2 NULL
    C5 S2 30
    C6 S3 NULL
    SELECT CHAR_ID
    FROM   CHARS C
    WHERE  LV = (SELECT MAX(LV)
                 FROM   CHARS
                 WHERE  SERVER_ID = C.SERVER_ID);
    1. C2, C5
    2. C2, C3, C5, C6
    3. C2, C3, C5
    4. C2, C3, C4, C5
  21. 21. 이벤트 1, 2에 참여한 계정 목록이 다음과 같다. (가)~(라) SQL의 결과로 옳은 것은? (결과는 (가) / (나) / (다) / (라) 순서)

    [EVT1]

    ACCT_ID
    A1
    A1
    A2
    A3
    NULL

    [EVT2]

    ACCT_ID
    A2
    A3
    A3
    A4
    NULL
    -- (가)
    SELECT COUNT(*) FROM (SELECT ACCT_ID FROM EVT1 UNION     SELECT ACCT_ID FROM EVT2);
    -- (나)
    SELECT COUNT(*) FROM (SELECT ACCT_ID FROM EVT1 MINUS     SELECT ACCT_ID FROM EVT2);
    -- (다)
    SELECT COUNT(*) FROM (SELECT ACCT_ID FROM EVT1 INTERSECT SELECT ACCT_ID FROM EVT2);
    -- (라)
    SELECT COUNT(*) FROM (SELECT ACCT_ID FROM EVT1 UNION ALL SELECT ACCT_ID FROM EVT2);
    1. 5 / 1 / 3 / 10
    2. 4 / 1 / 2 / 10
    3. 5 / 2 / 3 / 10
    4. 4 / 1 / 2 / 8
  22. 22. [PAY] 테이블에 다음 데이터가 있다. (가)~(라) SQL의 결과 행 수로 옳은 것은? (결과는 (가) / (나) / (다) / (라) 순서)

    [PAY]

    SERVER_ID PAY_TYPE AMT
    S1 CARD 1000
    S1 CARD 2000
    S1 PHONE 500
    S2 CARD 3000
    S3 PHONE 700
    -- (가)
    SELECT SERVER_ID, PAY_TYPE, SUM(AMT) FROM PAY GROUP BY ROLLUP(SERVER_ID, PAY_TYPE);
    -- (나)
    SELECT SERVER_ID, PAY_TYPE, SUM(AMT) FROM PAY GROUP BY CUBE(SERVER_ID, PAY_TYPE);
    -- (다)
    SELECT SERVER_ID, PAY_TYPE, SUM(AMT) FROM PAY GROUP BY GROUPING SETS(SERVER_ID, PAY_TYPE, ());
    -- (라)
    SELECT SERVER_ID, PAY_TYPE, SUM(AMT) FROM PAY GROUP BY SERVER_ID, ROLLUP(PAY_TYPE);
    1. 8 / 10 / 5 / 7
    2. 8 / 9 / 6 / 7
    3. 7 / 10 / 6 / 8
    4. 8 / 10 / 6 / 7
  23. 23. 다음 [CHARS] 테이블에 SQL을 실행했을 때, C2 행의 S1, S2 값과 C4 행의 CNT 값으로 옳은 것은? (결과는 C2의 S1 / C2의 S2 / C4의 CNT 순서)

    [CHARS]

    CHAR_ID LV
    C1 10
    C2 20
    C3 20
    C4 30
    SELECT CHAR_ID,
           SUM(LV) OVER (ORDER BY LV)                                    AS S1,
           SUM(LV) OVER (ORDER BY LV, CHAR_ID ROWS UNBOUNDED PRECEDING)  AS S2,
           COUNT(*) OVER (ORDER BY LV RANGE BETWEEN 10 PRECEDING
                                            AND CURRENT ROW)             AS CNT
    FROM   CHARS;
    1. 30 / 30 / 3
    2. 50 / 50 / 4
    3. 50 / 30 / 3
    4. 30 / 50 / 2
  24. 24. 다음 [LOGIN] 테이블에 SQL을 실행했다. A1 계정의 3월 3일 행의 GAP, LAST_DT 값과 A2 계정의 3월 2일 행의 GAP 값으로 옳은 것은? (Oracle, LOGIN_DT는 시각이 없는 DATE 타입, 날짜는 MM-DD로 표기)

    [LOGIN]

    ACCT_ID LOGIN_DT
    A1 03-01
    A1 03-03
    A1 03-07
    A2 03-02
    A2 03-05
    SELECT ACCT_ID, LOGIN_DT,
           LOGIN_DT - LAG(LOGIN_DT) OVER (PARTITION BY ACCT_ID ORDER BY LOGIN_DT)  AS GAP,
           LAST_VALUE(LOGIN_DT)     OVER (PARTITION BY ACCT_ID ORDER BY LOGIN_DT)  AS LAST_DT
    FROM   LOGIN;
    1. 2 / 03-03 / NULL
    2. 2 / 03-07 / NULL
    3. 2 / 03-07 / 0
    4. 4 / 03-03 / NULL
  25. 25. 결제 금액 상위 3건을 조회하려는 다음 SQL에 대한 설명으로 가장 적절한 것은? (Oracle 12c 이상)

    -- (가)
    SELECT PAY_NO, AMT FROM PAY WHERE ROWNUM <= 3 ORDER BY AMT DESC;
    
    -- (나)
    SELECT * FROM (SELECT PAY_NO, AMT FROM PAY ORDER BY AMT DESC)
    WHERE  ROWNUM <= 3;
    
    -- (다)
    SELECT PAY_NO, AMT FROM PAY ORDER BY AMT DESC FETCH FIRST 3 ROWS WITH TIES;
    
    -- (라)
    SELECT PAY_NO, AMT FROM PAY WHERE ROWNUM = 2;
    1. (가)는 결제 금액 상위 3건을 정확히 반환한다.
    2. (다)는 3위와 금액이 같은 결제가 더 있으면 그 행들까지 포함하여 3건보다 많이 반환할 수 있다.
    3. (라)는 두 번째로 읽힌 결제 1건을 반환한다.
    4. (나)의 바깥 조건을 ROWNUM > 3으로 바꾸면 4위 이하의 결제가 모두 반환된다.
  26. 26. [ACCT] 테이블의 REF_ID는 신규 가입 시 추천한 계정의 ACCT_ID이다. (가)~(다) SQL의 결과 행 수로 옳은 것은? (결과는 (가) / (나) / (다) 순서)

    [ACCT]

    ACCT_ID REF_ID
    A1 NULL
    A2 A1
    A3 A1
    A4 A2
    A5 A4
    A6 NULL
    A7 A6
    -- (가)
    SELECT ACCT_ID, LEVEL FROM ACCT
    START WITH REF_ID IS NULL
    CONNECT BY PRIOR ACCT_ID = REF_ID AND ACCT_ID <> 'A2';
    
    -- (나)
    SELECT ACCT_ID, LEVEL FROM ACCT
    WHERE  ACCT_ID <> 'A2'
    START WITH REF_ID IS NULL
    CONNECT BY PRIOR ACCT_ID = REF_ID;
    
    -- (다)
    SELECT ACCT_ID, LEVEL FROM ACCT
    START WITH ACCT_ID = 'A5'
    CONNECT BY ACCT_ID = PRIOR REF_ID;
    1. 4 / 6 / 3
    2. 6 / 6 / 4
    3. 4 / 4 / 4
    4. 4 / 6 / 4
  27. 27. 다음 [PAY] 테이블에 (가), (나) SQL을 실행했다. (가)의 결과 행 수 / (가) 결과에서 A2 행의 CARD 값 / (나)의 결과 행 수로 옳은 것은? (Oracle)

    [PAY]

    PAY_NO ACCT_ID PAY_TYPE AMT
    1 A1 CARD 100
    2 A1 PHONE 50
    3 A1 CARD 200
    4 A2 PHONE 70
    5 A2 GIFT 30
    -- (가)
    SELECT *
    FROM  (SELECT ACCT_ID, PAY_TYPE, AMT FROM PAY)
    PIVOT (SUM(AMT) FOR PAY_TYPE IN ('CARD' AS CARD, 'PHONE' AS PHONE));
    
    -- (나)
    SELECT *
    FROM   PAY
    PIVOT (SUM(AMT) FOR PAY_TYPE IN ('CARD' AS CARD, 'PHONE' AS PHONE));
    1. 2 / 0 / 2
    2. 2 / NULL / 2
    3. 3 / NULL / 5
    4. 2 / NULL / 5
  28. 28. 일일 플레이 시간을 누적 테이블에 반영하는 다음 MERGE 문을 실행했다. 실행 후 [CHAR_STAT]의 행 수와 PLAY_MIN 합계로 옳은 것은? (Oracle)

    [CHAR_STAT] (실행 전)

    CHAR_ID PLAY_MIN
    C1 100
    C2 200
    C3 50

    [DAILY_PLAY]

    CHAR_ID PLAY_MIN
    C1 30
    C3 10
    C4 40
    C5 0
    MERGE INTO CHAR_STAT T
    USING DAILY_PLAY S
    ON (T.CHAR_ID = S.CHAR_ID)
    WHEN MATCHED THEN
         UPDATE SET T.PLAY_MIN = T.PLAY_MIN + S.PLAY_MIN
         DELETE WHERE T.PLAY_MIN < 100
    WHEN NOT MATCHED THEN
         INSERT (CHAR_ID, PLAY_MIN) VALUES (S.CHAR_ID, S.PLAY_MIN)
         WHERE  S.PLAY_MIN > 0;
    1. 4행, 430
    2. 4행, 370
    3. 3행, 370
    4. 3행, 430
  29. 29. 캐릭터 C1의 골드가 1000으로 커밋되어 있는 상태에서, 한 세션이 다음 문장을 순서대로 실행했다. 마지막 SELECT의 결과로 옳은 것은? (Oracle, 모든 문장은 오류 없이 실행된다.)

    UPDATE CHARS SET GOLD = GOLD - 100 WHERE CHAR_ID = 'C1';
    SAVEPOINT SP1;
    UPDATE CHARS SET GOLD = GOLD - 200 WHERE CHAR_ID = 'C1';
    SAVEPOINT SP2;
    UPDATE CHARS SET GOLD = GOLD - 300 WHERE CHAR_ID = 'C1';
    ROLLBACK TO SP1;
    UPDATE CHARS SET GOLD = GOLD + 50  WHERE CHAR_ID = 'C1';
    CREATE TABLE TMP_X (A NUMBER);
    UPDATE CHARS SET GOLD = GOLD * 2   WHERE CHAR_ID = 'C1';
    ROLLBACK;
    SELECT GOLD FROM CHARS WHERE CHAR_ID = 'C1';
    1. 950
    2. 900
    3. 1900
    4. 1000
  30. 30. 다음은 길드와 캐릭터 테이블을 생성하는 DDL이다. 이에 대한 설명으로 가장 적절하지 않은 것은? (Oracle)

    CREATE TABLE GUILD (
      GUILD_ID   VARCHAR2(10) PRIMARY KEY,
      GUILD_NM   VARCHAR2(50) NOT NULL
    );
    
    CREATE TABLE CHARS (
      CHAR_ID    VARCHAR2(10) PRIMARY KEY,
      CHAR_NM    VARCHAR2(30) NOT NULL,
      GUILD_ID   VARCHAR2(10),
      CONSTRAINT FK_CHARS_GUILD FOREIGN KEY (GUILD_ID)
                 REFERENCES GUILD (GUILD_ID) ON DELETE SET NULL
    );
    1. CREATE TABLE CHARS_BAK AS SELECT * FROM CHARS로 복제하면 NOT NULL 제약은 복제되지만 PK·FK 제약과 인덱스는 복제되지 않는다.
    2. GUILD에서 길드 행을 삭제하면 그 길드에 속한 CHARS 행도 함께 삭제된다.
    3. TRUNCATE TABLE CHARS는 DDL이므로 실행 후 ROLLBACK으로 되돌릴 수 없다.
    4. CHARS의 GUILD_ID에 존재하지 않는 길드 ID를 입력하면 FK 제약 위반 오류가 발생하지만, NULL은 입력할 수 있다.
  31. 31. Oracle 백그라운드 프로세스의 역할에 대한 설명으로 가장 적절한 것은?

    1. DBWn은 트랜잭션이 커밋될 때마다 그 트랜잭션이 변경한 버퍼 블록을 즉시 데이터파일에 기록한다.
    2. LGWR은 체크포인트가 발생할 때만 Redo 로그 버퍼의 내용을 Redo 로그 파일에 기록한다.
    3. SMON은 인스턴스가 비정상 종료된 뒤 재기동될 때 Redo로 롤포워드하고 커밋되지 않은 트랜잭션을 롤백하는 인스턴스 복구를 수행한다.
    4. PMON은 Redo 로그 파일이 가득 차서 전환될 때 그 파일을 아카이브 로그 파일로 복사한다.
  32. 32. 대규모 이벤트 보상으로 수백만 캐릭터에게 아이템을 지급하는 배치를 실행하려 한다. Oracle의 Redo와 Undo에 대한 설명 중 옳은 것을 모두 고른 것은?

    • (ㄱ) 커밋 시점에는 Redo 로그 버퍼의 내용만 Redo 로그 파일에 기록되면 되고, 변경된 데이터 블록은 나중에 데이터파일에 기록되어도 된다.
    • (ㄴ) DBWn이 Dirty 블록을 데이터파일에 기록하기 전에, 해당 변경에 대한 Redo가 먼저 로그 파일에 기록되어야 한다.
    • (ㄷ) Undo는 트랜잭션 롤백, 다른 세션의 읽기 일관성 제공, 인스턴스 복구 시 미커밋 트랜잭션 롤백에 사용된다.
    • (ㄹ) Redo 로그는 빠른 커밋을 위해 변경된 데이터 블록 위치에 맞춰 Random I/O 방식으로 기록된다.
    1. (ㄱ), (ㄴ), (ㄷ)
    2. (ㄱ), (ㄷ)
    3. (ㄴ), (ㄷ), (ㄹ)
    4. (ㄱ), (ㄴ), (ㄷ), (ㄹ)
  33. 33. 캐릭터 조회 화면에서 다음과 같이 캐릭터ID마다 리터럴 값을 붙여 SQL을 실행하고 있다. 운영 중 Shared Pool 관련 대기 이벤트가 크게 늘었다. 이에 대한 설명으로 가장 적절한 것은?

    SELECT * FROM 캐릭터 WHERE 캐릭터ID = 'C10001';   -- (1)
    SELECT * FROM 캐릭터 WHERE 캐릭터ID = 'C10002';   -- (2)
    SELECT * FROM 캐릭터 WHERE 캐릭터ID = 'C10003';   -- (3)
    select * from 캐릭터 where 캐릭터ID = 'C10001';   -- (4)
    ...  (하루 수십만 종류의 SQL이 V$SQL에 등록됨)
    1. (1)과 (4)는 대소문자만 다를 뿐 의미가 같으므로 같은 커서를 공유한다.
    2. 캐릭터ID마다 SQL 텍스트가 달라 각각 하드 파싱되고 라이브러리 캐시에 따로 저장되므로, 파싱 부하와 라이브러리 캐시 관련 래치 경합이 증가한다.
    3. 바인드 변수로 바꾸면 바인드 변수 Peeking 여부와 상관없이 컬럼 히스토그램이 매 실행마다 정확히 반영된다.
    4. 소프트 파싱은 SQL을 라이브러리 캐시에서 찾지 못해 최적화와 로우 소스 생성을 다시 거치는 과정이다.
  34. 34. 결제 테이블(5억 건)의 결제수단 컬럼은 'CARD'가 95%, 'COUPON'이 0.1%를 차지하며 히스토그램이 수집되어 있다. 결제수단에는 단일 컬럼 인덱스가 있고, 아래 SQL을 바인드 변수로 실행한다. 어느 날 이 SQL의 성능이 갑자기 급격히 나빠졌다. 가장 적절한 원인 분석은? (바인드 변수 Peeking이 활성화되어 있다.)

    SELECT 결제ID, 계정ID, 결제금액, 결제일시
    FROM   결제
    WHERE  결제수단 = :pay_type
    AND    결제일시 >= TRUNC(SYSDATE) - 1;
    1. 바인드 변수를 사용하면 옵티마이저가 매 실행 시점의 값으로 히스토그램을 참조해 항상 값별 최적 계획을 새로 만든다.
    2. SQL을 리터럴 방식으로 바꾸면 하드 파싱 횟수가 줄어 성능이 안정된다.
    3. 바인드 변수 Peeking이 비활성화되어 있을 때만 첫 실행 값으로 계획이 고정되는 현상이 발생한다.
    4. 커서가 재생성될 때 첫 하드 파싱이 'COUPON' 값으로 수행되어 인덱스 계획이 만들어졌고, 이후 'CARD' 조회에도 같은 계획이 재사용되었을 가능성이 높다.
  35. 35. 아이템 지급 내역을 조회하는 SQL을 AutoTrace로 실행했더니 다음 통계가 출력되었다. 이 SQL의 버퍼캐시 히트율(BCHR)은?

    Statistics
    ----------------------------------------------------------
              0  recursive calls
            200  db block gets
           9800  consistent gets
           2500  physical reads
              0  redo size
          41230  bytes sent via SQL*Net to client
            113  SQL*Net roundtrips to/from client
           1680  rows processed
    1. 74.5%
    2. 75%
    3. 25%
    4. 97.5%
  36. 36. Single Block I/O와 Multiblock I/O에 대한 설명으로 가장 적절한 것은?

    1. 인덱스로 테이블 블록을 랜덤 액세스할 때는 한 번에 한 블록씩 읽는 Single Block I/O가 일어나며, 이때 대기 이벤트는 db file sequential read로 나타난다.
    2. Table Full Scan은 Single Block I/O로 블록을 하나씩 읽으며, 이때 대기 이벤트는 db file scattered read로 나타난다.
    3. Index Fast Full Scan은 인덱스 리프 블록의 연결 순서를 따라 Single Block I/O로 읽는다.
    4. Multiblock I/O는 한 번의 I/O Call로 여러 익스텐트에 걸친 블록도 함께 읽을 수 있다.
  37. 37. 다음 실행계획에 대한 설명으로 가장 적절한 것은?

    SELECT /*+ LEADING(A C S) USE_NL(C) USE_HASH(S) SWAP_JOIN_INPUTS(S) */ ...
    
    ----------------------------------------------------------------
    | Id | Operation                       | Name       | Rows  |
    ----------------------------------------------------------------
    |  0 | SELECT STATEMENT                |            |  1200 |
    |* 1 |  HASH JOIN                      |            |  1200 |
    |  2 |   TABLE ACCESS FULL             | 서버        |    20 |
    |  3 |   NESTED LOOPS                  |            |  1200 |
    |  4 |    NESTED LOOPS                 |            |  1200 |
    |  5 |     TABLE ACCESS BY INDEX ROWID | 계정        |   300 |
    |* 6 |      INDEX RANGE SCAN           | 계정_X01    |   300 |
    |* 7 |     INDEX RANGE SCAN            | 캐릭터_X01  |     4 |
    |  8 |    TABLE ACCESS BY INDEX ROWID  | 캐릭터      |     4 |
    ----------------------------------------------------------------
    
    Predicate Information (identified by operation id):
       1 - access("S"."서버ID"="C"."서버ID")
       6 - access("A"."가입일자">=:B1)
       7 - access("C"."계정ID"="A"."계정ID")
    1. 서버 테이블(Id 2)은 Probe Input으로 사용되며, 계정·캐릭터의 NL 조인 결과로 해시 테이블을 만든다.
    2. 계정_X01 인덱스(Id 6)는 서버 테이블에서 읽은 행 수만큼 반복해서 탐색된다.
    3. 서버 테이블로 해시 테이블을 만든 뒤, 계정과 캐릭터를 NL 조인한 결과를 Probe Input으로 사용해 해시 조인한다.
    4. 캐릭터_X01 인덱스(Id 7)는 계정 테이블에서 읽은 행 수와 관계없이 한 번만 탐색된다.
  38. 38. 접속로그_X01 인덱스는 (계정ID, 접속일시, 서버ID) 순으로 구성되어 있다. 다음 SQL의 실행계획과 Predicate Information에 대한 설명으로 가장 적절한 것은?

    SELECT 접속일시, 접속IP
    FROM   접속로그
    WHERE  계정ID = :B1
    AND    접속일시 >= :B2 AND 접속일시 < :B3
    AND    서버ID = :B4
    AND    결과코드 = 'F';
    
    ------------------------------------------------------------
    | Id | Operation                          | Name          |
    ------------------------------------------------------------
    |  0 | SELECT STATEMENT                   |               |
    |* 1 |  TABLE ACCESS BY INDEX ROWID       | 접속로그       |
    |* 2 |   INDEX RANGE SCAN                 | 접속로그_X01   |
    ------------------------------------------------------------
    
    Predicate Information (identified by operation id):
       1 - filter("결과코드"='F')
       2 - access("계정ID"=:B1 AND "접속일시">=:B2 AND "서버ID"=:B4 AND "접속일시"<:B3)
           filter("서버ID"=:B4)
    1. 서버ID 조건은 access에 나타나므로 인덱스 스캔 범위를 줄이는 데 기여한다.
    2. 결과코드 조건은 인덱스 리프 블록을 읽으면서 걸러진다.
    3. 인덱스를 (계정ID, 서버ID, 접속일시) 순으로 바꾸더라도 인덱스 스캔 범위는 지금과 같다.
    4. 인덱스로 읽은 행 중 결과코드 조건으로 버려지는 행이 많다면 결과코드를 인덱스에 추가해 테이블 랜덤 액세스를 줄일 수 있다.
  39. 39. 다음은 아이템거래 조회 SQL의 TKPROF 결과이다. 이에 대한 분석으로 가장 적절한 것은?

    call     count       cpu    elapsed       disk      query    current        rows
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    Parse        1      0.00       0.00          0          0          0           0
    Execute      1      0.00       0.00          0          0          0           0
    Fetch      201      1.82      14.36      41250      52830          0        3000
    ------- ------  -------- ---------- ---------- ---------- ----------  ----------
    total      203      1.82      14.36      41250      52830          0        3000
    
    Misses in library cache during parse: 0
    Optimizer mode: ALL_ROWS
    
    Rows     Row Source Operation
    -------  ---------------------------------------------------
       3000  TABLE ACCESS BY INDEX ROWID 아이템거래 (cr=52830 pr=41250 pw=0 time=14352110 us)
      48000   INDEX RANGE SCAN 아이템거래_X01 (cr=310 pr=25 pw=0 time=95120 us)
    1. 인덱스로 48,000건을 찾은 뒤 테이블에서 3,000건만 남았다. 테이블 단계의 필터 조건 컬럼을 인덱스에 추가하면 테이블 액세스에서 발생한 약 52,520블록의 I/O를 크게 줄일 수 있다.
    2. Misses in library cache during parse: 0이므로 이 SQL은 하드 파싱되었다.
    3. 논리적 I/O는 disk + query + current = 94,080블록이다.
    4. Fetch Call이 201회이므로 클라이언트의 ArraySize는 100으로 설정되어 있다.
  40. 40. 다음은 GATHER_PLAN_STATISTICS 힌트를 주고 실행한 뒤 DBMS_XPLAN.DISPLAY_CURSOR(NULL, NULL, 'ALLSTATS LAST')로 조회한 결과이다. 이에 대한 설명으로 가장 적절한 것은?

    -------------------------------------------------------------------------------------------
    | Id | Operation                      | Name      | Starts | E-Rows | A-Rows | Buffers |
    -------------------------------------------------------------------------------------------
    |  0 | SELECT STATEMENT               |           |      1 |        |   1205 |    3640 |
    |  1 |  NESTED LOOPS                  |           |      1 |     50 |   1205 |    3640 |
    |  2 |   NESTED LOOPS                 |           |      1 |     50 |   1205 |    2435 |
    |  3 |    TABLE ACCESS BY INDEX ROWID | 결제       |      1 |     50 |   1205 |    1220 |
    |* 4 |     INDEX RANGE SCAN           | 결제_X02   |      1 |     50 |   1205 |       8 |
    |* 5 |    INDEX UNIQUE SCAN           | 계정_PK    |   1205 |      1 |   1205 |    1215 |
    |  6 |   TABLE ACCESS BY INDEX ROWID  | 계정       |   1205 |      1 |   1205 |    1205 |
    -------------------------------------------------------------------------------------------
    1. Id 5의 E-Rows(1)와 A-Rows(1,205)가 크게 다르므로 계정_PK에 대한 카디널리티 추정이 잘못되었다.
    2. Id 6에서 계정 테이블 액세스 자체가 읽은 블록 수는 3,640이다.
    3. Id 4에서 E-Rows 50 대비 A-Rows 1,205로 카디널리티 추정이 크게 빗나갔으며, 이 추정 오류가 NL 조인 선택에 영향을 주었을 가능성이 있다.
    4. Id 3에서 결제 테이블 액세스 자체가 읽은 블록 수는 1,220이다.
  41. 41. B*Tree 인덱스 구조와 탐색에 대한 설명으로 가장 적절한 것은?

    1. 리프 블록은 단방향 링크로 연결되어 있어 오름차순 스캔만 가능하고, 내림차순 정렬은 별도 소트가 필요하다.
    2. 수직적 탐색은 조건을 만족하는 첫 번째 레코드를 찾는 과정이고, 수평적 탐색은 그 지점부터 리프 블록을 따라가며 조건을 만족하지 않는 레코드를 만날 때까지 스캔하는 과정이다.
    3. Oracle의 단일 컬럼 B*Tree 인덱스에는 해당 컬럼이 NULL인 행도 저장되므로 IS NULL 조건으로 Range Scan할 수 있다.
    4. 인덱스 리프 블록의 ROWID는 데이터 오브젝트 번호, 데이터파일 번호, 블록 번호로 구성되며 로우 번호는 포함하지 않는다.
  42. 42. 게임 운영 DB의 인덱스 구조 선택에 대한 설명으로 가장 적절하지 않은 것은?

    1. IOT는 PK 순으로 정렬된 상태로 리프 블록에 데이터를 저장하므로, PK 범위 조회 시 테이블 랜덤 액세스가 발생하지 않는다.
    2. 비트맵 인덱스는 Distinct 값이 적은 컬럼에서 저장 효율이 좋지만, 동시 DML이 많은 OLTP 환경에서는 Lock 경합을 일으킬 수 있다.
    3. SQL Server의 클러스터형 인덱스는 리프 페이지에 데이터 행이 저장된다는 점에서 Oracle의 IOT와 유사하다.
    4. 초당 수천 건이 INSERT되는 접속로그 테이블의 결과코드 컬럼에 비트맵 인덱스를 만들면 Lock이 행 단위로 한정되어 동시성이 좋아진다.
  43. 43. 캐릭터_X01 인덱스는 (서버ID, 캐릭터명) 순으로 구성되어 있고, 서버ID는 VARCHAR2 타입이다. 다음 중 캐릭터_X01을 Index Range Scan으로 처리할 수 있는 조건절은?

    CREATE INDEX 캐릭터_X01 ON 캐릭터 (서버ID, 캐릭터명);
    1. WHERE 서버ID = 'S01' AND 캐릭터명 LIKE '%검사'
    2. WHERE SUBSTR(서버ID, 1, 1) = 'S'
    3. WHERE 서버ID = 101
    4. WHERE 캐릭터명 = '용사'
  44. 44. 결제_X01 인덱스는 (결제수단, 결제일자) 순으로 구성되어 있다. 결제수단은 'CARD', 'PHONE', 'BANK', 'GIFT' 4개 값만 가진다. 다음 SQL의 실행계획에 대한 설명으로 가장 적절한 것은?

    SELECT /*+ INDEX_SS(P 결제_X01) */ COUNT(*)
    FROM   결제 P
    WHERE  결제일자 BETWEEN '20260301' AND '20260303';
    
    ---------------------------------------------------
    | Id | Operation        | Name     | Rows |
    ---------------------------------------------------
    |  0 | SELECT STATEMENT |          |    1 |
    |  1 |  SORT AGGREGATE  |          |    1 |
    |* 2 |   INDEX SKIP SCAN| 결제_X01  | 4500 |
    ---------------------------------------------------
    1. Index Skip Scan은 선두 컬럼의 Distinct 값이 많을수록 유리하다.
    2. 이 SQL을 Index Fast Full Scan으로 처리하면 결과가 결제일자 순으로 정렬되어 반환된다.
    3. 선두 컬럼인 결제수단의 Distinct 값이 적고 결제일자 조건의 선택도가 좋으므로, 결제수단 값마다 해당 결제일자 구간만 탐색하는 Skip Scan이 효율적이며 INDEX_SS 힌트로 유도할 수 있다.
    4. Index Skip Scan은 선두 컬럼에 대한 조건이 있어야만 사용할 수 있다.
  45. 45. 접속로그 테이블은 접속이 발생할 때마다 순서대로 INSERT되며, 계정ID 인덱스와 접속일시 인덱스가 각각 있다. 클러스터링 팩터(CF)에 대한 설명으로 가장 적절한 것은?

    1. CF가 좋을수록 인덱스 리프 블록을 스캔하는 양이 줄어든다.
    2. 접속일시 인덱스는 CF가 좋고 계정ID 인덱스는 CF가 나쁠 가능성이 높으므로, 같은 건수를 읽더라도 계정ID 인덱스를 통한 테이블 블록 I/O가 더 많을 가능성이 높다.
    3. 인덱스 손익분기점은 CF와 관계없이 항상 전체 데이터의 5~20% 사이로 일정하다.
    4. CF는 인덱스의 속성이므로 테이블 데이터를 특정 컬럼 순으로 정렬해 재생성하더라도 변하지 않는다.
  46. 46. 다음 SQL의 트레이스 결과를 보고 개선 방안을 검토하고 있다. 캐릭터_X02 인덱스는 (서버ID, 직업코드) 순이며, 캐릭터ID는 PK다. 가장 적절한 개선 방안은?

    SELECT 캐릭터ID, 레벨
    FROM   캐릭터
    WHERE  서버ID = :srv AND 직업코드 = :job AND 레벨 >= 90;
    
    Rows     Row Source Operation
    -------  ---------------------------------------------------
         12  TABLE ACCESS BY INDEX ROWID 캐릭터 (cr=30410 pr=28870 ...)
      30000   INDEX RANGE SCAN 캐릭터_X02 (cr=85 pr=3 ...)
    1. 인덱스를 (레벨, 서버ID, 직업코드) 순으로 재구성해 범위 조건 컬럼을 선두에 둔다.
    2. 캐릭터 테이블을 직업코드 기준 List 파티션으로 재구성한다.
    3. FULL 힌트로 Table Full Scan을 유도해 랜덤 액세스를 없앤다.
    4. 인덱스를 (서버ID, 직업코드, 레벨)로 바꾸면 인덱스 단계에서 12건만 남아 테이블 액세스가 12회로 줄고, 캐릭터ID까지 추가하면 테이블 액세스 없이 처리할 수 있다.
  47. 47. 거래소 화면에서 다음 SQL이 매우 자주 수행된다. 아이템코드는 5,000종, 거래상태는 5종('등록', '완료', '취소', '만료', '보류')이며 조회 기간은 보통 1일이다. 이 SQL만 고려할 때 가장 효율적인 인덱스 구성은?

    SELECT 거래ID, 거래일시, 거래가격, 판매캐릭터ID
    FROM   아이템거래
    WHERE  아이템코드 = :item
    AND    거래일시 BETWEEN :dt1 AND :dt2
    AND    거래상태 IN ('완료', '취소');
    1. (아이템코드, 거래상태, 거래일시)
    2. (거래일시, 아이템코드, 거래상태)
    3. (아이템코드, 거래일시, 거래상태)
    4. (거래상태, 거래일시, 아이템코드)
  48. 48. 계정별 최근 결제 20건을 보여 주는 화면의 SQL이다. 계정당 결제는 평균 수천 건이며, 결제상태는 'OK', 'CANCEL', 'FAIL' 중 하나다. 이 SQL을 소트 연산 없이 부분범위 처리하면서 가장 효율적으로 수행하기 위한 인덱스 구성은?

    SELECT *
    FROM  (SELECT 결제ID, 결제일시, 결제금액, 결제수단
           FROM   결제
           WHERE  계정ID = :acct
           AND    결제상태 = 'OK'
           ORDER  BY 결제일시 DESC)
    WHERE  ROWNUM <= 20;
    1. (결제일시, 계정ID, 결제상태)
    2. (계정ID, 결제상태, 결제일시)
    3. (계정ID, 결제일시)
    4. (결제상태, 계정ID)
  49. 49. NL 조인에 대한 설명으로 가장 적절하지 않은 것은?

    SELECT /*+ LEADING(A) USE_NL(C) */
           A.계정ID, C.캐릭터명, C.레벨
    FROM   계정 A, 캐릭터 C
    WHERE  A.가입일자 >= :dt
    AND    C.계정ID = A.계정ID;
    1. 랜덤 액세스 위주의 조인 방식이므로 대량 데이터를 조인할 때 불리하다.
    2. 한 레코드씩 순차적으로 처리하므로 부분범위 처리가 가능해 OLTP 화면 조회에 유리하다.
    3. Inner 쪽 조인 컬럼에 인덱스가 없어도 Inner 테이블은 Outer 건수와 관계없이 한 번만 Full Scan되므로 성능에 큰 영향이 없다.
    4. 드라이빙 테이블의 처리 범위에 따라 전체 일량이 결정되므로 조인 순서가 성능에 큰 영향을 준다.
  50. 50. 다음 SQL의 처리 일량을 추정하려 한다. 조건은 아래와 같다.

    • 계정_X01(가입일자) 인덱스로 읽은 계정은 10,000건이며, 이 중 등급 = 'VIP'인 계정은 500건이다.
    • 캐릭터_X01(계정ID) 인덱스가 있고, 계정당 캐릭터는 평균 4개이다.
    • 캐릭터 중 레벨 >= 50인 비율은 25%이다.

    캐릭터_X01 인덱스 탐색(수직적 탐색) 횟수와 캐릭터 테이블 액세스 횟수로 옳은 것은?

    SELECT /*+ LEADING(A) USE_NL(C) INDEX(A 계정_X01) INDEX(C 캐릭터_X01) */
           A.계정ID, C.캐릭터명, C.레벨
    FROM   계정 A, 캐릭터 C
    WHERE  A.가입일자 >= :dt
    AND    A.등급     = 'VIP'
    AND    C.계정ID   = A.계정ID
    AND    C.레벨    >= 50;
    1. 500회 / 2,000회
    2. 10,000회 / 40,000회
    3. 500회 / 500회
    4. 10,000회 / 2,000회
  51. 51. 다음 실행계획과 소트 머지 조인에 대한 설명으로 가장 적절한 것은?

    SELECT /*+ LEADING(E) USE_MERGE(L) */ E.이벤트ID, COUNT(*)
    FROM   이벤트구간 E, 접속로그 L
    WHERE  L.접속일시 BETWEEN E.시작일시 AND E.종료일시
    GROUP  BY E.이벤트ID;
    
    ------------------------------------------------------------
    | Id | Operation                     | Name           |
    ------------------------------------------------------------
    |  0 | SELECT STATEMENT              |                |
    |  1 |  HASH GROUP BY                |                |
    |  2 |   MERGE JOIN                  |                |
    |  3 |    TABLE ACCESS BY INDEX ROWID| 이벤트구간      |
    |  4 |     INDEX FULL SCAN           | 이벤트구간_X01  |
    |* 5 |    FILTER                     |                |
    |* 6 |     SORT JOIN                 |                |
    |  7 |      TABLE ACCESS FULL        | 접속로그        |
    ------------------------------------------------------------
    1. 소트 머지 조인은 등치(=) 조인 조건에서만 사용할 수 있다.
    2. 소트 머지 조인은 인덱스 유무와 관계없이 항상 양쪽 집합을 모두 정렬한다.
    3. 접속로그(Id 7)가 First 집합이고, 이벤트구간(Id 3)은 Second 집합이다.
    4. 조인 조건이 범위(비등치) 조건이라 해시 조인을 쓸 수 없는 대량 조인에 유용하며, First 집합은 인덱스를 이용해 소트를 생략할 수 있다.
  52. 52. 계정(100만 건), 캐릭터(500만 건), 결제(조건 적용 후 1만 건)를 조인하는 다음 SQL의 실행계획으로 옳은 것은? (아래 (가)~(라) 실행계획 중에서 고른다.)

    SELECT /*+ LEADING(A C P) USE_HASH(C) USE_HASH(P) SWAP_JOIN_INPUTS(P)
               FULL(A) FULL(C) FULL(P) */
           A.계정ID, C.캐릭터명, P.결제금액
    FROM   계정 A, 캐릭터 C, 결제 P
    WHERE  C.계정ID = A.계정ID
    AND    P.캐릭터ID = C.캐릭터ID
    AND    P.결제일시 >= TRUNC(SYSDATE);
    
    -- (가)
    HASH JOIN
      HASH JOIN
        TABLE ACCESS FULL 계정
        TABLE ACCESS FULL 캐릭터
      TABLE ACCESS FULL 결제
    
    -- (나)
    HASH JOIN
      TABLE ACCESS FULL 결제
      HASH JOIN
        TABLE ACCESS FULL 계정
        TABLE ACCESS FULL 캐릭터
    
    -- (다)
    HASH JOIN
      TABLE ACCESS FULL 결제
      HASH JOIN
        TABLE ACCESS FULL 캐릭터
        TABLE ACCESS FULL 계정
    
    -- (라)
    HASH JOIN
      HASH JOIN
        TABLE ACCESS FULL 결제
        TABLE ACCESS FULL 계정
      TABLE ACCESS FULL 캐릭터
    1. (가)
    2. (나)
    3. (다)
    4. (라)
  53. 53. 해시 조인에 대한 설명으로 가장 적절하지 않은 것은?

    1. Build Input이 Hash Area에 모두 담기면 In-Memory 해시 조인으로 처리되어 매우 빠르다.
    2. 해시 테이블을 PGA에 만들기 때문에 탐색 과정에서 래치 획득이 필요 없어 NL 조인보다 블록 탐색 비용이 낮다.
    3. 조인 조건이 BETWEEN이나 LIKE 같은 범위 조건이어도 해시 조인을 사용할 수 있다.
    4. 수행 빈도가 매우 높은 OLTP 쿼리에 해시 조인을 남용하면 해시 테이블 생성과 소멸이 반복되어 CPU와 메모리 사용량이 증가한다.
  54. 54. 최근 거래 이력이 있는 캐릭터를 조회하는 다음 SQL의 실행계획에 대한 설명으로 가장 적절한 것은?

    SELECT C.캐릭터ID, C.캐릭터명
    FROM   캐릭터 C
    WHERE  C.서버ID = 'S01'
    AND    EXISTS (SELECT /*+ NO_UNNEST */ 'x'
                   FROM   아이템거래 T
                   WHERE  T.판매캐릭터ID = C.캐릭터ID
                   AND    T.거래일시 >= TRUNC(SYSDATE) - 7);
    
    -------------------------------------------------------------
    | Id | Operation                     | Name            |
    -------------------------------------------------------------
    |  0 | SELECT STATEMENT              |                 |
    |* 1 |  FILTER                       |                 |
    |* 2 |   TABLE ACCESS FULL           | 캐릭터           |
    |* 3 |   INDEX RANGE SCAN            | 아이템거래_X03   |
    -------------------------------------------------------------
    
    Predicate Information (identified by operation id):
       1 - filter( EXISTS (SELECT 0 FROM "아이템거래" "T" WHERE ...))
       2 - filter("C"."서버ID"='S01')
       3 - access("T"."판매캐릭터ID"=:B1 AND "T"."거래일시">=TRUNC(SYSDATE@!)-7)
    1. 필터 오퍼레이션은 메인 쿼리의 행마다 서브쿼리를 수행하되 입력값별 결과를 캐싱한다. 그러나 캐릭터ID는 행마다 다른 값이므로 이 SQL에서는 캐싱 효과를 거의 기대할 수 없다.
    2. 필터 오퍼레이션은 서브쿼리 결과로 해시 테이블을 한 번 만든 뒤 메인 쿼리 행으로 탐색하는 방식이다.
    3. NO_UNNEST 힌트가 지정되었으므로 서브쿼리가 세미 조인으로 변환되었다.
    4. EXISTS 서브쿼리는 조건을 만족하는 첫 행을 찾아도 서브쿼리 범위의 끝까지 탐색을 계속한다.
  55. 55. 아이템거래 목록에 아이템명을 함께 보여 주기 위해 스칼라 서브쿼리를 사용하였다. 거래는 하루 수백만 건이지만 거래되는 아이템 종류는 수백 개 수준이다. 이에 대한 설명으로 가장 적절한 것은?

    SELECT T.거래ID, T.거래일시, T.거래가격,
           (SELECT I.아이템명 FROM 아이템 I WHERE I.아이템코드 = T.아이템코드) AS 아이템명
    FROM   아이템거래 T
    WHERE  T.거래일시 >= TRUNC(SYSDATE);
    1. 스칼라 서브쿼리 결과가 없으면 ORA-01403 오류가 발생하므로 아우터 조인으로 바꿔야 한다.
    2. 아이템코드의 Distinct 값이 많을수록 스칼라 서브쿼리 캐싱 효과가 커진다.
    3. 스칼라 서브쿼리가 두 건 이상을 반환하면 첫 번째 행의 값만 사용된다.
    4. 아이템코드 종류가 적으므로 캐싱으로 아이템 테이블 조회 횟수가 크게 줄어든다. 또 결과가 없을 때 NULL을 반환하므로 아이템과 아우터 조인한 것과 같은 결과를 낸다.
  56. 56. 캐릭터 테이블의 통계정보가 다음과 같다. 히스토그램이 없을 때, 아래 조건절에 대해 옵티마이저가 예상하는 카디널리티는?

    항목 값
    총 로우 수 1,000,000
    서버ID의 NDV(Distinct 값 수) 20
    직업코드의 NDV 10
    NULL 값 없음
    SELECT * FROM 캐릭터
    WHERE  서버ID = :srv
    AND    직업코드 = :job;
    1. 50,000
    2. 5,000
    3. 500
    4. 100,000
  57. 57. 옵티마이저 모드와 비용기반 옵티마이저(CBO)에 대한 설명으로 가장 적절한 것은?

    1. FIRST_ROWS_10 모드는 처음 10건을 가장 빨리 반환하는 계획을 선택하므로, 소트를 생략할 수 있는 인덱스나 NL 조인을 선호하는 경향이 있다.
    2. ALL_ROWS 모드는 첫 결과 행을 화면에 가장 빨리 보여 주는 것을 목표로 한다.
    3. 규칙기반 옵티마이저(RBO)는 테이블·인덱스 통계정보를 이용해 각 실행계획의 비용을 비교한다.
    4. 통계정보가 수집되지 않은 테이블은 CBO가 실행계획을 만들 수 없어 오류가 발생한다.
  58. 58. 다음 SQL과 실행계획에 대한 설명으로 가장 적절한 것은?

    SELECT /*+ LEADING(C) USE_NL(V) */
           C.캐릭터ID, C.캐릭터명, V.판매금액
    FROM   캐릭터 C,
           (SELECT /*+ NO_MERGE PUSH_PRED */ 판매캐릭터ID, SUM(거래가격) AS 판매금액
            FROM   아이템거래
            GROUP  BY 판매캐릭터ID) V
    WHERE  C.서버ID = 'S01'
    AND    C.가입일자 >= TRUNC(SYSDATE) - 1
    AND    V.판매캐릭터ID = C.캐릭터ID;
    
    ---------------------------------------------------------------
    | Id | Operation                        | Name            |
    ---------------------------------------------------------------
    |  0 | SELECT STATEMENT                 |                 |
    |  1 |  NESTED LOOPS                    |                 |
    |  2 |   TABLE ACCESS BY INDEX ROWID    | 캐릭터           |
    |* 3 |    INDEX RANGE SCAN              | 캐릭터_X03       |
    |  4 |   VIEW PUSHED PREDICATE          |                 |
    |  5 |    SORT GROUP BY                 |                 |
    |  6 |     TABLE ACCESS BY INDEX ROWID  | 아이템거래       |
    |* 7 |      INDEX RANGE SCAN            | 아이템거래_X02   |
    ---------------------------------------------------------------
    
    Predicate Information (identified by operation id):
       3 - access("C"."서버ID"='S01' AND "C"."가입일자">=TRUNC(SYSDATE@!)-1)
       7 - access("판매캐릭터ID"="C"."캐릭터ID")
    1. GROUP BY가 있는 뷰는 항상 메인 쿼리와 머징되어 처리된다.
    2. VIEW PUSHED PREDICATE는 뷰 안의 아이템거래를 모두 집계한 뒤 그 결과를 캐릭터와 조인하는 방식이다.
    3. 조인 조건 Pushdown이 적용되어 캐릭터 한 건마다 뷰 안에서 해당 판매캐릭터ID의 거래만 인덱스로 읽어 집계하며, 이를 유도하는 힌트는 PUSH_PRED이다.
    4. NO_MERGE 힌트만 지정하면 조인 조건 Pushdown이 반드시 발생한다.
  59. 59. 다음 SQL에 대해 옵티마이저가 수행할 수 있는 쿼리 변환에 대한 설명으로 가장 적절한 것은?

    SELECT A.계정ID, A.등급, P.결제ID, P.결제금액
    FROM   계정 A, 결제 P
    WHERE  P.계정ID = A.계정ID
    AND    A.계정ID = 'A100'
    AND    P.결제일시 >= TRUNC(SYSDATE) - 30;
    1. OR-Expansion은 OR 조건을 UNION으로 바꾸고, 중복 제거를 위해 SORT UNIQUE를 수행한다.
    2. 아우터 조인의 Inner 쪽 컬럼을 WHERE 절에서 IS NULL이 아닌 조건으로 걸러도, 옵티마이저는 아우터 조인을 그대로 유지한다.
    3. USE_CONCAT 힌트는 OR-Expansion이 일어나지 않도록 막는다.
    4. A.계정ID = 'A100'과 조인 조건 P.계정ID = A.계정ID로부터 P.계정ID = 'A100' 조건이 추가로 생성되어, 결제 테이블도 계정ID 인덱스로 액세스할 수 있다.
  60. 60. 다음 중 힌트가 의도대로 적용될 수 있는 SQL은? (각 인덱스와 테이블은 모두 존재한다.)

    1. SELECT /*+ INDEX(캐릭터 캐릭터_X01) */ * FROM 캐릭터 C WHERE C.서버ID = 'S01';
    2. SELECT /*+ LEADING(A) USE_HASH(C) */ * FROM 계정 A, 캐릭터 C WHERE C.레벨 BETWEEN A.최소레벨 AND A.최대레벨;
    3. SELECT /*+ FULL(C) PARALLEL(C 4) / 서버ID, COUNT() FROM 캐릭터 C GROUP BY 서버ID;
    4. SELECT /* + INDEX(C 캐릭터_X01) */ * FROM 캐릭터 C WHERE C.서버ID = 'S01';
  61. 61. 최근 7일간 결제한 계정 목록을 조회하는 다음 SQL에서 소트 부하를 줄이려 한다. 계정ID는 계정 테이블의 PK이고 결제 테이블에는 (계정ID, 결제일시) 인덱스가 있다. 가장 적절한 설명은?

    SELECT DISTINCT A.계정ID, A.계정명
    FROM   계정 A, 결제 P
    WHERE  P.계정ID = A.계정ID
    AND    P.결제일시 >= TRUNC(SYSDATE) - 7;
    1. DISTINCT를 GROUP BY A.계정ID, A.계정명으로 바꾸면 소트(또는 해시) 연산이 사라진다.
    2. EXISTS 서브쿼리로 바꾸면 계정ID가 PK이므로 결과가 같고, 계정마다 조건을 만족하는 결제 한 건만 찾으면 되어 중복 제거 연산과 불필요한 조인 결과 생성이 사라진다.
    3. DISTINCT만 제거해도 계정ID가 PK이므로 결과는 같다.
    4. ORDER BY A.계정ID를 추가하면 DISTINCT를 위한 소트가 생략된다.
  62. 62. 접속로그_X01 인덱스는 (계정ID, 서버ID, 접속일시) 순으로 구성되어 있다. 다음 중 INDEX RANGE SCAN (MIN/MAX) + FIRST ROW 방식으로 한 건만 읽고 멈추도록 처리될 수 있는 SQL은? (결과코드는 인덱스에 없는 컬럼이다.)

    1. SELECT MAX(접속일시) FROM 접속로그 WHERE 계정ID = :a AND 서버ID = :s;
    2. SELECT MAX(접속일시) FROM 접속로그 WHERE 계정ID = :a AND 결과코드 = 'S';
    3. SELECT MAX(TO_CHAR(접속일시, 'YYYYMMDDHH24MISS')) FROM 접속로그 WHERE 계정ID = :a AND 서버ID = :s;
    4. SELECT MAX(접속일시) FROM 접속로그 WHERE 서버ID = :s;
  63. 63. Direct Path Insert에 대한 설명으로 가장 적절하지 않은 것은?

    1. INSERT /*+ APPEND */ INTO … SELECT는 버퍼캐시를 거치지 않고 HWM 바깥 영역에 데이터를 직접 기록한다.
    2. Direct Path Insert를 수행하는 동안 대상 테이블에 Exclusive 모드 TM Lock이 걸려 다른 트랜잭션이 그 테이블에 DML을 할 수 없다.
    3. NOLOGGING 테이블에 Direct Path Insert를 하면 Redo 생성이 최소화되고, 입력 데이터에 대한 Undo도 거의 생성되지 않는다.
    4. INSERT /*+ APPEND */ INTO … VALUES 형태의 단건 INSERT도 항상 Direct Path로 처리되므로, 초당 수천 건이 들어오는 접속로그의 실시간 INSERT에 적용하는 것이 좋다.
  64. 64. 접속로그 테이블은 접속일시(DATE) 기준 월 단위 Range 파티션으로 구성되어 있다. 다음 SQL에 대한 설명으로 가장 적절한 것은?

    -- (가)
    SELECT COUNT(*) FROM 접속로그
    WHERE  접속일시 >= TO_DATE('20260301', 'YYYYMMDD')
    AND    접속일시 <  TO_DATE('20260401', 'YYYYMMDD');
    
    -- (나)
    SELECT COUNT(*) FROM 접속로그 WHERE TO_CHAR(접속일시, 'YYYYMM') = '202603';
    
    -- (다)
    SELECT COUNT(*) FROM 접속로그 WHERE 접속일시 >= :d1 AND 접속일시 < :d2;
    
    -- (라)
    SELECT COUNT(*) FROM 접속로그 WHERE 계정ID = :acct;
    1. (나)는 접속일시 조건이 있으므로 202603 파티션 하나만 읽는 정적 Pruning이 일어난다.
    2. (다)는 바인드 변수를 사용하므로 Pruning이 일어나지 않고 모든 파티션을 읽는다.
    3. (가)는 정적 Pruning으로 실행계획의 Pstart/Pstop에 파티션 번호가 표시되고, (다)는 동적 Pruning으로 KEY가 표시된다.
    4. (라)는 계정ID에 로컬 인덱스가 있으면 해당 계정의 데이터가 있는 파티션 하나만 액세스한다.
  65. 65. 월말 정산 배치에서 대용량 테이블을 병렬로 조인하려 한다. 병렬 처리에 대한 설명으로 가장 적절한 것은?

    1. PQ_DISTRIBUTE(P BROADCAST NONE)은 Outer 집합을 Inner 테이블 P를 처리하는 모든 병렬 서버에 복제하고 P는 재분배하지 않는 방식으로, Outer 집합이 매우 작을 때 적합하다.
    2. 병렬도(DOP)를 4로 지정하고 양쪽을 HASH-HASH 방식으로 재분배해 해시 조인하면 병렬 서버는 4개만 사용된다.
    3. 병렬 DML은 ALTER SESSION ENABLE PARALLEL DML 없이 PARALLEL 힌트만 지정해도 INSERT·UPDATE·DELETE가 모두 병렬로 처리된다.
    4. One SQL로 구현하는 것보다 커서를 열어 한 건씩 처리하는 절차형 방식이 Array 처리와 병렬 처리의 이점을 살리기 쉽다.
  66. 66. 결제수단별 월 결제 금액을 계정 단위로 집계하는 원래 SQL을 결제 테이블을 한 번만 읽도록 바꾸려 한다. 원래 SQL과 항상 같은 결과를 반환하는 SQL은? (아래 (가)~(라) 중에서 고른다.) (결제금액은 NOT NULL이며 결제수단은 'CARD', 'PHONE', 'GIFT' 중 하나이다.)

    -- 원래 SQL
    SELECT 계정ID, SUM(CARD_AMT) CARD_AMT, SUM(PHONE_AMT) PHONE_AMT
    FROM  (SELECT 계정ID, SUM(결제금액) CARD_AMT, 0 PHONE_AMT
           FROM   결제
           WHERE  결제수단 = 'CARD'  AND 결제일자 LIKE '202603%'
           GROUP  BY 계정ID
           UNION ALL
           SELECT 계정ID, 0, SUM(결제금액)
           FROM   결제
           WHERE  결제수단 = 'PHONE' AND 결제일자 LIKE '202603%'
           GROUP  BY 계정ID)
    GROUP  BY 계정ID;
    
    -- (가)
    SELECT 계정ID, SUM(DECODE(결제수단, 'CARD', 결제금액, 0)) CARD_AMT,
           SUM(DECODE(결제수단, 'PHONE', 결제금액, 0)) PHONE_AMT
    FROM   결제 WHERE 결제일자 LIKE '202603%'
    GROUP  BY 계정ID;
    
    -- (나)
    SELECT 계정ID, NVL(SUM(CASE WHEN 결제수단 = 'CARD'  THEN 결제금액 END), 0) CARD_AMT,
           NVL(SUM(CASE WHEN 결제수단 = 'PHONE' THEN 결제금액 END), 0) PHONE_AMT
    FROM   결제 WHERE 결제수단 IN ('CARD', 'PHONE') AND 결제일자 LIKE '202603%'
    GROUP  BY 계정ID;
    
    -- (다)
    SELECT 계정ID, SUM(CASE WHEN 결제수단 = 'CARD'  THEN 1 ELSE 0 END) CARD_AMT,
           SUM(CASE WHEN 결제수단 = 'PHONE' THEN 1 ELSE 0 END) PHONE_AMT
    FROM   결제 WHERE 결제수단 IN ('CARD', 'PHONE') AND 결제일자 LIKE '202603%'
    GROUP  BY 계정ID;
    
    -- (라)
    SELECT 계정ID, SUM(CASE WHEN 결제수단 = 'CARD'  THEN 결제금액 ELSE 0 END) CARD_AMT,
           SUM(CASE WHEN 결제수단 = 'PHONE' THEN 결제금액 ELSE 0 END) PHONE_AMT
    FROM   결제 WHERE 결제수단 IN ('CARD', 'PHONE') AND 결제일자 LIKE '202603%'
    GROUP  BY 계정ID, 결제수단;
    1. (가)
    2. (나)
    3. (다)
    4. (라)
  67. 67. 다음은 Oracle에서 네 세션이 순서대로 실행한 문장이다. 세션 1은 아직 커밋하지 않았다. 이에 대한 설명으로 가장 적절한 것은?

    -- 세션 1
    UPDATE 캐릭터 SET 골드 = 골드 - 100 WHERE 캐릭터ID = 'C1';
    
    -- 세션 2
    SELECT 골드 FROM 캐릭터 WHERE 캐릭터ID = 'C1';
    
    -- 세션 3
    UPDATE 캐릭터 SET 레벨 = 레벨 + 1 WHERE 캐릭터ID = 'C2';
    
    -- 세션 4
    SELECT 골드 FROM 캐릭터 WHERE 캐릭터ID = 'C1' FOR UPDATE NOWAIT;
    1. 세션 2의 SELECT는 세션 1이 커밋 또는 롤백할 때까지 대기한다.
    2. 세션 3의 UPDATE는 세션 1이 캐릭터 테이블에 TM Lock을 걸었으므로 대기한다.
    3. 세션 4의 SELECT … FOR UPDATE NOWAIT은 세션 1이 커밋할 때까지 기다린 뒤 실행된다.
    4. 세션 1의 UPDATE는 C1 행에 대한 TX Lock과 캐릭터 테이블에 대한 RX 모드 TM Lock을 획득하며, 이 상태에서 다른 세션이 캐릭터 테이블에 DDL을 수행하면 TM Lock 충돌이 발생한다.
  68. 68. 두 캐릭터 간 1:1 거래에서 다음과 같은 순서로 두 트랜잭션이 실행되었다. 이 상황에 대한 설명으로 가장 적절한 것은? (Oracle)

    시점  트랜잭션 T1 (C1 → C2 에게 판매)            트랜잭션 T2 (C2 → C1 에게 판매)
    ----  ----------------------------------------   ----------------------------------------
     t1   UPDATE 캐릭터 SET 골드 = 골드 + 500
          WHERE 캐릭터ID = 'C1';
     t2                                              UPDATE 캐릭터 SET 골드 = 골드 + 300
                                                     WHERE 캐릭터ID = 'C2';
     t3   UPDATE 캐릭터 SET 골드 = 골드 - 500
          WHERE 캐릭터ID = 'C2';   -- 대기
     t4                                              UPDATE 캐릭터 SET 골드 = 골드 - 300
                                                     WHERE 캐릭터ID = 'C1';   -- 대기
    1. Oracle은 교착상태를 감지하면 한 세션의 문장을 ORA-00060 오류와 함께 롤백한다. 이때 트랜잭션 전체가 아니라 해당 문장만 롤백되며, 갱신 순서를 캐릭터ID 순으로 통일하면 예방할 수 있다.
    2. Oracle은 교착상태를 감지하면 두 트랜잭션을 모두 자동으로 롤백한다.
    3. 교착상태는 UPDATE 사이에서만 발생하며, SELECT … FOR UPDATE를 사용하면 발생하지 않는다.
    4. 트랜잭션 격리성 수준을 SERIALIZABLE로 높이면 교착상태가 발생하지 않는다.
  69. 69. 주간 랭킹 리포트는 하나의 트랜잭션 안에서 서버별 캐릭터 수를 먼저 집계하고, 이어서 전체 캐릭터 수를 다시 집계해 비율을 계산한다. 그 사이 다른 세션에서 신규 캐릭터를 생성하고 커밋하는 경우가 있다. 이에 대한 설명으로 가장 적절한 것은?

    1. Oracle의 기본 격리성 수준은 Read Uncommitted이므로, 다른 세션이 커밋하지 않은 신규 캐릭터도 집계에 포함될 수 있다.
    2. Oracle은 Repeatable Read 수준을 기본 제공하므로, 한 트랜잭션 안에서 같은 집계를 반복하면 항상 같은 결과가 나온다.
    3. Oracle 기본 수준(Read Committed)에서는 두 집계 사이에 커밋된 신규 캐릭터가 두 번째 집계에만 포함될 수 있다(Phantom Read). SERIALIZABLE로 설정하면 트랜잭션 수준 읽기 일관성이 보장된다.
    4. SERIALIZABLE 수준에서 다른 트랜잭션이 이미 변경·커밋한 행을 갱신하려고 하면, 대기 후 최신 값을 기준으로 갱신이 정상 수행된다.
  70. 70. 거래소에서 아이템 구매 시 다음과 같이 처리하고 있다. 이 방식에 대한 설명으로 가장 적절한 것은?

    -- 1) 구매 화면 진입 시 조회 (Lock 없음)
    SELECT 거래ID, 아이템코드, 가격, 거래상태, 수정일시
    INTO   :tid, :item, :price, :status, :mod_ts
    FROM   거래소등록
    WHERE  거래ID = :tid;
    
    -- 2) 사용자가 [구매] 버튼 클릭 시
    UPDATE 거래소등록
    SET    거래상태 = '완료', 구매캐릭터ID = :buyer, 수정일시 = SYSTIMESTAMP
    WHERE  거래ID   = :tid
    AND    거래상태 = '등록'
    AND    수정일시 = :mod_ts;
    
    -- 3) SQL%ROWCOUNT = 0 이면 "이미 판매되었거나 변경된 아이템" 안내 후 재조회
    1. SELECT 시점부터 해당 행에 Lock을 걸어 다른 사용자의 변경을 막는 비관적 동시성 제어 방식이다.
    2. 낙관적 동시성 제어 방식으로, 조회 이후 다른 트랜잭션이 행을 변경했다면 UPDATE 처리 건수가 0이 되어 갱신 손실 없이 충돌을 감지할 수 있다. 단, 이 행을 변경하는 모든 프로그램이 수정일시를 갱신해야 한다.
    3. UPDATE의 WHERE 절에 수정일시 조건을 넣으면 Oracle이 자동으로 SERIALIZABLE 격리성 수준으로 전환한다.
    4. 이 방식은 조회와 갱신 사이에 Undo가 재사용되어 Snapshot too old 오류를 필연적으로 일으킨다.

AI가 SQLP 출제 범위를 바탕으로 새로 만든 예상 문제이며, 실제 기출문제가 아닙니다.