postgreSQL limit 절을 이용한 페이징 쿼리 만들기

페이징 처리는 웹서비스를 만들다보면 직면하는 가장 흔한 개발요건이다. 웹화면에서는 한 페이지에 보여줄 데이터는 한정적이고, 페이지를 나누어 효율적인 방법으로 최적의 조회성능을 내줘야 한다. 오늘 볼 예제는 대량의 데이터는 아니며, 간단히 10건 미만의 데이터를 기준으로 limit 를 이용한 페이징처리 쿼리정도만 정리해본다.

데이터 준비하기

아래는 아스키코드를 문자로 변경하는 함수 chr 를 사용한 예제이다. 단순히 A~H 까지 8개 row 를 가진 샘플 데이터를 만들기 위한 작업이다.

select chr(65);

chr
---
A  

A~H까지의 샘플 데이터를 만들기 위해서는 65~72까지의 숫자를 가진 테이블데이터가 필요하다. generate_series 함수를 이용해서 손쉽게 아래 데이터를 만들어준다. 아래 generate_series 함수 사용법 부분은 여기를 눌러 참고한다.

select aa, chr(aa)
  from generate_series(65, 72) T(aa)

aa|chr
--+---
65|A  
66|B  
67|C  
68|D  
69|E  
70|F  
71|G  
72|H  

우선 데이터 row 별로 몇번째 줄인지 index 정보를 추가해야 한다. row_number() 함수를 사용하면 order by 기준에 의해 순서정보가 추가된다. 여기까지하면 어느정도 페이징처리를 위한 정보는 정리된 것이다.

select chr(aa) letter
     , row_number() over (order by aa)
  from generate_series(65, 72) T(aa) 

letter|row_number
------+----------
A     |         1
B     |         2
C     |         3
D     |         4
E     |         5
F     |         6
G     |         7
H     |         8

페이징 쿼리 만들기

만약에 8개의 컨텐츠가 있고 한 페이지에 3건씩 표시한다고 하면, 원하는 그림은 아래처럼 3페이지로 나누어 표시되어야 할 것이다.


1페이지
-----------------
letter|row_number
------+----------
A     |         1
B     |         2
C     |         3

2페이지
-----------------
letter|row_number
------+----------
D     |         4
E     |         5
F     |         6

3페이지
-----------------
letter|row_number
------+----------
G     |         7
H     |         8

각 row 마다 몇 페이지에 표시될지 계산해서 page_num 컬럼으로 계산해놓는다. 이 컬럼이 limit 절에 사용되는 건 아니고, 단순 검증용으로 보면 될거 같다.

select chr(aa) letter
     , row_number() over (order by aa)
     , (row_number() over (order by aa) - 1) / 3 + 1 page_num
  from generate_series(65, 72) T(aa)

letter|row_number|page_num
------+----------+--------
A     |         1|       1
B     |         2|       1
C     |         3|       1
D     |         4|       2
E     |         5|       2
F     |         6|       2
G     |         7|       3
H     |         8|       3

limit 절은 뒤에 offset 이 중요한데, 어느 위치부터 몇건을 제한하여 조회할지를 결정한다. limit 3 offset 0 이면, 맨 첫줄부터 3건까지를 의미한다. offset 은 0 부터 시작하니 유념하자. 이걸 나중에 입력될 변수까지 고려하면 아래 쿼리로 나타낼 수 있다.

-- 1페이지 대상건 조회
select chr(aa) letter
     , row_number() over (order by aa)
     , (row_number() over (order by aa) - 1) / 3 + 1 page_num
  from generate_series(65, 72) T(aa)
-- limit 3 offset 0 과 동일
 limit 3 offset (1-1) * 3 

-- 출력결과
letter|row_number|page_num
------+----------+--------
A     |         1|       1
B     |         2|       1
C     |         3|       1

2페이지의 데이터는 limit 3 offset 3 로 지정하면 되고, offset은 0부터 이니, offset 3 은 4번째 row 위치를 의미한다.

-- 2페이지 대상건 조회
select chr(aa) letter
     , row_number() over (order by aa)
     , (row_number() over (order by aa) - 1) / 3 + 1 page_num
  from generate_series(65, 72) T(aa)
-- limit 3 offset 3 과 동일
 limit 3 offset (2-1) * 3 


-- 출력결과
letter|row_number|page_num
------+----------+--------
D     |         4|       2
E     |         5|       2
F     |         6|       2

3페이지 역시 위의 조건들 감안하면 아래 처럼 쉽게 조회할 수 있다.

-- 3페이지 대상건 조회
select chr(aa) letter
     , row_number() over (order by aa)
     , (row_number() over (order by aa) - 1) / 3 + 1 page_num
  from generate_series(65, 72) T(aa)
-- limit 3 offset 6 과 동일
 limit 3 offset (3-1) * 3 

-- 출력결과
letter|row_number|page_num
------+----------+--------
G     |         7|       3
H     |         8|       3

실제건수보다 넘어서는 offset 설정시, sql 에러가 날까? 에러가 발생하지 않으며, 다만 대상건이 없으니 아래와 같이 조회된다.

-- 실제건수보다 넘어서는 offset 설정시
select chr(aa) letter
     , row_number() over (order by aa)
     , (row_number() over (order by aa) - 1) / 3 + 1 page_num
  from generate_series(65, 72) T(aa)
 limit 3 offset 10000

-- 출력결과
letter|row_number|page_num
------+----------+--------

여기서 조회되는 쿼리는 postgreSQL DB 라면, 별도 테이블을 만들지 않고도 바로바로 실행할 수 있는 쿼리이니 본인이 조금씩 조정하면서 페이징쿼리를 차근차근 만들어보면 쉬울 것 같다. 다음에는 대량의 데이터를 기준으로 어느정도 수행시간으로 조회가 되는지, 그것을 보완할 방법 등은 있는지 문서를 찾아보고 포스팅을 해볼까 한다.


더 보면 좋을 글들