Devin.KR

완료 여부와 쪽수 필터

65분 안팎

학습 목표

논리 조건과 NULL을 구분합니다.

개념

보고서 대상부터 말로 정합니다

완료한 기록의 쪽수를 보고 싶다는 요청은 여러 해석을 가질 수 있습니다. 완료 표시가 1인 모든 기록인지, 그중 실제 읽은 쪽수가 양수인 기록인지 구분해야 합니다. 이번 조회의 계약은 완료했고 쪽수가 0보다 큰 행입니다. 이 문장을 두 조건으로 나누면 completed = 1과 pages > 0이며 두 조건이 함께 성립해야 하므로 AND로 연결합니다. 조건을 적기 전에 포함·제외 예시를 한 건씩 만드는 습관이 실수를 줄입니다.

WHERE는 표에서 조건이 참인 행만 남깁니다. SELECT의 열 목록은 남은 행에서 어떤 속성을 보여줄지 정합니다. 따라서 열을 줄이는 일과 행을 거르는 일은 서로 다른 책임입니다. id와 title만 선택해도 완료되지 않은 행은 남아 있습니다. 반대로 WHERE completed = 1을 써도 SELECT *이면 이름과 이메일까지 반환할 수 있습니다. 이번 레슨은 대상을 정확히 선택하는 판단에 집중합니다.

쪽수가 0인 행은 유효한 입력일 수 있지만 양수 보고서에는 포함되지 않습니다. 0은 측정된 값이고 NULL은 값이 알려지지 않았다는 표현입니다. 두 값을 같은 것으로 취급하면 데이터 품질 문제를 가리게 됩니다. 앞 모듈의 정상 JSON에서는 정수 pages만 허용하지만 조회 실습의 별도 fixture는 NULL을 포함합니다. 기존 저장소 계약을 바꾸는 것이 아니라 외부 표를 읽을 때 경계 상황을 배우기 위한 구성입니다.

비교와 결합을 나눠 읽습니다

같음은 =, 다름은 <>, 초과는 >, 이상은 >=로 표현합니다. pages > 0과 pages >= 0은 0쪽 행에서 결과가 달라집니다. 조건을 작성한 후 그 경계값을 직접 넣어 보는 이유입니다. 이 프로젝트의 쪽수는 정수이므로 양수 경계에서는 0과 1을 확인합니다. 조건을 만족하는 1쪽 행이 빠지거나 0쪽 행이 들어가면 요구 문장과 비교 기호를 다시 대조합니다.

AND는 양쪽 조건이 모두 참일 때 행을 남깁니다. OR는 어느 한쪽이 참이어도 남깁니다. completed = 1 OR pages > 0은 아직 완료하지 않았어도 쪽수가 양수인 기록을 포함하므로 현재 계약보다 넓은 보고서입니다. 테스트에서 미완료 20쪽 기록을 넣는 이유가 바로 이 오류를 잡기 위해서입니다. 완료 0쪽과 미완료 양수 기록을 함께 준비하면 두 조건이 독립적으로 적용되는지 볼 수 있습니다.

여러 조건이 섞이면 AND가 OR보다 먼저 묶입니다. 예를 들어 completed = 1 OR completed = 0 AND pages > 0은 완료한 0쪽 행도 남깁니다. 완료 상태가 0 또는 1이고 양수라는 의도라면 (completed = 1 OR completed = 0) AND pages > 0처럼 괄호를 씁니다. 자연어의 쉼표에 기대지 않고 조건 묶음을 표시해야 동료가 같은 의미로 읽습니다.

NULL 비교는 따로 확인합니다

NULL = 0이나 NULL = NULL은 참으로 판정되지 않습니다. WHERE는 참인 행만 남기므로 pages = NULL은 NULL 행을 찾아주지 못합니다. 알려지지 않은 값을 찾는 조건은 pages IS NULL이고 알려진 값은 pages IS NOT NULL입니다. 이것은 오류가 발생하지 않고 빈 결과가 나올 수 있는 실수입니다. 쿼리가 실행되었다는 사실만으로 조건의 의미가 맞았다고 결론 내리지 않습니다.

pages > 0도 pages가 NULL인 행을 남기지 않습니다. 따라서 양수 조회에 별도의 IS NOT NULL을 덧붙이지 않아도 해당 행은 빠집니다. 다만 누락 쪽수의 건수를 점검하는 별도 보고서는 IS NULL로 조회할 수 있습니다. 공개 통계에서 누락을 0쪽으로 바꾸면 실제로 0쪽인 독서와 미측정 독서를 구분하지 못하므로 값 대체 정책을 목적에 따라 정합니다.

