예전에 SQL 개인 과제 때, CTE 이용법을 배웠던 것을 활용해보기로 했다.
또 예전에 조회수가 가장 많은 중고거래 게시판의 첨부파일 조회하기 문제를 풀 때와 느낌이 비슷해, 당시의 문법과 비슷하게 작성하였다.
리뷰를 가장 많이 작성한 회원의 리뷰들을 조회해야 하는데, 이때 COUNT를 이용해 MEMBER_ID가 출현한 횟수를 세고 최댓값을 가지는 리뷰들을 조회하면 될 것 같았다.
그런데 MAX는 WHERE절에 쓸 수 없고, 따라서 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를 쓰는 것을 볼 수 있었다.
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 쿼리를 추가하였다.
그런데 첫 번째 답안은 오답이었다.
왜 틀렸을까? 다른 사람들 코드를 보니 나처럼 작성했다가 오답이 나온 사람들이 많았다. '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;
단순히 COUNT만 쓰면 될 줄 알았는데, 예시 답안에는 HOUR가 0부터 23까지의 값을 가지고 있는데 반해 테이블에는 HOUR값이 7부터 19까지밖에 없었다.
존재하지 않는 값을 만들어줘야 하나?
SELECT
HOUR(DATETIME) HOUR,
COUNT(*) COUNT
FROM ANIMAL_OUTS
GROUP BY HOUR
ORDER BY HOUR ASC
그러면 0부터 23까지의 값을 갖는 HOUR컬럼을 만들어서 ANIMAL_OUTS 테이블과 조인할 수 없을까?
사실 도저히 모르겠어서 다른 사람들의 답안을 참고했더니, 재귀 CTE가 있었다.
| 구분 | 설명 |
|---|---|
| 정의 | 하나의 CTE 안에서 자기 자신을 다시 참조해 반복적으로 결과 집합을 확장하는 기법. 트리·계층 구조 탐색이나 연속된 숫자·날짜 생성 등에 자주 사용. |
| 구조 | ① 앵커(Anchor) 부분 – 재귀를 시작할 최초 행 집합 ② 재귀(Recursive) 부분 – 이전 결과를 다시 참조해 다음 결과를 만들어 UNION ALL로 합침 |
| 종료 조건 | 재귀 부분의 WHERE 절에서 “더 이상 확장할 행이 없을 때” 멈춤. ANSI-SQL에선 명시적 최대 반복 제한이 없으나, DBMS마다 MAXRECURSION, RECURSIVE_LIMIT 등으로 안전장치를 제공. |
| 지원 DBMS | SQL 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 정렬
WITH hours AS
hours를 선언. 이 결과는 바로 아래 SELECT~UNION ALL 블록으로 채워진다.앵커 부분 SELECT 0 AS hr
재귀 부분 SELECT hr + 1 FROM hours
hours 결과 집합을 다시 읽어 hr에 1을 더한다종료 조건 WHERE hr < 23
LEFT JOIN ANIMAL_OUTS
hours가 왼쪽(기준)이어서 실제 데이터가 없는 시간대도 NULL대신 CNT=0으로 남길 수 있다. GROUP BY h.hr
SELECT h.hr AS HOUR
HOUR로 지정COUNT(a.ANIMAL_ID) AS CNT
ORDER BY h.hr
팁
- 앵커 + 재귀 + 종료 조건 세 덩어리로 구조를 시각화하면 이해가 빠르다.
- 숫자 시퀀스처럼 “예측 가능한 반복”은 재귀 CTE가 가장 깔끔한 해법.
- 대용량·깊은 재귀에서는 성능과 최대 깊이 제한을 반드시 테스트할 것.
리스트 안에 리스트가 있는 arr1, arr2
그리고 반복 연산
두 리스트를 한 번에 다룰 수 있는 zip을 사용하면 좋을 것이다.
def solution(arr1, arr2):
return [[a+b for a, b in zip(x, y)] for x, y in zip(arr1, arr2)]
a, b = map(int, input().strip().split(' ')) 설명
input().strip() .split(' ')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)