SELECT 기초로 “내 포인트 잔액/내역” 조회하기: 문법보다 ‘질문’을 먼저 만든다

SQL을 처음 배울 때 가장 빠른 길은 문법을 외우는 게 아니라, “데이터베이스에 어떤 질문을 던질 것인지”를 먼저 정하는 것입니다. 포인트 시스템에서 가장 자주 나오는 질문은 딱 2개입니다.

  1. 지금 잔액이 얼마인가?
  2. 어떤 내역 때문에 이렇게 되었는가?

이번 글은 SELECT의 핵심 문법(컬럼 선택, 별칭, 정렬, 제한)을 익히면서 위 2개 질문을 실제 쿼리로 만드는 데 집중합니다.

준비: 예시 데이터(작게 넣고 바로 확인하기)

실습을 위해 사용자 1명과 포인트 내역 몇 건을 넣습니다. 부호 정책은 “적립 +, 차감 -”로 통일합니다.

-- 사용자 1명 INSERT INTO users (uid, created_at) VALUES ('11111111-1111-1111-1111-111111111111', NOW()); -- 내역 5건 (적립 3, 사용 1, 만료 1) INSERT INTO point_history (uid, point_type, action_type, amount, ref_id, created_at) VALUES ('11111111-1111-1111-1111-111111111111', 'FREE', 'CHARGE', 1000, 'EVENT_20251214', NOW() - INTERVAL 10 DAY), ('11111111-1111-1111-1111-111111111111', 'PAID', 'CHARGE', 500, 'ORDER_9001', NOW() - INTERVAL 7 DAY), ('11111111-1111-1111-1111-111111111111', 'FREE', 'USE', -300, 'USE_7001', NOW() - INTERVAL 5 DAY), ('11111111-1111-1111-1111-111111111111', 'FREE', 'CHARGE', 200, 'EVENT_20251220', NOW() - INTERVAL 2 DAY), ('11111111-1111-1111-1111-111111111111', 'FREE', 'EXPIRE', -50, 'EXPIRE_001', NOW() - INTERVAL 1 DAY);

1) SELECT의 가장 기본: “보고 싶은 컬럼만” 선택한다

SELECT는 말 그대로 “어떤 컬럼을 보여줄 것인가”를 정하는 구문입니다. 실무에서 SELECT *는 편하지만, 습관이 되면 유지보수와 성능에서 손해를 보기 쉽습니다. 처음부터 필요한 컬럼만 뽑는 습관을 들이면 데이터베이스가 커져도 흔들리지 않습니다.

SELECT id, action_type, amount, ref_id, created_at FROM point_history;

2) 별칭(AS): 결과를 ‘읽기 쉬운 이름’으로 바꾼다

SQL 결과는 결국 사람이 읽어야 합니다. 특히 리포트성 조회는 컬럼명이 곧 문서의 제목이 되기도 합니다.

SELECT id, action_type AS action, amount AS delta, ref_id AS reference_id, created_at AS created_time FROM point_history;

3) 정렬(ORDER BY): 포인트 내역은 “최신순”이 기본이다

포인트 내역 화면을 떠올려 보면 대부분 최신 내역부터 보여줍니다. 그래서 ORDER BY는 사실상 필수입니다. 단, created_at이 동일한 값으로 들어올 수도 있으니(동시에 요청이 들어오는 경우), id까지 함께 정렬해 주면 결과가 흔들리지 않습니다.

SELECT id, action_type, amount, ref_id, created_at FROM point_history ORDER BY created_at DESC, id DESC;

4) LIMIT: 화면에 보여줄 만큼만 끊어서 가져온다

내역은 쌓이는 데이터입니다. 처음부터 “페이지 조회”를 염두에 두고 최신 20건처럼 끊어 읽는 습관을 들이면 운영에서 훨씬 안전합니다.

SELECT id, action_type, amount, ref_id, created_at FROM point_history ORDER BY created_at DESC, id DESC LIMIT 20;

