현재 선택한 SQL 과정

SQLP 이론 학습

이론 목록으로 돌아가기

스칼라 서브쿼리 기본 원리: 0행·1행·다중행과 반복 실행

Scalar Subquery의 0건·1건·다건 반환 규칙과 상관 실행의 반복 비용을 이해하고 Join·사전 집계 대안과 결과 의미를 비교합니다.

예상 읽기 22

핵심 요약

스칼라 서브쿼리(Scalar Subquery)는 SQL에서 하나의 값이 필요한 자리에 사용하는 서브쿼리입니다. SELECT 목록은 하나의 값을 반환하도록 구성하며, 실행 결과 행 수에 따라 다음 규칙이 적용됩니다.

서브쿼리 결과스칼라 값
0행NULL
1행해당 행의 값
2행 이상ORA-01427: single-row subquery returns more than one row

상관 스칼라 서브쿼리는 바깥 행의 값을 참조하므로 개념적으로는 바깥 Row별 평가로 이해할 수 있습니다. 그러나 실제 실행은 다음과 같이 달라질 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
상관 Row별 Lookup
서브쿼리 결과 재사용
Subquery Unnesting
Outer Join·Aggregate Join 변환
다른 동등한 Row Source 구조

따라서 SQL Text만 보고 실행 횟수나 Cache 효과를 단정하지 않고, 실제 Cursor의 Starts, A-Rows, Buffers, Predicate와 Join 구조를 확인합니다.

이 이론의 범위

이 이론은 SQLP SQL 고급 활용 및 튜닝 → 스칼라 서브쿼리 범위에서 값·행 Cardinality 규칙, Aggregate 결과, 상관 반복 비용, 첫 행 응답, Join·사전 집계 변환의 정합성을 다룹니다. 다중 Column 반환 대안은 후속 이론 ID 929에서 별도로 다룹니다.

학습 목표

이 이론을 학습한 뒤에는 다음을 설명할 수 있어야 합니다.

  1. 스칼라 서브쿼리와 일반 단일행 서브쿼리의 역할을 구분한다.
  2. 0행·1행·다중행 반환 규칙을 정확히 적용한다.
  3. 집계 함수가 포함된 스칼라 서브쿼리의 NULL·0 반환 차이를 설명한다.
  4. 상관 서브쿼리의 반복 비용을 Starts, A-Rows, Buffers로 해석한다.
  5. 스칼라 서브쿼리를 Join이나 사전 집계로 바꿀 때 결과 의미가 달라지는 조건을 판단한다.
  6. 첫 행 응답과 전체 처리량 중 어떤 목표에 적합한지 비교한다.

1. 스칼라 서브쿼리란 무엇인가

다음 SQL은 사원 한 명마다 소속 부서명을 하나의 값으로 조회합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       e.ename,
       (SELECT d.dname
        FROM   dept d
        WHERE  d.deptno = e.deptno) AS dname
FROM   emp e;

괄호 안의 서브쿼리는 바깥 SELECT 목록에서 하나의 표현식처럼 사용됩니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
사원 한 행
→ 사원의 DEPTNO를 이용해 부서 조회
→ 부서명 한 값을 바깥 행에 붙임

스칼라 서브쿼리는 SELECT 목록뿐 아니라 하나의 표현식을 허용하는 여러 위치에 사용할 수 있습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT order_id
FROM   orders
WHERE  order_amount >
       (SELECT AVG(order_amount)
        FROM   orders);
SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
UPDATE employee e
SET    grade_name =
       (SELECT g.grade_name
        FROM   salary_grade g
        WHERE  e.salary BETWEEN g.low_salary AND g.high_salary);

사용 가능 위치에는 제한이 있습니다. 대표적으로 GROUP BY, Column Default, CHECK Constraint, DML의 RETURNING 절, Function-Based Index 표현식 등에는 스칼라 서브쿼리를 직접 사용할 수 없습니다.

1.1 스칼라 표현식과 단일행 비교 서브쿼리

