본문으로 건너뛰기
안동민 개발노트 아이콘

안동민 개발노트

본문 시작
4장 : 값 가공·집계

계산 열과 표현식

산술식과 별칭으로 조회수의 파생 값을 계산하고 타입·NULL·중복 저장의 비용을 구분합니다.

회원 게시판에는 게시글 조회수를 정수로 저장합니다.

화면에서 목표 대비 조회 비율이 필요하다고 해서 같은 값을 view_ratio로 다시 저장하면 두 컬럼이 어긋날 가능성이 생깁니다.

원본 사실은 한 번 저장하고 표현에 필요한 값은 조회 시 계산하는 편이 안전합니다.

이번 문서는 view_count에서 목표 대비 비율과 남은 조회수를 계산합니다.

산술식의 입력 타입과 0 나눗셈, NULL 전파, 별칭의 사용 범위를 함께 확인해 ‘계산된다’와 ‘업무 의미가 맞다’를 구분합니다.


계산 예제 데이터

이 문서는 누적 posts의 행 수와 ID에 기대지 않습니다.

아래 세 행을 전용 테이블에 다시 만든 뒤 모든 계산 결과를 확인합니다.

ch4-1 계산 fixture
DROP TABLE IF EXISTS ch4_calculation_post_demo;
CREATE TABLE ch4_calculation_post_demo (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  title VARCHAR(80) NOT NULL,
  status VARCHAR(16) NOT NULL,
  view_count BIGINT UNSIGNED NOT NULL
) ENGINE = InnoDB;

INSERT INTO ch4_calculation_post_demo (id, title, status, view_count)
VALUES
  (1, 'SELECT 기초', 'PUBLISHED', 30),
  (2, 'WHERE 조건', 'PUBLISHED', 75),
  (3, 'JOIN 계획', 'DRAFT', 40);

별칭 재사용의 한계

조회수를 목표 대비 비율로 만든 뒤 그 별칭을 같은 SELECT 목록의 다음 표현식에서 사용하려고 합니다.

사람이 위에서 아래로 읽는 순서와 달리 같은 SELECT 목록의 별칭은 형제 표현식에서 일반 컬럼처럼 보이지 않습니다.

정의 직후 alias 재사용
SET @target_views = 60;

SELECT
  id,
  view_count / NULLIF(@target_views, 0) AS view_ratio,
  view_ratio * 100 AS view_percent
FROM ch4_calculation_post_demo;

MySQL은 view_ratio를 원본 테이블의 컬럼으로 찾다가 오류가 발생합니다.

별칭이 결과 이름이지 순차 변수 선언이 아니기 때문입니다.

오류 실행 결과
ERROR 1054 (42S22): Unknown column 'view_ratio' in 'field list'

별칭은 결과 행이 만들어질 때 붙는 이름입니다.

같은 SELECT 단계의 다른 표현식이 그 이름에 의존하게 하면 평가 모델이 모호해집니다.

짧은 식은 반복하고, 복잡한 식을 한 번만 계산해야 한다면 뒤에서 배우는 파생 테이블, CTE(Common Table Expression, 공통 테이블 표현식), 뷰처럼 명시적인 단계로 분리합니다.

단지 중복 타이핑을 피하려고 파생 값을 영구 컬럼으로 저장하면 갱신 책임이 더 커집니다.


계산 열의 성격

산술 연산자는 행마다 독립적으로 적용됩니다.

+, -, *, /의 타입은 입력 타입과 DBMS 규칙에 영향을 받으므로 금액과 비율에는 원하는 정밀도를 명시합니다.

view_count * 1.0 / NULLIF(@target_views, 0)처럼 소수 연산을 명시하고 ROUND는 표시 자리수에 적용합니다.

NULL 입력은 대부분 NULL 결과로 전파되며 0으로 나누는 식은 SQL 모드에 따라 NULL과 경고 또는 오류가 될 수 있으므로 분모의 업무 제약을 먼저 확인합니다.

기본 산술식과 AS는 표준 SQL입니다.

ROUND도 널리 지원되지만 숫자 타입과 반올림 규칙의 세부는 DBMS별로 확인합니다.

MySQL의 /는 나눗셈이고 정수 나눗셈에는 DIV라는 전용 연산자가 있습니다.

이 교재에서는 이식 가능한 /와 명시적 소수 타입을 우선합니다.


계산식과 단위 표현

목표 대비 조회 비율을 원본 조회수에서 계산합니다.

