[내일배움캠프] SQL/Python 코드카타

sleekstar·2025년 6월 10일

SQL 코드카타

문제 1.

그룹별 조건에 맞는 식당 목록 출력하기

IDEA

예전에 SQL 개인 과제 때, CTE 이용법을 배웠던 것을 활용해보기로 했다.
또 예전에 조회수가 가장 많은 중고거래 게시판의 첨부파일 조회하기 문제를 풀 때와 느낌이 비슷해, 당시의 문법과 비슷하게 작성하였다.
리뷰를 가장 많이 작성한 회원의 리뷰들을 조회해야 하는데, 이때 COUNT를 이용해 MEMBER_ID가 출현한 횟수를 세고 최댓값을 가지는 리뷰들을 조회하면 될 것 같았다.
그런데 MAXWHERE절에 쓸 수 없고, 따라서 MAX(COUNT)WHERE절에 서브쿼리로 넣어야 할 것이다.
이때, COUNT를 집계하는 테이블을 CTE를 통해서 먼저 넣어주었다.

작성 코드(정답)

WITH REVIEW_CNT AS(
    SELECT
        COUNT(*) CNT,
        MEMBER_ID
    FROM REST_REVIEW
    GROUP BY MEMBER_ID
    )
SELECT 
    M.MEMBER_NAME,
    R.REVIEW_TEXT,
    DATE_FORMAT(R.REVIEW_DATE, '%Y-%m-%d') REVIEW_DATE
FROM MEMBER_PROFILE M
    LEFT JOIN REST_REVIEW R 
    ON M.MEMBER_ID = R.MEMBER_ID
    LEFT JOIN REVIEW_CNT C
    ON M.MEMBER_ID = C.MEMBER_ID
WHERE C.CNT = (SELECT 
                MAX(CNT)
              FROM REVIEW_CNT)
ORDER BY REVIEW_DATE ASC, REVIEW_TEXT ASC;

다른 사람들의 답안도 참고해 보니, 대부분 CTE를 쓰는 것을 볼 수 있었다.

문제 2.

오프라인/온라인 판매 데이터 통합하기

처음 작성한 답안(오답)

SELECT 
    DATE_FORMAT(SALES_DATE, '%Y-%m-%d') SALES_DATE,
    PRODUCT_ID,
    USER_ID,
    SALES_AMOUNT
FROM ONLINE_SALE
WHERE SALES_DATE BETWEEN '2022-03-01' AND '2022-03-31'
UNION ALL
SELECT 
    DATE_FORMAT(SALES_DATE, '%Y-%m-%d') SALES_DATE,
    PRODUCT_ID,
    'NULL' USER_ID,
    SALES_AMOUNT
FROM OFFLINE_SALE
WHERE SALES_DATE BETWEEN '2022-03-01' AND '2022-03-31'
ORDER BY SALES_DATE ASC, PRODUCT_ID ASC, USER_ID ASC

JOIN 말고 UNION ALL을 이용하였다. 그런데 OFFLINE_SALES에는 USER_ID 컬럼이 없고, 문제에서 USER_ID값은 NULL로 표시하라는 조건이 있었기에 'NULL' USER_ID 쿼리를 추가하였다.

그런데 첫 번째 답안은 오답이었다.

WHY?

왜 틀렸을까? 다른 사람들 코드를 보니 나처럼 작성했다가 오답이 나온 사람들이 많았다. 'NULL' USER_ID에서 따옴표를 빼니 정답이었다. 안그래도 헷갈렸는데...

정답

SELECT 
    DATE_FORMAT(SALES_DATE, '%Y-%m-%d') SALES_DATE,
    PRODUCT_ID,
    USER_ID,
    SALES_AMOUNT
FROM ONLINE_SALE
WHERE SALES_DATE BETWEEN '2022-03-01' AND '2022-03-31'

UNION ALL

SELECT 
    DATE_FORMAT(SALES_DATE, '%Y-%m-%d') SALES_DATE,
    PRODUCT_ID,
    NULL USER_ID,
    SALES_AMOUNT
FROM OFFLINE_SALE
WHERE SALES_DATE BETWEEN '2022-03-01' AND '2022-03-31'
ORDER BY SALES_DATE ASC, PRODUCT_ID ASC, USER_ID ASC;

문제 3.

입양 시각 구하기(2)

PROBLEM

단순히 COUNT만 쓰면 될 줄 알았는데, 예시 답안에는 HOUR가 0부터 23까지의 값을 가지고 있는데 반해 테이블에는 HOUR값이 7부터 19까지밖에 없었다.
존재하지 않는 값을 만들어줘야 하나?

처음 작성한 답안(오답)

SELECT 
    HOUR(DATETIME) HOUR,
    COUNT(*) COUNT
FROM ANIMAL_OUTS
GROUP BY HOUR
ORDER BY HOUR ASC

IDEA

그러면 0부터 23까지의 값을 갖는 HOUR컬럼을 만들어서 ANIMAL_OUTS 테이블과 조인할 수 없을까?
사실 도저히 모르겠어서 다른 사람들의 답안을 참고했더니, 재귀 CTE가 있었다.