5) 이제 핵심 질문 1: “내 잔액은 얼마인가?”

잔액은 원장(point_history)을 합산하면 됩니다. 포인트 데이터가 어떤 방식으로 쌓이든(적립/차감/만료/취소), amount의 합계는 “현재 상태”를 만들어 줍니다.

SELECT uid, SUM(amount) AS balance FROM point_history WHERE uid = '11111111-1111-1111-1111-111111111111' GROUP BY uid;

여기서 중요한 포인트는 두 가지입니다.

  • WHERE로 “누구의 데이터인지”를 먼저 좁힌다
  • SUM으로 “원장 전체를 합산”해 현재 잔액을 만든다

6) 핵심 질문 2: “잔액이 이렇게 된 이유(내역)는 무엇인가?”

잔액만 보여주면 분쟁이 생깁니다. 포인트 시스템은 반드시 “근거(내역)”를 보여줄 수 있어야 합니다. 그래서 잔액 쿼리보다 내역 쿼리가 더 자주 쓰입니다.

SELECT id, point_type, action_type, amount, ref_id, created_at FROM point_history WHERE uid = '11111111-1111-1111-1111-111111111111' ORDER BY created_at DESC, id DESC LIMIT 50;

7) 실전에서 바로 쓰는 “요약형 조회” 3가지

SELECT 기본 문법을 익혔다면, 운영에서 자주 보는 요약 쿼리도 같이 익혀두면 좋습니다. 지금은 깊게 설명하지 않고 “이렇게 질문을 만든다” 정도로만 소개합니다.

(1) 기간 내역 조회

SELECT id, action_type, amount, ref_id, created_at FROM point_history WHERE uid = '11111111-1111-1111-1111-111111111111' AND created_at >= NOW() - INTERVAL 30 DAY ORDER BY created_at DESC, id DESC;

(2) 유형별 합계(무료/유료)

SELECT point_type, SUM(amount) AS balance FROM point_history WHERE uid = '11111111-1111-1111-1111-111111111111' GROUP BY point_type;

(3) 액션별 합계(적립/사용/만료가 얼마나 발생했나)

SELECT action_type, SUM(amount) AS total_amount, COUNT(*) AS cnt FROM point_history WHERE uid = '11111111-1111-1111-1111-111111111111' GROUP BY action_type ORDER BY cnt DESC;

자주 하는 실수: “정렬 없이” 내역을 가져오기

정렬 없이 내역을 가져오면 화면에서 내역이 뒤죽박죽 보일 수 있습니다. 데이터베이스는 “입력 순서대로” 결과를 보장하지 않습니다. 그래서 내역 조회에는 ORDER BY를 습관처럼 붙이는 편이 안전합니다.

다음 글 예고: WHERE로 “조건별 내역”을 정확하게 필터링하기

다음 글에서는 WHERE를 본격적으로 다룹니다. 기간 조건, 포인트 유형(무료/유료), 액션 타입(적립/사용/만료)처럼 포인트 화면에서 실제로 필요한 필터를 만들면서, 실수하기 쉬운 조건 조합(AND/OR)도 함께 정리합니다.

간단 검증: 이번 글의 쿼리들이 ‘가치 있는 콘텐츠’가 되는 이유

단순 문법 설명에 그치지 않고, 실제 서비스에서 나오는 질문(잔액/내역/기간/유형)을 재현 가능한 예시 데이터와 함께 보여줬기 때문입니다. 이 방식은 글을 읽는 사람이 “그대로 따라 해서 확인”할 수 있어, 검색 의도(실습/문제 해결)에 직접적으로 응답합니다.

댓글

이 블로그의 인기 게시물

포인트 시스템으로 배우는 SQL 로드맵: 적립·차감·만료를 끝까지 완성하는 20단계 계획

포인트 시스템에 필요한 최소 테이블 목록 설계: “나중에 고치기 어려운 것”부터 잡는다