16주차 미리보기
W16 미리보기 · 웹소설 본편

STARRY PASS 12 — 정산 주방의 주문 순서

쓴 순서와 움직이는 순서는 다르다

범위D1–D6 · 월–토p518 L7–24 + p519–540 + p541 L5–12
정본 상태SOURCE AUDIT PASSPDF·source·workbook byte 폐쇄
실행 상태W16 D1–D6 LABS NOT_RUNtestsExecutedByAudit=0

이 이야기는 W16 개요와 D1–D6만 다룹니다. 정확한 범위는 p518 L7–24 + p519–540 + p541 L5–12이며, p518 L5의 W15 복구 꼬리와 p541 L14부터 시작하는 D7·p546 주간 통합·p547 W17은 제외합니다. 공통 코어 · 트랙 선택 시 증권 기본으로 표시하지만 증권 전용 기능 주차는 아닙니다. source audit은 PASS여도 testsExecutedByAudit=0이므로 현재 상태는 W16 D1–D6 LABS NOT_RUN입니다.

상가 축제 날, STARRY는 하루 동안 카레 가게가 되었다. 니지카는 앞치마를 두르고 주문을 받았고 키타는 접시를 꾸몄다. 료는 계산대 옆에서 감자 한 조각을 시식한 뒤 다섯 번째 조각도 검사라고 주장했다. 히토리는 주방에 숨으면 손님을 피할 수 있을 줄 알았지만 양파 산 앞에 배치되었다.

“사람 대신 양파를 만나니 눈물이 나는 이유가 더 분명해졌어요.” 히토리가 말했다.

“고토의 눈물도 재료가 되는 좋은 가게.” 료가 대답했다.

키타가 주문표 스무 장을 흔들었다. 손님은 완성 접시에 무엇을 보여 달라고 먼저 적었지만, 주방이 실제로 움직일 때는 냉장고에서 재료 상자를 꺼내는 일이 먼저였다. 니지카는 메뉴판의 글 순서와 주방 안의 처리 순서를 바닥에 따로 붙이자고 했다.

니지카가 태블릿으로 SQL 주문표와 논리 처리 순서를 점검하는 공식 장면
그림 한눈에: 공식 장면컷은 주문표를 함께 점검하는 분위기 전환용이며, 학습 사건·SQL·수치는 원문 감사에 따른 비공식 구성입니다.

니지카가 첫 화살표를 FROM에 놓았다. 어떤 상자에서 행을 가져올지 정하는 단계였다. WHERE는 아직 낱개인 행을 걸렀고, GROUP BY는 남은 행을 같은 계좌끼리 묶었다. HAVING은 완성된 묶음을 다시 걸렀다. 그 뒤 SELECT가 보여 줄 열과 gross 같은 별칭을 만들었고, ORDER BY가 마지막 줄을 세웠다.

키타는 주문표에 SELECT가 먼저 쓰여 있으니 별칭도 처음부터 존재한다고 생각했다. 히토리는 WHERE gross > 1000이라는 쪽지를 냉장고 문에 붙였다가 아무도 gross를 모른다는 답을 들었다. 같은 query level의 WHERE 시점에는 SELECT 별칭이 아직 태어나지 않았다. 반면 마지막 ORDER BY에 도착했을 때는 완성된 별칭을 사용할 수 있었다.

“미래의 접시 이름으로 과거의 양파를 부르면 양파가 대답하지 않는군요.” 히토리가 말했다.

“그리고 낱개 재료를 거르는 WHERE와 묶음 합계를 거르는 HAVING도 자리를 바꾸면 안 돼.” 니지카가 덧붙였다.

FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY 순서와 별칭 생성 시점을 보여 주는 도식
그림 한눈에: FROM→WHERE→GROUP BY→HAVING→SELECT→ORDER BY의 논리 순서와 gross 별칭이 생기는 시점을 함께 봅니다.

그 순서는 SQL 언어를 이해하기 위한 logical processing order였다. 실제 PostgreSQL이 디스크와 메모리에서 물리 연산자를 어떤 순서로 골랐는지는 optimizer와 실행 계획의 문제였다. 바닥 화살표를 외웠다고 물리 plan이나 성능까지 증명한 것은 아니었다.

두 번째 주문은 계좌별 영수증 수와 검사 기록 수를 한 접시에 보여 달라는 것이었다. 계좌 한 줄 옆에는 ledger 영수증 두 장, audit 검사표 세 장이 달려 있었다. 둘을 detail 상태로 동시에 붙이자 두 장과 세 장의 모든 조합이 생겨 한 계좌가 여섯 줄로 불어났다.

히토리는 원래 한 계좌였는데 접시가 여섯 개인 것을 보고 의자를 더 가져왔다. 료는 SQL 오류가 난 것이 아니라 각 결과 행의 뜻이 계좌 한 건에서 계좌·ledger·audit 한 조합으로 바뀐 것이라고 설명했다. 그 상태에서 SUM을 하면 ledger 값은 audit 수만큼, audit 값은 ledger 수만큼 반복될 수 있었다.

