CSVファイルをSqlite にインポートする

CSVファイルをSqlite にインポートする

最初に

import sqlite3
import csv

# CSVとDBファイルのパス
csv_file = 'receipts.csv'
db_file = 'receipts.db'

# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        store TEXT,
        item TEXT,
        price INTEGER,
        quantity INTEGER
    )
''')

# CSV読み込み&データ挿入
with open(csv_file, newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        cursor.execute('''
            INSERT INTO purchases (date, store, item, price, quantity)
            VALUES (?, ?, ?, ?, ?)
        ''', (row['日付'], row['店舗'], row['商品名'], int(row['単価']), int(row['数量'])))

conn.commit()
conn.close()

print("CSVファイルからSQLiteにデータを取り込みました。")

としたけど
毎日買い物するためCSVファイルは 20250618_create.csv のように ファイル名は 日付_店舗名.csv となる このファイルが複数になる

✅ 目的
すべての *.csv をスキャンし、SQLiteに取り込む。

なので

./csv_files/

から取り込むようにするので

mkdir csv_files
mv *.csv csv_files 

で移動しておく

そしてこれを取り込むようにコード変更

import sqlite3
import csv
import os
import re

# フォルダとDBファイルのパス
csv_folder = './csv_files'
db_file = 'receipts.db'

# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        store TEXT,
        item TEXT,
        price INTEGER,
        quantity INTEGER
    )
''')

# ファイル名のパターン: 20250618_イオン.csv
pattern = re.compile(r"(\d{8})_(.+)\.csv")

# フォルダ内のCSVファイルをループ処理
for filename in os.listdir(csv_folder):
    match = pattern.match(filename)
    if not match:
        continue  # 無効なファイル名はスキップ

    date = match.group(1)
    store = match.group(2)
    filepath = os.path.join(csv_folder, filename)

    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        for row in reader:
            try:
                cursor.execute('''
                    INSERT INTO purchases (date, store, item, price, quantity)
                    VALUES (?, ?, ?, ?, ?)
                ''', (
                    date,
                    store,
                    row['商品名'],
                    int(row['単価']),
                    int(row['数量'])
                ))
            except Exception as e:
                print(f"[エラー] {filename} の {row} に問題があります: {e}")

conn.commit()
conn.close()
print("すべてのCSVファイルをSQLiteに取り込みました。")

✅ 今後追加可能な機能
* すでに取り込んだCSVファイルを記録してスキップ(import_log.dbなどで管理)
* 重複チェック(同じ日付・店舗・商品・価格が存在すればスキップ)

すでに取り込んだCSVファイルを記録してスキップ(import_log.dbなどで管理) 重複チェック(同じ日付・店舗・商品・価格が存在すればスキップ) を追加する

✅ 追加機能の詳細
1. 取り込み済みCSVファイルを記録してスキップ
 → 別テーブル import_log を用意し、同じファイルは2回処理しないようにする。
2. 同一商品の重複データ(同じ日付+店舗+商品+単価)をスキップ
 → purchases テーブルに一意制約(UNIQUE)を追加。

これで

import sqlite3
import csv
import os
import re

# フォルダとDBファイルのパス
csv_folder = './csv_files'
db_file = 'receipts.db'

# データベース接続とテーブル作成
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

# 商品データ用テーブル(重複防止のためUNIQUE制約付き)
cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        date TEXT,
        store TEXT,
        item TEXT,
        price INTEGER,
        quantity INTEGER,
        UNIQUE(date, store, item, price)
    )
''')

# CSVインポート済みファイルの記録テーブル
cursor.execute('''
    CREATE TABLE IF NOT EXISTS import_log (
        filename TEXT PRIMARY KEY
    )
