2つのCSVファイルを読み込んで、ハッシュを使って、同一のキー項目を元に照合を行うスクリプトです。
「1対nマッチング(照合)」は、「トランザクションファイル」において、同一のキーを持つレコードが複数存在する場合のマッチング(照合)処理として[2-3-4.1対nマッチング(照合)]で取り上げましたが、同様の処理についてハッシュを使って行う方法を以下に挙げます。[2-3-4.1対nマッチング(照合)]と異なる点は、入力データがキー順に並べ替えてある必要がない点です。
下記の例では「マッチング(照合)した場合」と「マスタオンリーの場合」を同じファイルに出力し、「トランザクションオンリーの場合」をエラーとして出力していますが、こうした分け方は、必要に応じて変更します。
# matching2_hash.rb
# 内容 : マッチングプログラム(2ファイルの1対nマッチングプログラム)
# 前提 : マスタファイルとトランザクションファイルの両方とも
# マージするキーごとに昇順に並べ替えておく必要はない
# Copyright (c) 2012-2015 Mitsuo Minagawa, All rights reserved.
# (minagawa@fb3.so-net.ne.jp)
# 使用方法 : c:\>ruby matching2_hash.rb
#
# ファイルのオープン
in1_file = open("master.txt","r") #マスターファイル
in2_file = open("transaction.txt","r") #トランザクションファイル
out1_file = open("matching.txt","w") #マッチングファイル
out2_file = open("masteronly.txt","w") #マスターオンリーファイル
out3_file = open("tranonly.txt","w") #トランザクションオンリーファイル
# 集計結果を入れるハッシュ
data1 = Hash.new
# エラー出力用ハッシュ
data2 = Hash.new
# 合計用の配列
total = Array.new
# トランザクションファイルの件数
out2_ctr = 0
# マスタデータの入力
while (line1 = in1_file.gets)
line1.chomp!
# タブ区切りのとき
# in1 = line1.split("\t",-1)
# カンマ区切りのとき
in1 = (line1 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
# キー項目が単独のとき
in1_key = in1[0]
# キー項目が複数のとき
# 1番目と2番目と3番目の項目がキーとなる場合
# in1_key = in1[0] + in1[1] + in1[2]
# ハッシュに初期値0の配列を代入する
data1[in1_key] = [0,0]
end
# トランザクションデータの入力
while (line2 = in2_file.gets)
line2.chomp!
# タブ区切りのとき
# in2 = line2.split("\t",-1)
# カンマ区切りのとき
in2 = (line2 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
# キー項目が単独のとき
in2_key = in2[0]
# キー項目が複数のとき
# 1番目と2番目と3番目の項目がキーとなる場合
# in2_key = in2[0] + in2[1] + in2[2]
out2_ctr += 1
w_key = in2_key + sprintf("%08d",out2_ctr)
# トランザクションデータのキーがマッチングした場合、数値を加算する
if (data1.include?(in2_key))
total = data1[in2_key]
total[0] += in2[1].to_i
total[1] += in2[2].to_i
data1[in2_key] = total
# 集計する項目が1つの場合
# data1[in2_key] += in2[1].to_i
# ハッシュのキーが存在しない場合
else
data2[w_key] = in2
end
end
# マッチングファイルを出力する
data1.sort_by{|key| key }.each{|key,value|
w_temp = [key,value].join("\t")
if value != [0,0]
out1_file.print "#{w_temp}\n"
end
}
# マスターオンリーファイルを出力する
data1.sort_by{|key| key }.each{|key,value|
w_temp = [key,value].join("\t")
if value == [0,0]
out2_file.print "#{w_temp}\n"
end
}
# トランザクションオンリーファイルを出力する
data2.sort_by{|key| key }.each{|key,value|
w_temp = [value].join("\t")
out3_file.print "#{w_temp}\n"
}
# ファイルのクローズ
in1_file.close
in2_file.close
out1_file.close
out2_file.close
out3_file.close
それでは、スクリプトの解説を個別に行っていきましょう。
マスタファイルの入力
ここではマスタファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目をハッシュのキーとし件数を合計するための初期値として0「ゼロ」をハッシュの値としてセットしています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。
# マスタデータの入力
while (line1 = in1_file.gets)
line1.chomp!
# タブ区切りのとき
# in1 = line1.split("\t",-1)
# カンマ区切りのとき
in1 = (line1 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
# キー項目が単独のとき
in1_key = in1[0]
# キー項目が複数のとき
# 1番目と2番目と3番目の項目がキーとなる場合
# in1_key = in1[0] + in1[1] + in1[2]
# ハッシュに初期値0の配列を代入する
data1[in1_key] = [0,0]
end
ハッシュに初期値0の配列を代入する場合、上記では
# ハッシュに初期値0の配列を代入する
data1[in1_key] = [0,0]
# ハッシュに初期値0の配列を代入する
data1[in1_key] = Array.new(2,0)
トランザクションファイルの入力
次にトランザクションファイルを入力し、CSVファイルを配列にセットした上で、CSVファイルの中でキーとなる項目が件数を合計するためのハッシュのキーとして存在している場合は、トランザクションファイルにある数値項目を合計しています。なお、キーとなる項目は1番目の項目を想定していますが、必要であれば、異なる項目に変更します。
# トランザクションデータの入力
while (line2 = in2_file.gets)
line2.chomp!
# タブ区切りのとき
# in2 = line2.split("\t",-1)
# カンマ区切りのとき
in2 = (line2 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
# キー項目が単独のとき
in2_key = in2[0]
# キー項目が複数のとき
# 1番目と2番目と3番目の項目がキーとなる場合
# in2_key = in2[0] + in2[1] + in2[2]
out2_ctr += 1
w_key = in2_key + sprintf("%08d",out2_ctr)
# トランザクションデータのキーがマッチングした場合、数値を加算する
if (data1.include?(in2_key))
total = data1[in2_key]
total[0] += in2[1].to_i
total[1] += in2[2].to_i
data1[in2_key] = total
# 集計する項目が1つの場合
# data1[in2_key] += in2[1].to_i
# ハッシュのキーが存在しない場合
else
data2[w_key] = in2
end
end
ここでは複数の項目を集計していますので、ハッシュを一度配列(total)にセットしてから、配列の各項目に数値を加算した後、配列を元のハッシュに代入して戻しています。集計する項目が1つしかない場合は、直接ハッシュの項目に対して、加算していきます。
合計値の出力
最後にトランザクションファイルの数値合計を集計したハッシュからデータを出力します。
# マッチングファイルを出力する
data1.sort_by{|key| key }.each{|key,value|
w_temp = [key,value].join("\t")
if value != [0,0]
out1_file.print "#{w_temp}\n"
end
}
マッチしないエラーデータの出力
また、マスターオンリーファイルとトランザクションオンリーファイルを出力します。ここでは、マスターオンリーファイルを別にしていますが、処理によっては、マッチングファイルと同じ出力先にする場合もあります。
# マスターオンリーファイルを出力する
data1.sort_by{|key| key }.each{|key,value|
w_temp = [key,value].join("\t")
if value == [0,0]
out2_file.print "#{w_temp}\n"
end
}
# トランザクションオンリーファイルを出力する
data2.sort_by{|key| key }.each{|key,value|
w_temp = [value].join("\t")
out3_file.print "#{w_temp}\n"
}
その他注意すべきこと
その他、注意すべき点は入力データがタブ区切りなどの場合とキーが複数ある場合の処理です。
上記のスクリプトでは、入力ファイルがCSVファイルであれば、どのようなものでも対応できるようになっています。具体的には以下の箇所になります。
CSV形式の入力データを配列に変換する
# カンマ区切りのとき
in1 = (line1 + ',')
.scan(/"([^"\\]*(?:\\.[^"\\]*)*)",|([^,]*),/)
.collect{|x,y| y || x.gsub(/(.)/, '\1')}
上記のスクリプトはCSV2形式以外の引用符(")を使用する場合を含め、あらゆるCSVファイルに対応できるようになっています(CSV2形式などについては、[3-1-1.固定長データとCSVデータ]を参照してください)が、CSV2形式のCSVファイルであれば、上記のスクリプトは以下のように簡略化できます。
1.入力データがCSV2形式の場合、
in1 = line1.split(",",-1)
とします。
2.入力データがタブ区切りの場合、
in1 = line1.split("\t",-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,1100
01,200,1200
01,300,1300
02,200,1200
04,400,1400
04,500,1500
04,600,1600
04,700,1700
06,600,1600
06,700,1700
07,700,1700
08,800,1800
09,900,1900
09,1000,2000
09,1100,2100
10,1200,2200
01 600 3600 02 200 1200 07 700 1700 08 800 1800 09 3000 6000
03 0 05 0
04 400 1400 04 500 1500 04 600 1600 04 700 1700 06 600 1600 06 700 1700 10 1200 2200