到達点は、SQLの値を安全に渡し、失敗した更新を取り消すことです。前提は第06・11回です。
検索できる形で保存する
SQLiteは単一ファイルなどにデータを保存できるデータベースです。Python標準ライブラリのsqlite3から使えます。テーブルに型や制約を定義し、SELECTで取得、INSERTで追加、UPDATEで変更、DELETEで削除します。
import sqlite3
db = sqlite3.connect(":memory:")
try:
db.execute("CREATE TABLE tasks(id INTEGER PRIMARY KEY, title TEXT NOT NULL UNIQUE)")
db.commit()
try:
with db:
db.execute("INSERT INTO tasks(title) VALUES (?)", ("復習",))
db.execute("INSERT INTO tasks(title) VALUES (?)", ("復習",))
except sqlite3.IntegrityError:
print("重複したため取り消しました")
print(db.execute("SELECT COUNT(*) FROM tasks").fetchone()[0])
with db:
db.execute("INSERT INTO tasks(title) VALUES (?)", ("Python's SQL",))
for row in db.execute("SELECT id, title FROM tasks ORDER BY id"):
print(row)
finally:
db.close()
main.pyで実行すると重複の通知、件数0、その後に追加された一行が出ます。二回の追加を同じトランザクションにまとめたため、二回目が失敗すると最初も取り消されます。この例は3.11以降で使える既定のトランザクション制御を前提にしています。autocommit設定を変える場合は挙動を確認してください。
SQLと値を混ぜない
プレースホルダの疑問符へ、引数タプルで値を渡します。一要素タプルの末尾のカンマを忘れないようにします。f文字列でSQLへ入力を連結すると、SQLインジェクションの原因になります。テーブル名や並び替え列は値のプレースホルダにできないため、許可した候補から選びます。
with dbはトランザクションの確定・取り消しを支援しますが、接続そのものを閉じるものではありません。最後にcloseします。多数の行にはfetchallで全件を保持するか、逐次処理するかも選びます。
インデックスは検索を助ける代わりに、書き込み費用と保存領域を増やします。外部キーを使う場合、接続で PRAGMA foreign_keys = ON を有効にすることも確認します。複数接続での同時更新にはロック待ちと再試行の設計が必要です。
練習と解答
練習:最初のINSERTの直後にcommitしてしまうと、重複時の件数はどうなりますか。
解答:最初の一行は確定済みなので残ります。トランザクションの境界は、何を「全部成功か全部失敗」にしたいかから決めます。
公式資料
sqlite3とSQLiteのトランザクションが参照先です。
Python全20回の目次 | 前の回 | 次の回