이번 문제는 LEVEL-4입니다.
문제를 풀고 나니, 확실히 LEVEL-3보다는 조금 더 복잡하긴 하네요 ㅎㅎ..



문제를 요약해보도록 합시다.
*테이블 정보
-> FIRST_HALF 테이블(상반기 주문 정보), JULY 테이블(7월의 아이스크림 주문 정보)
*각 테이블에서의 속성들
-> FIRST_HALF: SHIPMENT_ID, FLAVOR(FK), TOTAL_ORDER
-> JULY: SHIPMENT_ID(PK), FLAVOR(FK, FIRST_HALF의 기본키를 참조. 일반 참조(이게 PK의 일부가 되는 건 아니므로).) TOTAL_ORDER
*문제 요약
7월은 주문량이 많아, 서로 다른 두 공장에서 아이스크림 가게로 출하
->이 경우 같은 맛이더라도, 다른 출하 번호를 가짐.※문제: 7월의 총 주문량(JULY)과 상반기의 총 주문량(FIRST_HALF)을 더한 값이 큰 순서대로 상위 3개의 맛을 조회하라
->DESC, LIMIT 3
저는 문제를 보자마자, 그리고 JULY 테이블을 보자마자 딱 떠오른 개념 1개가 있습니다.
->그건 바로 '집계 함수(GROUP BY)'입니다.
'GROUP BY'는 GROUP BY로 설정한 열을 기준으로, 같은 것(행)들끼리 모은 후에, SUM까지 진행하죠.(그룹화+SUM)
윈도우 함수(WINDOW FUNCTION)의 PARTITION BY와 다른 점이, PARTITION BY는 그룹화만 하고 행은 합치지 않고 살려둔다는 점이죠.(그룹화만)
| GROUP BY | PARTITION BY |
|---|---|
| 그룹화+SUM | 그룹화만 |
<위 'GROUP BY' VS 'PARTITION BY'의 설명에 오류가 있어 바로 수정하겠습니다.>






즉 요약하자면,
- 그니깐 둘 다 그룹화하는 함수이고,
차이점은 단지 행을 합치냐(GROUP BY) VS 행을 합치지 않느냐(PARTITION BY)인 것이다.2.GROUP BY도 '그룹화만'이 맞다.
- 집계 함수 없이도 GROUP BY만 쓸 수 있다. 하지만, 그것은 GROUP BY로 설정한 열만 해당이고, 나머지 열을 같이 쓰려면 집계 함수가 동반되어야 한다.(이건 상황보고 판단하면 좋을 듯 합니다.)
그럼 일단 여기까지의 정보들을 토대로
- JULY 테이블 GROUP BY 시전
- 서로 양 테이블 간에 GROUP BY를 할 수 있나?
- DESC,LIMIT 3 시전
이렇게 나눌 수 있겠네요.
SELECT FLAVOR,
SUM(TOTAL_ORDER)
FROM JULY
GROUP BY FLAVOR;

이렇게 제가 원하는 대로 결과가 나왔습니다.
이 방법은 어떻게 할까해서 구글 웹 검색을 해봤습니다.
이런 답변이 나오더라고요.

->그렇습니다. 먼저 JOIN 후에, 한 테이블로 만들어서 GROUP BY를 하라고 하더군요.
그러면 이제, '어떤 조인'을 사용할 것이며, '어떤 열을 공통된 열로 사용'할 것인가에 대해 고민을 해봐야합니다.
잠시의 고민 끝에, 결론은 'LEFT OUTER JOIN'이었습니다.
Q. 왜 왼쪽 조인이냐?
A. 내부 조인(우리가 흔히 아는 세타 조인,동등 조인,자연 조인)만 하면, 교집합 결과만(공통된 결과만) 생성 됨.
근데, 우리는 OUTER JOIN을 통해서 7월에는 없어도 NULL로 표기하고 이를 0으로 대체해서 덧셈을 시켜야 함.
왼쪽 테이블은 FIRST_HALF, 오른쪽 테이블은 2-1번 코드 테이블임.
(이 과정에서 여러번 수정한 코드까지는 첨부하지 않도록 하겠습니다..글이 너무 길어지고 중구난방해질 거 같아서요..)
SELECT
FIRST_HALF.FLAVOR,
FIRST_HALF.TOTAL_ORDER,
NEW_1.NEW_1_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR;
서브쿼리를 통해서 만들었습니다.
즉, 2-1 테이블이 서브쿼리를 통해 만들어진 것이죠. 하지만, 주의할 점은 실제로 서브쿼리를 통해 만들어진 결과는 테이블로 저장되지는 않습니다!(뒤에도 한번 더 언급할 예정입니다.)
나머지 코드 부분들은 충분히 해석하실 수 있을 거라 생각합니다.

