Oracle 함수로 글자 사이에 공백을 넣어 균등 분할(균등 정렬)하기
출력물을 만드는데, 쓰던 리포트 도구가 HTML 뷰어에서는 텍스트 상자의 균등 분할 서식을 지원하지 않았다. 제조사 답변은 “그 서식은 전용 뷰어에서만 적용되니, HTML 뷰어에서는 공백 문자로 글자 간격을 맞추라”는 것이었다. 그래서 데이터를 꺼낼 때 DB 에서 미리 글자 사이에 공백을 넣어 주기로 했다.
울면서개발하기 (폭 20) → 울 면 서 개 발 하 기
함수
CREATE OR REPLACE FUNCTION FN_SPREAD_TEXT (
P_TEXT IN VARCHAR2,
P_WIDTH IN PLS_INTEGER -- 맞출 전체 글자 수
) RETURN VARCHAR2
DETERMINISTIC
IS
L_LEN PLS_INTEGER := LENGTH(P_TEXT);
L_GAPS PLS_INTEGER;
L_AVG PLS_INTEGER;
L_EXTRA PLS_INTEGER;
L_OUT VARCHAR2(32767);
BEGIN
-- 빈 값, 한 글자, 이미 폭을 채운 글자는 그대로
IF P_TEXT IS NULL OR L_LEN < 2 OR L_LEN >= P_WIDTH THEN
RETURN P_TEXT;
END IF;
L_GAPS := L_LEN - 1; -- 글자 사이 칸 수
L_AVG := TRUNC((P_WIDTH - L_LEN) / L_GAPS); -- 칸마다 기본 공백 수
L_EXTRA := MOD(P_WIDTH - L_LEN, L_GAPS); -- 남는 공백(앞 칸부터 하나씩)
FOR I IN 1 .. L_LEN LOOP
L_OUT := L_OUT || SUBSTR(P_TEXT, I, 1);
IF I < L_LEN THEN
L_OUT := L_OUT || RPAD(' ', L_AVG + CASE WHEN I <= L_EXTRA THEN 1 ELSE 0 END);
END IF;
END LOOP;
RETURN L_OUT;
END;
/
SELECT FN_SPREAD_TEXT('울면서개발하기', 20) AS TXT
, LENGTH(FN_SPREAD_TEXT('울면서개발하기', 20)) AS LEN
FROM DUAL;
-- 울 면 서 개 발 하 기 / 20
7글자를 20자로 맞추면 공백 13개를 칸 6개에 나눈다. 칸마다 2개씩, 남는 1개는 첫 칸에 붙는다. RPAD(' ', 0) 은 NULL 이 되는데, || 로 이어 붙일 때 NULL 은 빈 문자열처럼 처리되므로 문제없다.
처음 코드에서 고친 것
처음에는 AI 가 만들어 준 익명 블록을 그대로 썼는데, 다시 보니 세 군데가 틀렸다.
- 공백이 너무 많이 들어간다. 칸마다
LPAD(' ', AVG_SPACE + 1)을 붙였는데,LPAD(' ', n)은 이미 공백 n 개다. 칸마다 하나씩 더 들어가서 결과가 원하는 폭보다글자 수 - 1만큼 길어졌다. 원문 주석의울면서개발하기→ 폭 10 결과도 실제로는 16자였다. - 한 글자면 0 으로 나누기 오류(
ORA-01476)가 난다. 칸 수가 0 이기 때문이다. - 글자 수가 폭보다 많거나
NULL이면 공백 개수가 음수·NULL이 되어 엉뚱한 결과가 나온다.
또 제목은 FUNCTION 이었는데 코드는 익명 블록이라, SELECT 에서 바로 쓸 수 있게 함수로 바꿨다.
한글이 섞이면 “폭”을 어떻게 셀까
위 함수는 글자 수(LENGTH)로 맞춘다. 출력물이 고정폭 글꼴이고 한글 한 자가 영문 두 칸을 차지한다면, 글자 수가 같아도 한글과 영문이 섞인 줄은 폭이 달라진다. 이럴 때 바이트 수(LENGTHB)를 쓰고 싶어지는데, 결과가 DB 문자셋에 따라 다르다.
| DB 문자셋 | 한글 한 자의 LENGTHB |
|---|---|
AL32UTF8 (요즘 기본) |
3 |
KO16MSWIN949 |
2 |
그래서 LENGTHB 를 화면 폭으로 쓰면 AL32UTF8 DB 에서는 한글이 3칸으로 계산된다. 문자셋과 상관없이 “멀티바이트 글자는 2칸”으로 세려면 한 글자씩 보는 편이 확실하다.
CREATE OR REPLACE FUNCTION FN_DISPLAY_WIDTH (P_TEXT IN VARCHAR2)
RETURN PLS_INTEGER
DETERMINISTIC
IS
L_W PLS_INTEGER := 0;
BEGIN
FOR I IN 1 .. NVL(LENGTH(P_TEXT), 0) LOOP
L_W := L_W + CASE WHEN LENGTHB(SUBSTR(P_TEXT, I, 1)) > 1 THEN 2 ELSE 1 END;
END LOOP;
RETURN L_W;
END;
/
폭을 이 기준으로 맞추려면 FN_SPREAD_TEXT 에서 “넣을 공백 수”(P_WIDTH - L_LEN)와 첫 검사(L_LEN >= P_WIDTH)의 글자 수를 FN_DISPLAY_WIDTH(P_TEXT) 로 바꾸면 된다. 칸 수(L_GAPS)와 반복 횟수는 그대로 글자 수 기준이다. 다만 비례폭 글꼴이면 어느 방식이든 정확히 맞지는 않는다.
쓰기 전에 확인할 것
- HTML 은 기본적으로 연속된 공백을 하나로 합쳐 보여 준다. 쓰는 뷰어가 공백을 그대로 살리는지(또는
로 바꿔야 하는지) 먼저 확인한다. 내가 쓴 리포트 도구는 제조사 안내대로 공백이 살아 있었다. - 함수가
SELECT에서 돌려줄 수 있는 길이는VARCHAR2(4000)이 한계다(MAX_STRING_SIZE = EXTENDED면 32767).
다시 쓰며 바로잡은 점
- 공백이 칸마다 하나씩 더 들어가던 계산 오류를 고쳤다.
- 한 글자 입력의 0 나누기, 폭보다 긴 입력,
NULL입력을 처리했다. - 익명 블록을
SELECT에서 쓸 수 있는 함수로 바꿨다. - 한글 폭을
LENGTHB로 세면 문자셋에 따라 달라진다는 점을 덧붙였다.