본문 바로가기
Algorithm/SQL

[SQL] 프로그래머스 - 핵심 문제만 풀어보기

by 힘팽 2022. 2. 13.

프로그래머스의 일부 문제와 해설을 복원한 글

◈ 오류 정정 및 피드백 환영


IFNULL / LIMIT

[문제]

동물의 생물 종, 이름, 성별 및 중성화 여부를 조회하시오.

- 이름이 없는 동물의 이름은 No name으로 표시

- 5개만 조회

- ID 순으로 조회

 

[풀이]

SELECT ANIMAL_TYPE, IFNULL(NAME, 'No name'), SEX_UPON_INTAKE FROM ANIMAL_INS
ORDER BY ANIMAL_ID
LIMIT 5
;

 


IF / LIKE / REGEXP

[문제]

동물의 아이디와 이름, 중성화 여부를 조회하시오.

- 중성화된 동물은 SEX_UPON_INTAKE 컬럼에서 'Neutered' 또는 'Spayed'라는 단어 포함

- 중성화가 되어있다면 'O', 아니라면 'X'라고 표시

- ID 순으로 조회

 

[풀이]

1. LIKE 사용

SELECT ANIMAL_ID, NAME, IF(SEX_UPON_INTAKE NOT LIKE '%Intact%','O','X') AS tmp
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
;
 
SELECT ANIMAL_ID, NAME, 
IF(SEX_UPON_INTAKE LIKE '%Neutered%' OR SEX_UPON_INTAKE LIKE '%Spayed%','O','X') AS tmp
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
;
 

2. REGEXP 사용

SELECT ANIMAL_ID, NAME, IF(SEX_UPON_INTAKE REGEXP 'Neutered|Spayed','O','X') AS tmp
FROM ANIMAL_INS
ORDER BY ANIMAL_ID
;

※ IF를 사용할 때는 반드시 alias 붙여줘야 함

 

[문제]

고양이와 개가 각각 몇 마리인지 조회하시오.

- 고양이를 개보다 먼저 조회

 

[풀이]

고양이(cat)와 개(dog)는 알파벳 순이므로 ANIMAL_TYPE으로 정렬하면 된다.

SELECT ANIMAL_TYPE, COUNT(ANIMAL_ID) FROM ANIMAL_INS
WHERE ANIMAL_TYPE IN ('Cat', 'Dog') 
GROUP BY ANIMAL_TYPE
ORDER BY ANIMAL_TYPE
;

[문제]

2번 이상 쓰인 동물 이름과 그 이름이 쓰인 횟수를 조회하시오.

- 이름이 없는 동물은 제외

- 이름 순으로 조회

 

[풀이]

집계함수는 WHERE에서 사용할 수 없기 때문에 HAVING을 이용하면 된다.

SELECT NAME, COUNT(NAME) COUNT FROM ANIMAL_INS
GROUP BY NAME 
HAVING COUNT(NAME) > 1 AND NAME IS NOT NULL
ORDER BY NAME
;

SubQuery / HAVING

헤비 유저가 소유한 장소

[문제]

공간을 둘 이상 등록한 사람을 의미하는 헤비 유저가 등록한 공간의 정보를 조회하시오.

- 아이디 순으로 조회

 

[풀이]

서브쿼리를 이용해 헤비 유저의 모든 공간 정보를 조회하면 된다.

SELECT * FROM PLACES
WHERE HOST_ID IN (SELECT HOST_ID FROM PLACES
                  GROUP BY HOST_ID
                  HAVING COUNT(ID) > 1)
ORDER BY ID
;

 


HOUR / DATE_FORMAT / BETWEEN

입양 시각 구하기 - (1)

[문제]

09:00부터 19:59까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하시오.

- 시간 순으로 조회

 

[풀이]

HOUR로 시간만 추출해 계산하며녀 된다.

SELECT HOUR(DATETIME) AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS
GROUP BY HOUR
HAVING HOUR BETWEEN 9 AND 19
ORDER BY HOUR
;

+) 변형 - 입양 시각 구하기 - (1)

[문제]

09:30부터 18:30까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하시오.

- 시간 순으로 조회

 

[풀이]

분 단위까지 살펴봐야 하므로 DATE_TIME의 형태를 바꿔준 뒤에 조회하면 된다.