저는 2-2의 결과를 돌리기 전에, 이렇게 나올 거라고 예상했습니다.
조인 후의 TOTAL_ORDER는 각각의 테이블의 TOTAL_ORDER로 표시될 거임.
이를 FLAVOR 기준으로 SUM(2개의 TOTAL_ORDER)로 진행하면 됨.
그리고 ORDER BY DESC와 LIMIT 3;하면 끝.
근데, 이는 잘못되었습니다.
위의 결과 사진을 보면 알 수 있듯이, GROUP BY가 아닌 그냥 새로운 열(TOTAL_ORDER와 NEW_1_SUM을 합치는 열)을 하나 만들면 되더군요.
->CLAUDE도 이 부분을 지적하더군요.
저는 이를 어떻게 구현할까 하다가 처음엔 이렇게 작성해보았습니다.
(SELECT
FIRST_HALF.FLAVOR,
FIRST_HALF.TOTAL_ORDER,
NEW_1.NEW_1_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR) AS NEW_2_TABLE;
ALTER TABLE NEW_2_TABLE ADD REAL_FINAL_SUM INT;
위 2-2번에서 만든 코드를 묶어서 'NEW_2_TABLE'로서 별칭을 칭하고, ALTER문으로 열을 추가하는 방식으로 구현을 시도해보았습니다.
하지만, 당연히 오류가 나더군요.
이유를 알아보자 하니,

그렇습니다.
앞에서도 말했다시피, 서브 쿼리 결과문은 테이블로 저장이 안되므로, ALTER TABLE문이 적용이 안 되는 겁니다.
그래서, CLAUDE가 힌트를 주더군요.

두 칼럼을 합치면 된답니다.
저는 또 처음에 이 합치는 구문을
SUM(FIRST_HALF.TOTAL_ORDER,NEW_1.NEW_1_SUM)
이렇게 구현했는데, 오류가 났습니다.
왜 오류가 났을까요?
Q. 왜 오류가 났을까?
A. SUM()은 같은 열에서 여러 행을 합칠 때(더할 때) 쓰는 집계 함수입니다. 반대로 '+'는 같은 행에서 여러 컬럼을 합칠 때(더할 때) 쓰는 집계식이죠.
즉, 각각은 행과 열의 순서만 바뀐 것입니다.

이렇게 해서 ORDER BY DESC와 LIMIT 3까지 적용한 최종 코드는 다음과 같습니다.
-- 코드를 입력하세요
SELECT
FIRST_HALF.FLAVOR,
FIRST_HALF.TOTAL_ORDER,
NEW_1.NEW_1_SUM,
FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR
ORDER BY REAL_FINAL_SUM DESC
LIMIT 3;
하지만...
또 틀렸습니다..!!! ㅠㅠㅠ
CLAUDE가 2가지의 문제가 있을 거라고 하더군요.
- '상위 3개의 맛을 조회'
즉, 맛 컬럼(열)만 조회하면 되는 거였습니다.
- NULL 처리 문제
이거는 앞에서 제가 말을 한 적이 있죠.
왼쪽 조인 시에, 공통된 열 기준에서 그 열에 해당하는 값이 없으면 오른쪽 테이블은 전부 NULL 처리가 됩니다. 하지만 NULL은 말 그대로 '빈 값'을 의미하죠. 이를 0이라는 숫자로 대체해야합니다.
그 코드는 이렇게 나타낼 수 있다고 하더군요.
COALESCE(NEW_1.NEW_1_SUM, 0) -- NULL이면 0으로 대체하지만, 제가 결과를 직접 확인했을 때에는 왼쪽 테이블과 오른쪽 테이블 모두 공통된 열에서의 튜플들이 모두 일치해서, 이 작업은 따로 안 해도 됐습니다.
(하지만, 이 작업은 필수입니다. 이 문제 경우에서만 예외죠.)
그렇게 해서 코드를 수정했는데,
SELECT
FIRST_HALF.FLAVOR,
-- FIRST_HALF.TOTAL_ORDER,
-- NEW_1.NEW_1_SUM,
FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR
ORDER BY REAL_FINAL_SUM DESC
LIMIT 3;
여기서 또 문제인게,
제가 SELECT문을 통해서 REAL_FINAL_SUM을 정의했고, 따로 저장은 안 했죠. 근데, 우리는 이름만을 결과로 내야합니다.
어떻게 하면 될까요?
여기에도 2가지 방법이 있습니다.
- ORDER BY에 계산식 직접 넣기
SELECT FIRST_HALF.FLAVOR
FROM ...
ORDER BY (FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM) DESC
LIMIT 3;
이렇게 직접 넣으면 단숨에 해결이 됩니다.
이 문제 경우에는 계산식(변수명)이 짧아서 다행히지만, 길어지면 보기 어렵다는 단점이 있죠.
-- 코드를 입력하세요
SELECT
FIRST_HALF.FLAVOR
-- FIRST_HALF.TOTAL_ORDER,
-- NEW_1.NEW_1_SUM,
-- FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR
-- ORDER BY REAL_FINAL_SUM DESC
ORDER BY (FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM) DESC
LIMIT 3;

