본문 바로가기
Data Science/SQL

[SQL/오답] 대칭차집합의 데이터 정합성 검수 (LeetCode1965 Medium)

by 에르모사 쩐뉴 2026. 2. 16.

1. Problem

사원 정보가 담긴 Employees 테이블과 급여 정보가 담긴 Salaries 테이블을 비교하여, 정보가 누락된 사원의 ID를 찾아야 한다. 누락의 기준은 '이름이 없거나' 혹은 '급여 정보가 없는' 경우이며, 이는 집합론적으로 두 집합의 합집합에서 교집합을 뺀 대칭차집합을 구하는 문제와 같다.

2. Solution

가장 직관적인 집계 방식부터 성능 최적화를 고려한 방식까지 총 세 가지 전략으로 접근할 수 있다.

[전략 A] 집계 중심: UNION ALL + HAVING (기발한 접근) 두 테이블의 ID를 수직으로 쌓은 뒤, 딱 한 번만 등장한 ID를 필터링한다. 조인 문법 없이도 명확하게 '혼자 있는 데이터'를 찾아낼 수 있다.

[전략 B] 논리 중심: NOT IN + UNION (표준적 접근) 각 테이블에서 상대방에게 없는 ID만 명확하게 골라내어 합친다. 문제의 조건을 쿼리 구조가 그대로 반영하고 있어 가독성이 뛰어나다.

[전략 C] 성능 중심: NOT EXISTS (고급 접근) 데이터 규모가 클 때 가장 효율적이다. 존재 여부만 확인하는 즉시 연산을 멈추는 'Early Exit' 특성 덕분에 대용량 데이터 환경에서 객관적인 우위를 점한다.

3. Takeaway (데이터 불일치 탐지 인사이트)

실무에서 두 시스템 간의 데이터가 맞지 않을 때 분석가가 취해야 할 기술적 태도를 정립한다.

  • 상황에 맞는 도구 선택: 데이터 양이 적고 직관적인 확인이 필요할 때는 GROUP BY 방식이 훌륭하지만, 수억 건의 대용량 환경에서는 성능 최적화를 위해 NOT EXISTS를 우선적으로 고려해야 한다.
  • NULL의 함정 방지: NOT IN은 서브쿼리에 NULL이 포함될 경우 예기치 않은 결과를 낼 수 있다. 데이터 무결성이 보장되지 않은 환경에서는 NOT EXISTS나 LEFT JOIN이 훨씬 안전한 선택지이다.
  • 분석가의 무기, 대칭차집합: 인사 시스템과 급여 시스템처럼 서로 다른 출처의 데이터를 비교할 때, 대칭차집합은 데이터 품질을 검수하는 최후의 방어선이다. 단순히 조인만이 정답이 아니라는 사고의 유연함이 중요하다.

https://leetcode.com/problems/employees-with-missing-information/

Table: Employees

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| employee_id | int     |
| name        | varchar |
+-------------+---------+
employee_id is the column with unique values for this table.
Each row of this table indicates the name of the employee whose ID is employee_id.
 

Table: Salaries

+-------------+---------+
| Column Name | Type    |
+-------------+---------+
| employee_id | int     |
| salary      | int     |
+-------------+---------+
employee_id is the column with unique values for this table.
Each row of this table indicates the salary of the employee whose ID is employee_id.
 

Write a solution to report the IDs of all the employees with missing information. The information of an employee is missing if:

The employee's name is missing, or
The employee's salary is missing.
Return the result table ordered by employee_id in ascending order.

The result format is in the following example.

 

Example 1:

Input: 
Employees table:
+-------------+----------+
| employee_id | name     |
+-------------+----------+
| 2           | Crew     |
| 4           | Haven    |
| 5           | Kristian |
+-------------+----------+
Salaries table:
+-------------+--------+
| employee_id | salary |
+-------------+--------+
| 5           | 76071  |
| 1           | 22517  |
| 4           | 63539  |
+-------------+--------+
Output: 
+-------------+
| employee_id |
+-------------+
| 1           |
| 2           |
+-------------+
Explanation: 
Employees 1, 2, 4, and 5 are working at this company.
The name of employee 1 is missing.
The salary of employee 2 is missing.

 

 [SQL/Easy] Employees With Missing Information

1. 문제 풀이의 핵심: "대칭 차집합(Symmetric Difference)"

두 테이블을 비교하여 양쪽에 다 있지 않고 한쪽에만 누락된 ID를 찾는 것이 목표입니다.

  • 방법 1 (사용자님 방식): 두 테이블의 ID를 일단 다 수직으로 쌓고(UNION ALL), 딱 한 번만 등장한 ID(COUNT = 1)를 찾는다.
  • 방법 2 (전통적 방식): FULL OUTER JOIN을 수행한 후, 이름이나 급여가 NULL인 행을 찾는다.

2. 사용자 쿼리 분석 및 최적화

WITH summary AS(
    -- 두 테이블의 ID를 모두 모음 (컬럼명/타입이 같아야 함)
    SELECT employee_id FROM Employees 
    UNION ALL
    SELECT employee_id FROM Salaries)

SELECT employee_id
FROM summary
GROUP BY employee_id
HAVING COUNT(employee_id) = 1 -- 두 번(양쪽) 등장하지 않은 ID만 필터링
ORDER BY employee_id ASC;

🔍 왜 이 방식이 훌륭한가요?

  1. 가독성: 조인 조건(ON s.id = e.id)이나 NULL 체크(IS NULL) 없이도 직관적으로 "혼자 있는 놈을 찾는다"는 논리가 명확합니다.
  2. 성능: 인덱스가 잘 잡혀 있다면 대량의 데이터를 조인하는 것보다 단순 수직 통합 후 그룹화하는 것이 더 빠를 때가 있습니다.