“DISTINCT로 지우면 되지 않을까요?” 키타가 물었다.

“같은 금액의 서로 다른 사건까지 지울 수 있어. 중복의 원인이 grain인데 값만 보고 지우면 다른 문제를 만든다.” 료가 말했다.

account 한 행에 ledger 두 행과 audit 세 행을 붙여 여섯 행이 되는 JOIN fan-out 도식
그림 한눈에: 부모 1행이 ledger 2 × audit 3의 여섯 조합으로 불어나는 fan-out을 봅니다.

니지카는 두 자식 상자를 계좌별 한 줄로 먼저 접었다. ledger는 account_id별 count, audit도 account_id별 count를 만든 뒤 account에 붙였다. 이제 부모 한 줄이라는 결과 grain이 유지되었다. 다만 D2 PDF의 개념 fixture는 ledger와 audit 두 자식을 말하지만, 함께 제시된 pre-aggregation SQL의 두 번째 집계는 별도 audit table이 아니라 ledger_entry.reversal_of IS NOT NULL을 센다. 비유의 이름과 실제 코드의 source를 같은 것처럼 바꾸어 부르면 안 됐다.

빈 집계행에는 값이 없었다. COALESCE를 쓰면 보고서에서 그 빈자리를 0으로 표시할 수 있었다. 그러나 잘못된 join key나 이미 여섯 줄로 불어난 grain을 COALESCE가 고쳐 주지는 않았다.

두 child를 account별 한 행으로 먼저 집계한 뒤 LEFT JOIN하는 grain 보존 도식
그림 한눈에: 각 child를 account별 한 행으로 먼저 모은 뒤 LEFT JOIN해 부모 grain을 지킵니다.
네 사람이 JOIN grain과 행 폭증 반례를 함께 토론하는 공식 장면
그림 한눈에: 공식 장면컷은 join 전후의 한 줄 의미를 토론하는 분위기용이며, 현재 SQL 실행 장면이 아닙니다.

세 번째 주문은 “영수증이 하나도 없는 계좌를 찾아 주세요”였다. 료는 각 account를 바깥에서 한 줄씩 들고 ledger 상자에 같은 account_id가 존재하는지 물었다. 하나라도 있으면 제외하고, 전혀 없을 때만 남기는 NOT EXISTS였다. 고정 예에서 1번과 2번에는 ledger가 있고 3번에는 없으므로 결과는 account 3이었다.

키타는 account를 왼쪽에 둔 LEFT JOINe.id IS NULL을 고르는 방법도 적었다. exact correlation과 결과 grain이 같다면 두 방식은 같은 세 번째 계좌를 찾을 수 있었다. 하지만 상관 조건 e.account_id = a.id를 빠뜨리면 다른 질문이 되었다.

히토리는 세 번째 방법으로 NOT IN을 썼다. ledger 목록에 account_id가 NULL인 찢어진 영수증 한 장을 넣자 3번 계좌까지 사라졌다. 3 <> NULL은 TRUE가 아니라 UNKNOWN이고, WHERE는 UNKNOWN인 행을 남기지 않았다. PDF는 이 함정을 설명하지만 canonical D7 SQL이 직접 그 반례 query까지 실행했다는 뜻은 아니다.

원장 없는 account 3을 찾는 NOT EXISTS와 NOT IN NULL UNKNOWN 함정을 비교한 도식
그림 한눈에: account 3을 남기는 NOT EXISTS와 NULL 하나 때문에 0행이 될 수 있는 NOT IN을 비교합니다.

“빈 봉투와 0원 영수증 한 장은 같은가요?” 히토리가 물었다.

니지카는 3번 계좌에 0원 ledger 한 장을 넣어 보였다. 합계는 여전히 0이지만 이제 EXISTS는 참이었다. 행 없음합계 0은 합계만 보면 같아도 존재를 묻는 질문에서는 달랐다.

네 번째 주문은 계좌마다 들어온 돈, 나간 돈, 거래 수를 보여 달라는 것이었다. account를 왼쪽에 두고 ledger를 LEFT JOIN해야 거래가 없는 계좌도 남았다. 그 계좌에서 COUNT(*)는 NULL 확장행을 한 줄로 셀 수 있지만 COUNT(e.id)는 실제 ledger id만 세므로 0을 만들었다.

양수 signed_amount는 credits, 음수는 debits 바구니에 넣었다. PDF 예시는 debit을 양의 표시 금액으로 만들기 위해 음수에 마이너스를 한 번 더 붙였다. CASE ... ELSE 0은 각 입력 행이 어느 바구니에도 맞지 않을 때 0을 내고, 바깥 COALESCE(SUM(...), 0)는 집계 자체가 NULL일 때 표시값을 0으로 바꿨다. 두 0은 같은 역할이 아니었다.