다음 두 형태는 모두 최대 한 행을 요구하지만 사용 위치가 다릅니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
-- 스칼라 서브쿼리 표현식
SELECT (SELECT MAX(salary) FROM emp)
FROM   dual;
SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
-- 단일행 비교 연산자와 서브쿼리
SELECT employee_id
FROM   emp
WHERE  salary = (SELECT MAX(salary) FROM emp);

스칼라 서브쿼리는 하나의 expr이 필요한 위치에 값을 공급합니다. =, >, < 같은 단일행 비교에서도 오른쪽 서브쿼리가 여러 행을 반환하면 ORA-01427이 발생합니다.

반면 IN, ANY, ALL, EXISTS는 다중행을 처리하도록 설계된 연산자입니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
= (subquery)
→ 최대 한 행

IN (subquery)
→ 여러 행 가능

EXISTS (subquery)
→ 행 존재 여부

2. 한 컬럼과 한 행은 서로 다른 조건이다

스칼라 서브쿼리는 다음 두 조건을 모두 만족해야 합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT 목록
→ 하나의 값만 반환

실행 결과
→ 최대 한 행만 반환

SELECT 목록이 두 개면 문법적으로 맞지 않는 형태

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       (SELECT d.dname, d.loc
        FROM   dept d
        WHERE  d.deptno = e.deptno)
FROM   emp e;

부서명과 지역은 두 개의 값이므로 일반적인 스칼라 표현식 하나에 바로 놓을 수 없습니다.

SELECT 목록은 하나지만 결과가 두 행 이상인 형태

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.customer_id,
       (SELECT o.order_date
        FROM   orders o
        WHERE  o.customer_id = c.customer_id) AS order_date
FROM   customer c;

한 고객에게 주문이 여러 건이면 서브쿼리가 여러 행을 반환하므로 ORA-01427이 발생합니다.

오류를 피하려고 무조건 MAX, MIN, ROWNUM = 1을 추가하기 전에 어떤 한 행 또는 어떤 대표값을 원하는지 업무 기준부터 정해야 합니다.


3. 0행·1행·다중행 규칙

다음과 같은 데이터가 있다고 가정합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
DEPT
DEPTNO  DNAME
10      ACCOUNTING
20      RESEARCH
20      DEVELOPMENT

조건에 맞는 행이 없는 경우

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT (SELECT dname
        FROM   dept
        WHERE  deptno = 30) AS dname
FROM   dual;

서브쿼리 결과가 0행이므로 전체 SELECT는 다음 값을 반환합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
DNAME
-----
NULL

바깥 행이 사라지는 것이 아니라, 스칼라 서브쿼리 자리에 NULL이 들어갑니다.

이 NULL은 Select List 표현식의 데이터 타입을 따르는 Typed NULL로 이해합니다. 예를 들어 DATE Column을 반환하는 스칼라 서브쿼리가 0행이면 DATE 타입의 NULL 값이 됩니다.

조건에 맞는 행이 한 개인 경우

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT (SELECT dname
        FROM   dept
        WHERE  deptno = 10) AS dname
FROM   dual;

결과는 ACCOUNTING입니다.

조건에 맞는 행이 여러 개인 경우

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT (SELECT dname
        FROM   dept
        WHERE  deptno = 20) AS dname
FROM   dual;

두 행이 반환되므로 ORA-01427이 발생합니다.

이 오류는 단순한 성능 문제가 아니라 데이터의 유일성 또는 SQL의 결과 정의가 불명확하다는 신호로 봐야 합니다.

3.1 다중행 오류를 Top-1로 해결할 때

다중행 오류를 피하기 위해 다음처럼 임의의 한 행을 고르면 결과가 비결정적일 수 있습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT (
    SELECT order_date
    FROM   orders o
    WHERE  o.customer_id = c.customer_id
    AND    ROWNUM = 1
)
FROM customer c;

업무가 “가장 최근 주문”이라면 전체 순서를 명시합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT (
    SELECT order_date
    FROM   orders o
    WHERE  o.customer_id = c.customer_id
    ORDER BY order_date DESC, order_id DESC
    FETCH FIRST 1 ROW ONLY
)
FROM customer c;

order_id 같은 고유 Tie-Breaker를 포함해야 같은 날짜의 상대 순서까지 결정됩니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
ORA-01427 제거
≠
업무상 올바른 대표 행 선택

