문제 요약
USED_GOODS_BOARD와 USED_GOODS_USER 테이블에서 중고 거래 게시물을 3건 이상 등록한 사용자의 사용자 ID, 닉네임, 전체주소, 전화번호를 조회하는 SQL문을 작성해주세요.
이때, 전체 주소는 시, 도로명 주소, 상세 주소가 함께 출력되도록 해주시고, 전화번호의 경우 xxx-xxxx-xxxx 같은 형태로 하이픈 문자열(-)을 삽입하여 출력해주세요.
결과는 회원 ID를 기준으로 내림차순 정렬해주세요.
👉🏻 문제보러가기
프로그래머스
코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.
programmers.co.kr
1. IN을 사용한 풀이
STEP1 | 서브쿼리 작성
used_goods_board 테이블에서 중복된 writer_id를 제거하고, 3건 이상의 거래 게시물을 등록한 writer_id를 찾아내야 합니다. 이를 위해 아래와 같은 서브쿼리를 사용합니다.
SELECT writer_id
FROM used_goods_board
GROUP BY writer_id
HAVING COUNT(board_id) >= 3
STEP2 | IN을 사용해 해당하는 유저 정보 가져오기
used_goods_user 테이블에서 위 서브쿼리의 결과 집합에 해당하는 유저 정보를 가져오기 위해 IN을 사용합니다.
SELECT *
FROM used_goods_user
WHERE user_id IN (SELECT writer_id
FROM used_goods_board
GROUP BY writer_id
HAVING COUNT(board_id) >= 3)
STEP3 | 문제에서 요구하는 형태로 유저 정보 출력하기
used_goods_user 테이블의 주소와 전화번호 데이터는 아래와 같은 형태로 저장되어 있습니다.
- city, street_address1, street_address2 세 개의 컬럼으로 나뉘어져 저장되어 있는 주소 정보를 하나로 합쳐주기 위해 CONCAT 함수를 사용합니다. 컬럼과 컬럼 사이에 ' ' 공백을 추가하여 보기 좋게 출력되도록 합니다.
- 전화번호 사이사이에 하이픈을 넣어 출력하기 위해 SUBSTRING, LEFT, RIGHT 함수를 사용해 각 영역을 잘라주고, CONCAT함수로 합쳐줍니다. (물론 SUBSTRING만 사용해도 되지만, 여러 함수를 활용해보기 위해 여기서는 LEFT와 RIGHT도 사용했습니다.)
- 문제 요구대로 정렬 조건을 추가합니다.
최종으로 완성된 쿼리문은 다음과 같습니다.
SELECT user_id
, nickname
, CONCAT(city, ' ', street_address1, ' ', street_address2) AS `전체주소`
, CONCAT(LEFT(tlno,3), '-', SUBSTRING(tlno,4,4), '-', RIGHT(tlno,4)) AS `전화번호`
FROM used_goods_user
WHERE user_id IN (SELECT writer_id
FROM used_goods_board
GROUP BY writer_id
HAVING COUNT(board_id) >= 3)
ORDER BY user_id DESC
2. EXISTS를 사용한 풀이
1번 방식과 모두 동일하면서, STEP2에서 IN 대신 EXISTS를 사용할 수 있습니다.
EXISTS를 사용할 때는 서브쿼리의 결과 집합과 본쿼리가 연관이 있는지(어떤 컬럼을 참조하는지) 고려해서 연관시켜주어야 올바른 결과를 얻을 수 있습니다. 서브쿼리의 결과 집합이 본쿼리와 연관 있는 컬럼을 가진 행들로만 제한되어야 하기 때문입니다.
따라서 여기서는 서브쿼리에서 used_goods_board의 writer_id 컬럼과 used_goods_user의 user_id 컬럼을 비교하는 조건을 추가해야 합니다. (WHERE writer_id = user_id)
SELECT *
FROM used_goods_user
WHERE EXISTS (SELECT writer_id
FROM used_goods_board
WHERE writer_id = user_id
GROUP BY writer_id
HAVING COUNT(board_id) >= 3)
EXISTS를 사용한 최종 쿼리는 다음과 같습니다.
SELECT user_id
, nickname
, CONCAT(city, ' ', street_address1, ' ', street_address2) AS `전체주소`
, CONCAT(LEFT(tlno,3), '-', SUBSTRING(tlno,4,4), '-', RIGHT(tlno,4)) AS `전화번호`
FROM used_goods_user
WHERE EXISTS (SELECT writer_id
FROM used_goods_board
WHERE writer_id = user_id
GROUP BY writer_id
HAVING COUNT(board_id) >= 3)
ORDER BY user_id DESC
Lessons Learned
이 문제를 풀면서 새롭게 정리하고 복습하게 된 내용을 추가로 정리해보았습니다.
1. SUBSTRING
SUBSTRING(string, start, length)
SELECT SUBSTRING("SQL Tutorial", 5, 3)
>> Tut
SELECT SUBSTRING("SQL Tutorial", -5, 5)
>> orial
✔️ 헷갈렸던 부분
- SUBSTRING(string, start position, end position)라고 생각해서 세 번째 파라미터에 끝나는 지점의 위치 숫자를 적었었습니다.
- 포지션의 숫자를 셀 때 인덱스처럼 스트링의 첫 번째 글자를 0으로 두고 센다고 생각했었습니다. 실제로는 그렇지 않고, 1부터 포지션 위치를 세면 됩니다.
2. IN vs EXISTS
EXISTS
- 서브쿼리의 쿼리 결과가 존재하는지 여부만을 판단합니다. 결과 집합이 비어있지 않으면 TRUE를 반환합니다.
- 결과 집합 자체를 가져오지는 않기 때문에 IN보다 효율적인 경우가 많습니다.
IN
- IN 구문의 결과 값 중에서 하나라도 일치하는 것이 있으면 조회된다.
- 서브쿼리를 먼저 실행해서 결과 집합을 가져온 뒤, 메인 쿼리의 테이블에서 row 하나를 가져와 해당 row의 값이 서브쿼리의 결과 집합과 하나라도 일치하면 해당 row를 출력하는 방식으로 동작합니다. (메인쿼리의 row 개수만큼 반복하여 비교하기 때문에 데이터가 많을수록 EXISTS보다 성능이 떨어지는 경우가 많습니다.)
'MySQL' 카테고리의 다른 글
프로그래머스 : 자동차 대여 기록에서 대여중 / 대여 가능 여부 구분하기 (1) | 2023.04.21 |
---|---|
SolveSQL : 지역별 자전거 대여 현황 문제 풀이 (다양한 방식으로 쿼리 짜보기) (0) | 2023.01.20 |
프로그래머스 : 헤비 유저가 소유한 장소 문제 풀이 (0) | 2023.01.20 |
MySQL로 데이터 가공하는 몇 가지 케이스들 (0) | 2023.01.20 |