거래 0건 계좌에서 COUNT와 SUM, COALESCE 결과를 비교한 도식
그림 한눈에: 거래 0건 계좌에서 COUNT(*)·COUNT(e.id)·SUM·COALESCE가 서로 다른 값을 만드는 이유를 봅니다.

니지카는 전체 큰 저울과 계좌별 작은 저울을 함께 꺼냈다. 고정 project fixture의 account balance는 100, 50, 0이고 ledger signed_amount는 +100, +70, -20이라 전역 합은 150이었다. 그러나 전체 150이 맞아도 다른 계좌의 +20과 -20이 우연히 상쇄될 수 있었다. account별로 100=100, 50=70-20, 0=0인지 비교해야 분배 오류가 드러났다.

“합계가 맞다는 말과 배치가 맞다는 말은 다른 질문이네요.” 키타가 말했다.

“맞아. 큰 저울 하나의 일치는 작은 저울 전부의 일치를 증명하지 않아.” 니지카가 답했다.

D3의 필수 통합 Q23은 최근 7일 거래를 고객별로 합치는 문제였다. 료는 오늘이라는 말을 벽시계에서 떼어 fixture_clock.as_of라는 고정 시각에 박았다. 시작은 as_of에서 7일을 뺀 순간을 포함하고, 끝 as_of는 포함하지 않는 반열린 구간으로 정했다. 다음 구간과 경계 거래를 두 번 세지 않기 위한 선택이었다.

첫 CTE는 bounds, 둘째는 recent transactions, 마지막 결과는 customer 한 줄이라는 grain을 가졌다. CTE는 이름 붙인 query 단계이지 항상 물리 materialization된다는 약속은 아니었다. 거래가 없는 customer까지 0으로 보존하려면 시작 table과 join shape를 별도로 설계해야 했다.

별도 source audit이 제공한 Q23 SQL은 all status를 포함하고 recent row가 있는 고객만 남기는 학습용 예시다. PDF가 완성 SQL이나 그 정책을 정본 답안으로 제공한 것은 아니다. 따라서 FAILED·PROCESSING을 포함하는 선택과 zero-customer가 사라지는 경계를 예시의 가정으로 표시했다.

fixture_clock as_of 기준 반열린 최근 7일과 CTE 집계 단계를 보여 주는 도식
그림 한눈에: 고정 as_of를 기준으로 [시작, 끝) 최근 7일 창을 만들고 customer grain까지 이동합니다.

D5의 Q24는 금액을 SMALL, MEDIUM, LARGE로 분류했다. CASE는 위에서부터 조건을 읽고 처음 TRUE인 branch에서 멈췄다. 넓은 조건을 앞에 두면 뒤의 좁은 조건에 도달하지 못할 수 있었다.

PDF 문제는 threshold 숫자를 주지 않는다. 별도 audit-authored Q24 학습용 예시는 account.balance < 100000을 SMALL, account.balance < 500000을 MEDIUM, 나머지를 LARGE로 둔 명시적 가정이다. 그 가정에서는 100000이 MEDIUM, 500000이 LARGE다. 이 숫자와 4·2·2 결과를 업무 정본 정책으로 확대하지 않았다.

SMALL, MEDIUM, LARGE 예시 구간과 100000, 500000 경계를 표시한 도식
그림 한눈에: 명시적 예시 threshold에서 100,000과 500,000이 어느 편에 들어가는지 봅니다.

같은 날 축제 본부는 OPENING 사건과 DEPOSIT 사건을 한 목록으로 달라고 했다. 둘을 UNION ALL로 이어 붙이면 같은 금액이어도 id와 account_id가 다른 사건을 모두 보존했다. UNION은 projection 전체가 같은 중복 row를 제거하며 sort나 hash 비용이 들 수 있었다.

두 명단에 모두 있는 row는 INTERSECT, 왼쪽에만 있는 row는 EXCEPT였다. EXCEPT는 방향이 있으므로 순서를 바꾸면 다른 초승달이 남았다. 각 query는 열 수와 호환 가능한 type을 맞춰야 했고, 최종 ORDER BY는 결합 결과 뒤에서 한 번 적용했다. output에 entry_type을 projection하지 않았다면 downstream이 OPENING과 DEPOSIT을 그 열로 구분할 수 없었다.

UNION 중복 제거와 UNION ALL 사건 보존, INTERSECT와 EXCEPT를 비교한 도식
그림 한눈에: UNION의 중복 제거와 UNION ALL의 사건 보존, INTERSECT·EXCEPT의 의미를 함께 봅니다.
카운터에 모인 네 사람이 SQLD 오답 범주를 정리하는 공식 장면
그림 한눈에: 공식 장면컷은 여섯 모듈의 오답 원인을 함께 분류하는 분위기용이며, 실제 시험 성적을 뜻하지 않습니다.

