SAS 9.4 한국어 환경의 데이터셋은 대부분 encoding=EUC-KR 로 저장돼 있고, SAS Viya 4 의 CAS 는 UTF-8 만 정상으로 취급한다. EUC-KR 데이터셋을 그대로 CAS 로 올리면 다음 오류를 만난다.
ERROR: Some character data was lost during transcoding.
ERROR: Transcoding failed.
ERROR: Invalid multibyte character.
ERROR: Character variable has data that does not conform to the encoding.
버그가 아니라 설계 차이다. OS 로케일만 UTF-8 로 바꾸거나 Viya 쪽에서 options encoding=euckr; 로 우회하는 것은 해결이 아니다. 변환은 이관 과정에서 끝내야 한다.
proc contents data=inlib._all_ noprint out=work.meta;
run;
proc print data=work.meta;
var libname memname encoding;
run;
개별 테이블은 proc contents data=inlib.mytable; run; 의 Encoding 항목을 본다.
출력 라이브러리에 outencoding= 을 주고 PROC COPY 로 옮기는 것이 기본형이다.
libname inlib '/old/euc_data';
libname outlib '/new/utf8_data' outencoding='UTF-8';
proc copy in=inlib out=outlib noclone;
run;
PROC COPY 는 문자 컬럼 전체를 UTF-8 로 다시 쓰는 작업이라, 한 글자라도 변환에 실패하면 해당 테이블 전체가 실패한다. 실패한 테이블 때문에 전체 작업이 멈추는 것을 막으려면 오류 상한을 열어 둔다.
options errors=0;
이 상태에서는 변환 불가능한 바이트가 대체 문자로 바뀌고 테이블은 생성된다. 원문을 완벽히 보존하는 것보다 UTF-8 정합성을 확보하는 쪽이 CAS 적재에는 유리하다. 다만 어떤 테이블에서 무엇이 바뀌었는지는 반드시 기록해 두어야 한다.
특정 테이블만 문제가 될 때는 DATA step 이 PROC COPY 보다 관대하다.
data outlib.bad_table (encoding='UTF-8');
set inlib.bad_table;
run;
PROC COPY 는 실패 지점을 로그에만 남기므로 자동화에 불리하다. 테이블을 하나씩 변환하면서 실패한 것만 모으는 방식이 확실하다.
options errors=0;
libname inlib '/old/euc_data';
libname outlib '/new/utf8_data';
proc sql noprint;
select memname into :tbl1 -
from dictionary.tables
where libname='INLIB' and memtype='DATA';
%let tblcnt = &sqlobs;
quit;
data work.error_tables;
length table $32 reason $200;
stop;
run;
%macro convert_all;
%do i = 1 %to &tblcnt;
%let t = &&tbl&i;
%put NOTE: ===== converting &t =====;
data outlib.&t (encoding='UTF-8');
set inlib.&t;
run;
%if &syserr > 0 %then %do;
proc sql;
insert into work.error_tables values ("&t", "Encoding conversion failed");
quit;
%end;
%end;
%mend;
%convert_all;
proc print data=work.error_tables;
run;
문자 컬럼이 많은 테이블부터 사고 확률이 높으므로 우선순위를 잡을 때 참고한다.
proc sql;
create table work.suspect as
select libname, memname, count(*) as char_cols
from dictionary.columns
where libname='INLIB' and type='char'
group by libname, memname
having calculated char_cols >= 5
order by char_cols desc;
quit;
원본에 EUC-KR 로도 UTF-8 로도 해석되지 않는 바이트가 섞여 있는 경우다. 외부 시스템에서 가져온 자유 입력 컬럼, 길이가 긴 메모성 컬럼에서 주로 나온다. 문자 컬럼을 일괄 정제한 뒤 다시 변환한다.
data outlib.bad_table (encoding='UTF-8');
set inlib.bad_table;
array c {*} _character_;
do i = 1 to dim(c);
c[i] = compress(c[i], , 'kw');
end;
drop i;
run;
compress 의 kw 수정자는 영숫자와 공백만 남기므로 한글까지 함께 지워진다. 컬럼 성격에 따라 수정자를 골라야 하고, 무조건 적용하면 정상 한글까지 잃는다 (확인 필요).
문자 길이가 아니라 바이트 길이가 변수 길이를 넘으면 잘린다. UTF-8 에서 한글 한 글자는 3바이트이므로 EUC-KR(2바이트) 기준으로 잡아 둔 길이는 부족해진다.
proc sql;
select max(lengthc(col)) as char_len,
max(length(col)) as byte_len
from mylib.mytable;
quit;
lengthc() 는 문자 수, length() 는 바이트 길이다. byte_len 이 정의된 길이를 넘으면 잘림이 발생한다. 변환 대상 테이블은 문자 변수 길이를 미리 1.5배 정도 늘려 두는 편이 안전하다.
proc casutil;
load data=outlib.mytable outcaslib="public" casout="mytable" promote replace;
quit;
proc cas;
table.fetch / table={name="mytable", caslib="public"}, to=5;
quit;
적재가 성공했다고 해서 문자가 온전하다는 뜻은 아니다. 반드시 표본을 조회해 한글이 제대로 보이는지 확인한다.