MAX(order_date)는 가장 큰 날짜 만 필요할 때 적합합니다. 해당 날짜 Row의 다른 Column까지 필요하면 Top-1·KEEP·LATERAL 등 행 선택 규칙을 별도로 설계합니다.


4. 집계 함수가 있을 때의 결과 규칙

집계 함수는 스칼라 서브쿼리에서 자주 사용됩니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.customer_id,
       (SELECT SUM(o.order_amount)
        FROM   orders o
        WHERE  o.customer_id = c.customer_id) AS total_amount
FROM   customer c;

GROUP BY가 없는 전체 집계는 입력 행이 없어도 결과 행 하나를 만듭니다.

표현식일치 행이 없을 때
SUM(amount)한 행을 반환하고 값은 NULL
AVG(amount)한 행을 반환하고 값은 NULL
MIN(amount)한 행을 반환하고 값은 NULL
MAX(amount)한 행을 반환하고 값은 NULL
COUNT(*)한 행을 반환하고 값은 0
COUNT(amount)한 행을 반환하고 값은 0

따라서 다음 두 결과는 다릅니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT (SELECT SUM(amount)
        FROM   orders
        WHERE  customer_id = -1) AS sum_amount,
       (SELECT COUNT(*)
        FROM   orders
        WHERE  customer_id = -1) AS order_count
FROM   dual;
CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
SUM_AMOUNT  ORDER_COUNT
----------  -----------
NULL        0

GROUP BY가 포함되면 다시 행 수를 판단한다

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.customer_id,
       (SELECT SUM(o.order_amount)
        FROM   orders o
        WHERE  o.customer_id = c.customer_id
        GROUP BY o.order_status) AS total_amount
FROM   customer c;

한 고객에게 주문 상태가 두 종류 이상이면 상태별 Group이 여러 행을 만들 수 있어 ORA-01427이 발생합니다. 집계 함수를 사용했다는 사실만으로 한 행이 보장되는 것은 아니며, GROUP BY 후 Group 수를 확인해야 합니다.

입력이 없을 때도 구분합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
GROUP BY 없음
→ Aggregate 결과 Row 1건
→ SUM 등은 NULL, COUNT는 0

GROUP BY 있음
→ 생성할 Group이 없음
→ 서브쿼리 결과 0행
→ 스칼라 표현식 값 NULL

두 경우 모두 바깥에서는 NULL처럼 보일 수 있지만 내부 Row Source와 COUNT 의미는 다릅니다.


5. 비상관 서브쿼리와 상관 서브쿼리

비상관 스칼라 서브쿼리

바깥 Query의 Column을 참조하지 않습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       e.sal,
       (SELECT AVG(sal) FROM emp) AS company_avg_sal
FROM   emp e;

회사 전체 평균은 모든 사원 행에서 동일합니다. Optimizer와 실행 엔진은 이 값을 효율적으로 계산하거나 재사용할 수 있습니다.

상관 스칼라 서브쿼리

바깥 Query의 현재 행 값을 참조합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       e.sal,
       (SELECT AVG(x.sal)
        FROM   emp x
        WHERE  x.deptno = e.deptno) AS dept_avg_sal
FROM   emp e;

e.deptno가 바깥 행마다 달라질 수 있으므로 논리적으로는 부서번호별 조회가 필요합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
바깥 EMP 행 읽기
→ 현재 DEPTNO 전달
→ 해당 부서 평균 계산
→ 결과 한 값을 바깥 행에 결합

실제 실행에서는 다음 중 하나가 선택될 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
바깥 행마다 반복 실행
반복된 상관 Key 결과를 실행 중 재사용
서브쿼리를 Join·집계 형태로 변환

따라서 SQL Text의 모양과 실제 실행 구조를 구분합니다.

5.1 비상관 스칼라 서브쿼리

비상관 스칼라 서브쿼리는 바깥 Row의 Column을 참조하지 않습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.employee_id,
       (SELECT AVG(salary) FROM emp) AS company_avg
FROM   emp e;

