숏컷 코드
SELECT DISTINCT category AS kind
FROM products
ORDER BY kind
LIMIT 10
OFFSET 20;문법
DISTINCT는 중복을 제거하지만 정렬 비용이 따라온다
DISTINCT는 결과 집합에서 동일한 행을 하나만 남깁니다. 내부적으로 PostgreSQL은 중복 제거를 위해 정렬이나 해시 연산을 수행하므로, 대량 데이터에서 DISTINCT를 남용하면 성능 부담이 생깁니다. 관계가 이미 올바르게 설계된 경우에는 DISTINCT 없이도 중복이 발생하지 않아야 정상이므로, DISTINCT가 자주 필요하다면 JOIN 조건이나 데이터 모델을 먼저 점검하는 것이 좋습니다.
-- 카테고리 목록 중복 제거
SELECT DISTINCT category FROM products ORDER BY category;
-- DISTINCT ON: 특정 열 기준으로만 중복 제거 (PostgreSQL 전용)
SELECT DISTINCT ON (user_id) user_id, created_at, title
FROM posts
ORDER BY user_id, created_at DESC; -- 유저별 최신 글 한 건DISTINCT, LIMIT, OFFSET, 별칭은 결과를 "다듬는" 절들이다
이 카드의 요소들은 행을 찾는 기본 조건이라기보다, 이미 나온 결과를 정리하고 보기 좋게 만드는 데 가깝습니다. 중복 제거, 개수 제한, 건너뛰기, 이름 바꾸기를 각각 따로 읽으면 훨씬 단순합니다.
SELECT DISTINCT category AS kind
FROM products
ORDER BY kind
LIMIT 10
OFFSET 20;LIMIT 없이 ORDER BY를 쓰면 결과 순서를 보장할 수 없다
ORDER BY가 없으면 PostgreSQL은 내부 처리 순서(힙 파일 순서, 실행 계획 등)에 따라 임의 순서로 결과를 반환합니다. 같은 쿼리를 두 번 실행해도 순서가 다를 수 있습니다. LIMIT으로 결과를 제한할 때는 반드시 ORDER BY를 함께 써야 "어떤 N개"인지 결정론적으로 정해집니다. LIMIT 1로 최댓값·최솟값을 찾거나 최신 항목 하나를 가져올 때도 마찬가지입니다.
-- 최신 게시글 5개 (ORDER BY 없으면 매 실행마다 다른 결과 가능)
SELECT id, title, created_at
FROM posts
WHERE published = true
ORDER BY created_at DESC
LIMIT 5;OFFSET 기반 페이지네이션은 페이지가 깊을수록 느려진다
OFFSET n은 쿼리가 실제로 n행을 스캔하고 버린 뒤 그 다음 행을 반환합니다. 페이지 100, 페이지당 20개라면 PostgreSQL은 2000행을 스캔하고 2000개를 버려야 합니다. 데이터가 수백만 건이면 마지막 페이지로 갈수록 지수적으로 느려집니다. 대안은 커서(keyset) 페이지네이션으로, 마지막으로 본 행의 정렬 키 값을 WHERE 조건으로 넘기는 방식입니다.
-- OFFSET 방식 (깊은 페이지에서 느림)
SELECT * FROM posts ORDER BY created_at DESC LIMIT 20 OFFSET 2000;
-- Keyset 방식 (항상 빠름)
SELECT * FROM posts
WHERE created_at < '2026-03-01 10:00:00'
ORDER BY created_at DESC
LIMIT 20;AS 별칭은 결과 열 이름을 제어하고 서브쿼리 참조를 가능하게 한다
AS는 SELECT 결과 열에 이름을 붙이고, 테이블이나 서브쿼리에도 짧은 별칭을 줄 수 있습니다. 열 별칭은 ORDER BY에서 참조할 수 있지만, WHERE나 HAVING에서는 참조할 수 없습니다(쿼리 실행 순서상 SELECT보다 WHERE가 먼저 평가되기 때문). AS 키워드 자체는 생략 가능하지만, 가독성을 위해 붙이는 것이 관용입니다.
SELECT
u.name AS author,
COUNT(p.id) AS post_count
FROM users AS u
LEFT JOIN posts AS p ON p.user_id = u.id
GROUP BY u.id, u.name
ORDER BY post_count DESC; -- 별칭을 ORDER BY에서 참조 가능체크포인트
| 상황 | 적합한 선택 |
|---|---|
| 결과에서 중복 행을 제거할 때 | DISTINCT (대량 데이터에서는 비용 고려) |
| 상위 N개만 가져올 때 | LIMIT n + ORDER BY 필수 |
| 얕은 페이지(1~수십 페이지)의 페이지네이션 | LIMIT + OFFSET |
| 깊은 페이지 또는 대용량 페이지네이션 | Keyset 방식 (WHERE col < last_val) |
주의할 점
OFFSET 기반 페이지네이션은 페이지가 깊어질수록 버려야 할 행 수가 늘어나 점점 느려집니다.
수백만 건 이상의 테이블에서 깊은 페이지를 지원해야 한다면, 마지막으로 본 행의 정렬 키를 WHERE
조건으로 사용하는 Keyset 페이지네이션으로 전환하는 것이 좋습니다. 또한 ORDER BY 없이
LIMIT만 쓰면 매 실행마다 다른 행이 반환될 수 있습니다.
SELECT DISTINCT category, created_at
FROM products;DISTINCT는 선택한 전체 열 조합 기준으로 중복을 제거합니다. "category만 하나씩 보고 싶다"면 created_at까지 같이 뽑는 순간 기대와 다른 결과가 나올 수 있습니다.
참고 링크
2 sources