데이터베이스 4주차 SQL 문제 풀이

박서영·2026년 3월 27일

Q1.

프로그래머스 클래스 문제: 등록한 방이 2개 이상인 헤비 유저의 등록 건수 출력

처음에 서브쿼리를 안쓰고 문제를 풀 수 있나 생각해봤는데 HOST_ID를 기준으로 그룹을 만들어서 COUNT를 해야하는데 그룹을 만든 이상, 해당 호스트 아이디의 다른 튜플들을 출력할 수 없을 것 같아 결국 서브 쿼리를 사용했다.

하위 쿼리에서 우선 HOST_ID를 기준으로 그룹을 만들었을 때 COUNT(*)의 개수가 2 이상인 것들의 HOST_ID를 선택해 이 호스트 아이디와 일치하는 튜플들만 상위 쿼리에서 출력하도록 하였다.

처음에 HOST_ID가 아니라 ID를 하위 쿼리에서 셀렉트해서 잘못 출력했었다. ID를 기준으로하면, HOST_ID로 그룹화했을 때 하나의 등록 ID만 남으니 그렇게 하면 안됐던 것 같다.

  • 풀고 나서 찾아보니 EXISTS 로도 풀 수 있다는거 같아서 찾아보고 풀어보려했는데? 약간 감이 잘 안왔다. 그냥 서브쿼리가 아니라 상관 부속질의(그러니까 상하 관계있는? 서로 연결된?) 그런 서브쿼리가 필요했다.
SELECT ID, NAME, HOST_ID
FROM PLACES P1
WHERE EXISTS (
    SELECT *
    FROM PLACES P2
    GROUP BY HOST_ID
    HAVING COUNT(*) >= 2
)
ORDER BY ID;

처음에는 위에처럼 썼는데 의외로 에러는 안났다. 대신 당연히 답은 틀렸는데 이유를 찾아보니 두 쿼리 사이의 연결관계가 설정되지 않아서 였다. 쿼리를 보면, 상위 쿼리의 행을 가지고 들어가지 않으니 하위 쿼리에서 전체 PLACES 테이블을 HOST_ID를 기준으로 그룹화하고 하나라도 HAVING의 조건을 만족하면 참을 반환하고 있었다.

SELECT * 
FROM PLACES P1 
WHERE EXISTS ( 
	SELECT * 
    FROM PLACES P2 
    WHERE P1.HOST_ID = P2.HOST_ID 
    GROUP BY HOST_ID 
    HAVING COUNT(*) >= 2 ) 
ORDER BY ID;

이걸 고쳐서 위에처럼 연결관계를 설정해주었다. WHERE 절을 사용해서 상위 쿼리에서 가지고 들어온 행의 HOST_ID와 일치하는 경우의 전체 등록 수만을 기준으로 EXISTS를 판단할 수 있도록 수정했다.


Q2.

프로그래머스 클래스 문제: 입양되지 않은 동물 중 보호소에 들어온지 오래된 3마리 출력

다른 부분은 풀 수 있었는데 처음에 가장 오래된 3마리를 어떻게 출력해야할까?였다. 우선은 DATETIME으로 정렬하는것까지는 알았는데 딱 3마리 출력하는걸 모르겠어서 찾아봤다.

LIMIT을 사용하면 출력할 행의 수를 제한할 수 있었다. 이걸 사용해서 앞에 짜두었던 SQL문에서 제한을 3줄만 출력하도록 제한해 문제를 풀었다.

입양되지 않은 동물을 찾는 것은 전에는 서브쿼리 사용했었는데, 이번에는 입양된 동물들 테이블 기준 외부 조인을 사용해서 합친 테이블에서 ANIMAL_OUT 테이블의 속성이 널인 것을 이용해서 WHERE 문을 통해 구했다.


Q3.

사이버캠퍼스 문제: 입양시각 구하기

DATETIME()연-월-일 시:분:초를 모두 갖는데, 이런 데이터를 HOUR() 함수에 넣게되면 시간만을 얻을 수 있다.

조건에 따라 테이블에 시간을 출력하기 위해 HOUR(DATETIME)을 통해 시간만 얻어낸 것을 출력명을 지정해주고, 해당 속성명을 사용해 아래에서도 그룹화를 진행했다. WHERE절의 조건에서는 SELECT에서 사용한 별칭을 사용할 수 없기에 따로 함수를 사용했다.

문제 풀다가 기왕 정리하는김에 날짜 데이터 다루는 것과 관련된 내용 찾을 겸 정리를 했다.

날짜 데이터타입

DATE: 날짜 정보를 갖는 타입. 'YYYY-MM-DD' 형식 사용
TIME: 시간 정보를 갖는 타입. 'YYYY-MM-DD' 형식 사용
DATETIME: 날짜와 시간 정보를 모두 갖는 타입. 'YYYY-MM-DD YYYY-MM-DD' 형식 사용

사용

SELECT나 WHERE 절에서 YEAR(), MONTH(), DAY(), HOUR(), MINUTE(), SECOND() 를 통해 사용하면 각각 연/월/일/시/분/초로 출력 형식을 정하거나 조건을 확인할 수 있다.