토요일에는 SQLD 기본 SQL 모의 두 회분을 각각 50분에 풀었다. 한 문항에서 3분이 지나도 길이 보이지 않으면 표시하고 first pass를 이어 갔다. 오답은 logical order, JOIN grain, NULL, subquery 네 상자 중 하나에 넣었다. 맞혔지만 근거가 없는 추측은 정답 더미가 아니라 lucky 더미로 옮겼다.

PDF의 CSV에서 score와 elapsed_min이 0이고 worksheet의 first_answer와 recheck 칸이 비어 있는 것은 실제 성적이 아니라 작성할 TEMPLATE였다. 빈 표를 열었다는 사실을 두 회 통과로 해석하지 않았다. 오답은 3–6행 최소 fixture로 재현하고 project schema SQL로 바꾸어 보아야 원인을 자기 말로 설명할 수 있었다.

50분 모의와 문항당 3분 분기, 네 SQL 오답 범주를 보여 주는 도식
그림 한눈에: 회당 50분·문항당 3분 분기와 네 오답 상자, lucky 분리를 한 화면에서 봅니다.

마지막으로 니지카는 D1–D6의 learner artifact validator를 계산대에 붙였다. validator는 evidence 파일이 존재하는지, 40 bytes 이상인지, TODO 같은 placeholder가 없는지, 비공백 줄이 세 줄 이상인지, SHA-256을 계산할 수 있는지만 확인했다. 표시되는 LOCAL_ARTIFACT_VALIDATED는 그 좁은 구조 검사 결과였다.

그 검사는 SQL을 parse하거나 PostgreSQL에서 실행하지 않았고, 예상 행과 비교하거나 설명 의미를 채점하지도 않았다. 파일이 존재하고 process exit가 0이어도 SQL Green이나 사람 의미 검토가 끝났다는 뜻은 아니었다. 그래서 증거 옆에 HUMAN_SEMANTIC_REVIEW_REQUIRED를 남겼다.

D1부터 D6 validator가 확인하는 파일 구조와 확인하지 않는 SQL 의미를 나눈 도식
그림 한눈에: D1–D6 validator가 확인하는 파일 모양과 확인하지 않는 SQL 정답·현재 Green을 나눕니다.

카레가 모두 팔리고 주방 수레가 멈췄다. 벽에는 logical order, 2×3 fan-out, account 3, 거래 0건, 최근 7일, CASE 경계, UNION ALL, 50분 time box가 남았다. p541 L14부터의 D7 project runner와 48시간 누적 재풀이는 오늘 이야기 밖이라 문을 닫아 두었다.

손님이 “그럼 이번 주 SQL이 전부 Green인가요?”라고 물었다.

히토리는 고개를 저었다. “오늘 만든 건 미리보기예요. source audit PASS는 PDF와 source가 맞물리는지 확인한 것이고, 이번 감사에서 실제 실행한 test는 0개예요. 각 evidence의 내용과 실행 결과는 따로 확인해야 해요.”

키타가 덧붙였다. “대신 무엇을 확인했고 무엇을 아직 확인하지 않았는지는 한눈에 볼 수 있어요!”

료는 마지막 감자 조각을 집어 들었다. “쓴 순서와 움직이는 순서가 다르다는 것도.”

니지카는 바닥 화살표를 떼지 않았다. 내일 다른 주문이 와도 한 줄의 뜻, 경계, NULL, 증거의 범위를 먼저 묻게 하기 위해서였다.

이미지 출처와 이용 안내: 공식 장면컷은 TV 애니메이션 《봇치 더 록!》 공식 사이트 자료입니다. ©はまじあき/芳文社・アニプレックス. 이 문서는 개인 학습용 비공식 구성으로, 원작의 공식 설정과 별개의 학습 비유입니다. 개념도는 원문 감사 내용에 맞춰 새로 만든 교육용 SVG입니다.

W16 미리보기 · 히토리 질문 노트

히토리의 질문 노트 — STARRY PASS 12

본편의 비유를 실제 SQLD·PostgreSQL 기술 이름과 증거 경계로 다시 연결합니다. 답은 W16 개요와 D1–D6에만 한정하며, source audit PASS를 현재 실행 Green으로 바꾸어 말하지 않습니다.

1. 이번 미리보기의 strict PDF 범위는 어디인가요?

p518의 W16 heading과 개요인 p518 L7–24, D1–D6 본문인 p519–540, D6 최종 Gate인 p541 L5–12입니다. p518 L5의 W15 ERD 복구 꼬리는 포함하지 않습니다.

p541 L14의 일 모듈 | W16 누적 재풀이와 설명부터 D7이므로 제외합니다. p541 하단–545 D7, p546 주간 통합, p547 W17도 범위 밖입니다.

실제 기술 이름mixed-page semantic boundary — W16 overview and D1–D6 only

2. W16 D1–D6은 어떤 순서로 SQL을 배우나요?

D1 SELECT logical order, D2 JOIN cardinality와 pre-aggregation, D3 correlated subquery와 NOT EXISTS·Q23, D4 GROUP BY·HAVING·NULL, D5 set operators·Q24, D6 SQLD 기본 SQL 모의 두 회분 순서입니다.

