울면서개발하기
DB / ORACLE

Oracle SELECT 로 출력물 다단(왼쪽·오른쪽 2단) 만들기

· 2026년 10월 25일 다시 씀

명단·라벨 같은 출력물은 한 페이지를 두 단으로 나눠 찍는 경우가 많다. 리포트 도구에서 다단 기능을 못 쓰거나 쓰기 번거로울 때는 SQL 에서 아예 “한 행 = 출력물의 한 줄(왼쪽 칸, 오른쪽 칸)” 모양으로 만들어 넘기면 편하다.

목표: 한 페이지 20건, 왼쪽 단 10건 → 오른쪽 단 10건 → 다음 페이지 순서.

공식

데이터에 1부터 순번(rn)을 매기고, 한 단의 줄 수를 N(=10), 단 수를 K(=2)라고 하면:

값 식 뜻
페이지 CEIL(rn / (N*K)) 20건마다 한 페이지
줄 MOD(rn - 1, N) + 1 페이지 안에서 몇 번째 줄인가 (1~10)
단 MOD(FLOOR((rn - 1) / N), K) 0 = 왼쪽, 1 = 오른쪽

같은 (페이지, 줄) 끼리 묶어서 단별로 한 칸씩 펼치면 된다.

숫자 1~50 으로 확인하기

WITH src AS (
    SELECT LEVEL AS rn
      FROM dual
   CONNECT BY LEVEL <= 50
)
, pos AS (
    SELECT rn
         , CEIL(rn / 20)                AS page_no
         , MOD(rn - 1, 10) + 1          AS line_no
         , MOD(FLOOR((rn - 1) / 10), 2) AS col_no
      FROM src
)
SELECT page_no
     , line_no
     , MAX(CASE WHEN col_no = 0 THEN rn END) AS l
     , MAX(CASE WHEN col_no = 1 THEN rn END) AS r
  FROM pos
 GROUP BY page_no, line_no
 ORDER BY page_no, line_no;

결과(일부):

PAGE_NO LINE_NO L R
1 1 1 11
1 2 2 12
… … … …
1 10 10 20
2 1 21 31
… … … …
2 10 30 40
3 1 41 (NULL)
… … … …
3 10 50 (NULL)

마지막 페이지는 오른쪽 단이 비어 NULL 로 나온다.

실제 테이블에 쓰기

순번은 ROW_NUMBER() 로 매기고, 찍을 컬럼마다 MAX(CASE ...) 를 하나씩 둔다.

WITH src AS (
    SELECT ROW_NUMBER() OVER (ORDER BY e.dept_no, e.emp_no) AS rn
         , e.emp_no
         , e.emp_name
      FROM emp e
     WHERE e.retire_yn = 'N'
)
, pos AS (
    SELECT s.*
         , CEIL(rn / 20)                AS page_no
         , MOD(rn - 1, 10) + 1          AS line_no
         , MOD(FLOOR((rn - 1) / 10), 2) AS col_no
      FROM src s
)
SELECT page_no
     , line_no
     , MAX(CASE WHEN col_no = 0 THEN emp_no   END) AS l_emp_no
     , MAX(CASE WHEN col_no = 0 THEN emp_name END) AS l_emp_name
     , MAX(CASE WHEN col_no = 1 THEN emp_no   END) AS r_emp_no
     , MAX(CASE WHEN col_no = 1 THEN emp_name END) AS r_emp_name
  FROM pos
 GROUP BY page_no, line_no
 ORDER BY page_no, line_no;
  • 한 칸에는 값이 하나뿐이라 MAX 는 그 값을 그대로 꺼내는 역할만 한다. 문자열에도 쓸 수 있다.
  • ROW_NUMBER() 의 정렬 기준은 유일해야 한다. 같은 값이 있는 컬럼으로만 정렬하면 실행할 때마다 순서가 바뀌어 출력물이 달라질 수 있다. 끝에 PK(emp_no)를 붙여 두자.
  • 3단으로 바꾸려면 20(=N×K)을 30 으로, 단 계산의 2 를 3 으로 바꾸고 col_no = 2 칸을 추가한다.

PIVOT 으로 쓰기

같은 결과를 PIVOT 으로도 만들 수 있다. 컬럼이 많을 때는 이쪽이 짧다.

WITH src AS (
    SELECT ROW_NUMBER() OVER (ORDER BY e.dept_no, e.emp_no) AS rn
         , e.emp_no
         , e.emp_name
      FROM emp e
     WHERE e.retire_yn = 'N'
)
SELECT *
  FROM (
        SELECT CEIL(rn / 20)                AS page_no
             , MOD(rn - 1, 10) + 1          AS line_no
             , MOD(FLOOR((rn - 1) / 10), 2) AS col_no
             , emp_no
             , emp_name
          FROM src
       )
 PIVOT (
        MAX(emp_no)   AS emp_no
      , MAX(emp_name) AS emp_name
        FOR col_no IN (0 AS l, 1 AS r)
       )
 ORDER BY page_no, line_no;

결과 컬럼 이름은 L_EMP_NO, L_EMP_NAME, R_EMP_NO, R_EMP_NAME 이 된다.

다시 쓰며 바로잡은 점

  • 원래 글은 rn - FLOOR((rn + 10 - 1) / 20) * 10 하나로 “페이지를 이어 붙인 줄 번호”를 계산했다. 결과는 맞지만 읽기 어려워서 페이지·줄·단을 따로 계산하도록 바꿨다. 출력 도구에서 페이지 나눔에 page_no 를 바로 쓸 수 있다.
  • CONNECT BY LEVEL < 51 은 <= 50 으로, DECODE 는 CASE 로 바꿔 의도가 드러나게 했다.
  • 실제 테이블에 쓸 때 정렬 기준을 유일하게 잡아야 한다는 점을 덧붙였다.