3. 출제자의 의도 분석

이 문제는 지원자가 **"데이터 불일치(Data Mismatch)"**를 어떻게 탐지하는지 확인합니다.

  1. 데이터 무결성 체크: 두 시스템(인사 시스템 vs 급여 시스템) 간의 데이터가 맞지 않을 때 이를 찾아낼 수 있는가?
  2. 집합 연산의 활용: UNION이나 JOIN을 상황에 맞게 선택하여 원하는 부분집합을 추출할 수 있는가?
  3. 정렬의 중요성: 결과가 늘어나는 순서(ASC)를 지켰는가?

오답노트: "조인만이 정답은 아니다"

"두 테이블의 차집합을 구할 때, UNION ALL + HAVING COUNT = 1 조합은 매우 강력한 무기다." 복잡한 JOIN 문법이 생각나지 않을 때, 데이터를 일단 한 바구니에 다 담고 개수를 세어보는 것만으로도 훌륭한 분석 결과를 낼 수 있습니다.

 

SQL의 성능과 표준적인 관점에서 가장 **'모범답안'**으로 꼽히는 방식은 UNION을 이용한 대칭 차집합(Symmetric Difference) 방식입니다.

이 방식은 각 테이블에서 상대방에게 없는 ID만 명확하게 골라내어 합치기 때문에, 데이터의 의도를 가장 잘 드러냅니다.


1. 모범 답안 (UNION 방식)

-- 1. 이름은 있는데 급여 정보가 없는 사람
SELECT employee_id
FROM Employees
WHERE employee_id NOT IN (SELECT employee_id FROM Salaries)

UNION

-- 2. 급여 정보는 있는데 이름이 없는 사람
SELECT employee_id
FROM Salaries
WHERE employee_id NOT IN (SELECT employee_id FROM Employees)

ORDER BY employee_id;

왜 이 쿼리가 '모범'인가?

  1. 가독성 (Readability): 문제에서 제시한 두 가지 조건("이름이 없거나", "급여가 없거나")을 쿼리 구조가 그대로 반영하고 있습니다. 나중에 다른 사람이 코드를 읽었을 때 의도를 파악하기 가장 쉽습니다.
  2. 인덱스 활용 (Performance): employee_id에 인덱스가 걸려 있다면, NOT IN이나 NOT EXISTS는 매우 빠르게 작동합니다. 사용자님의 GROUP BY 방식은 모든 데이터를 일단 합친 뒤 전체를 다시 훑어야(Full Scan) 하지만, 이 방식은 필요한 부분만 콕 집어낼 가능성이 높습니다.
  3. 결과 보장 (Robustness): UNION을 사용함으로써 혹시 모를 중복 ID 발생 가능성까지 원천 차단합니다.

 

2. 실무에서 더 선호하는 방식 (LEFT JOIN 활용)

현업에서는 LEFT JOIN 시 발생하는 NULL을 체크하는 방식을 선호하기도 합니다. 이는 데이터가 비어있는 '상태'를 직접 확인할 수 있기 때문입니다.

SELECT T.employee_id
FROM (
    SELECT employee_id FROM Employees
    UNION
    SELECT employee_id FROM Salaries
) T
LEFT JOIN Employees E ON T.employee_id = E.employee_id
LEFT JOIN Salaries S ON T.employee_id = S.employee_id
WHERE E.name IS NULL OR S.salary IS NULL
ORDER BY T.employee_id;

비교 요약

방식 특징 추천 상황
UNION ALL + GROUP BY (사용자님) 기발함, 조인이 필요 없음 데이터 양이 적고 직관적인 집계가 필요할 때
NOT IN + UNION (모범 답안) 성능 최적화, 논리적 명확성 실제 코딩 테스트 및 대용량 데이터 처리
LEFT JOIN + NULL 체크 데이터 불일치 상세 확인 가능 누락된 데이터의 구체적인 컬럼을 같이 보고 싶을 때

 

3. 성능의 제왕: NOT EXISTS

NOT IN은 서브쿼리의 모든 데이터를 가져와서 일일이 비교하지만, **NOT EXISTS**는 조건에 맞는 데이터를 찾는 즉시 멈춥니다(Early Exit). 데이터가 많아질수록 이 차이는 엄청나게 커지죠.

✅ NOT EXISTS를 이용한 모범 답안

-- 1. 이름은 있는데 급여 정보가 존재하지 않는 경우
SELECT employee_id
FROM Employees E
WHERE NOT EXISTS (
    SELECT 1 FROM Salaries S WHERE S.employee_id = E.employee_id
)

UNION

-- 2. 급여 정보는 있는데 이름이 존재하지 않는 경우
SELECT employee_id
FROM Salaries S
WHERE NOT EXISTS (
    SELECT 1 FROM Employees E WHERE E.employee_id = S.employee_id
)
ORDER BY employee_id;

💡 왜 NOT EXISTS가 더 좋을까?

  1. NULL에 강함: NOT IN은 서브쿼리에 NULL이 하나라도 섞여 있으면 결과가 통째로 안 나올 수 있는 위험이 있지만, NOT EXISTS는 NULL에 영향을 받지 않고 정확하게 작동합니다.
  2. 연산 효율: "있는지 없는지만 확인하면 끝!"이라는 논리라 DB 엔진이 가장 좋아하는 방식입니다.
  3. SELECT 1: 서브쿼리에서 실제 컬럼 값을 가져올 필요가 없으므로 관습적으로 1을 써서 부하를 줄입니다.

 

"대용량 데이터에서 누락된 정보를 찾을 때는 NOT EXISTS를 떠올리세요."
초보자는 NOT IN을 쓰고,
중급자는 LEFT JOIN을 쓰며,
고수는 **NOT EXISTS**로 성능까지 챙깁니다.