SELECT HOUR(DATETIME) AS HOUR, COUNT(*) AS COUNT FROM ANIMAL_OUTS
WHERE DATE_FORMAT(DATETIME, "%H:%i") BETWEEN '09:30' AND '18:30'
GROUP BY HOUR 
ORDER BY HOUR
;

cf.) MySQL 날짜·시간 함수 

 

MySQL :: MySQL 5.7 Reference Manual :: 12.7 Date and Time Functions

12.7 Date and Time Functions This section describes the functions that can be used to manipulate temporal values. See Section 11.2, “Date and Time Data Types”, for a description of the range of values each date and time type has and the valid formats

dev.mysql.com

Ex.) 년-월-일 → "%Y-%m-%d"

 


JOIN / ORDER BY

오랜 기간 보호한 동물 - (2)

[문제]

입양을 간 동물 중, 보호 기간이 가장 길었던 동물 두 마리의 아이디와 이름을 조회하시오.

- 보호 기간이 긴 순으로 조회

 

[풀이]

LEFT JOIN을 사용한다면 보호소에 들어온 적은 있지만 나간 기록이 없는 동물이 포함된다. 보호기간이 길었던 동물을 조회하는 문제이므로 채점에는 문제가 없지만 INNER JOIN을 사용해서 들어오고 나간 기록이 있는 동물만 조회하도록 하자.

SELECT ins.ANIMAL_ID, ins.NAME
FROM ANIMAL_INS AS ins
INNER JOIN ANIMAL_OUTS AS outs 
USING (ANIMAL_ID)
ORDER BY outs.DATETIME-ins.DATETIME DESC
LIMIT 2
;

 


SubQuery / JOIN / USING / LIMIT

cf.) 서브쿼리로 접근할 때 조금 더 간단한 문제

없어진 기록 찾기

[문제]

입양을 간 기록은 있는데, 보호소에 들어온 기록이 없는 동물의 ID와 이름을 조회하시오.

- ID 순으로 조회

 

[풀이]

1. 서브쿼리 사용

SELECT ANIMAL_ID, NAME FROM ANIMAL_OUTS
WHERE ANIMAL_ID NOT IN (SELECT ANIMAL_ID FROM ANIMAL_INS)
;
 

2. LEFT JOIN 사용

SELECT outs.ANIMAL_ID, outs.NAME FROM ANIMAL_OUTS AS outs
LEFT JOIN ANIMAL_INS AS ins
USING (ANIMAL_ID)
WHERE ins.ANIMAL_ID IS NULL
ORDER BY outs.ANIMAL_ID
;

 

 

있었는데요 없어졌습니다

[문제]

관리자의 실수로 일부 동물의 입양일이 잘못 입력되었습니다. 보호 시작일보다 입양일이 더 빠른 동물의 아이디와 이름을 조회하시오.

- 보호 시작일이 빠른 순으로 조회

 

[풀이]

1. 서브쿼리 사용

SELECT outs.ANIMAL_ID, outs.NAME FROM ANIMAL_OUTS AS outs, ANIMAL_INS AS ins 
WHERE outs.ANIMAL_ID = ins.ANIMAL_ID
AND outs.DATETIME < ins.DATETIME
ORDER BY ins.DATETIME
;

 

2. LEFT JOIN 사용

SELECT outs.ANIMAL_ID, outs.NAME FROM ANIMAL_OUTS AS outs
LEFT JOIN ANIMAL_INS AS ins 
USING (ANIMAL_ID)
WHERE outs.DATETIME < ins.DATETIME
ORDER BY ins.DATETIME
;

 

오랜 기간 보호한 동물(1)

[문제]

아직 입양을 못 간 동물 중, 가장 오래 보호소에 있었던 동물 3마리의 이름과 보호 시작일을 조회하시오.

- 보호 시작일 순으로 조회

 

[풀이]

1. 서브쿼리 사용

SELECT NAME, DATETIME FROM ANIMAL_INS
WHERE ANIMAL_ID NOT IN (SELECT ANIMAL_ID FROM ANIMAL_OUTS)
ORDER BY DATETIME
LIMIT 3
;

 

2. LEFT JOIN 사용