정리하다가 궁금해진거는 SELECT에서 설정한 별칭을 왜 GROUP BY에서 사용할 수 있을까였다. ORDER BYSELECT 이후에 실행되니까 그렇다고쳐도, GROUP BY는 이전에 실행되니까...?

=> 찾아봤을 때는 실행되는 순서는 저게 맞고, 실제로는 사용이 안되지만? MySQL 같은 현대적인 DB 엔진에서 저런 부분을 허용해주는 경우가 있다고 한다. 오라클 같은 데에서는 못 쓸 수도 있다고...?

위 상황을 생각해서 아래처럼 수정을 했다. WHERE 절에서 사용한 것처럼 HOUR()안에 넣어주었다.


Q4.

NULL 처리하기

앞의 다른 문제랑 비슷하게 조건은 처리할 수 있었다. NULL 값을 처리하는 방법을 몰랐었는데 클래스에 있는 문제 풀면서 익혔던 부분이라 IFNULL을 사용해서 처리했다.

정리

IFNULL(): 해당 컬럼의 값이 널일 때, 다른 값을 출력하도록함

SELECT IFNULL(컬럼명,"NULL일 때 대체값)
FROM ...


COALESCE(): 지정한 표현식들 중 널값이 아닌 첫 번째 값을 반환

SELECT COALESCE(컬럼명1, 컬럼명2,....)
SELECT COALESCE(컬럼명1, "컬러명1이 널값일 때 대체값")

COALESCE 써서도 아래처럼 풀었는데 똑같은 형식으로 사용하면 됐다.


Q5.

DATETIME에서 DATE로 형 변환

DATETIME의 출력 형식을 바꾸는 것 중에 DATE_FORMAT을 사용해서 풀 수 있는 문제였다. 다른 부분은 비슷하고, SELECT에서 포맷만 설정해주었다.

DATE_FORMAT 정리

DATE_FORMAT(날짜, 형식)과 같은 식으로 넣어서 사용해준다. 형식을 지정하는 기호들은 아래와 같은 것들이 있다.
%Y: 4자리 년도, %y: 2자리 년도
%m: 숫자 월, %d: 일자
%H-%i-%S: 시(24시간)-분-초
%I: 시간(12시간)
%T: hh:mm:SS


Q6.

중성화 여부 파악하기

CASE WHEN을 사용해서 풀이한 문제였다. CASE WHEN을 사용할 줄 몰라서 일단 찾아봤는데 찾았는데도 계속 오류가 나서 뭐가 문제일까 고민했다.

나중에 다른 방식을 사용해서 풀고 찾아보니 일전에는 계속

CASE SEX_UPON_INTAKE
	WHEN LIKE 'Neutered%'...

이런식으로 썼었는데 LIKE을 사용할 때는 CASE 컬럼(단순형)... 형식을 쓰면 안되고 검색형을 사용해야한다고 한다. (검색형은 CASE 뒤에 컬럼을 바로 쓰는게 아니라, WHEN 뒤에 조건식을 쓰는 형식이다.

CASE "여기는 비우고"
	WHEN SEX_UPON_INTAKE LIKE 'Neutered%'...

이렇게 조건식을 쓰기.

CASE WHEN

SELECT (CASE (컬럼명/값)
WHEN (값: 단, 단순형에서는 무조건 = 비교만 가능) THEN
WHEN ... THEN
END) AS (새로운 컬럼명)


SELECT (CASE
WHEN (조건식) THEN
...
END) AS (새로운 컬럼명)


Q7.

solvesql: 우유와 요거트가 담긴 장바구니

일단은 문제 보고 바로 생각난 풀이는 진짜 비효율적인? 그런데 제일 쉬운 그런 풀이였다. 맞긴 맞았는데 이것보다 좋은 방법이 있는 것 같아서 고민하다가 찾아봤다.

GROUP BY하고 HAVING을 사용하면 더 효율적으로 풀 수 있을 것 같긴했는데 어떤 식으로 사용해야할 지 잘 감이 안왔는데, WHERE절에서 먼지 일단 우유와 요거트가 든 행만 남긴다 -> 그리고 GROUP BY를 통해 ID 별로 묶고, HAVING 절에서 개수가 2 이상인 것(우유랑 요거트 행만 남겼으니까 2이상이면 무조건 이 둘이다 단, COUNT(DISTINCT)를 사용해서 우유가 2개 이런 경우는 제외시켜준다.)

처음에 GROUP BY하고 HAVING을 써야할 것 같았는데, 이러면 다른 상품의 개수도 포함된다고 생각하고 그냥 직관적으로 바로 생각나는걸로 풀었는데 진짜 WHERE에서 조건을 사용해서 한 번 걸러주면 되는데 이 생각을 못했다. 그리고 DISTINCT 사용해서 또 걸러내는 것도 생각을 못했었다.

위의 방법대로 푼 방식은 위와 같다. 훨씬 효율적이고 쿼리 길이도 짧다.


후기?
문제 풀다가 중간쯤에 깨달은 건데? 이거 소문자로 써도 괜찮았는데 왜 계속 대문자로 썼지 싶다(컬럼명). 아무생각없이 문제가 대문자라 계속 대문자로 썼는데 소문자로 써도 돌아갔다 생각해보니.

profile
이불 밖은 위험해.

0개의 댓글