196. Delete Duplicate Emails; DELETE, Self Join
1. Problem
중복된 이메일을 가진 행을 삭제하되, 가장 작은 id를 가진 행 하나만 남겨야 한다.
- 핵심 과제:
- SELECT가 아닌 DELETE 문을 직접 작성할 것.
- 동일 테이블을 참조하여 삭제 대상(더 큰 ID)을 식별할 것.
2. Solution: Self Join을 이용한 타겟팅 삭제
테이블을 두 개(p1, p2)로 가정한 뒤, 같은 이메일 중 ID가 더 큰 행만 골라 삭제한다.
3. Takeaway: 왜 서브쿼리 방식은 실패하는가? (객관적 분석)
- 암시적 조인 vs 명시적 조인:
- FROM Person p1, Person p2는 콤마(,)를 이용한 암시적 조인이다.
- 현대적인 SQL 환경에서는 가독성을 위해 JOIN ... ON을 사용하는 명시적 조인이 더 권장되지만, DELETE 문에서는 암시적 조인 구조가 직관적으로 타겟 테이블(DELETE p1)을 지정하기 편해 자주 쓰인다.
Table: Person
+-------------+---------+
| Column Name | Type |
+-------------+---------+
| id | int |
| email | varchar |
+-------------+---------+
id is the primary key (column with unique values) for this table.
Each row of this table contains an email. The emails will not contain uppercase letters.
Write a solution to delete all duplicate emails, keeping only one unique email with the smallest id.
For SQL users, please note that you are supposed to write a DELETE statement and not a SELECT one.
For Pandas users, please note that you are supposed to modify Person in place.
After running your script, the answer shown is the Person table. The driver will first compile and run your piece of code and then show the Person table. The final order of the Person table does not matter.
The result format is in the following example.
Example 1:
Input:
Person table:
+----+------------------+
| id | email |
+----+------------------+
| 1 | john@example.com |
| 2 | bob@example.com |
| 3 | john@example.com |
+----+------------------+
Output:
+----+------------------+
| id | email |
+----+------------------+
| 1 | john@example.com |
| 2 | bob@example.com |
+----+-
정답 쿼리 (Self Join 방식)
DELETE p1
FROM Person p1, Person p2
-- 1. 이메일이 같은 놈들을 매칭시킨다.
WHERE p1.email = p2.email
-- 2. 그중에서 ID가 더 큰 놈(p1)을 삭제 대상으로 정한다.
AND p1.id > p2.id;
암시적 조인 (Implicit Join)
JOIN이라는 단어만 없을 뿐, 내부적으로는 Inner Join과 완전히 동일하게 작동합니다.
- 명시적 조인 (Explicit Join): FROM A JOIN B ON A.id = B.id (현대적이고 권장되는 방식)
- 암시적 조인 (Implicit Join): FROM A, B WHERE A.id = B.id (옛날 방식, 하지만 여전히 유효함)
FROM Person p1, Person p2라고 쓰는 순간, SQL 엔진은 두 테이블의 모든 가능한 조합을 만드는 **카테시안 곱(Cartesian Product)**을 먼저 생성하려 합니다. 그 방대한 조합 속에서 WHERE 절의 조건(email이 같고 id가 큰 것)에 맞는 놈들만 골라내어 DELETE의 타겟으로 삼는 것이죠.
DELETE p1
FROM Person p1
JOIN Person p2 ON p1.email = p2.email -- 여기서 이메일로 엮고
WHERE p1.id > p2.id; -- 여기서 큰 놈만 골라낸다
** 오답 **
!!! 오류 코드!!!
DELETE FROM Person WHERE id NOT IN (SELECT MIN(id) FROM Person GROUP BY email)
결과: MySQL에서는 **"삭제하려는 테이블을 서브쿼리에서 동시에 참조할 수 없다"**는 에러(Error 1093).
- MySQL의 특수한 제약 (오답 분석):
- 오답: DELETE FROM Person WHERE id NOT IN (SELECT MIN(id) FROM Person GROUP BY email)
- 원인 (Error 1093): MySQL은 **"데이터를 변경(UPDATE/DELETE) 중인 동일한 테이블을 서브쿼리(FROM 절)에서 직접 참조할 수 없다"**는 강력한 규칙이 있다. 데이터 정합성을 보호하기 위한 잠금(Locking) 메커니즘 때문이다.
- 해결책: Self Join을 사용하거나, 서브쿼리를 한 번 더 감싸서(임시 테이블화) 참조를 끊어야 한다.
'Data Science > SQL' 카테고리의 다른 글
| [SQL/오답] 다중 조인 필터링과 조건부 집계 'IF(조건문,1,0)'로 취소율 계산하기 (LeetCode262 Hard) (0) | 2026.02.05 |
|---|---|
| [SQL/오답] 날짜 데이터의 함정: JOIN 조건절(ON)에 DATEDIFF 사용하여 테이블 연결하기 (LeetCode197 Easy) (0) | 2026.02.05 |
| [SQL/분석] 부서별 최고 연봉자 찾기: 윈도우 함수(RANK)와 다중 컬럼 IN 절의 조화 (LeetCode184 Medium) (0) | 2026.02.05 |
| SQL 윈도우 함수: 순위 함수 ROW_NUMBER, RANK, DENSE_RANK (0) | 2026.02.05 |
| C사 SQL 쿼리테스트 대비: 재구매율 분석 (0) | 2026.02.05 |