SELECT ins.NAME, ins.DATETIME FROM ANIMAL_INS AS ins
LEFT JOIN ANIMAL_OUTS AS outs
USING (ANIMAL_ID)
WHERE outs.ANIMAL_ID IS NULL
ORDER BY ins.DATETIME
LIMIT 3
;

 


SubQuery / JOIN / USING / LIKE

cf.) JOIN으로 접근할 때 조금 더 간단한 문제

보호소에서 중성화한 동물

[문제]

보호소에 들어올 당시에는 중성화되지 않았지만, 보호소를 나갈 당시에는 중성화된 동물의 아이디와 생물 종, 이름을 조회하시오.

- ID 순으로 조회

 

[풀이]

1. 서브쿼리 사용

SELECT ins.ANIMAL_ID, ins.ANIMAL_TYPE, ins.NAME
FROM ANIMAL_INS AS ins, ANIMAL_OUTS AS outs
WHERE ins.ANIMAL_ID = outs.ANIMAL_ID 
AND ins.SEX_UPON_INTAKE LIKE '%Intact%' 
AND outs.SEX_UPON_OUTCOME NOT LIKE '%Intact%'
ORDER BY ANIMAL_ID
;
 

2. LEFT JOIN 사용

동일한 key로 join한다면 USING을 사용하면 조금 더 간단해진다.

SELECT ins.ANIMAL_ID, ins.ANIMAL_TYPE, ins.NAME FROM ANIMAL_INS AS ins 
LEFT JOIN ANIMAL_OUTS AS outs
USING (ANIMAL_ID)
WHERE ins.SEX_UPON_INTAKE LIKE '%Intact%' 
AND outs.SEX_UPON_OUTCOME NOT LIKE '%Intact%'
ORDER BY ins.ANIMAL_ID
;

※ LIKE는 대소문자를 구분 X

 

 


WITH RECURSIVE / SET

[문제]

0시부터 23시까지, 각 시간대별로 입양이 몇 건이나 발생했는지 조회하시오.

- 시간 순으로 조회

 

[풀이]

테이블에는 9시부터 19시까지밖에 없기 때문에 0부터 23까지의 숫자로 구성된 임의의 테이블을 생성해야 한다. 어떻게 해야 할까?

Python이었다면 list comprehension을 사용하면 된다.

 

# Python
[i for i in range(24)]
 

그리고 Oracle 역시 CONNECT BY LEVEL을 사용하면 간단하게 구현이 가능하다.

# Oracle
SELECT LEVEL FROM DUAL CONNECT BY LEVEL <= 23
;
 

1. WITH RECURSIVE 사용

하지만 안타깝게도 MySQL에서는 그리 간단하지가 않다. 다음과 같이 UNION ALL을 재귀적으로 호출해서 값을 하나씩 추가해 주어야 한다.

# MySQL
WITH RECURSIVE cte (0)
  AS (SELECT 1
       UNION ALL
      SELECT n+1 FROM cte WHERE n < 23
     )
SELECT * FROM cte
;

따라서 풀이는 다음과 같다. 복잡하다.

WITH RECURSIVE cte AS (SELECT 0 AS HOUR
                       UNION ALL
                       SELECT HOUR+1 FROM cte WHERE HOUR < 23
                      )
SELECT cte.HOUR, COUNT(ani.ANIMAL_ID) AS COUNT
FROM cte
LEFT JOIN ANIMAL_OUTS AS ani
ON cte.HOUR = HOUR(ani.DATETIME)
GROUP BY cte.HOUR
ORDER BY cte.HOUR
;

 

2. SET 사용

그래도 사용자 정의 변수를 사용해 값을 대입하는 방식은 조금 더 간단하다.

SET @HOUR = -1;
SELECT @HOUR := @HOUR + 1 HOUR, (SELECT COUNT(*) FROM ANIMAL_OUTS WHERE HOUR(DATETIME) = @HOUR) COUNT 
FROM ANIMAL_OUTS  
WHERE @HOUR < 23
;

만약 WHERE로 조건을 주지 않는다면 ANIMAL_OUTS의 데이터 개수만큼의 값이 생성된다.

ex) WHERE @HOUR < 23절을 삭제할 경우

데이터 개수가 100개이기 때문에 HOUR을 0부터 99까지 생성

댓글