''')

# ファイル名のパターン: 20250618_イオン.csv
pattern = re.compile(r"(\d{8})_(.+)\.csv")

# フォルダ内のCSVファイルをループ処理
for filename in os.listdir(csv_folder):
    match = pattern.match(filename)
    if not match:
        continue  # 無効なファイル名はスキップ

    # すでに取り込み済みかチェック
    cursor.execute('SELECT 1 FROM import_log WHERE filename = ?', (filename,))
    if cursor.fetchone():
        print(f"[スキップ] すでに取り込み済み: {filename}")
        continue

    date = match.group(1)
    store = match.group(2)
    filepath = os.path.join(csv_folder, filename)

    with open(filepath, newline='', encoding='utf-8') as f:
        reader = csv.DictReader(f)
        inserted = 0
        for row in reader:
            try:
                cursor.execute('''
                    INSERT OR IGNORE INTO purchases (date, store, item, price, quantity)
                    VALUES (?, ?, ?, ?, ?)
                ''', (
                    date,
                    store,
                    row['商品名'],
                    int(row['単価']),
                    int(row['数量'])
                ))
                if cursor.rowcount > 0:
                    inserted += 1
            except Exception as e:
                print(f"[エラー] {filename} の {row} に問題があります: {e}")

    # 成功したらimport_logに記録
    cursor.execute('INSERT OR IGNORE INTO import_log (filename) VALUES (?)', (filename,))
    print(f"[完了] {filename} を取り込みました(新規 {inserted} 件)")

conn.commit()
conn.close()
print("すべてのCSVファイルの取り込みが完了しました。")

とする

そして
セルフメディケーション対応商品の商品名と金額、今年購入した合計金額を表示

✅ やりたいことまとめ
* 商品名に「セルフメディケーション対象」とわかるものをリストアップ(例:風邪薬、胃腸薬など)
* 購入履歴のうち、対象商品の2025年の購入金額合計を商品別に表示
* 合計金額も出す

DBで購入管理したい そのために 購入店舗名 購入年月日 商品名 価格 個数 をCSVファイルにしたい また食品なども同時に購入することがあるため セルフメディケーション対象商品を識別できるようにしたい

最初にGPTで提案してきたのは
対象商品のリスト(キーワードまたは手動リスト)
 たとえば以下のように事前定義します:

self_medication_keywords = [
    "風邪薬", "解熱剤", "鎮痛剤", "胃薬", "整腸剤", "花粉症", "鼻炎薬", "目薬", "かゆみ止め", "湿布", "トローチ"
]

これだとリストを手動で追加する必要がある

DBで購入管理したい そのために 購入店舗名 購入年月日 商品名 価格 個数 をCSVファイルにしたい また食品なども同時に購入することがあるため セルフメディケーション対象商品を識別できるようにしたい 現在購入している店舗ては ◆ もしくは セ] が商品名の先頭についている

この場合、◆をCSVにいれる以外の方法で何かあるか?
に対して

方法1:**商品名とは別に「分類」カラムを持つ(推奨)
方法2:対象商品のリストを別ファイル or 別テーブルで管理する
方法3:商品名はそのまま、別フラグ列 is_self_med を追加

✅ 結論:どの方法がいい?
方法 長所 短所 おすすめ度
方法1: 分類列 視認性と拡張性◎、CSVで完結 手入力ミスの可能性 ⭐⭐⭐⭐☆
方法2: マスタ マスタ更新で一括制御できる(大量商品向け) 初期セットアップが必要 ⭐⭐⭐⭐☆
方法3: フラグ 分類が2値なら簡単 他カテゴリ分類に弱い ⭐⭐⭐☆☆
方法0: 記号 手軽・一目でわかる データが汚れ、分類が混在する可能性 ⭐⭐☆☆☆

が回答

結論として

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
ドラッグストア クリエイト 袋井山名店,2025/06/21,ロイヤルブレッド(6),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,日興フーズ 王様のデーツ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,家族の潤いマスカット,108,108,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,ビオヨーグルト プレーン加糖,178,356,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,雪印メグミルク 牧場の朝ヨー,99,198,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,ブルボン デザートマルシェ白,93,278,3,0
ドラッグストア クリエイト 袋井山名店,2025/06/21,フルーツフェスタフルーツミックス,198,396,2,0

というように
カラムの追加とその値に金額を入れることにした

CSVデータ作成を依頼すると重複データを送られることがある。
実際のデータレシートは1枚のため
同一のレシート内容がある場合、削除する機能が欲しい

ハッシュで重複検出
をGPTは提案してくるが
CSVファイル全体の内容が完全一致した場合だけ「重複」とみなしてスキップ
つまり差分のチェックはこれだとできない

基本的に購入管理なので商品名と日付の判定で十分。 同じ日付で同一店舗で複数購入することはまず無いため
これを元に再度作成する

✔ 重複判定キー:
sql
コピーする編集する
UNIQUE(date, store, item)
これにより、
* 同じ日付
* 同じ店舗
* 同じ商品名
が1回しか登録されないようになる

✅ 補足:複数買い(同じ商品を同日に2回買う)を防ぎたい場合
万が一「同日に同じ商品を複数回買う」ことが発生してしまうと、2回目は登録されません(意図どおり)。
でもその場合も quantity や total_price を工夫すれば、「まとめて登録したことにできる」ので問題ありません。

ということ

この制約で動くインポートスクリプト全体を提供し
重複スキップされた商品名を CSV記録する

✅ 要件まとめ
* csv_files/ フォルダに複数のCSVがあり、すべて処理対象
* CSV形式:

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額

* UNIQUE制約:購入店舗名, 購入年月日, 商品名 の3つで一意
* 重複データは登録せずスキップ
* スキップされた商品情報をCSVに記録(例:skipped.csv)

 vim import_receipts_with_dedup_and_log.py

でファイルを作成

import sqlite3
import csv
import os
from datetime import datetime
import re

# 設定
csv_folder = './csv_files'
db_file = 'receipts.db'
skipped_log = 'skipped.csv'

# SQLite接続
conn = sqlite3.connect(db_file)
cursor = conn.cursor()

# purchasesテーブル(UNIQUE制約付き)
cursor.execute('''
    CREATE TABLE IF NOT EXISTS purchases (
        id INTEGER PRIMARY KEY AUTOINCREMENT,
        store TEXT,
        date TEXT,
        item TEXT,
        unit_price INTEGER,
        total_price INTEGER,
        quantity INTEGER,
        self_med_amount INTEGER,
        UNIQUE(date, store, item)
    )