논리적으로 회사 평균은 모든 바깥 Row에서 같은 값입니다. Oracle은 이를 한 번 계산하거나 다른 동등한 구조로 처리할 수 있지만, 실행 횟수·재사용 방식을 SQL 문법의 계약으로 가정하지 않습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
비상관
→ 바깥 Row 값과 독립
→ 같은 결과값

상관
→ 바깥 Row Key에 의존
→ Key별 결과 가능

실제 처리 방식은 실행계획과 Runtime 통계로 확인합니다.


6. 반복 비용은 어떻게 계산하는가

변환과 재사용이 없다고 단순화하면 상관 스칼라 서브쿼리의 비용은 다음과 같이 생각할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
전체 비용
≈ 바깥 Row Source 생성 비용
 + 바깥 행 수 × 서브쿼리 1회 처리 비용

예를 들어 바깥에서 100,000행이 나오고 서브쿼리 한 번에 평균 3개의 Buffer를 읽는다면 반복 부분만 약 300,000 Buffer가 될 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer A-Rows = 100,000
Subquery Starts = 100,000
Inner Buffers ≈ 300,000

다만 상관 Key가 반복되고 실행 중 결과가 재사용되거나 서브쿼리가 Unnesting되면 실제 Starts와 작업량은 줄어들 수 있습니다.

재사용은 성능 최적화일 뿐 결과 정확성의 전제가 아닙니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Cache가 있다고 가정해 SQL을 설계
→ 위험

실제 Starts·Buffers가 감소했는지 확인
→ 올바른 진단

Key NDV가 작아도 재사용률·충돌·Plan 변환은 실행 환경에 따라 달라질 수 있으므로 고정된 Cache 크기나 Hit율을 일반 규칙처럼 사용하지 않습니다.

실행계획 예시

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT STATEMENT
  TABLE ACCESS FULL EMP
  SCALAR SUBQUERY
    TABLE ACCESS BY INDEX ROWID DEPT
      INDEX UNIQUE SCAN DEPT_PK

실측에서 확인할 항목은 다음과 같습니다.

항목판단 내용
바깥 A-Rows스칼라 값이 필요한 바깥 행 수
서브쿼리 Starts해당 Row Source가 시작된 횟수
안쪽 A-Rows전체 실행에서 만들어진 안쪽 행 수
Buffers반복 탐색에서 발생한 Logical I/O
상관 Key NDV서로 다른 입력 Key의 개수
A-Time반복 부분의 실제 시간 기여도

Starts는 Row Source가 시작된 횟수이며, SQL Text에 보이는 바깥 행 수와 항상 같지는 않습니다.

6.1 같은 Table을 조회하는 여러 스칼라 서브쿼리

다음 SQL은 같은 주문 Table을 고객마다 세 번 조회할 수 있습니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.customer_id,
       (SELECT MAX(o.order_date)
        FROM orders o
        WHERE o.customer_id = c.customer_id) AS last_order_date,
       (SELECT SUM(o.order_amount)
        FROM orders o
        WHERE o.customer_id = c.customer_id) AS total_amount,
       (SELECT COUNT(*)
        FROM orders o
        WHERE o.customer_id = c.customer_id) AS order_count
FROM customer c;

가능한 개선 후보입니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
고객별 사전 집계 한 번
→ MAX·SUM·COUNT 동시 계산
→ Customer와 LEFT JOIN

다만 첫 화면의 적은 Row만 Fetch하고 고객별 Index Lookup이 매우 선택적이면 여러 Lookup이 전체 사전 집계보다 빠를 수도 있습니다. Starts와 전체 Fetch 목표를 기준으로 비교합니다.


7. 첫 행 응답과 전체 처리량

스칼라 서브쿼리는 바깥 행 하나를 읽고 필요한 값을 바로 조회할 수 있어 앞쪽 몇 행의 응답에 유리할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Outer 첫 행
→ Index Lookup
→ 첫 결과 반환

반면 사전 집계 Join은 상세 데이터를 먼저 모두 읽고 집계한 뒤 결과를 반환할 수 있습니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
상세 Table 전체 처리
→ GROUP BY
→ Join
→ 결과 반환

그러나 결과를 끝까지 모두 Fetch하면 반복 Lookup이 누적되어 스칼라 서브쿼리가 더 비싸질 수 있습니다.