목표가 NULL이거나 0일 수 있다면 NULLIF로 0 나눗셈을 막고 결과가 계산 불가임을 NULL로 보존합니다.

목표 조회수와 달성률 계산
SET @target_views = 60;

SELECT
  id,
  title,
  view_count,
  ROUND(view_count / NULLIF(@target_views, 0), 2) AS view_ratio,
  @target_views AS target_views,
  ROUND(
    view_count * 100.0 / NULLIF(@target_views, 0),
    1
  ) AS target_percent
FROM ch4_calculation_post_demo
ORDER BY id;

단위가 별칭에 드러나고 목표가 없는 행은 잘못된 무한대나 0%가 아니라 계산 불가인 NULL로 남습니다.

개선 실행 결과
id | title       | views | view_ratio | target_views | target_percent
1  | SELECT 기초 | 30    | 0.50       | 60           | 50.0
2  | WHERE 조건  | 75    | 1.25       | 60           | 125.0
3  | JOIN 계획   | 40    | 0.67       | 60           | 66.7

SET @target_views = NULL로 다시 실행하면 모든 비율이 NULL이 되어 목표 없음과 0%를 구분할 수 있습니다.

125%를 100%로 잘라 보여 줄지는 계산 문법이 아니라 제품 판단입니다.

목표 초과 조회를 보존해야 분석에서 실제 반응을 볼 수 있고, 진행 막대만 100에 제한할 수도 있습니다.

저장값과 표시값을 구분하면 원본을 훼손하지 않고 여러 화면 정책을 적용할 수 있습니다.


저장된 계산값의 불일치

과거 내보내기에서 조회 비율 캐시를 가져왔다면 원본 조회수과 다시 계산한 값이 다른 행을 진단해야 합니다.

영구 스키마에 캐시 열을 추가하지 않고 실습용 CTE로 기존 값을 표현하며, 부동소수점 동등 비교 대신 허용 오차를 사용합니다.

계산 캐시 불일치 진단
SET @target_views = 60;

WITH legacy_view_ratios AS (
  SELECT 1 AS id, CAST(0.50 AS DECIMAL(6,2)) AS cached_ratio
  UNION ALL
  SELECT 2, CAST(1.10 AS DECIMAL(6,2))
)
SELECT
  s.id,
  s.view_count,
  legacy.cached_ratio,
  ROUND(s.view_count / NULLIF(@target_views, 0), 2) AS calculated_ratio
FROM ch4_calculation_post_demo AS s
JOIN legacy_view_ratios AS legacy ON legacy.id = s.id
WHERE ABS(
  legacy.cached_ratio - ROUND(s.view_count / NULLIF(@target_views, 0), 2)
) >= 0.01;

결과가 있다면 어떤 값이 기준인지 정한 뒤 마이그레이션합니다.

파생값 캐시가 정말 필요한 경우 생성 컬럼, 트리거, 애플리케이션 이벤트 중 한 책임자를 정하고 재계산 절차를 함께 둬야 합니다.

금액과 조회 비율에는 FLOAT보다 정확한 소수 자릿수를 갖는 DECIMAL이 적합한 경우가 많습니다.

ROUND는 출력 자릿수를 줄일 뿐 잘못 선택한 저장 타입의 누적 오차를 되돌리지는 못합니다.

단위도 타입만큼 중요합니다.

조회수와 고유 방문자 수를 같은 INT 의미로 섞으면 정상 숫자처럼 저장되므로 컬럼명, API 이름, 제약에서 단위를 고정합니다.

계산식 테스트에는 0, 1, 경계 최대값, NULL, 목표 초과값을 넣습니다.

BIGINT UNSIGNED의 범위를 넘어가는 곱셈이 더 넓은 타입으로 평가되는지, 결과를 다시 좁은 컬럼에 저장할 때 오버플로가 나는지도 분리해 확인합니다.

표시 반올림과 정산 반올림의 시점도 다릅니다.

행마다 반올림한 뒤 합산한 값과 합산 후 반올림한 값은 다를 수 있으므로 업무가 어느 쪽을 요구하는지 문서화합니다.

게시판 통계는 원본 조회수를 합산한 뒤 마지막에 필요한 비율을 계산하는 편이 정보 손실이 적습니다.

파생값을 API에서 제공할 때는 계산 버전도 고려합니다.

달성률 정책이 바뀌면 과거 응답과 현재 응답의 의미가 달라질 수 있습니다.

