0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

PythonでCSV結合を検証、合計が同じでも誤り3件

0
Posted at

「CSVを結合したら、合計を照合する」。僕もまずそこを見ますが、明細ごとの一致件数も確かめたいです。

今日、架空の売上3行を顧客マスタと結んだら、4万円のまま4行になりました。合計が合うのは気持ち悪いですね。

4万円が合ってしまう内訳

売上はC001の1万円が2行、C002の2万円が1行。マスタにはC001が2行あり、C002は未登録です。IDが一致する組み合わせをすべて残すと、C001の売上は各2回残り、計4万円へ。C002の2万円は落ち、増減が相殺しました。

担当名を付けただけで、売上の中身まで変わっています。

顧客C001の売上2行は各2回残り、未登録C002の売上1行は消える。合計4万円は一致するが、3件の明細が正常に結合できていない流れ

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にどの設定値を使いますか。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?