공통 코어 · 트랙 선택 시 증권 기본입니다. 증권 전용 주문·체결 기능을 만드는 주차가 아니라, 계좌와 원장 예시로 SQL의 행 의미·경계·증거를 익히는 주차입니다.

실제 기술 이름SQLD SQL progression from logical processing to time-boxed diagnostic practice

3. source audit PASS와 현재 W16 실행 Green은 왜 다른가요?

source audit PASS는 PDF·canonical source·illustrative workbook source의 bytes와 경계를 확인했다는 뜻입니다. statusMeaning도 PASS가 W16 runner execution이나 learner Green이 아니라고 명시하고, testsExecutedByAudit=0입니다.

따라서 상태는 W16 D1–D6 LABS NOT_RUN입니다. PDF의 LOCAL_ARTIFACT_VALIDATED나 예상 결과 문자열은 현재 실행 로그가 아니라 실행했을 때 기대하는 좁은 marker 계약입니다.

실제 기술 이름byte-verified source closure versus unexecuted runtime evidence

4. SELECT의 논리 처리 순서는 무엇인가요?

핵심 순서는 FROM → WHERE → GROUP BY → HAVING → SELECT → ORDER BY입니다. JOIN은 FROM 단계의 관계 구성에 포함되고, DISTINCT·LIMIT 같은 단계는 query 모양에 따라 SELECT 이후 결과를 더 다듬습니다.

이 순서는 각 절이 보는 중간 집합을 설명합니다. SQL 문장에 SELECT를 먼저 썼다고 SELECT 결과가 WHERE보다 먼저 만들어지는 것은 아닙니다.

실제 기술 이름SQL logical query processing order

FROM, WHERE, GROUP BY, HAVING, SELECT, ORDER BY 순서와 별칭 생성 시점을 보여 주는 도식
그림 한눈에: gross 별칭이 SELECT에서 생기기 전과 생긴 뒤를 논리 순서 위에서 봅니다.

5. SELECT alias를 WHERE에서 못 쓰고 ORDER BY에서 쓸 수 있는 이유는 무엇인가요?

같은 query level의 WHERE가 평가될 때 SELECT list의 alias는 아직 만들어지지 않았습니다. 그래서 WHERE gross > 1000은 gross가 source column이 아닌 한 사용할 수 없습니다.

GROUP 결과를 거르려면 HAVING SUM(amount) > 1000처럼 원래 표현을 쓰거나, query를 CTE·subquery로 감싼 뒤 바깥 WHERE에서 gross를 사용합니다. 최종 ORDER BY는 SELECT 이후라 별칭을 볼 수 있습니다.

실제 기술 이름alias scope across WHERE, HAVING, outer query, and ORDER BY

6. logical order를 알면 PostgreSQL의 물리 실행 순서까지 아는 건가요?

아닙니다. logical order는 SQL 언어가 중간 결과를 어떻게 정의하는지 설명하고, physical plan은 optimizer가 scan·join·aggregate·sort를 어떤 연산자로 배치할지 결정한 결과입니다.

논리상 같은 결과를 내는 query도 통계와 index에 따라 다른 plan을 가질 수 있습니다. plan과 성능은 EXPLAIN·실행 측정으로 별도 확인해야 합니다.

실제 기술 이름declarative logical semantics versus optimizer-selected physical plan

7. JOIN grain은 무엇이고 왜 행이 2×3=6개로 늘어나나요?

grain은 결과 한 행이 무엇 한 건을 뜻하는지입니다. account 한 행에 ledger 2행과 audit 3행을 detail 상태로 함께 join하면 가능한 child 조합이 생겨 account 하나가 여섯 행이 됩니다.

SQL 오류가 없어도 SUM은 한쪽 child의 수만큼 반복될 수 있습니다. 값이 같다는 이유로 DISTINCT를 쓰면 실제로 다른 사건까지 지울 수 있어 일반 해결책이 아닙니다.

실제 기술 이름one-to-many fan-out and row-grain multiplication

account 한 행에 ledger 두 행과 audit 세 행을 붙여 여섯 행이 되는 JOIN fan-out 도식
그림 한눈에: 부모 1행이 두 child의 조합 여섯 행으로 바뀌는 순간을 봅니다.

8. pre-aggregation은 row 폭증을 어떻게 막나요?

각 child를 원하는 부모 key인 account_id로 먼저 GROUP BY해 부모당 한 행으로 줄입니다. 그 두 aggregate result를 account에 join하면 결과 grain을 account 한 행으로 유지할 수 있습니다.

D2의 개념 fixture는 ledger와 audit를 말하지만 제시된 SQL의 두 번째 집계는 별도 audit table이 아니라 ledger_entry.reversal_of IS NOT NULL count입니다. 비유의 child 이름을 실제 source라고 바꾸어 읽으면 안 됩니다.

실제 기술 이름aggregate each child to parent grain before joining