처리 목표우선 검토
첫 화면의 소수 행을 빠르게 표시선택적인 Scalar Lookup
대량 결과 전체 처리사전 집계·집합 Join
바깥 행이 적고 안쪽 Index가 효율적Scalar 또는 LATERAL
바깥 행이 많고 서로 다른 Key도 많음사전 집계 Join

FIRST_ROWS 성격의 응답과 ALL_ROWS 성격의 전체 처리량을 따로 측정합니다.


8. Join으로 바꿀 때 결과가 같아지는 조건

다음 스칼라 서브쿼리를 생각해 봅니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       (SELECT d.dname
        FROM   dept d
        WHERE  d.deptno = e.deptno) AS dname
FROM   emp e;

0건일 때 바깥 사원 행을 보존하고 NULL을 반환하므로, 일반적인 대안은 LEFT OUTER JOIN입니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       d.dname
FROM   emp e
LEFT JOIN dept d
       ON d.deptno = e.deptno;

두 SQL의 결과를 같게 만들려면 다음 조건을 확인합니다.

  1. DEPT.DEPTNO가 최대 한 행과 일치하는가?
  2. 일치하지 않는 사원 행을 보존해야 하는가?
  3. 오른쪽 Table 조건을 ONWHERE 중 어디에 둘 것인가?
  4. NULL을 다른 값으로 치환하지 않았는가?
  5. Join으로 바꾼 뒤 바깥 행이 중복되지 않는가?
  6. 오른쪽 Filter를 ON에서 WHERE로 옮겨 Outer Join을 사실상 Inner Join으로 바꾸지 않았는가?

오른쪽 조건의 위치

원래 스칼라 서브쿼리가 조건 불일치 시 NULL을 반환한다고 가정합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       (SELECT d.dname
        FROM   dept d
        WHERE  d.deptno = e.deptno
        AND    d.active_yn = 'Y') AS dname
FROM emp e;

동등한 Outer Join 후보는 오른쪽 조건을 ON에 둡니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT e.empno,
       d.dname
FROM emp e
LEFT JOIN dept d
  ON d.deptno = e.deptno
 AND d.active_yn = 'Y';

다음처럼 WHERE d.active_yn='Y'에 두면 Match가 없는 바깥 Row가 제거되어 원래의 NULL 보존 의미가 달라질 수 있습니다.

중복이 있을 때의 차이

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
Scalar Subquery
→ 2행 이상이면 ORA-01427

LEFT JOIN
→ Match 수만큼 바깥 행이 증가

따라서 오류를 제거하기 위해 Join으로만 바꾸면 데이터 오류가 행 증식으로 바뀔 수 있습니다. PK·UK Constraint나 사전 집계를 이용해 한 Key당 최대 한 행을 먼저 보장해야 합니다.


9. 사전 집계로 바꾸는 경우

상세 Table에서 고객별 합계를 반복해서 계산하는 SQL은 다음처럼 사전 집계할 수 있습니다.

반복 스칼라 집계

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.customer_id,
       (SELECT SUM(o.order_amount)
        FROM   orders o
        WHERE  o.customer_id = c.customer_id) AS total_amount
FROM   customer c;

사전 집계 후 Join

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT c.customer_id,
       a.total_amount
FROM   customer c
LEFT JOIN (
    SELECT customer_id,
           SUM(order_amount) AS total_amount
    FROM   orders
    GROUP BY customer_id
) a
ON a.customer_id = c.customer_id;

사전 집계 결과의 Grain은 다음과 같아야 합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
한 행 = 고객 한 명의 집계 결과

GROUP BY customer_id, order_status처럼 Group Key가 늘어나면 고객당 여러 행이 생기므로 바깥 고객 행이 증가할 수 있습니다.

또한 원래 SUM은 일치 행이 없을 때 NULL입니다. 다음처럼 NVL을 추가하면 결과 의미가 바뀝니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
NVL(a.total_amount, 0)

업무에서 NULL0을 같은 의미로 볼 수 있을 때만 적용합니다.