- (일반적) 서브쿼리로 한번 더 감싸기
SELECT FLAVOR
FROM (
SELECT
FIRST_HALF.FLAVOR,
FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM ...
) AS 별칭
ORDER BY REAL_FINAL_SUM DESC
LIMIT 3;
이렇게 서브쿼리를 한번 더 만드는 방법입니다.
일반적인 방법이라고 하더군요.
다음은 이를 반영한 제 코드입니다.
SELECT ONE_MORE_TIME.FIRST_HALF.FLAVOR
FROM (
SELECT
FIRST_HALF.FLAVOR,
-- FIRST_HALF.TOTAL_ORDER,
-- NEW_1.NEW_1_SUM,
FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR) AS ONE_MORE_TIME
ORDER BY REAL_FINAL_SUM DESC
-- ORDER BY (FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM) DESC
LIMIT 3;
하지만, 또 또 문제가 생기더군요..!!
CLAUDE가 '칼럼 참조 방식'이 잘못됐다고 합니다.

근데, 저는 여기서 의문이 들었습니다.




즉, 위 사진들을 요약하자면(이거만 보셔도 됩니다.),

->1. 우리는 왼쪽 조인을 할 때, 공통된 열(FLAVOR)를 기준으로 했고
->2. 내가 SELECT한 FLAVOR가 FIRST_HALF.FLAVOR만 있으므로(양쪽의 FLAVOR가 있는 게 아닌), FLAVOR로만 칭해도 된다.
이 말입니다.
그럼 이제 이 2가지 버전을 적용한 정답 코드를 보여드리도록 하겠습니다.
<버전_1>
-- 코드를 입력하세요
SELECT FLAVOR
FROM (
SELECT
FIRST_HALF.FLAVOR,
-- FIRST_HALF.TOTAL_ORDER,
-- NEW_1.NEW_1_SUM,
FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR) AS ONE_MORE_TIME
ORDER BY REAL_FINAL_SUM DESC
-- ORDER BY (FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM) DESC
LIMIT 3;

<버전_2>
-- 코드를 입력하세요
SELECT ONE_MORE_TIME.FLAVOR
FROM (
SELECT
FIRST_HALF.FLAVOR,
-- FIRST_HALF.TOTAL_ORDER,
-- NEW_1.NEW_1_SUM,
FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM AS REAL_FINAL_SUM
FROM FIRST_HALF LEFT OUTER JOIN (SELECT FLAVOR, SUM(TOTAL_ORDER) AS NEW_1_SUM FROM JULY GROUP BY FLAVOR)AS NEW_1 ON FIRST_HALF.FLAVOR = NEW_1.FLAVOR) AS ONE_MORE_TIME
ORDER BY REAL_FINAL_SUM DESC
-- ORDER BY (FIRST_HALF.TOTAL_ORDER + NEW_1.NEW_1_SUM) DESC
LIMIT 3;

이렇게 해서 이번에는 프로그래머스 LEVEL-4의 문제를 한번 풀어보았습니다.
확실히, 이전에 풀었던 LEVEL-3보다는 고려해야할 부분이 몇개 더 추가됐네요. 재밌게 풀었습니다.
이렇게 해서, 이번 글을 여기서 마치도록 하겠습니다.
끝까지 봐주셔서 감사합니다..!! :) bb