두 child를 account별 한 행으로 먼저 집계한 뒤 LEFT JOIN하는 grain 보존 도식
그림 한눈에: 두 자식을 각각 account별 한 행으로 접고 나서 LEFT JOIN합니다.

9. LEFT JOIN의 오른쪽 조건을 WHERE에 두면 왜 0건 부모가 사라질 수 있나요?

LEFT JOIN은 짝 없는 오른쪽 열을 NULL로 채워 왼쪽 parent를 남깁니다. 그런데 뒤의 WHERE에서 e.status = 'POSTED'를 요구하면 NULL 확장행은 조건을 통과하지 못해 사라집니다.

parent 보존이 계약이라면 오른쪽 행을 고르는 조건을 ON에 두거나, NULL 보존을 명시한 별도 조건을 설계합니다. predicate 위치는 단순 문체 차이가 아니라 결과 집합 차이입니다.

실제 기술 이름null-rejecting WHERE predicate after an outer join

10. 거래 0건 계좌에서 COUNT(*)와 COUNT(e.id)는 왜 다른가요?

LEFT JOIN 결과에는 거래 없는 account도 NULL 확장행 한 줄로 존재합니다. COUNT(*)는 그 결과 행을 세어 1이 될 수 있지만 COUNT(e.id)는 NULL이 아닌 child id만 세므로 0입니다.

SUM이 NULL이면 COALESCE(SUM(...), 0)로 보고서 표시값을 0으로 만들 수 있습니다. 0으로 보인다는 사실이 실제 child row가 존재한다는 뜻은 아닙니다.

실제 기술 이름outer-join count semantics and zero-preserving aggregation

거래 0건 계좌에서 COUNT와 SUM, COALESCE 결과를 비교한 도식
그림 한눈에: NULL 확장행에서 count 대상과 SUM·COALESCE의 역할을 분리합니다.

11. NOT EXISTS anti join은 어떤 질문을 하나요?

바깥 account 한 행마다 WHERE l.account_id = a.id인 ledger가 존재하는지 묻고, 하나도 없을 때만 account를 남깁니다. 고정 fixture에서는 account 3이 유일한 no-ledger 결과입니다.

LEFT JOIN 뒤 e.id IS NULL도 exact join predicate와 output grain이 같으면 같은 부재 질문을 만들 수 있습니다. 상관 조건을 빠뜨리거나 오른쪽의 다른 nullable column을 검사하면 뜻이 달라질 수 있습니다.

실제 기술 이름correlated NOT EXISTS anti join

원장 없는 account 3을 찾는 NOT EXISTS와 NOT IN NULL UNKNOWN 함정을 비교한 도식
그림 한눈에: account 3을 남기는 존재 검사와 NOT IN의 NULL 함정을 비교합니다.

12. EXISTS 안의 SELECT 1은 숫자 1을 찾는다는 뜻인가요?

아닙니다. EXISTS는 subquery가 한 행이라도 반환하는지만 봅니다. SELECT list의 값은 결과 존재 여부에 영향을 주지 않으므로 관용적으로 SELECT 1을 씁니다.

중요한 것은 상관 predicate와 추가 filter입니다. SELECT 1SELECT l.id로 바꾸는 것보다 l.account_id = a.id를 정확히 쓰는 것이 의미를 결정합니다.

실제 기술 이름existence predicate with projection-insensitive SELECT list

13. NOT IN subquery에 NULL이 들어가면 왜 결과가 전부 사라질 수 있나요?

x NOT IN (1, 2, NULL)은 x가 1·2와 다르더라도 NULL과의 비교가 UNKNOWN입니다. 전체 조건이 확실한 TRUE가 되지 않아 WHERE에서 행이 남지 않을 수 있습니다.

PDF는 이 three-valued logic 함정을 설명하지만 W16 canonical anti-join SQL이 완전한 NOT IN 반례를 실행한 것은 아닙니다. 설명된 위험과 실행된 증거를 구분해야 합니다.

실제 기술 이름SQL three-valued logic and the NOT IN NULL trap

14. ‘행이 없음’과 ‘합계가 0’은 왜 다른가요?

ledger가 전혀 없는 account와 +10·-10 두 행이 있는 account는 SUM만 보면 둘 다 0으로 표시될 수 있습니다. 하지만 EXISTS·COUNT(e.id)·감사 추적에서 두 상태는 다릅니다.

0원 ledger 한 행도 합계는 0이지만 존재 검사는 TRUE입니다. 무엇을 묻는 query인지 먼저 정하지 않으면 부재와 영 값을 혼동합니다.

실제 기술 이름absence, zero-valued rows, and zero aggregate as distinct states

15. 계좌별 reconciliation query는 어떤 모양이어야 하나요?

account를 시작점으로 ledger를 LEFT JOIN하고 account key와 stored balance로 GROUP BY합니다. HAVING a.balance <> COALESCE(SUM(e.signed_amount), 0)이면 불일치 계좌를 찾을 수 있습니다.