분석용 데이터셋은 원본 필드와 계산식을 함께 남겨 재현성을 확보합니다.

조회 식이 여러 화면에 반복되면 복사한 SQL을 각각 수정하지 말고 명확한 뷰나 쿼리 계층으로 승격할 시점을 검토합니다.

하지만 계산이 단순하고 소비자별 반올림이 다르면 원본 조회수만 제공하는 API가 더 안정적일 수 있습니다.

음수 조회수는 UNSIGNED나 검사로 저장 단계에서 막아야 하며, 조회에서 ABS로 감춰서는 안 됩니다.

목표가 0인 행도 업무상 허용되는지 먼저 정합니다.

0을 ‘목표 없음’으로 사용하면 NULL과 의미가 겹치므로 가능하면 NULL 또는 별도 상태로 표현합니다.

시간대 변환으로 실제 경과 시간을 계산할 때는 단순 시각 뺄셈과 달력 기준의 차이도 확인합니다.

계산 열은 숫자를 만드는 기술이 아니라 원본 사실에서 어떤 의미를 도출할지 선언하는 모델입니다.

이 단계의 회원 게시판 스키마에서는 파생 계산을 새 컬럼으로 확정하지 않습니다.

목표 조회수는 쿼리 매개변수, view_count는 저장 사실로 두고 달성률은 조회 규칙에 둡니다.

이후 통계 테이블을 설계할 때도 원본 조회수 합계를 기준선으로 삼아 재계산할 수 있습니다.

계산식을 문서화할 때는 수식만 적지 말고 분모가 없을 때, 목표 초과일 때, 표시 반올림 자리와 같은 정책을 함께 적습니다.

같은 이름의 achievement가 화면·분석·알림에서 서로 다른 절단 규칙을 사용하지 않는지 API 예시로 비교합니다.

SQL 표현식은 이러한 정책을 실행 가능한 형태로 고정하는 마지막 단계입니다.


계산식 배치 기준

판단 축확인할 질문
원본다시 계산할 수 있는 최소 사실이 무엇인가?
타입정밀도와 최대 범위를 견디는 숫자 타입인가?
분모0과 NULL에서 계산 의미가 정의됐는가?
단위컬럼과 별칭에 조회수·비율·퍼센트가 드러나는가?
재사용반복 계산을 뷰나 생성 컬럼으로 승격할 근거가 있는가?

먼저 SELECT 계산식으로 정확성을 검증하고 실제 비용이 확인된 뒤 저장이나 캐시를 검토합니다.

계산 비용보다 불일치 복구 비용이 더 클 수 있다는 점을 설계 판단에 포함합니다.


연습 문제

각 공개 게시글에 대해 목표까지 남은 조회수와 달성률을 구하세요.

목표를 초과하면 남은 조회수는 0으로 표시하되 달성률은 100을 넘는 실제 값을 보존합니다.

목표가 NULL 또는 0이면 두 계산 결과 모두 NULL이어야 합니다.

해설과 예시 답안

GREATEST는 MySQL에서 남은 조회수의 음수를 0으로 제한하는 데 사용할 수 있습니다.

목표 부재를 먼저 CASE로 분리하면 계산 의도가 선명합니다.

SET @target_views = 60;

SELECT
  id,
  CASE
    WHEN @target_views IS NULL OR @target_views = 0 THEN NULL
    ELSE GREATEST(@target_views - view_count, 0)
  END AS remaining_views,
  CASE
    WHEN @target_views IS NULL OR @target_views = 0 THEN NULL
    ELSE ROUND(view_count * 100.0 / @target_views, 1)
  END AS target_percent
FROM ch4_calculation_post_demo
WHERE status = 'PUBLISHED'
ORDER BY id;

목표 미달, 정확히 달성, 초과 달성, 목표 없음 네 행을 넣어 결과를 확인합니다.

초과 달성 행의 남은 조회수는 0이고 백분율은 100보다 커야 합니다.


핵심 정리

  • 파생값은 가능한 한 최소 원본 사실에서 조회 시 계산합니다.
  • 별칭은 같은 SELECT 목록의 순차 변수처럼 재사용하지 않습니다.
  • 0·NULL·정밀도·단위를 계산식의 경계 조건으로 다룹니다.
  • 저장 캐시는 책임자와 재계산 절차가 있을 때만 도입합니다.

다음 문서에서는 문자열, NULL 대체, 날짜 함수가 표시와 저장 의미를 어떻게 바꾸는지 다룹니다.