조회와 필터부터 시작
160분 안팎
학습 목표
제공 테이블의 행과 조건을 읽고 집계합니다.
개념
행을 읽어야 숫자를 설명할 수 있습니다
기획자가 쿼리 결과만 복사하면 집계 단위가 바뀌어도 알아차리기 어렵습니다. task_attempts는 신청 상태 확인의 가명 시도 테이블입니다. attempt_id는 시도, user_id는 사용자, task_id는 과업, started_at은 시작 시각, is_test는 테스트 여부, outcome은 결과, duration_seconds는 성공 완료 시간입니다. 먼저 SELECT로 작은 행 목록을 읽고 WHERE로 대상 행을 남긴 뒤 COUNT로 셉니다. 숫자의 출처를 따라갈 수 있으면 개발자에게 조건을 구체적으로 질문할 수 있습니다.
조회는 저장된 값을 바꾸지 않습니다
SELECT attempt_id, outcome FROM task_attempts는 필요한 열을 읽습니다. FROM 뒤에는 테이블 이름을 씁니다. 쉼표는 출력 열을 구분하고 세미콜론은 문장 끝입니다. 이번 실습은 조회만 작성합니다. 입력 SQL의 CREATE TABLE과 INSERT는 매 테스트가 독립 자료를 준비하는 장치이고 답안에 다시 붙이지 않습니다. 브라우저는 테스트별 새 데이터베이스에 준비 SQL을 먼저 실행합니다. 표 이름을 임의로 바꾸면 no such table 오류가 나오므로 제공 계약을 먼저 확인합니다.
조건을 하나씩 더해 원인을 찾습니다
전체 건수에서 시작해 task_id = T01, is_test = 0, 시작 이상, 종료 미만 순서로 조건을 더합니다. 결과가 갑자기 사라지면 마지막 조건의 열 이름과 값 형식을 읽습니다. 문자열 T01과 날짜 시각은 작은따옴표로 감쌉니다. SELECT에 적은 열 이름에는 따옴표를 붙이지 않습니다. SQLite에서 잘못된 열을 문자열로 쓰면 오류 대신 그 문자가 반복 출력될 수 있으므로 실제 행 값이 나오는지도 점검합니다. 이 연습은 열 이름을 그대로 쓰는 습관을 익힙니다.
AND는 모두 충족하는 행을 남깁니다
T01이면서 비테스트 계정이면서 지정 기간인 시도를 원하므로 조건을 AND로 연결합니다. OR로 이어 쓰면 기간 밖 행도 T01이라는 이유로 포함될 수 있습니다. 날짜 조건을 이해할 때는 “첫날 0시 포함, 다음 주 0시 제외”를 먼저 말로 읽습니다. BETWEEN은 양 끝을 포함하므로 인접 구간의 경계를 두 번 세기 쉽습니다. 기간을 나눠 보고할 때 started_at 이상·미만 조합을 사용하고 경계 시각 한 행을 테스트에 넣어 의도를 확인합니다.
문자열 시각 비교의 전제를 확인합니다
자료의 시각은 YYYY-MM-DDTHH:MM:SS+09:00 형식으로 길이와 시간대가 같습니다. 이 조건에서는 문자열 정렬이 시각 순서와 맞습니다. 2026-10-01 같은 날짜만 쓰거나 +00:00 시각을 섞으면 같은 계약이 아닙니다. 이번 답안에서 날짜 함수로 임의 변환하지 않고 제공 문자열의 구간과 비교합니다. 실제 로그를 받는다면 같은 형식인지, 시작 시각과 수집 시각 중 어느 열인지, 지연 수집이 있는지부터 확인합니다. 쿼리 문법이 실행되는 것과 측정 의미가 맞는 것은 다른 확인입니다.
COUNT는 분모를 만듭니다
COUNT(*)는 남은 행 수를 셉니다. success만 WHERE에 남긴 뒤 COUNT하면 분모까지 성공 건수가 됩니다. 완료율의 분모는 결과와 무관하게 모든 유효 시도이므로 outcome 필터를 WHERE에 추가하지 않습니다. 성공 건수는 SUM(CASE WHEN outcome = success THEN 1 ELSE 0 END)로 별도 계산합니다. CASE는 행마다 성공이면 1, 아니면 0을 만들며 SUM이 합칩니다. 분모와 분자를 같은 행 집합에서 만들면 제외 조건이 둘 사이에서 어긋날 가능성을 줄일 수 있습니다.
빈 합계는 표시 규칙으로 처리합니다
해당 기간에 행이 없으면 COUNT(*)는 0이고 SUM은 NULL이 됩니다. COALESCE(SUM(...), 0)은 비어 있는 성공 건수를 정수 0으로 바꿉니다. 이는 성공 건수의 표시 규칙이며 완료율이 0%라는 뜻은 아닙니다. 브라우저 답안은 ATTEMPTS SUCCESS 두 정수를 한 행으로 출력합니다. 빈 기간도 0 0 한 행을 출력해야 합니다. GROUP BY를 불필요하게 쓰면 빈 기간에서 행 자체가 없어질 수 있으므로 이 레슨의 단일 기간 집계에는 그룹을 만들지 않습니다.
NULL 결과를 실패라고 이름 바꾸지 않습니다
outcome이 NULL이면 결과를 기록하지 못한 unknown 시도입니다. CASE의 성공 조건에는 맞지 않으므로 성공 합계에 0을 더하지만 COUNT(*)에는 포함됩니다. WHERE outcome != failure로 필터하면 NULL 비교가 참이 아니어서 누락 행이 사라질 수 있습니다. 이 레슨에서는 결과로 대상 행을 제거하지 않는 편이 정확합니다. 누락이 많은 자료는 따로 조사할 필요가 있으며 비율이 낮은 원인이 화면 문제인지 로그 품질인지 이 쿼리 하나로 결정할 수 없습니다.
정렬은 검산을 가능하게 합니다
행 목록을 보여 줄 때 ORDER BY started_at, attempt_id로 순서를 정합니다. 시간이 같은 두 행이 있으면 ID로 순서를 결정하므로 동료의 화면과 대조하기 쉽습니다. 집계 한 행에는 정렬이 필요 없지만 조회 단계는 순서가 없으면 다시 실행할 때 배열이 달라질 수 있습니다. 먼저 어떤 ID가 포함되어야 하는지 손으로 표시하고 그 목록을 읽은 뒤 숫자를 셉니다. 숫자만 맞았다고 잘못된 행 포함과 누락이 서로 상쇄된 경우를 놓치지 않습니다.
재시도는 사용자와 다른 숫자입니다
동일 user_id가 다른 attempt_id로 두 번 나타날 수 있습니다. 첫 시도 실패와 두 번째 성공은 시도 2건, 성공 1건, 사용자 1명입니다. COUNT(DISTINCT user_id)는 사람의 종류 수를 세며 이번 출력 분모를 대신하지 않습니다. attempt_id 기본 키는 같은 ID의 중복 삽입을 거절합니다. UNIQUE constraint failed 메시지가 나오면 전송 중복 사례를 실제 재시도와 혼동했는지 확인합니다. 이 브라우저 fixture는 중복 전송을 제거한 상태로 제공하므로 답안에서 사용자 ID로 행을 지우지 않습니다.
오류는 위치와 계약을 함께 읽습니다
near FROM: syntax error는 SELECT 마지막 열 뒤 쉼표나 CASE의 END 누락을 살펴봅니다. no such column은 user_id 같은 열 이름 오타를 확인합니다. misuse of aggregate function은 WHERE에 SUM 같은 집계를 넣었는지 읽습니다. 실행 오류가 없는데 기대 건수와 다르면 시작·종료 경계, 테스트 제외, 과업 조건 순서로 검사합니다. 출력 열은 시도 수가 먼저이고 성공 수가 나중입니다. 순서를 뒤집어도 숫자는 그럴듯하므로 출력 계약과 결과 머리말을 함께 검산합니다.
쿼리와 손 집계가 서로를 검증합니다
첫 테스트의 행을 읽고 일반 T01만 표시한 뒤 기간 밖 행을 지웁니다. 남은 시도 중 success의 수를 손으로 적고 SQL 결과와 비교합니다. 빈 자료, 모두 제외, 시작 경계, 종료 경계, 같은 사용자 재시도를 각각 실행합니다. 모든 테스트를 통과한 쿼리는 정의한 사례를 재현했다는 뜻이며 실제 서비스 전체 데이터 품질을 보증하지 않습니다. 서재의 집계 함수 장은 다른 테이블과 여러 그룹의 예제를 다루므로 더 읽기로 이어갑니다. 여기서는 행사 상태 확인 계약을 정확히 조회하는 데 집중합니다.
따라하기
원자료의 행을 조회합니다
sqlite3 -separator ' ' -nullvalue NULL :memory:로 연습 DB를 엽니다. 공백 구분과 NULL 표시를 맞춥니다. 아래 테이블 생성·삽입·조회를 순서대로 붙여 넣습니다. 다음 단계 쿼리는 이 DB에 그대로 입력합니다. 다시 시작하려면 SQLite를 종료하고 새 메모리 DB를 엽니다. 브라우저 실습은 테스트 입력이 초기 자료를 준비하므로 조회 쿼리만 제출합니다.
CREATE TABLE task_attempts(
attempt_id TEXT PRIMARY KEY, user_id TEXT NOT NULL,
task_id TEXT NOT NULL, started_at TEXT NOT NULL,
is_test INTEGER NOT NULL CHECK(is_test IN (0,1)),
outcome TEXT, duration_seconds REAL);
INSERT INTO task_attempts VALUES
('B01','U01','T01','2026-10-01T00:00:00+09:00',0,'success',30),
('B02','U02','T01','2026-10-02T10:00:00+09:00',0,'failure',NULL),
('B03','U02','T01','2026-10-03T10:00:00+09:00',0,'success',70),
('B04','U03','T01','2026-10-07T23:59:59+09:00',0,NULL,NULL),
('A01','U01','T01','2026-10-08T00:00:00+09:00',0,'success',20),
('A02','U04','T01','2026-10-09T10:00:00+09:00',0,'success',30),
('A03','U04','T01','2026-10-10T10:00:00+09:00',0,'success',40),
('A04','U05','T01','2026-10-14T23:59:59+09:00',0,'assisted',NULL),
('X01','TEST','T01','2026-10-02T10:00:00+09:00',1,'success',10),
('X02','TEST','T01','2026-10-09T10:00:00+09:00',1,'success',10),
('X03','U06','T02','2026-10-02T10:00:00+09:00',0,'success',15),
('X04','U06','T01','2026-09-30T23:59:59+09:00',0,'success',15),
('X05','U06','T01','2026-10-15T00:00:00+09:00',0,'success',15);
SELECT attempt_id, user_id, outcome FROM task_attempts ORDER BY started_at, attempt_id;실행 결과
X04 U06 success B01 U01 success B02 U02 failure X01 TEST success X03 U06 success B03 U02 success B04 U03 NULL A01 U01 success A02 U04 success X02 TEST success A03 U04 success A04 U05 assisted X05 U06 success
기간과 제외 조건을 적용합니다
앞에서 준비한 연습 DB에 다음 쿼리를 입력하고 포함된 시도와 집계 단위를 확인합니다.
SELECT attempt_id, outcome FROM task_attempts WHERE task_id = 'T01' AND is_test = 0
AND started_at >= '2026-10-01T00:00:00+09:00'
AND started_at < '2026-10-08T00:00:00+09:00' ORDER BY started_at, attempt_id;실행 결과
B01 success B02 failure B03 success B04 NULL
분모와 분자를 한 번에 셉니다
앞에서 준비한 연습 DB에 다음 쿼리를 입력하고 포함된 시도와 집계 단위를 확인합니다.
SELECT COUNT(*) AS attempts,
COALESCE(SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END), 0) AS success
FROM task_attempts
WHERE task_id = 'T01' AND is_test = 0
AND started_at >= '2026-10-01T00:00:00+09:00'
AND started_at < '2026-10-08T00:00:00+09:00';실행 결과
4 2
빈 자료도 한 행으로 출력합니다
앞에서 준비한 연습 DB에 다음 쿼리를 입력합니다. 빈 자료 확인 단계는 새 메모리 DB에서 같은 테이블만 만들고 INSERT 없이 실행합니다.
SELECT COUNT(*) AS attempts,
COALESCE(SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END), 0) AS success
FROM task_attempts
WHERE task_id = 'T01' AND is_test = 0
AND started_at >= '2026-10-01T00:00:00+09:00'
AND started_at < '2026-10-08T00:00:00+09:00';실행 결과
0 0
확인 문제
실습
task_attempts에서 T01·is_test=0·2026-10-01T00:00:00+09:00 이상·2026-10-08T00:00:00+09:00 미만 시도를 집계합니다. ATTEMPTS SUCCESS 순서의 정수 두 개를 한 행으로 출력합니다. success만 분자이며 NULL·failure·assisted도 분모에는 포함합니다. 같은 사용자의 서로 다른 시도는 모두 셉니다. 빈 기간도 0 0 한 행입니다. 입력은 테스트별 초기화 SQL이며 답안에는 SELECT 조회만 작성합니다.
모범 답안
SELECT COUNT(*) AS attempts,
COALESCE(SUM(CASE WHEN outcome = 'success' THEN 1 ELSE 0 END), 0) AS success
FROM task_attempts
WHERE task_id = 'T01' AND is_test = 0
AND started_at >= '2026-10-01T00:00:00+09:00'
AND started_at < '2026-10-08T00:00:00+09:00';더 읽기
면접 질문
- 기능 개선 여부를 확인할 지표를 정하는 방법을 설명해 주시면 됩니다.