[SQL] LIMIT OFFSET 사용 시 NULL 반환 차이

Hyunjun Kim·2025년 7월 18일
0

SQL

목록 보기
54/90

LIMIT OFFSET 사용 시 NULL 반환 차이 완벽 이해하기

SQL에서 페이징이나 N번째 데이터를 구할 때 자주 사용하는 LIMITOFFSET.
그러나 이 구문을 CTE(Common Table Expression) 또는 서브쿼리에서 사용할 경우, "결과가 없을 때"의 동작 방식이 서로 다르다는 사실을 알고 있는가?

이번 글에서는 "두 번째로 높은 급여"를 구하는 예제를 통해, LIMIT OFFSET 사용 시 결과가 없을 때 NULL을 반환하는 경우와 그렇지 않은 경우를 비교하여 살펴본다.


1. 두 번째로 높은 급여를 구하는 두 가지 방식

CTE 방식: 결과 없을 경우 아무 것도 반환하지 않음

WITH cte AS (
    SELECT salary AS SecondHighestSalary
    FROM Employee
    ORDER BY 1 DESC
    LIMIT 1 OFFSET 1
)
SELECT *
FROM cte;
  • LIMIT 1 OFFSET 1은 두 번째 행을 의미한다.
  • 만약 Employee 테이블에 급여가 하나뿐이라면, 두 번째 급여가 없으므로 CTE는 빈 결과를 반환한다.
  • 따라서 SELECT * FROM cte0행을 반환하며 아무 값도 출력되지 않는다.
  • 결과가 NULL이 아닌, 결과 자체가 존재하지 않는 상태가 된다.

서브쿼리 방식: 결과 없을 경우 NULL 반환

SELECT (
    SELECT DISTINCT salary
    FROM Employee
    ORDER BY salary DESC
    LIMIT 1 OFFSET 1
) AS SecondHighestSalary;
  • 서브쿼리는 LIMIT 1 OFFSET 1으로 결과가 없으면, NULL 값을 반환한다.
  • NULL 값은 SecondHighestSalary 컬럼에 담겨 1행으로 반환된다.
  • 결과가 없을 때도 항상 1개의 row가 존재하며, 값이 NULL이 된다.

2. 요약 비교

구조결과 없을 때 반환리턴 row 수반환값
WITH cte ... SELECT * FROM cte아무 것도 없음 (빈 결과 집합)0행없음
SELECT (SELECT ... LIMIT 1 OFFSET 1)NULL1행NULL

3. CTE 방식에서도 NULL을 반환하려면?

아래와 같이 UNION ALLNOT EXISTS를 사용하면, 결과가 없을 때 강제로 NULL을 반환할 수 있다.

WITH cte AS (
    SELECT salary AS SecondHighestSalary
    FROM Employee
    ORDER BY salary DESC
    LIMIT 1 OFFSET 1
)
SELECT COALESCE(SecondHighestSalary, NULL) AS SecondHighestSalary
FROM cte
UNION ALL
SELECT NULL
WHERE NOT EXISTS (SELECT 1 FROM cte)
LIMIT 1;
  • NOT EXISTS로 CTE 결과가 없을 경우를 감지한다.
  • UNION ALLLIMIT 1 조합으로 결과가 있을 때는 그 값을, 없으면 NULL 하나만 반환된다.

4. 서브쿼리 결과가 없을 때 NULL이 나올까?

SQL에서 서브쿼리를 사용할 때, 서브쿼리 결과가 없으면 NULL을 반환할까? 아니면 아예 결과 행이 없을까?
이 차이는 SQL 설계에서 매우 중요하다.


예시 테이블 생성 및 데이터 삽입

CREATE TABLE product (
  id INT,
  name VARCHAR(20)
);

CREATE TABLE review (
  product_id INT,
  score INT
);

INSERT INTO product VALUES (1, 'A'), (2, 'B');
INSERT INTO review VALUES (1, 5);

예시 쿼리: 스칼라 서브쿼리에서 결과가 없을 때

SELECT
  p.id,
  p.name,
  (
    SELECT r.score
    FROM review r
    WHERE r.product_id = p.id AND r.score >= 10
  ) AS high_score
FROM product p;

이 쿼리는 각 상품마다 리뷰 점수가 10 이상인 것을 찾는다.
하지만 score >= 10을 만족하는 리뷰가 없으므로, 내부 서브쿼리는 결과를 반환하지 않는다.
그럼에도 불구하고, 쿼리 결과는 아래와 같이 NULL이 채워진다.

idnamehigh_score
1ANULL
2BNULL

5. 왜 결과가 없는데 NULL이 반환될까?

이 동작은 SQL에서 스칼라 서브쿼리(scalar subquery)로 감쌌기 때문에 발생하는 것이다.

스칼라 서브쿼리란?

  • SELECT (...)처럼 값을 하나 반환하는 서브쿼리이다.
  • SQL은 스칼라 서브쿼리의 "결과가 없다"는 것을 "NULL 값 하나 반환"으로 해석한다.
  • 이로 인해 외부 쿼리는 항상 1행 1열의 결과를 받을 수 있다.

예시

SELECT (
  SELECT DISTINCT salary
  FROM Employee
  ORDER BY salary DESC
  LIMIT 1 OFFSET 1
) AS SecondHighestSalary;

이 쿼리에서 내부 서브쿼리의 결과가 없더라도, 외부에서는 아래와 같이 1행이 존재하고 값은 NULL로 채워진다.

SecondHighestSalary
NULL

6. 빈 결과와 NULL은 어떻게 다를까?

이 차이를 이해하는 것이 중요하다:

상황반환 행 수반환 값설명
SELECT DISTINCT salary ... LIMIT 1 OFFSET 1 (단독 실행)0행없음빈 결과 집합
스칼라 서브쿼리 내부에서 결과 없음1행NULL값이 없음을 NULL로 치환

즉, 빈 결과(empty result)는 "행 자체가 없음"이고,
NULL 반환은 "행은 있는데 값이 없음"을 의미한다.


핵심 정리

  • 서브쿼리를 SELECT (...)로 감싸면, SQL은 "반드시 하나의 값"을 기대한다.
  • 결과가 없으면 SQL 엔진은 NULL로 자동 변환하여 결과를 채운다.
  • 이 특성은 CTE처럼 직접 테이블을 반환하는 쿼리와는 매우 다르다.

참고 자료

profile
Data Analytics Engineer 가 되

0개의 댓글