COUNT(*) 스칼라 집계는 Match가 없을 때 0을 반환합니다. 사전 집계 View에는 해당 Key Row 자체가 없으므로 LEFT JOIN 결과는 NULL이 됩니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
-- 원래 의미가 COUNT 0이면
NVL(a.order_count, 0)

이 경우에는 NVL이 원래 COUNT 의미를 복원합니다. SUM에 NVL을 적용하는 것과 COUNT에 NVL을 적용하는 것은 업무 의미가 다를 수 있습니다.


10. 실행계획으로 검증하는 방법

실제 Row Source 통계를 수집합니다.

SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT /*+ GATHER_PLAN_STATISTICS */
       e.empno,
       (SELECT d.dname
        FROM   dept d
        WHERE  d.deptno = e.deptno) AS dname
FROM   emp e;
SQL코드 영역 안에서 좌우로 이동할 수 있습니다.
SELECT *
FROM TABLE(
    DBMS_XPLAN.DISPLAY_CURSOR(
        NULL,
        NULL,
        'ALLSTATS LAST +PREDICATE +ALIAS +MEMSTATS +NOTE'
    )
);

다음 순서로 확인합니다.

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
1. 바깥 Row Source의 A-Rows를 확인한다.
2. Scalar Subquery 또는 변환된 Join의 실제 구조를 확인한다.
3. Subquery Row Source의 Starts를 확인한다.
4. Starts당 A-Rows·Buffers를 계산한다.
5. 상관 Key의 NDV와 편중을 확인한다.
6. 전체 Fetch와 일부 Fetch를 구분한다.
7. Join·사전 집계 대안과 결과 행 수·NULL을 비교한다.
8. 여러 Scalar Subquery가 같은 Table을 반복 조회하는지 확인한다.
9. 첫 행 시간과 전체 Fetch 시간, Buffer·Memory·TEMP를 함께 비교한다.

Optimizer가 서브쿼리를 Join으로 변환했다면 실행계획에 별도의 SCALAR SUBQUERY가 보이지 않을 수 있습니다. 이 경우 SQL Text가 아니라 실제 Row Source를 기준으로 분석합니다.


혼동하기 쉬운 판단

혼동하기 쉬운 판단정확한 기준
0행이면 바깥 행도 사라진다스칼라 표현식 값이 NULL이 되고 바깥 행은 유지된다
집계 함수를 쓰면 항상 값이 존재한다전체 집계는 한 행을 만들지만 SUM·AVG·MIN·MAX 값은 NULL일 수 있다
집계 함수가 있으면 항상 한 행이다GROUP BY가 여러 Group을 만들면 다중행 오류가 가능하다
상관 서브쿼리는 무조건 바깥 행 수만큼 실행된다Unnesting과 실행 중 재사용 여부를 실제 Starts로 확인한다
Join으로 바꾸면 항상 같은 결과다유일성·Outer 보존·NULL·조건 위치·행 증식을 검증한다
MAX를 넣으면 올바른 한 행이 된다어떤 값을 대표값으로 선택할지 업무 규칙이 먼저 필요하다
결과가 몇 건 안 되면 비용도 작다결과를 만들기 전에 읽은 행과 Buffer를 확인한다
ROWNUM=1이면 원하는 대표 행이다결정적 ORDER BY와 Tie-Breaker가 필요하다
비상관 서브쿼리는 문법상 반드시 한 번만 실행실제 Plan·Starts로 확인한다
Scalar Cache 크기와 Hit율은 고정 SQL 규칙구현·Plan·Key 분포에 따라 달라 실제 통계로 확인한다
LEFT JOIN으로 바꾸면 조건 위치는 무관오른쪽 조건을 WHERE에 두면 바깥 Row가 제거될 수 있다
COUNT 사전 집계 LEFT JOIN도 Match 없음에서 자동 0Join 결과는 NULL이므로 원래 COUNT 의미면 NVL 0 검토

핵심 판단 순서

