ターミナルで同じ取込を2回動かす。1回目は1件登録、2回目は0件。ここまでは順調でした。ところが、同じ受注IDの金額を変えても、エラーなしで0件になった。
「重複したら無視する」だけの処理を、僕は業務の取込には使いたくありません。再実行で増やさないことと、訂正データを握りつぶさないことを、セットで確かめます。
架空の受注データで再現しました。CSVを読んだ後の登録部分に使う例です。IDは文字列、金額は整数で渡します。
SQLiteの重複無視には、内容の照合がない
ON CONFLICT(id) DO NOTHINGは、同じIDがあれば挿入を見送ります。既存行の金額が一致するかまでは調べません。
初回が1,000円、再取込が1,100円でも、IDが同じなら通り過ぎる。件数だけをログに残していると、同じファイルを流したのか、訂正を捨てたのか区別がつきません。
そこで、挿入を見送った行だけ既存の金額と照合します。一致すれば再取込として無視。違えば例外を出し、その回の取込を全部取り消す方針にしました。
途中まで入った行も取り消すコード
Python 3.12以降が必要です。CPython 3.14.8、SQLite 3.54.1で実行しました。import_check.pyに保存し、python3 import_check.pyで動かせます。
import sqlite3
db = sqlite3.connect(":memory:", autocommit=False)
db.execute("""
CREATE TABLE orders (
id TEXT PRIMARY KEY NOT NULL,
yen INTEGER NOT NULL
)
""")
db.commit()
def take(rows):
added = 0
with db:
for key, yen in rows:
cur = db.execute(
"INSERT INTO orders VALUES (?, ?) "
"ON CONFLICT(id) DO NOTHING", (key, yen)
)
if cur.rowcount == 1:
added += 1
else:
old = db.execute(
"SELECT yen FROM orders WHERE id = ?", (key,)
).fetchone()
if old != (yen,):
raise ValueError(f"{key}: 金額が既存行と違います")
return added
print("初回:", take([("A001", 1000)]))
print("再取込:", take([("A001", 1000)]))
try:
take([("A002", 2000), ("A001", 1100)])
except ValueError as e:
print(e)
print("保存済み:", db.execute("SELECT * FROM orders ORDER BY id").fetchall())
db.close()
出力は次のとおりでした。
初回: 1
再取込: 0
A001: 金額が既存行と違います
保存済み: [('A001', 1000)]
3回目はA002を挿入した後、A001の金額違いを見つけました。最後の一覧にA002が残っていないところまで確認します。
僕が最初に見たのは、エラーが出るかだけでした。先に入った行まで確かめないと、半分だけ登録された取込を見逃します。
例外を受け取る位置で、保存結果が変わる
autocommit=Falseでトランザクションを使い、with db:を正常に抜ければ確定、例外で抜ければ取り消します。実行環境の一次資料も確認しました。help(sqlite3.Connection.__exit__)に、この確定と取り消しの動作が記載されています。
exceptはwith db:の外側です。内側で例外を握りつぶし、正常終了させると、途中まで挿入した行が確定し得ます。エラーを表示する位置は好みの問題ではありません。
空のIDも別途弾きたいところです。NOT NULLで拒めるのはNoneに対応するNULLで、空文字は通ります。取込元で空白しか入っていないIDを許すかは、先に決める必要があります。
この例が照合するのは金額だけです。納品先や品目も扱うなら、それらも比較対象に足します。明細が複数ある受注では、受注IDと明細番号の組をキーにします。会社名をキーにすると、別の受注まで重複扱いになります。
実データは接続先を:memory:からファイル名へ変え、再実行時も同じDBを開きます。既存行の訂正手順は別途必要です。
自動取込を任せる範囲が広がるほど、失敗後に同じデータを流す機会も増えると見ています。成功件数だけでは、その再実行を評価できません。新規、同内容の再送、内容が変わった再送で、残る行をそれぞれ確かめられます。手元の取込処理にもこの3パターンを入れ、エラーの後に保存された行まで確認してください。
