トランスコーディングは、大抵の場合、データセットの別環境への移送も含みます。
ので、そこら辺も絡めてちょっと言及しておきます。
前振りとして。
SASの文字変数は、固定長文字列としてバイト長で定義されています。その為、トランスコーディングに伴う文字列の実際のバイト長に対して問題が起きやすいです。
また、固定長文字列で実際のデータも配置される為、RDBMSなどでは普通に行える事であっても出来ないあるいは難易度の高い事があります。文字列長を簡単には変更出来ません。
SASデータセットは文字変数の長さを直接変更は出来ない。
以下の例をご覧下さい。
/* テストデータはWORK.CLASS */ data WORK.CLASS ; set SASHELP.CLASS ; run ; /* 列定義変更っぽい事をしてみる */ data WORK.CLASS ; set WORK.CLASS ; length NAME $100 ; run ;
328 /* テストデータはWORK.CLASS */ 329 data WORK.CLASS ; 330 set SASHELP.CLASS ; 331 run ; NOTE: データセットSASHELP.CLASSから19オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSは19オブザベーション、5変数です。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 332 333 /* 列定義変更っぽい事をしてみる */ 334 data WORK.CLASS ; 335 set WORK.CLASS ; 336 length NAME $100 ; WARNING: 文字変数Nameの長さはすでに設定されています。 文字変数の長さを宣言するには、 DATAステップの最初にLENGTHステートメントを使用してください。 337 run ; NOTE: データセットWORK.CLASSから19オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSは19オブザベーション、5変数です。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒
/* テストデータはWORK.CLASS */ data WORK.CLASS ; set SASHELP.CLASS ; run ; /* 列定義変更前の定義を取得 */ proc contents data=WORK.CLASS out=WORK.CLASSDEF1 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) noprint ; quit ; /* 列定義変更 */ proc sql ; alter table WORK.CLASS modify NAME length 100 ; quit ; /* 列定義変更後の定義を取得 */ proc contents data=WORK.CLASS out=WORK.CLASSDEF2 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) noprint ; quit ; /* 列定義変更の前後の差を比較するも完全一致 */ proc sort data=WORK.CLASSDEF1 ; by LIBNAME MEMNAME VARNUM ; quit ; proc sort data=WORK.CLASSDEF2 ; by LIBNAME MEMNAME VARNUM ; quit ; proc compare base=WORK.CLASSDEF1 comp=WORK.CLASSDEF2 method=absolute note ; id LIBNAME MEMNAME VARNUM ; quit ;
807 /* テストデータはWORK.CLASS */ 808 data WORK.CLASS ; 809 set SASHELP.CLASS ; 810 run ; NOTE: データセットSASHELP.CLASSから19オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSは19オブザベーション、5変数です。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 811 812 /* 列定義変更前の定義を取得 */ 813 proc contents 814 data=WORK.CLASS 815 out=WORK.CLASSDEF1 816 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) 817 noprint 818 ; 819 quit ; NOTE: データセットWORK.CLASSDEF1は5オブザベーション、12変数です。 NOTE: PROCEDURE CONTENTS処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 820 821 /* 列定義変更 */ 822 proc sql ; 823 alter table WORK.CLASS 824 modify NAME length 100 825 ; NOTE: テーブルWORK.CLASS (5列)は変更されました。 826 quit ; NOTE: PROCEDURE SQL処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 827 828 /* 列定義変更後の定義を取得 */ 829 proc contents 830 data=WORK.CLASS 831 out=WORK.CLASSDEF2 832 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) 833 noprint 834 ; 835 quit ; NOTE: データセットWORK.CLASSDEF2は5オブザベーション、12変数です。 NOTE: PROCEDURE CONTENTS処理(合計処理時間): 処理時間 0.01 秒 CPU時間 0.01 秒 836 837 /* 列定義変更の前後の差を比較するも完全一致 */ 838 proc sort data=WORK.CLASSDEF1 ; 839 by LIBNAME MEMNAME VARNUM ; 840 quit ; NOTE: データセットWORK.CLASSDEF1から5オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSDEF1は5オブザベーション、12変数です。 NOTE: PROCEDURE SORT処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 841 proc sort data=WORK.CLASSDEF2 ; 842 by LIBNAME MEMNAME VARNUM ; 843 quit ; NOTE: データセットWORK.CLASSDEF2から5オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSDEF2は5オブザベーション、12変数です。 NOTE: PROCEDURE SORT処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 844 proc compare 845 base=WORK.CLASSDEF1 846 comp=WORK.CLASSDEF2 847 method=absolute 848 note 849 ; 850 id LIBNAME MEMNAME VARNUM ; 851 quit ; NOTE: 不等な値はありません。比較した変数はすべて同等でした。 NOTE: データセットWORK.CLASSDEF1とWORK.CLASSDEF2は完全に同等です。 NOTE: データセットWORK.CLASSDEF1から5オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSDEF2から5オブザベーションを読み込みました。 NOTE: PROCEDURE COMPARE処理(合計処理時間): 処理時間 0.02 秒 CPU時間 0.01 秒
最初の方法では、エラーになりますし、二番目の方法では、エラーを吐かないですが変更されていません。
ちょっと回りくどいですが、以下のような方法で変更は可能ではあります。(サンプルはあんまり美しくはないのですが、でも面倒臭いのでそのまま)
/* テストデータはWORK.CLASS */ data WORK.CLASS ; set SASHELP.CLASS ; run ; /* 列定義変更前の定義を取得 */ proc contents data=WORK.CLASS out=WORK.CLASSDEF1 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) noprint ; quit ; /* 列定義変更 */ data WORK.CLASS ; length Name $100 ; set WORK.CLASS ; run ; /* このままだと変数の並び順がおかしい。 変数を元の並び順にする場合には 以下のような事をする(他にもやりようはある) */ %let _WK_VARLIST = ; proc sql ; select cats(NAME) into:_WK_VARLIST separated by ',' from WORK.CLASSDEF1 order by LIBNAME,MEMNAME,VARNUM ; quit ; proc sql ; create table WORK.CLASS as select &_WK_VARLIST. from WORK.CLASS ; quit ; %symdel _WK_VARLIST ; /* 列定義変更後の定義を取得 */ proc contents data=WORK.CLASS out=WORK.CLASSDEF2 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) noprint ; quit ; /* 列定義変更の前後の差を比較する (Variable:Nameについては若干イカサマしているのでこの例では 変更点としては出ないが Capが定義情報の差として出る事はしばしばある) */ proc sort data=WORK.CLASSDEF1 ; by LIBNAME MEMNAME VARNUM ; quit ; proc sort data=WORK.CLASSDEF2 ; by LIBNAME MEMNAME VARNUM ; quit ; proc compare base=WORK.CLASSDEF1 comp=WORK.CLASSDEF2 method=absolute note ; id LIBNAME MEMNAME VARNUM ; quit ;
1043 /* テストデータはWORK.CLASS */ 1044 data WORK.CLASS ; 1045 set SASHELP.CLASS ; 1046 run ; NOTE: データセットSASHELP.CLASSから19オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSは19オブザベーション、5変数です。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.00 秒 1047 1048 /* 列定義変更前の定義を取得 */ 1049 proc contents 1050 data=WORK.CLASS 1051 out=WORK.CLASSDEF1 1052 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) 1053 noprint 1054 ; 1055 quit ; NOTE: データセットWORK.CLASSDEF1は5オブザベーション、12変数です。 NOTE: PROCEDURE CONTENTS処理(合計処理時間): 処理時間 0.01 秒 CPU時間 0.00 秒 1056 1057 /* 列定義変更 */ 1058 data WORK.CLASS ; 1059 length Name $100 ; 1060 set WORK.CLASS ; 1061 run ; NOTE: データセットWORK.CLASSから19オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSは19オブザベーション、5変数です。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.00 秒 1062 /* 1063 このままだと変数の並び順がおかしい。 1064 変数を元の並び順にする場合には 1065 以下のような事をする(他にもやりようはある) 1066 */ 1067 %let _WK_VARLIST = ; 1068 proc sql ; 1069 select cats(NAME) into:_WK_VARLIST separated by ',' 1070 from WORK.CLASSDEF1 order by LIBNAME,MEMNAME,VARNUM ; NOTE: 指定したクエリにはSELECT句にない項目によるソートが含まれます。 1071 quit ; NOTE: PROCEDURE SQL処理(合計処理時間): 処理時間 0.01 秒 CPU時間 0.01 秒 1072 1073 proc sql ; 1074 create table WORK.CLASS 1075 as select &_WK_VARLIST. from WORK.CLASS 1076 ; WARNING: This CREATE TABLE statement recursively references the target table. A consequence of this is a possible data integrity problem. NOTE: テーブルWORK.CLASS(行数19、列数5)が作成されました。 1077 quit ; NOTE: PROCEDURE SQL処理(合計処理時間): 処理時間 0.01 秒 CPU時間 0.01 秒 1078 %symdel _WK_VARLIST ; 1079 1080 /* 列定義変更後の定義を取得 */ 1081 proc contents 1082 data=WORK.CLASS 1083 out=WORK.CLASSDEF2 1084 (keep=LIBNAME MEMNAME VARNUM NAME TYPE LENGTH FORMAT: IN:) 1085 noprint 1086 ; 1087 quit ; NOTE: データセットWORK.CLASSDEF2は5オブザベーション、12変数です。 NOTE: PROCEDURE CONTENTS処理(合計処理時間): 処理時間 0.01 秒 CPU時間 0.01 秒 1088 1089 /* 列定義変更の前後の差を比較する 1090 (Variable:Nameについては若干イカサマしているのでこの例では 1091 変更点としては出ないが 1092 Capが定義情報の差として出る事はしばしばある) */ 1093 proc sort data=WORK.CLASSDEF1 ; 1094 by LIBNAME MEMNAME VARNUM ; 1095 quit ; NOTE: データセットWORK.CLASSDEF1から5オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSDEF1は5オブザベーション、12変数です。 NOTE: PROCEDURE SORT処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 1096 proc sort data=WORK.CLASSDEF2 ; 1097 by LIBNAME MEMNAME VARNUM ; 1098 quit ; NOTE: データセットWORK.CLASSDEF2から5オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSDEF2は5オブザベーション、12変数です。 NOTE: PROCEDURE SORT処理(合計処理時間): 処理時間 0.00 秒 CPU時間 0.01 秒 1099 proc compare 1100 base=WORK.CLASSDEF1 1101 comp=WORK.CLASSDEF2 1102 method=absolute 1103 note 1104 ; 1105 id LIBNAME MEMNAME VARNUM ; 1106 quit ; NOTE: 次の1変数の値は不等です: LENGTH NOTE: データセットWORK.CLASSDEF1とWORK.CLASSDEF2には不等な値があります。 NOTE: データセットWORK.CLASSDEF1から5オブザベーションを読み込みました。 NOTE: データセットWORK.CLASSDEF2から5オブザベーションを読み込みました。 NOTE: PROCEDURE COMPARE処理(合計処理時間): 処理時間 0.01 秒 CPU時間 0.01 秒
トランスコーディングの前に・・・・・・何に文字コードを寄せるかという時に。
巷では、ほぼUTF-8でエンコーディングを寄せるという発想が主流です。
日本語の取り扱いで、SASでは今でもデフォルトはSJISですし、SJISの方が良いことも多々あります。
- 既存のデータが何の手当もなく使える
- 既存のプログラム資産も同様に使える
逆に、SASの世界で何故今でもSJISが使われているかというと、
です。
ただ、国際化の今、SJISでは中国語や韓国語を取り扱えませんし、各種ツールもUTF-8対応が十分に進んでいる為、そろそろユーザ側に近いアプリケーションではあるSASもUTF-8を基本として使う方がいいと思います。
まあ、経験上、例えばJavaやWebアプリはUnicodeが基本ですし、各システムとの連携でももうUnicodeでない方が問題が多い所もありますし。
トランスコーディングでの罠。あるいはCVPオプション。
SJISからUTF-8へ変換する、SJISのデータセットをそのまま使う、際に、現状のSASでは、必ず手当が必要です。
SAS 9.4各国語サポート(NLS): リファレンスガイド(PDF)については、一度読むことをオススメします。
SJISのデータセットであっても、V8以降では、本来は特に文字コード変換を描けなくても、使用時に自動的に変換してくれる、CEDA(Cross-Environment Data Access)という機能が使えるはずです。
はずなんですが、SJISからUTF8環境へのCDEAの適用の場合、ほぼCVPオプションによる「対応」が必須です。
CEDAの機能が中途半端だなあと。
CVPオプションは、主に倍率の方を使用されると思いますが、例えば、SJISとUTF8では、「だいたいは1.5倍で大丈夫」です。
ただ、ちょっとヤバイ所がありまして、第三水準、第四水準の実装がShift_JIS-2004ベースで為されている場合の文字は4バイトになるので問題が発生します。
が、元々ちょっと取り扱いしていないだろうとは思います。WindowsだとSJIS実装されてなかったはずですし
例
/* SJIS環境で実施 */ libname TEST "C:\temp" ; proc copy in=SASHELP out=TEST ; select CLASS ; quit ;
/* UTF8環境で実施 */ libname TEST "C:\temp" ; data _null_ ; set TEST.CLASS ; putlog _all_ ; run ;
14 /* UTF8環境で実施 */ 15 libname TEST "D:\temp" ; NOTE: ライブラリ参照名TESTを次のように割り当てました。 エンジン: V9 物理名: D:\temp 16 data _null_ ; 17 set TEST.CLASS ; NOTE: データファイルTEST.CLASS.DATAは別なホストにネイティブな形式が使用されているか 、またはエンコーディングがセッションエンコーディングと一致していません。 クロス環境データアクセスが使用されるため、追加のCPUリソースが必要となり、 パフォーマンスが低下します。 18 putlog _all_ ; 19 run ; WARNING: データセットTEST.CLASSのトランスコード時に文字データが一部損失しました。 新しいエンコーディングで表せない文字がデータに含まれていたか、またはト ランスコード時に切り捨てが発生しました。 Name=アルフレ Sex=男 Age=14 Height=69 Weight=112.5 _ERROR_=0 _N_=1 Name=アリス Sex=女 Age=13 Height=56.5 Weight=84 _ERROR_=0 _N_=2 Name=バーバラ Sex=女 Age=13 Height=65.3 Weight=98 _ERROR_=0 _N_=3 Name=キャロル Sex=女 Age=14 Height=62.8 Weight=102.5 _ERROR_=0 _N_=4 Name=ヘンリー Sex=男 Age=14 Height=63.5 Weight=102.5 _ERROR_=0 _N_=5 Name=ジェーム Sex=男 Age=12 Height=57.3 Weight=83 _ERROR_=0 _N_=6 Name=ジェーン Sex=女 Age=12 Height=59.8 Weight=84.5 _ERROR_=0 _N_=7 Name=ジャネッ Sex=女 Age=15 Height=62.5 Weight=112.5 _ERROR_=0 _N_=8 Name=ジェフリ Sex=男 Age=13 Height=62.5 Weight=84 _ERROR_=0 _N_=9 Name=ジョン Sex=男 Age=12 Height=59 Weight=99.5 _ERROR_=0 _N_=10 Name=ジョイス Sex=女 Age=11 Height=51.3 Weight=50.5 _ERROR_=0 _N_=11 Name=ジュディ Sex=女 Age=14 Height=64.3 Weight=90 _ERROR_=0 _N_=12 Name=ルイーズ Sex=女 Age=12 Height=56.3 Weight=77 _ERROR_=0 _N_=13 Name=メアリー Sex=女 Age=15 Height=66.5 Weight=112 _ERROR_=0 _N_=14 Name=フィリッ Sex=男 Age=16 Height=72 Weight=150 _ERROR_=0 _N_=15 Name=ロバート Sex=男 Age=12 Height=64.8 Weight=128 _ERROR_=0 _N_=16 Name=ロナルド Sex=男 Age=15 Height=67 Weight=133 _ERROR_=0 _N_=17 Name=トーマス Sex=男 Age=11 Height=57.5 Weight=85 _ERROR_=0 _N_=18 Name=ウィリア Sex=男 Age=15 Height=66.5 Weight=112 _ERROR_=0 _N_=19 NOTE: データセットTEST.CLASSから19オブザベーションを読み込みました。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.03 秒 CPU時間 0.03 秒
/* UTF8環境で実施 */ libname TEST "D:\temp" cvpmultiplier=1.5 ; data _null_ ; set TEST.CLASS ; putlog _all_ ; run ;
26 /* UTF8環境で実施 */ 27 libname TEST "D:\temp" cvpmultiplier=1.5; NOTE: ライブラリ参照名TESTを次のように割り当てました。 エンジン: CVP 物理名: D:\temp 28 data _null_ ; 29 set TEST.CLASS ; NOTE: データファイルTEST.CLASS.DATAは別なホストにネイティブ な形式が使用されているか、またはエンコーディング がセッションエンコーディングと一致していません。 クロス環境データアクセスが使用されるため、追加の CPUリソースが必要となり、パフォーマンスが低下しま す。 30 putlog _all_ ; 31 run ; Name=アルフレッド Sex=男子 Age=14 Height=69 Weight=112.5 _ERROR_=0 _N_=1 Name=アリス Sex=女子 Age=13 Height=56.5 Weight=84 _ERROR_=0 _N_=2 Name=バーバラ Sex=女子 Age=13 Height=65.3 Weight=98 _ERROR_=0 _N_=3 Name=キャロル Sex=女子 Age=14 Height=62.8 Weight=102.5 _ERROR_=0 _N_=4 Name=ヘンリー Sex=男子 Age=14 Height=63.5 Weight=102.5 _ERROR_=0 _N_=5 Name=ジェームズ Sex=男子 Age=12 Height=57.3 Weight=83 _ERROR_=0 _N_=6 Name=ジェーン Sex=女子 Age=12 Height=59.8 Weight=84.5 _ERROR_=0 _N_=7 Name=ジャネット Sex=女子 Age=15 Height=62.5 Weight=112.5 _ERROR_=0 _N_=8 Name=ジェフリー Sex=男子 Age=13 Height=62.5 Weight=84 _ERROR_=0 _N_=9 Name=ジョン Sex=男子 Age=12 Height=59 Weight=99.5 _ERROR_=0 _N_=10 Name=ジョイス Sex=女子 Age=11 Height=51.3 Weight=50.5 _ERROR_=0 _N_=11 Name=ジュディー Sex=女子 Age=14 Height=64.3 Weight=90 _ERROR_=0 _N_=12 Name=ルイーズ Sex=女子 Age=12 Height=56.3 Weight=77 _ERROR_=0 _N_=13 Name=メアリー Sex=女子 Age=15 Height=66.5 Weight=112 _ERROR_=0 _N_=14 Name=フィリップ Sex=男子 Age=16 Height=72 Weight=150 _ERROR_=0 _N_=15 Name=ロバート Sex=男子 Age=12 Height=64.8 Weight=128 _ERROR_=0 _N_=16 Name=ロナルド Sex=男子 Age=15 Height=67 Weight=133 _ERROR_=0 _N_=17 Name=トーマス Sex=男子 Age=11 Height=57.5 Weight=85 _ERROR_=0 _N_=18 Name=ウィリアム Sex=男子 Age=15 Height=66.5 Weight=112 _ERROR_=0 _N_=19 NOTE: データセットTEST.CLASSから19オブザベーションを読み込 みました。 NOTE: DATAステートメント処理(合計処理時間): 処理時間 0.11 秒 CPU時間 0.07 秒
注意点としては、「元データセットを更新する訳ではないですが」、「定義から1.5倍された状態で見えます」。
例えば、SJISで長さ12バイトとして定義されていたデータセットは、cvpmultiplier=1.5で、18バイト元からあったように見えます。
上記指定でもダメな所・・・・・・フォーマットが悪さをする。
CVPオプションは、文字列変数の「変数長に対してのみ効果が出ます」。
さて。
最近、しばしば見かけるデータセットでは、何故かわざわざ文字変数に同じ長さの文字フォーマットを設定している事があります。
Enterprise GuideでExcelファイルを取り込んだりするとそういうのが出来てたりします。
そういったフォーマットが付けられている場合に、自動でフォーマット長も設定されてたりするんですね。その場合には、「フォーマットの長さは変更されません」。
そういう場合には、フォーマットの設定を外して下さい。「format _all_ ;」で消えます。が、まあ、WORKなど現在の環境に複写してUTF8に変換された状態でないとダメです(元データは違うエンコーディングで、更新は出来ません)。
SDTMとか扱っている場合には、そもそも変数にフォーマット付けない方がいいです。XPORTファイル、フォーマットカタログに対応していないですし。