NOT(completed = 1)은 완료 표시가 NULL인 행을 포함하지 않습니다. 완료가 아닌 것과 완료 여부가 알려지지 않은 것은 다릅니다. 상태 0과 미확인을 함께 보려면 completed = 0 OR completed IS NULL처럼 의도를 적습니다. COALESCE(completed,0)는 NULL을 미완료로 합치는 정책이므로 편리하다는 이유만으로 선택하지 않습니다. 미확인 상태를 숨기지 않고 별도로 설명하는 편이 입력 자료의 한계를 드러냅니다.

정렬과 결과 수로 조건을 검증합니다

WHERE 뒤에 ORDER BY id를 붙이면 조건을 통과한 행만 ID 오름차순으로 보여줍니다. 입력에서 b02를 먼저 넣어도 결과는 b01부터 나오도록 계약할 수 있습니다. WHERE가 없다면 정렬만으로 대상이 줄어들지 않습니다. 결과가 예상보다 많을 때는 ORDER BY를 고치는 대신 필터를 점검합니다. 예상 순서가 다를 때는 필터를 바꾸기보다 정렬을 점검합니다. 문제를 두 책임으로 나누면 수정 범위가 작아집니다.

빈 결과는 문법 오류와 다릅니다. 표가 비어 있거나 모든 행이 조건을 만족하지 않으면 SELECT는 정상 종료하면서 출력 행이 없습니다. 테스트에는 이런 경우의 기대 출력을 빈 문자열로 둡니다. 빈 결과를 채우려고 가짜 0 행을 추가하면 조회 계약을 바꾸게 됩니다. 이후 집계 레슨에서 빈 집합의 합계 한 행과 목록 조회의 0행이 어떻게 다른지 확인합니다.

near AND: syntax error 같은 메시지가 나오면 WHERE 뒤 조건이 빠졌는지 확인합니다. no such column: true_value는 값을 열 이름처럼 적었을 가능성이 있습니다. 이 프로젝트는 완료 여부를 정수 0과 1로 저장하므로 문자열 완료를 비교하지 않습니다. 입력 fixture의 CREATE TABLE과 INSERT를 읽어 실제 표현을 확인한 다음 조건을 작성합니다. 자료형과 실제 값을 함께 읽는 것이 빠른 디버깅 방법입니다.

제출 전에 정상 양수 완료 행, 완료 0쪽, 미완료 양수, NULL 쪽수, NULL 완료 표시를 각각 판단해 봅니다. 첫 경우만 남는지 확인하고 빈 표에서도 정상 종료하는지 살펴봅니다. 테스트가 일부 통과했다고 OR 오류를 놓치지 않도록 제외 사례를 읽습니다. 다음 단계에서는 필터를 적용한 대상이 어떤 통계의 분모가 되는지 연결하고, 이번 SQL 문장을 미션의 완료 보고서에 재사용합니다.

따라하기

AND로 대상 좁히기

0쪽·미완료·미확인 쪽수를 제외합니다.

CREATE TABLE r(id TEXT,pages INTEGER,completed INTEGER); INSERT INTO r VALUES('a',0,1),('b',10,0),('c',1,1),('d',NULL,1); SELECT id,pages FROM r WHERE completed=1 AND pages>0 ORDER BY id;

실행 결과

c 1

NULL 찾기

첫 조회는 출력이 없고 두 번째 조회만 b를 반환합니다.

CREATE TABLE r(id TEXT,pages INTEGER); INSERT INTO r VALUES('a',0),('b',NULL); SELECT id FROM r WHERE pages = NULL; SELECT id FROM r WHERE pages IS NULL;

실행 결과

b

괄호 비교

첫 두 줄은 괄호 없는 결과이고 마지막 줄은 괄호를 적용한 결과입니다.

CREATE TABLE r(id TEXT,pages INTEGER,completed INTEGER); INSERT INTO r VALUES('a',0,1),('b',1,0); SELECT id FROM r WHERE completed=1 OR completed=0 AND pages>0 ORDER BY id; SELECT id FROM r WHERE (completed=1 OR completed=0) AND pages>0 ORDER BY id;

실행 결과

a
b
b

확인 문제

실습

완료 1이면서 pages가 0보다 큰 행의 id·title·pages를 ID 오름차순으로 반환합니다.

모범 답안
SELECT id, title, pages FROM reading_records WHERE completed = 1 AND pages > 0 ORDER BY id;

더 읽기

면접 질문

  • NULL 쪽수와 0쪽수를 조건에서 구분하는 이유를 설명합니다.