이 작은 fixture의 mismatch 0건은 그 fixture에서 관찰한 조건입니다. production 회계 완전성, 통화 정책, 동시 write isolation, double-entry 균형까지 증명하지 않습니다.

실제 기술 이름parent-preserving per-account balance reconciliation

16. COALESCE는 무엇을 고치고 무엇을 고치지 못하나요?

COALESCE는 NULL 표현을 다음 값으로 바꿉니다. 거래가 없어 SUM이 NULL인 계좌를 0으로 보여 주는 보고서 계약에 유용합니다.

잘못된 join key, child fan-out, 빠진 상태 filter, 중복 사건, 잘못된 GROUP BY를 고치지는 못합니다. 0이 보인다는 이유로 query grain이 맞다고 결론 내리면 안 됩니다.

실제 기술 이름NULL substitution with no correction of relational logic errors

17. 전체 합 150이 맞아도 계좌별 배치가 틀릴 수 있나요?

고정 예에서 account balance는 100·50·0이고 ledger는 +100·+70·-20이라 전역 합이 둘 다 150입니다. 하지만 한 계좌의 +20과 다른 계좌의 -20이 서로 상쇄되면 전체만 맞고 계좌별 값은 틀릴 수 있습니다.

그래서 global sum과 per-account difference는 별도 predicate입니다. 큰 저울 하나의 일치는 작은 저울 모두의 일치가 아닙니다.

실제 기술 이름global aggregate agreement versus partition-level reconciliation

18. WHERE와 HAVING은 무엇을 각각 거르나요?

WHERE는 GROUP BY 전에 원본 행을 거릅니다. HAVING은 grouping과 aggregate가 끝난 뒤 group 결과를 거릅니다.

amount > 0처럼 개별 ledger 조건은 WHERE에, SUM(amount) >= 1000처럼 group total 조건은 HAVING에 둡니다. 같은 숫자를 비교해도 입력 단위가 다릅니다.

실제 기술 이름pre-aggregation row filter versus post-aggregation group filter

19. CTE는 무엇이며 항상 임시 table로 저장되나요?

CTE는 query 안에서 중간 result에 이름을 붙여 단계와 grain을 읽기 쉽게 만듭니다. bounds, recent_tx, customer aggregate처럼 경계를 분리할 수 있습니다.

항상 물리적으로 materialized된다고 가정하면 안 됩니다. PostgreSQL version과 query 조건, MATERIALIZED 지정 등에 따라 optimizer 처리가 달라질 수 있으며, CTE 이름은 성능 증명이 아닙니다.

실제 기술 이름common table expression as a named relational step

20. Q23의 최근 7일은 어떻게 결정적으로 고정하나요?

wall clock의 현재 시각 대신 fixture_clock.as_of를 한 번 읽고 [as_of - 7 days, as_of)로 범위를 만듭니다. 시작은 포함하고 끝은 제외하면 인접 구간이 경계 event를 중복 계산하지 않습니다.

별도 audit-authored Q23은 all status를 포함하고 recent row가 있는 customer만 남기는 학습용 예시입니다. PDF의 정본 답안이나 모든 업무의 status 정책으로 확대하지 않습니다.

실제 기술 이름deterministic half-open time window and customer-grain aggregation

fixture_clock as_of 기준 반열린 최근 7일과 CTE 집계 단계를 보여 주는 도식
그림 한눈에: 고정 as_of에서 bounds→recent_tx→customer grain으로 이동합니다.

21. CASE는 여러 조건이 참이면 어느 값을 고르나요?

searched CASE는 위에서부터 WHEN을 검사하고 처음 TRUE인 branch의 값을 반환합니다. 넓은 조건을 먼저 두면 뒤의 좁은 조건에 도달하지 못할 수 있습니다.

조건이 서로 겹치지 않는지, 정확한 경계값이 어느 branch에 드는지 최소 fixture로 확인합니다. ELSE를 생략하면 아무 WHEN도 맞지 않을 때 NULL입니다.

실제 기술 이름ordered first-true searched CASE evaluation

22. Q24의 100000과 500000은 어느 band이고 어디서 온 숫자인가요?

별도 illustrative SQL의 명시적 가정에서 balance <100000은 SMALL, <500000은 MEDIUM, 나머지는 LARGE입니다. 따라서 100000은 MEDIUM이고 500000은 LARGE이며 고정 예 결과는 SMALL 4·MEDIUM 2·LARGE 2입니다.

PDF Q24는 threshold 수치를 제시하지 않습니다. 이 값과 count는 audit-authored fixture-bound 학습용 예시이지 shipped canonical answer나 증권 상품 분류 정책이 아닙니다.

실제 기술 이름assumption-labeled CASE band boundaries in illustrative Q24

SMALL, MEDIUM, LARGE 예시 구간과 100000, 500000 경계를 표시한 도식
그림 한눈에: 두 threshold의 등호가 어느 편으로 가는지와 예시 provenance를 함께 봅니다.

