[SQL] LeetCode 문제풀이 (#1378, #1407, #1484, #1517)

Jinyoung Cheon·2026년 4월 29일

LeetCode

목록 보기
8/9

1378. Replace Employee ID With The Unique Identifier

문제설명

Write a solution to show the unique ID of each user, If a user does not have a 
unique ID replace just show null. Return the result table in any order.

두 테이블을 LEFT JOIN을 진행하면 되는 간단한 문제였다.

SELECT 
    uni.unique_id, 
    e.name
FROM Employees e
LEFT JOIN EmployeeUNI uni
ON e.id = uni.id

1407. Top Travellers

문제설명

Write a solution to report the distance traveled by each user.
Return the result table ordered by travelled_distance in descending order, 
if two or more users traveled the same distance, order them by their name in 
ascending order.

각 유저(Users)별로 차량 탑승 기록(Rides)의 총 이동 거리를 구하는 문제이다.

SELECT 
    u.name,
    IFNULL(SUM(r.distance), 0) AS travelled_distance
FROM 
    Users u
LEFT JOIN 
    Rides r ON u.id = r.user_id
GROUP BY 
    u.id
ORDER BY 
    travelled_distance DESC, 
    u.name ASC;

오늘의 핵심 개념: IFNULL vs COALESCE

위 1407번 문제에서 탑승 기록이 없어 distance 합계가 null이 되는 경우를 0으로 바꿔주기 위해 IFNULL을 사용했다. 다른 분들의 풀이를 보니 COALESCE를 많이 사용하길래, 두 함수의 차이점을 정리했다.

비교 항목IFNULLCOALESCE
작동 원리1번째 값이 NULL이면 2번째 값 반환괄호 안의 값 중 첫 번째로 NULL이 아닌 값 반환
인자 개수딱 2개 (값, 대체값)2개 이상 제한 없음 (값1, 값2, 값3...)
표준 여부MySQL 등 일부 DB 전용 함수표준 SQL (Oracle, PostgreSQL 등 대부분 지원)

요약 및 적용

  • 단순 대체 (IFNULL):
    지금처럼 단순히 값이 NULL일 때 0으로 바꾸는 등의 1차원적인 작업에서는 코드가 직관적인 IFNULL을 써도 무방하다.
  • 다중 조건 & 호환성 (COALESCE)
    만약 여러 컬럼을 순차적으로 확인해야 하거나(ex: 사내번호 -> 휴대폰번호 -> '없음' 순으로 데이터 추출), 향후 다양한 DB 환경을 고려한다면 표준 문법인 COALESCE를 사용하는 것이 더 좋은 습관이다.

SQL-- COALESCE 활용 예시
SELECT COALESCE(NULL, NULL, 10, 20); -- 결과: 10


1484. Group Sold Products By The Date

문제설명

Write a solution to find for each date the number of different products sold and 
their names. The sold products names for each date should be sorted lexicographically.
Return the result table ordered by sell_date.

날짜별로 판매된 상품들을 그룹화하고, 하나의 문자열로 묶어서 출력하는 문제이다.

트러블 슈팅: 기존 쿼리의 문제점 분석

제가 처음에 작성했던 오답 쿼리와 발생한 문제점은 다음과 같다.

[초기 쿼리]

SELECT 
    sell_date,
    COUNT(product) as num_sold,
    GROUP_CONCAT(product) as products
FROM Activities 
GROUP BY sell_date
ORDER BY 
    sell_date ASC,
    products ASC;

[문제점]

  • 중복 값이 그대로 출력됨:
    동일한 날짜에 'Mask'가 두 번 팔렸다면, num_sold도 1개가 더 카운트되고 products 문자열에도 'Mask'가 두 번 들어가게 된다.

  • 문자열 내부 정렬 안 됨:
    쿼리 맨 마지막에 있는 ORDER BY products ASC는 결과로 나온 행(Row) 전체의 순서를 정렬할 뿐, GROUP_CONCAT으로 묶인 단어들 내부의 순서(사전순)를 정렬해 주지 못한다.

해결 방법 및 정답 쿼리

이 문제를 해결하려면 COUNT()GROUP_CONCAT() 함수 내부에서 직접 DISTINCTORDER BY를 사용해야 한다.

num_sold: COUNT(DISTINCT product)를 사용하여 중복을 제거한 고유 상품 개수만 센다.

products: GROUP_CONCAT(DISTINCT product ORDER BY product ASC)를 사용하여 결합하기 전에 미리 중복을 제거하고 사전순으로 정렬되도록 처리한다.

SELECT 
    sell_date,
    COUNT(DISTINCT product) AS num_sold,
    GROUP_CONCAT(DISTINCT product ORDER BY product ASC SEPARATOR ',') AS products
FROM 
    Activities
GROUP BY 
    sell_date
ORDER BY 
    sell_date ASC;

핵심 개념: GROUP_CONCAT 함수 파헤치기

MySQL에서 여러 행의 데이터를 하나의 문자열로 결합할 때 사용하는 굉장히 유용한 함수이다. 데이터를 요약해서 보여주어야 할 때 자주 활용된다.

1. 기본 문법

GROUP_CONCAT(
    [DISTINCT] 컬럼명
    [ORDER BY 정렬할_컬럼명 ASC/DESC]
    [SEPARATOR '구분자']
)

2. 주요 옵션 설명

  • DISTINCT: 결합할 데이터 중 중복되는 값을 하나로 합쳐준다.

  • ORDER BY: 문자열로 결합되기 전에 괄호 안에서 데이터들의 순서를 먼저 정렬한다. (전체 쿼리 끝에 쓰는 ORDER BY와는 적용 시점과 대상이 다르다!)

  • SEPARATOR: 단어와 단어 사이를 이어줄 구분자를 지정한다.
    기본값은 쉼표(,)이다. 만약 띄어쓰기나 슬래시 등으로 연결하고 싶다면 SEPARATOR ' / ' 와 같이 지정할 수 있다.

3. 활용 예시 비교

만약 묶어줄 데이터가 [A, B, A, C] 순서로 존재한다고 가정.

  • 기본 결합: GROUP_CONCAT(col) \rightarrow "A,B,A,C"
  • 중복 제거: GROUP_CONCAT(DISTINCT col) \rightarrow "A,B,C"
  • 중복 제거 & 역순 정렬: GROUP_CONCAT(DISTINCT col ORDER BY col DESC) \rightarrow "C,B,A"
  • 구분자 변경: GROUP_CONCAT(col SEPARATOR ' | ') \rightarrow "A | B | A | C"

1517. Find Users With Valid E-Mails

문제설명

Write a solution to find the users who have valid emails.
A valid e-mail has a prefix name and a domain where:
The prefix name is a string that may contain letters (upper or lower case), digits, 
underscore '_', period '.', and/or dash '-'. The prefix name must start with a letter.
The domain is '@leetcode.com'. Return the result table in any order.

정규표현식(Regular Expression)을 활용하여 복잡한 문자열 조건을 필터링하는 문제이다.
정규식 문법뿐만 아니라, MySQL의 대소문자 구분(Case Sensitivity) 처리와 버전별 호환성 에러까지 해결해야 하는 꽤 까다롭고 배울 점이 많은 문제였다.

📌 문제 이해 및 정규표현식 설계

유효한 이메일인지 판별하기 위해 문제에서 제시한 규칙을 정규표현식 기호로 하나씩 치환했다

  1. 무조건 문자로 시작 (대문자 or 소문자)
    • ^[a-zA-Z] : ^는 문자열의 시작을 의미한다.
  2. @ 앞에는 문자, 숫자, 특수문자(_, ., -)만 허용
    • [a-zA-Z0-9_.-]* : 괄호 안의 문자들이 0개 이상(*) 올 수 있음을 의미한다.
  3. 도메인은 무조건 @leetcode.com으로 끝날 것
    • @leetcode\\.com$ : 마침표(.)는 정규식에서 '아무 문자 1개'를 의미하므로, 진짜 마침표를 표현하기 위해 이스케이프(\\) 처리한다. $는 문자열의 끝을 의미한.

[완성된 기본 정규표현식]
'^[a-zA-Z][a-zA-Z0-9_.-]*@leetcode\\.com$'


트러블 슈팅 1: 대소문자 구분의 함정

위에서 만든 정규표현식을 WHERE mail REGEXP 구문에 넣고 돌렸더니, winston@leetcode.COM 이라는 오답 데이터가 검색 결과에 포함되었다.

  • 원인: MySQL의 REGEXP 연산자는 기본적으로 대소문자를 구분하지 않는다(Case-insensitive). 따라서 소문자로 @leetcode.com이라 적었어도 대문자인 COM까지 모두 유효하다고 판단한 것이다.
  • 1차 해결 시도: 정규식에서 대소문자를 엄격하게 구분하도록 REGEXP 뒤에 BINARY 키워드를 추가하였다. (WHERE mail REGEXP BINARY '정규식')

트러블 슈팅 2: 문자열 셋(Character set) 충돌 에러

BINARY 키워드를 넣었더니 이번에는 아래와 같은 끔찍한 에러가 발생했습니다.

Character set 'utf8mb3_general_ci' cannot be used in conjunction with 'binary' in call to regexp_like.

  • 원인: LeetCode의 테이블 컬럼은 대소문자를 무시하는 문자열 셋(utf8mb3_general_ci)을 사용 중인데, 쿼리에서는 바이트 단위로 비교하는 BINARY를 강제로 적용하려고 하니 MySQL 내부에서 충돌이 일어난 것이다.

최종 해결: REGEXP_LIKE 함수와 매칭 옵션

최신 MySQL 8.0 이상 환경에서는 이 문제를 아주 깔끔하게 해결할 수 있는 REGEXP_LIKE 전용 함수와 매칭 옵션을 제공한다.

  • 문법: REGEXP_LIKE(컬럼명, '정규식', '매칭옵션')
  • 매칭 옵션에 'c' (Case-sensitive)를 주면, 대소문자를 아주 엄격하게 구분하여 매칭해주고, 반대로 대소문자를 무시하고 싶다면 'i'를 사용한다.

💻 최종 정답 쿼리

SELECT 
    user_id, 
    name, 
    mail
FROM 
    Users
WHERE 
    REGEXP_LIKE(mail, '^[a-zA-Z][a-zA-Z0-9_.-]*@leetcode\\.com$', 'c');
profile
데이터를 향해, 한 걸음씩 천천히.

0개의 댓글