재귀 CTE(Recursive Common Table Expression)란?

구분설명
정의하나의 CTE 안에서 자기 자신을 다시 참조해 반복적으로 결과 집합을 확장하는 기법. 트리·계층 구조 탐색이나 연속된 숫자·날짜 생성 등에 자주 사용.
구조앵커(Anchor) 부분 – 재귀를 시작할 최초 행 집합
재귀(Recursive) 부분 – 이전 결과를 다시 참조해 다음 결과를 만들어 UNION ALL로 합침
종료 조건재귀 부분의 WHERE 절에서 “더 이상 확장할 행이 없을 때” 멈춤. ANSI-SQL에선 명시적 최대 반복 제한이 없으나, DBMS마다 MAXRECURSION, RECURSIVE_LIMIT 등으로 안전장치를 제공.
지원 DBMSSQL Server, PostgreSQL, Oracle 11g+, MySQL 8.0+, SQLite 3.8.3+, DB2 등(버전별 차이 주의).
대표적 활용- 조직도·카테고리 등 계층 구조 조회
- 그래프 탐색(경로 찾기)
- 일정 범위의 숫자·날짜 시퀀스 생성
- 파티션별 누적 합, 연결 리스트 처리 등

정답 쿼리 및 해석

WITH hours AS (                -- (1) CTE 정의 시작
    SELECT 0 AS hr             -- (2) 앵커: hr = 0
    UNION ALL
    SELECT hr + 1              -- (3) 재귀: 이전 hr에 +1
    FROM   hours
    WHERE  hr < 23             -- (4) 종료 조건: 23이 될 때 멈춤
)
SELECT 
    h.hr AS HOUR,              -- (7) 결과 컬럼: 0~23
    COUNT(a.ANIMAL_ID) AS CNT  -- (8) 건수 집계
FROM   hours h
LEFT JOIN ANIMAL_OUTS a        -- (5) 모든 시간대 보존
       ON DATEPART(hour, a.DATETIME) = h.hr
GROUP BY h.hr                  -- (6) 시간대별 집계
ORDER BY h.hr                  -- (9) 0→23 정렬
  1. WITH hours AS

    • CTE 이름 hours를 선언. 이 결과는 바로 아래 SELECT~UNION ALL 블록으로 채워진다.
  2. 앵커 부분 SELECT 0 AS hr

    • 최초 한 행(값 0)을 만들어 재귀의 시발점으로 사용
  3. 재귀 부분 SELECT hr + 1 FROM hours

    • 직전 단계에서 나온 hours 결과 집합을 다시 읽어 hr에 1을 더한다
  4. 종료 조건 WHERE hr < 23

    • 값이 23보다 작을 때만 다음 행을 생성하므로 최종적으로 0~23 사이 24행이 만들어진다.
  5. LEFT JOIN ANIMAL_OUTS

    • hours가 왼쪽(기준)이어서 실제 데이터가 없는 시간대도 NULL대신 CNT=0으로 남길 수 있다.
  6. GROUP BY h.hr

    • 각 시간대별로 집계할 준비
  7. SELECT h.hr AS HOUR

    • 최종 출력 컬럼 이름을 HOUR로 지정
  8. COUNT(a.ANIMAL_ID) AS CNT

    • 해당 시간대의 입양 건수를 계산(입양이 없으면 0)
  9. ORDER BY h.hr

    • 결과를 0시, 1시 ... 23시 순으로 정렬

  • 앵커 + 재귀 + 종료 조건 세 덩어리로 구조를 시각화하면 이해가 빠르다.
  • 숫자 시퀀스처럼 “예측 가능한 반복”은 재귀 CTE가 가장 깔끔한 해법.
  • 대용량·깊은 재귀에서는 성능과 최대 깊이 제한을 반드시 테스트할 것.

Python 코드카타

문제 1.

행렬의 덧셈

IDEA

리스트 안에 리스트가 있는 arr1, arr2
그리고 반복 연산
두 리스트를 한 번에 다룰 수 있는 zip을 사용하면 좋을 것이다.

정답 코드

def solution(arr1, arr2):
    return [[a+b for a, b in zip(x, y)] for x, y in zip(arr1, arr2)]

문제 2.

직사각형 별찍기

a, b = map(int, input().strip().split(' ')) 설명

  1. input()
  • 사용자로부터 문자열 입력을 받는 함수
  1. .strip()
  • 입력된 문자열의 양쪽 공백 문자(띄어쓰기, 탭, 줄바꿈 등) 제거
  1. .split(' ')
  • 문자열을 공백 기준으로 나누어 리스트로 반환
  1. map(int, ...)
  • 리스트의 각 요소를 정수로 반환

전체 흐름

입력: 5 3

→ input() → '5 3'  
→ strip() → '5 3'  
→ split(' ') → ['5', '3']  
→ map(int, ...) → 5, 3  
→ a = 5, b = 3

정답 코드

a, b = map(int, input().strip().split(' '))
for i in range(b):
    print('*' * a)
profile
기록용

0개의 댓글