2つのCSVファイルを読み込んで、ハッシュを使って、同一のキー項目を元に照合を行うスクリプトです。
「マスタファイル」も「トランザクションファイル」もそれぞれ、同一のキーを持つレコードが1つしか存在しない場合のマッチング(照合)処理は[2-3-2.1対1マッチング(照合)]で取り上げましたが、同様の処理についてハッシュを使って行う方法を以下に挙げます。[2-3-2.1対1マッチング(照合)]と異なる点は、入力データがキー順に並べ替えてある必要がない点です。
マッチング(照合)した場合は、「マスタファイル」と「トランザクションファイル」の両方が元データになりますが、どちらのファイルのデータをどのように編集するかは、それぞれのケースによって異なるので、各自工夫してください。
「マスタオンリーの処理」と「トランザクションオンリーの処理」では、それぞれ「マスタファイル」や「トランザクションファイル」からすべてのデータをセットして、出力していますが、必要に応じて、条件をつけたり、集計したり、必要な項目だけをセットするなど、内容を変更して利用することになります。
# matching1_hash.pl # 内容 : マッチングプログラム(2ファイルの1対1マッチングプログラム) # 前提 : マスタファイルとトランザクションファイルの両方とも # マッチングするキーごとに昇順に並べ替えておく必要はない # Copyright (c) 2012 Mitsuo Minagawa, All rights reserved. # (minagawa@fb3.so-net.ne.jp) # 使用方法 : c:\>perl matching1_hash.pl # # マスタデータを入れるハッシュ my %data1; # トランザクションデータを入れるハッシュ my %data2; # マッチング(照合)したデータを入れるハッシュ my %out1; # マスタのみのデータを入れるハッシュ my %out2; # トランザクションのみのデータを入れるハッシュ my %out3; # マスタデータをハッシュに入れる open(IN1,"master.txt"); while($line1 = <IN1>) { chomp($line1); #---区切り文字により、処理を変更する---------- #タブ区切りのとき # @in1 = split("\t",$line1); #カンマ区切りのとき my $tmp = $line1; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in1 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); #----------------- $in1_key = $in1[0]; # 配列のリファレンスを生成し、ハッシュに配列を代入する $data1{$in1_key} = [@in1]; } close(IN1); # トランザクションデータをハッシュに入れる open(IN2,"transaction.txt"); while($line2 = <IN2>) { chomp($line2); #---区切り文字により、処理を変更する---------- #タブ区切りのとき # @in2 = split("\t",$line2); #カンマ区切りのとき my $tmp = $line2; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in2 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); #----------------- $in2_key = $in2[0]; # 配列のリファレンスを生成し、ハッシュに配列を代入する $data2{$in2_key} = [@in2]; } close(IN2); foreach my $key (sort keys %data1) { # ハッシュのキーがすでに存在する場合(マッチしたとき) if (exists $data2{$key}) { $out1{$key} = $data1{$key}; } # ハッシュのキーが存在しない場合(マスタにしかないとき) else { $out2{$key} = $data1{$key}; } } foreach my $key (sort keys %data2) { # ハッシュのキーが存在しない場合(トランザクションにしかないとき) unless (exists $data1{$key}) { $out3{$key} = $data2{$key}; } } open(OUT1,">matching.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out1) { # 配列のリファレンスを配列に戻して出力する print OUT1 join("\t", @{$out1{$key}})."\n"; } close(OUT1); open(OUT2,">masteronly.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out2) { # 配列のリファレンスを配列に戻して出力する print OUT2 join("\t", @{$out2{$key}})."\n"; } close(OUT2); open(OUT3,">tranonly.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out3) { # 配列のリファレンスを配列に戻して出力する print OUT3 join("\t", @{$out3{$key}})."\n"; } close(OUT3);
それでは、スクリプトの解説を個別に行っていきましょう。
マスタファイルの入力
ここではマスタファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし、CSVファイルを配列にしたものをハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。
# マスタデータをハッシュに入れる open(IN1,"master.txt"); while($line1 = <IN1>) { chomp($line1); #---区切り文字により、処理を変更する---------- #タブ区切りのとき # @in1 = split("\t",$line1); #カンマ区切りのとき my $tmp = $line1; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in1 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); #----------------- $in1_key = $in1[0]; # 配列のリファレンスを生成し、ハッシュに配列を代入する $data1{$in1_key} = [@in1]; } close(IN1);
上記では、入力したCSVファイルを各項目をすべてハッシュの値としてセットしていますが、入力したCSVファイルの各項目の一部をハッシュの値としてセットする場合は、
$data1{$in1_key} = [@in1];
の部分を以下のように変更します。
$data1{$in1_key} = [$in1[1],$in1[2]];
トランザクションファイルの入力
次にトランザクションファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし、CSVファイルを配列にしたものをハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。
# トランザクションデータをハッシュに入れる open(IN2,"transaction.txt"); while($line2 = <IN2>) { chomp($line2); #---区切り文字により、処理を変更する---------- #タブ区切りのとき # @in2 = split("\t",$line2); #カンマ区切りのとき my $tmp = $line2; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in2 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g); #----------------- $in2_key = $in2[0]; # 配列のリファレンスを生成し、ハッシュに配列を代入する $data2{$in2_key} = [@in2]; } close(IN2);
上記では、入力したCSVファイルを各項目をすべてハッシュの値としてセットしていますが、入力したCSVファイルの各項目の一部をハッシュの値としてセットする場合は、
$data2{$in2_key} = [@in2];
の部分を以下のように変更します。
$data2{$in2_key} = [$in2[1],$in2[2]];
マッチング(2つのファイルの照合)処理
次に入力した2つのデータを照合し、マスタファイルとトランザクションファイルの両方に存在するか、それともどちらか一方にしか存在しないかによって、データを振り分けて別のハッシュにセットします。
ここではマスタファイルをハッシュのキー順に並べ替えながら、マスタファイルのキーがトランザクションファイルに存在するかどうかをチェックし、存在する場合(マッチしたとき)は、マッチング(照合)データを入れるハッシュにセットし、マスタファイルのキーがトランザクションファイルに存在しない場合(マスタにしかないとき)は、マスタのみのデータを入れるハッシュにセットします。
foreach my $key (sort keys %data1) { # ハッシュのキーがすでに存在する場合(マッチしたとき) if (exists $data2{$key}) { $out1{$key} = $data1{$key}; } # ハッシュのキーが存在しない場合(マスタにしかないとき) else { $out2{$key} = $data1{$key}; } }
以下では、トランザクションファイルをハッシュのキー順に並べ替えながら、トランザクションファイルのキーがマスタファイルに存在するかどうかをチェックし、存在しない場合(トランザクションにしかないとき)は、トランザクションのみのデータを入れるハッシュにセットします。
foreach my $key (sort keys %data2) { # ハッシュのキーが存在しない場合(トランザクションにしかないとき) unless (exists $data1{$key}) { $out3{$key} = $data2{$key}; } }
なお、上記ではマスタファイルとトランザクションファイルの両方に同一のキー項目が存在した場合には、マスタファイルにあるデータを優先させていますが、これをトランザクションファイルにあるデータを優先させるようにするには、以下の部分を
foreach my $key (sort keys %data1) { # ハッシュのキーがすでに存在する場合(マッチしたとき) if (exists $data2{$key}) { $out1{$key} = $data1{$key}; } # ハッシュのキーが存在しない場合(マスタにしかないとき) else { $out2{$key} = $data1{$key}; } } foreach my $key (sort keys %data2) { # ハッシュのキーが存在しない場合(トランザクションにしかないとき) unless (exists $data1{$key}) { $out3{$key} = $data2{$key}; } }
下記のように変更します。
foreach my $key (sort keys %data2) { # ハッシュのキーがすでに存在する場合(マッチしたとき) if (exists $data1{$key}) { $out1{$key} = $data2{$key}; } # ハッシュのキーが存在しない場合(トランザクションにしかないとき) else { $out3{$key} = $data2{$key}; } } foreach my $key (sort keys %data1) { # ハッシュのキーが存在しない場合(マスタにしかないとき) unless (exists $data2{$key}) { $out2{$key} = $data1{$key}; } }
出力処理
最後に振り分けたそれぞれのファイルをキー順に出力していきます。下記ではタブ区切りで出力していますが、必要に応じて他の区切り文字を使って出力することもできます。
open(OUT1,">matching.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out1) { # 配列のリファレンスを配列に戻して出力する print OUT1 join("\t", @{$out1{$key}})."\n"; } close(OUT1); open(OUT2,">masteronly.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out2) { # 配列のリファレンスを配列に戻して出力する print OUT2 join("\t", @{$out2{$key}})."\n"; } close(OUT2); open(OUT3,">tranonly.txt"); # ハッシュのキーをソート(並べ替え)しながら foreach my $key (sort keys %out3) { # 配列のリファレンスを配列に戻して出力する print OUT3 join("\t", @{$out3{$key}})."\n"; } close(OUT3);
その他注意すべきこと
その他、注意すべき点は入力データがタブ区切りなどの場合とキーが複数ある場合の処理です。
上記のスクリプトでは、入力ファイルがCSVファイルであれば、どのようなものでも対応できるようになっています。具体的には以下の箇所になります。
CSV形式の入力データを配列に変換する
my $tmp = $line1; $tmp =~ s/(?:\x0D\x0A|[\x0D\x0A])?$/,/; @in1 = map {/^"(.*)"$/ ? scalar($_ = $1, s/""/"/g, $_) : $_} ($tmp =~ /("[^"]*(?:""[^"]*)*"|[^,]*),/g);
上記のスクリプトはCSV2形式以外の引用符(")を使用する場合を含め、あらゆるCSVファイルに対応できるようになっています(CSV2形式などについては、[3-1-1.固定長データとCSVデータ]を参照してください)が、このCSVファイルに分解するロジックは、大崎 博基(OHZAKI Hiroki)さんの「Perlメモ」に記載されていたものを参考にしています。また、CSV2形式のCSVファイルであれば、上記のスクリプトは以下のように簡略化できます。
1.入力データがCSV2形式の場合、
@in1 = split(",",$line1,-1);
とします。
2.入力データがタブ区切りの場合、
@in1 = split("\t",$line1,-1);
とします。
3.キーが複数の項目から成り立っている場合は、
「$in1_key = $in1[0]」の部分を
$in1_key = $in1[0].$in1[1].$in1[2];
のように変更します(文字列の連結は「.」(ピリオド)を使います)。
01,11111 02,22222 03,33333 05,44444 07,77777 08,88888 09,99999
01,100 02,200 04,400 06,600 07,700 08,800 09,900 10,1000
01 11111 02 22222 07 77777 08 88888 09 99999
03 33333 05 44444
04 400 06 600 10 1000