23. Q24의 ELSE가 항상 500000 이상만 뜻하나요?

예시 fixture에서 account.balance가 NOT NULL이고 앞 두 WHEN이 순서대로 있다면 ELSE는 500000 이상을 받습니다. 그러나 input column이 nullable하거나 조건 순서를 바꾸면 ELSE의 의미도 달라질 수 있습니다.

분류 대상은 account.balance이고 alias가 amount_band라고 해서 business_tx.amount나 currency 정책을 분류하는 것은 아닙니다. column lineage를 이름보다 먼저 봅니다.

실제 기술 이름CASE ELSE coverage and source-column lineage

24. UNION·UNION ALL·INTERSECT·EXCEPT는 어떻게 다른가요?

UNION은 중복 result row를 제거하고 UNION ALL은 그대로 보존합니다. INTERSECT는 양쪽에 있는 row, EXCEPT는 왼쪽에만 있는 row를 남기므로 EXCEPT는 방향이 있습니다.

각 branch는 같은 열 수와 호환 가능한 type을 가져야 합니다. 최종 ORDER BY는 결합 뒤에 적용하고, 사건 identity를 보존해야 한다면 projection과 ALL 여부를 함께 설계합니다.

실제 기술 이름SQL set operators, duplicate semantics, and directional difference

UNION 중복 제거와 UNION ALL 사건 보존, INTERSECT와 EXCEPT를 비교한 도식
그림 한눈에: 같은 값처럼 보여도 다른 사건을 보존해야 할 때 UNION ALL을 선택합니다.

25. W16 고정 fixture의 exact 값과 constraint 경계는 무엇인가요?

D7에 제시된 project fixture는 account balance 100·50·0과 ledger signed_amount +100·+70·-20을 사용해 top account, no-ledger account 3, global sum 150 같은 좁은 oracle을 만듭니다. 이번 D1–D6 미리보기에서는 수치를 설명용으로만 참조하고 D7 runner 실행을 주장하지 않습니다.

canonical fixture의 ledger_entry.account_id FK에는 NOT NULL이 없고 account.balance의 NOT NULL은 음수 금지를 뜻하지 않습니다. 작은 fixture에 없는 값이 schema상 불가능하다고 단정하면 안 됩니다.

실제 기술 이름deterministic fixture oracle with nullable-FK and domain-constraint boundary

26. D1–D6 learner artifact validator는 무엇을 증명하나요?

각 validator는 evidence 파일 존재, 최소 40 bytes, TODO·TBD·FILL_ME·PLACEHOLDER 부재, 비공백 세 줄 이상, SHA-256 계산만 확인합니다. LOCAL_ARTIFACT_VALIDATED는 그 파일 구조 조건의 통과 marker입니다.

SQL parse·database 실행·예상 행 비교·설명 의미 채점은 하지 않습니다. 그래서 파일 구조 통과 뒤에도 HUMAN_SEMANTIC_REVIEW_REQUIRED이며 현재 SQL Green과 같지 않습니다.

실제 기술 이름structural learner-artifact validation with semantic-review boundary

D1부터 D6 validator가 확인하는 파일 구조와 확인하지 않는 SQL 의미를 나눈 도식
그림 한눈에: validator의 다섯 확인 항목과 확인하지 않는 다섯 영역을 나눕니다.

27. D6 모의 두 회분의 0점 표는 실제 결과인가요?

아닙니다. PDF의 score=0·elapsed_min=0, 빈 first_answer·recheck는 실제 응시 뒤 채울 TEMPLATE입니다. 각 회분 50분, 한 문항 3분 뒤 표시, first pass 유지라는 수행 계약만 담습니다.

오답은 logical order·JOIN grain·NULL·subquery 네 범주로 나누고, 맞혔지만 근거가 약한 답은 lucky로 분리합니다. 템플릿 파일 존재나 초기 0을 통과 성적으로 읽지 않습니다.

실제 기술 이름time-boxed first pass and evidence-backed error taxonomy

50분 모의와 문항당 3분 분기, 네 SQL 오답 범주를 보여 주는 도식
그림 한눈에: 50분·3분 분기와 네 오답 상자, lucky 분리를 봅니다.

28. 코드 뒤풀이의 canonical 6개와 illustrative 2개를 어떻게 읽어야 하나요?

코드 뒤풀이는 compose·fixture·세 invariant SQL·runner의 canonical 6개와 Q23·Q24 illustrative 2개를 분리합니다. Q23·Q24는 assumption-labeled fixture-bound example이며 shipped 정본 답안이 아닙니다.

그 코드 문서는 W16 전체 source를 설명하므로 p541 L14부터의 D7 runner 내용도 포함합니다. 미리보기의 D1–D6 범위와 코드 문서의 전체 범위를 혼동하지 않고, source closure PASS를 current runtime Green으로 바꾸지 않는 것이 마지막 증거 경계입니다.

실제 기술 이름canonical-versus-illustrative provenance and scope-aware evidence claim