''')

# import_logテーブル
cursor.execute('''
    CREATE TABLE IF NOT EXISTS import_log (
        filename TEXT PRIMARY KEY
    )
''')

# skipped.csv が存在していたら上書き、なければ新規作成
with open(skipped_log, 'w', newline='', encoding='utf-8') as skipfile:
    skip_writer = csv.writer(skipfile)
    skip_writer.writerow(['購入店舗名', '購入年月日', '商品名', '単品税抜価格', '価格', '個数', 'セルフメディケーション対象金額'])

    for filename in os.listdir(csv_folder):
        if not filename.endswith('.csv'):
            continue

        filepath = os.path.join(csv_folder, filename)

        # ファイルの重複チェック
        cursor.execute('SELECT 1 FROM import_log WHERE filename = ?', (filename,))
        if cursor.fetchone():
            print(f"[スキップ] {filename}(すでにインポート済み)")
            continue

        print(f"[処理中] {filename}")
        try:
            with open(filepath, newline='', encoding='utf-8') as f:
                reader = csv.DictReader(f)
                for row in reader:
                    store = row['購入店舗名'].strip()
                    date = datetime.strptime(row['購入年月日'], '%Y/%m/%d').strftime('%Y%m%d')
                    item = row['商品名'].strip()
                    unit_price = int(row['単品税抜価格'])
                    total_price = int(row['価格'])
                    quantity = int(row['個数'])
                    self_med_amount = int(row['セルフメディケーション対象金額'])

                    try:
                        cursor.execute('''
                            INSERT INTO purchases
                            (store, date, item, unit_price, total_price, quantity, self_med_amount)
                            VALUES (?, ?, ?, ?, ?, ?, ?)
                        ''', (store, date, item, unit_price, total_price, quantity, self_med_amount))
                    except sqlite3.IntegrityError:
                        # 重複データをskipped.csvに記録
                        skip_writer.writerow([
                            store, row['購入年月日'], item,
                            unit_price, total_price, quantity, self_med_amount
                        ])

            # ファイル名をインポート済みとして記録
            cursor.execute('INSERT INTO import_log (filename) VALUES (?)', (filename,))
            print(f"[完了] {filename} をインポートしました。")

        except Exception as e:
            print(f"[エラー] {filename}: {e}")

conn.commit()
conn.close()
print("✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。")

これを実行したら

 python import_receipts_with_dedup_and_log.py
[処理中] 20250621.csv
[エラー] 20250621.csv: time data '購入年月日' does not match format '%Y/%m/%d'
[処理中] 20240618-19.csv
[完了] 20240618-19.csv をインポートしました。
[処理中] 20250612.csv
[完了] 20250612.csv をインポートしました。
[処理中] 20250613.csv
[完了] 20250613.csv をインポートしました。
[処理中] 20250614-19.csv
[完了] 20250614-19.csv をインポートしました。
[処理中] 20250605.csv
[完了] 20250605.csv をインポートしました。
[処理中] 20250611.csv
[完了] 20250611.csv をインポートしました。
✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。

となる

cat skipped.csv 

で中身を確認

購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0

で調べるとスキップされている

 cat csv_files/20240618-19.csv 
で中身を確認
購入店舗名,購入年月日,商品名,単品税抜価格,価格,個数,セルフメディケーション対象金額
杏林堂 袋井旭町店,2025/06/18,サンテビオ 15mL,968,871,1,871
ピアゴ 袋井店,2025/04/18,タイレノールA 30錠,1790,1772,1,1772
COOP ユーコープ 袋井町店,2025/06/19,COゼリーエネルギー,118,354,3,0
COOP ユーコープ 袋井町店,2025/06/19,鮭・くず桜,238,190,1,0
COOP ユーコープ 袋井町店,2025/06/19,天ぷら用えび,398,238,1,0
COOP ユーコープ 袋井町店,2025/06/19,たい,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,塩銀鮭切身,398,398,1,0
COOP ユーコープ 袋井町店,2025/06/19,真あじ開き,398,796,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,日興フーズ 王様のチーズ,298,298,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,ロイヤルブレッド山型(5),139,139,1,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,家族の潤い白桃ミックス,108,216,2,0
ドラッグストア クリエイト 袋井山名店,2025/06/18,鶏卵卵パック MS 10個,228,228,1,0

で調べると

これは row[‘購入年月日’] の値が本来 “2025/06/18” のような日付になるはずのところ、
誤って “購入年月日”(つまりヘッダーの文字列)が入っているため、datetime.strptime() が失敗しています。
とのこと

CSVファイル内に ヘッダー行がもう一度繰り返されている可能性が非常に高いです(コピペミス、マージ時の事故など)。

✅ 解決策:誤って混入したヘッダー行をスキップ
以下の1行を for row in reader: の直後に追加

# ヘッダー行がデータ行に混入していたらスキップ
if row['購入年月日'] == '購入年月日':
    continue

これを処理に追加して

with open(filepath, newline='', encoding='utf-8') as f:
    reader = csv.DictReader(f)
    for row in reader:
        if row['購入年月日'] == '購入年月日':
            continue  # ← ここで2回目以降のヘッダー行を除外

        store = row['購入店舗名'].strip()
        date = datetime.strptime(row['購入年月日'], '%Y/%m/%d').strftime('%Y%m%d')
        ...

とすることで
✅ 結果
* ✔ ヘッダーが再登場しても自動でスキップ
* ✔ 異常な日付データがあってもクラッシュせず処理継続

保存して再度実行

 python import_receipts_with_dedup_and_log.py
[処理中] 20250621.csv
[完了] 20250621.csv をインポートしました。
[スキップ] 20240618-19.csv(すでにインポート済み)
[スキップ] 20250612.csv(すでにインポート済み)
[スキップ] 20250613.csv(すでにインポート済み)
[スキップ] 20250614-19.csv(すでにインポート済み)
[スキップ] 20250605.csv(すでにインポート済み)
[スキップ] 20250611.csv(すでにインポート済み)
✅ すべてのCSVの処理が完了しました。スキップされた商品は skipped.csv に保存されています。

となって無事に成功

次に
セルフメディケーション対象商品の合計金額(2025年) 商品別セルフメディケーション金額ランキング を表示したい

SQLiteに保存された購入データ(テーブル名:purchases)から、以下の2つを表示

セルフメディケーション対象商品の合計金額(2025年)

SELECT SUM(self_med_amount) AS total_amount
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
  AND self_med_amount > 0;

商品別セルフメディケーション金額ランキング(2025年)

SELECT item, SUM(self_med_amount) AS total_amount
FROM purchases
WHERE date BETWEEN '20250101' AND '20251231'
  AND self_med_amount > 0
GROUP BY item
ORDER BY total_amount DESC;

これがSQLになる

 vim medication_summary.py
import sqlite3

# SQLiteに接続
conn = sqlite3.connect("receipts.db")
cursor = conn.cursor()

print("📋 セルフメディケーション対象商品の合計金額(2025年)")
cursor.execute('''
    SELECT SUM(self_med_amount)
    FROM purchases
    WHERE date BETWEEN '20250101' AND '20251231'
      AND self_med_amount > 0
''')
total = cursor.fetchone()[0] or 0
print(f"▶ 合計金額:¥{total:,}")
print()

print("📊 商品別セルフメディケーション対象金額ランキング(2025年)")
cursor.execute('''
    SELECT item, SUM(self_med_amount) AS total
    FROM purchases
    WHERE date BETWEEN '20250101' AND '20251231'
      AND self_med_amount > 0
    GROUP BY item
    ORDER BY total DESC
''')

rows = cursor.fetchall()
if not rows:
    print("(対象商品はありません)")
else:
    print("商品名\t\t金額")
    print("-" * 30)
    for item, amount in rows:
        print(f"{item[:14]:<14} ¥{amount:,}")

conn.close()

これを実行すると

python medication_summary.py 
📋 セルフメディケーション対象商品の合計金額(2025年)
▶ 合計金額:¥2,643

📊 商品別セルフメディケーション対象金額ランキング(2025年)
商品名		金額
------------------------------
タイレノールA 30錠    ¥1,772
サンテビオ 15mL     ¥871

となる

コメントを残す

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です