CODE코드 영역 안에서 좌우로 이동할 수 있습니다.
결과가 한 값이어야 하는가?
→ 단일행 비교인지 IN·EXISTS 같은 다중행 연산인지 확인
→ 0행·1행·다중행 규칙은 무엇인가?
→ 대표 한 행이면 결정적 ORDER BY가 있는가?
→ 상관 Key와 바깥 A-Rows·NDV는 얼마인가?
→ 실제 Starts·A-Rows·Buffers는 얼마인가?
→ 같은 Table의 Scalar Lookup이 여러 번 반복되는가?
→ 첫 행 응답과 전체 처리량 중 어느 목표인가?
→ Join·사전 집계로 바꿔도 NULL·0·중복·Grain·조건 위치가 같은가?
스스로 확인하기

개념 확인 문제

문제를 누르면 바로 아래에서 정답과 해설을 확인할 수 있습니다.

01스칼라 서브쿼리의 결과가 0행이면 바깥 표현식에는 어떤 값이 들어가는가?
정답 및 해설

0행이면 NULL입니다. 스칼라 서브쿼리 값이 Typed NULL이 되고 바깥 Row는 유지됩니다.

02스칼라 서브쿼리가 두 행 이상을 반환하면 어떤 오류가 발생하는가?
정답 및 해설

2행 이상이면 ORA-01427이 발생합니다. 하나의 값이 필요한 위치에서 여러 Row가 반환됐기 때문입니다.

03스칼라 서브쿼리에서 “SELECT 목록 한 값”과 “결과 최대 한 행”은 어떻게 다른 조건인가?
정답 및 해설

Select List 한 Column과 결과 최대 한 Row는 별도 조건입니다. 한 Row가 두 Column을 반환해도 Scalar 값 하나가 아니며, 한 Column이 여러 Row를 반환해도 다중행 오류입니다.

04일치하는 행이 없을 때 SUM(amount)와 COUNT()는 각각 무엇을 반환하는가?
정답 및 해설

GROUP BY 없는 빈 입력 Aggregate에서 SUM·AVG·MIN·MAX는 NULL, COUNT는 0입니다. Aggregate 결과 Row 자체는 한 건입니다.

05집계 함수가 있는데도 ORA-01427이 발생할 수 있는 대표적인 이유는 무엇인가?
정답 및 해설

GROUP BY가 여러 Group을 만들면 집계 함수가 있어도 여러 Row가 반환될 수 있습니다. 반대로 GROUP BY 입력이 없으면 Group이 없어 0행이 될 수 있습니다.

06상관 스칼라 서브쿼리는 비상관 스칼라 서브쿼리와 무엇이 다른가?
정답 및 해설

상관 서브쿼리는 바깥 Row의 Column을 참조하고, 비상관 서브쿼리는 바깥 Row와 독립적입니다. 실제 반복·재사용·Unnesting은 Plan과 Starts로 확인합니다.

07반복 실행 병목을 확인할 때 가장 먼저 비교할 실행 통계는 무엇인가?
정답 및 해설

바깥 A-Rows와 Subquery Starts·A-Rows·Buffers를 비교합니다. Buffers ÷ Starts로 Lookup 1회 평균 비용을 볼 수 있지만 누적 통계 특성도 고려합니다.

08스칼라 서브쿼리를 LEFT JOIN으로 바꿀 때 Join Key의 유일성을 확인해야 하는 이유는 무엇인가?
정답 및 해설

Scalar Subquery는 0행에서 바깥 Row를 보존하고 NULL을 반환하므로 일반적으로 LEFT JOIN 의미가 필요합니다. 오른쪽 Key가 중복이면 Join은 Row를 증식하므로 PK·UK 또는 사전 집계로 한 Key당 한 Row를 보장합니다.

09대량 결과 전체 처리에서 사전 집계 Join이 유리할 수 있는 이유는 무엇인가?
정답 및 해설

사전 집계는 상세 Table을 한 번 읽어 Key별 MAX·SUM·COUNT를 동시에 계산하고 결합할 수 있습니다. 다만 첫 소수 Row 응답과 전체 Fetch 목표를 분리해 비교합니다.

10실행계획에 SCALAR SUBQUERY가 보이지 않을 수 있는 이유는 무엇인가?
정답 및 해설

Optimizer가 Subquery를 Join 등으로 Unnesting하거나 동등한 구조로 변환할 수 있기 때문입니다. SQL Text보다 실제 Cursor Row Source를 기준으로 분석합니다.