「CSVを結合したら、合計を照合する」。僕もまずそこを見ますが、明細ごとの一致件数も確かめたいです。
今日、架空の売上3行を顧客マスタと結んだら、4万円のまま4行になりました。合計が合うのは気持ち悪いですね。
4万円が合ってしまう内訳
売上はC001の1万円が2行、C002の2万円が1行。マスタにはC001が2行あり、C002は未登録です。IDが一致する組み合わせをすべて残すと、C001の売上は各2回残り、計4万円へ。C002の2万円は落ち、増減が相殺しました。
担当名を付けただけで、売上の中身まで変わっています。
Pythonで一致件数を調べる
checked_joinで、売上1行につきマスタが1行一致するか調べます。0件も2件も止め、対象の明細を残す。ここ、地味に効きます。
Python 3.14.8で実行済み。追加パッケージは不要。check_join.pyに保存し、python3 check_join.pyで動きます。エラーを表示して終了します。
import csv
from collections import Counter
from io import StringIO
def read(text):
return list(csv.DictReader(StringIO(text)))
def checked_join(sales, customers, key="customer_id"):
if any(not r[key].strip() for r in sales + customers):
raise ValueError("結合キーが空欄")
if len({r["sale_id"] for r in sales}) != len(sales):
raise ValueError("売上明細IDが重複")
counts = Counter(r[key] for r in customers)
bad = {r["sale_id"]: counts[r[key]] for r in sales
if counts[r[key]] != 1}
if bad:
raise ValueError(f"一致件数を確認: {bad}")
names = {r[key]: r["name"] for r in customers}
return [r | {"name": names[r[key]]} for r in sales]
sales = read("""sale_id,customer_id,amount
S01,C001,10000
S02,C001,10000
S03,C002,20000
""")
customers = read("""customer_id,name
C001,青葉商事
C001,青葉商事・旧担当
C003,南町商店
""")
wrong = [s | {"name": c["name"]} for s in sales for c in customers
if s["customer_id"] == c["customer_id"]]
total = lambda rows: sum(int(r["amount"]) for r in rows)
assert len(wrong) == 4
assert total(wrong) == total(sales) == 40000
print(f"元データ: {len(sales)}行 / {total(sales)}円")
print(f"単純な結合: {len(wrong)}行 / {total(wrong)}円")
try:
checked_join(sales, customers)
except ValueError as e:
print(e)
else:
raise AssertionError("誤った結合を通した")
元データ: 3行 / 40000円
単純な結合: 4行 / 40000円
一致件数を確認: {'S01': 2, 'S02': 2, 'S03': 0}
二重ループはINNER JOINの再現用。CounterでマスタのID別の行数を数えると、S01とS02は2件、S03は0件。誤りのある明細は3件です。
検査後に辞書から名前を取り出します。先に辞書へ変換すると重複IDの名前が上書きされます。
手元のCSVはcsv.DictReaderで読み込んだリストに替えます。UTF-8ならencoding="utf-8-sig"、newline=""で開きます。IDは文字列で先頭の0も残ります。列名合わせも案外手間ですね。
台帳を直してから集計へ
どのC001を残すか、C002を登録してよいかは台帳で確認します。「先頭を採用」では旧担当を選ぶ可能性も。検査は売上に使うIDが対象です。
空欄キーと重複した売上明細IDも止めます。金額は整数円。IDの表記ゆれは直しません。履歴入りのマスタでは、取引日時点の有効行を選ぶ必要もあります。
僕なら集計表へ渡す直前に置きます。自動化が進むほど、合計をすり抜けた誤りも週報へ入りやすくなります。顧客IDだけで結べるか、支店まで含むキーが要るかは先に確認します。あなたの業務では、結合キーのkeyにどの設定値を使いますか。
