2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

DuckDBを試してみたら、Pandasとの相性が良くて便利だったので使い方をまとめてみた

2
Last updated at Posted at 2026-04-11

はじめに

私は機械学習エンジニアをしており、現在はLLMを扱うことが多く、RAGシステムの構築などを行っています。
バリバリのバックエンドエンジニアというわけではないので、SQLを日常的に使う機会はそれほど多くありません。

ただ、こういった場面ではDBを使用したくなることがあります。

  • RAGシステムでドキュメントのメタデータ(ファイル名・チャンクID・埋め込みベクトルのインデックス等)を管理したい
  • データの結合・加工を行いたい

2つ目に関して、なんのこっちゃと思う方もいるかもしれませんが、複数の文書間でIDなどのデータで紐づいていたり、特定のフラグや文言が存在するデータのみを抽出して学習データに使用したい、といった場面がたびたびあります。

フォーマットがバラバラな文書をPandasでちまちま加工して必要なデータを成形していると、コードも複雑化してマジックストリング(ハードコードされたカラム名)だらけ、ぱっと見すごくわかりにくく使い回せないスクリプトが出来上がるわけです。

そういった場合に、ある程度成形した文書ごとのデータをDBにとりあえず入れておき、SQL操作で必要なデータを取り出したほうが直感的でわかりやすく、あとから必要なデータの要件を変えやすいため、活用することがあります。

このようなユースケースでSQLiteを使っていたのですが、最近DuckDBを使ってみたところPandasとの相性が良く使いやすかったので、記事にまとめてみました。


DuckDBとは?

DuckDBは、組み込み型の分析用RDBMSです

SQLiteと同様にサーバーレスで動作し、単一ファイルに全データを保存できます。大きな違いは、SQLiteが行指向(OLTP向け)なのに対し、DuckDBは列指向(OLAP向け)であることです。

特徴をひと言でまとめると、「Pandasと融合できるSQLite」 という感じです。


SQLite vs DuckDB 比較表

項目 SQLite DuckDB
アーキテクチャ 行指向(OLTP) 列指向(OLAP)
得意な処理 小〜中規模のトランザクション処理 大量データの集計・分析クエリ
サーバー 不要(組み込み) 不要(組み込み)
ファイル形式 .db / .sqlite .duckdb / .db
Pandas連携 pd.read_sql() で読み取り可能 DataFrameを直接クエリ・挿入可能
並列クエリ 非対応 対応(マルチスレッド)
対応言語 Python, C/C++, Java, etc. Python, R, Java, Node.js, etc.
インメモリモード 非対応 対応(:memory: で起動)
CSVやParquetの直接読み込み 不可(要インポート) SQL上で直接クエリ可能
Pythonパッケージ sqlite3(標準ライブラリ) duckdb(pip install)
ライセンス Public Domain MIT

SQLiteはPythonのSTLに含まれておりインストール不要という強みがありますが、DuckDBはpip install duckdbだけで使えるので導入コストはほぼ変わりません。


インストール

pip install duckdb

基本的な使い方

DBの作成・接続

import duckdb

# ファイルに保存するDB(永続化)
conn = duckdb.connect("my_database.duckdb")

# インメモリDB(セッション終了で消える)
conn = duckdb.connect(":memory:")

テーブルの作成・データ挿入

conn.execute("""
    CREATE TABLE IF NOT EXISTS documents (
        id INTEGER PRIMARY KEY,
        filename VARCHAR,
        chunk_id INTEGER,
        content TEXT,
        created_at TIMESTAMP DEFAULT current_timestamp
    )
""")

conn.execute("""
    INSERT INTO documents (id, filename, chunk_id, content)
    VALUES (1, 'report_2026.pdf', 0, 'DuckDBはとても便利なデータベースです。')
""")

クエリ

result = conn.execute("SELECT * FROM documents").fetchall()
print(result)
# [(1, 'report_2026.pdf', 0, 'DuckDBはとても便利なデータベースです。', datetime.datetime(...))]

PandasとのシームレスなDataFrame連携

ここがDuckDBの最大の魅力です。

DataFrameを直接SQLでクエリする

import pandas as pd
import duckdb

df = pd.DataFrame({
    "filename": ["doc_a.txt", "doc_b.txt", "doc_c.txt"],
    "chunk_id": [0, 0, 1],
    "token_count": [512, 128, 300],
    "source": ["web", "local", "web"],
})

# DataFrameをそのままSQLでクエリ
result = duckdb.sql("SELECT filename, token_count FROM df WHERE source = 'web'").df()
print(result)
    filename  token_count
0  doc_a.txt          512
2  doc_c.txt          300

dfという変数名をSQL内でそのままテーブル名として使えます。pd.read_sql()のような変換処理は不要です。

クエリ結果をDataFrameとして受け取る

conn = duckdb.connect("my_database.duckdb")
conn.execute("""
    CREATE TABLE IF NOT EXISTS logs (
        run_id VARCHAR,
        metric VARCHAR,
        value FLOAT
    )
""")

conn.execute("INSERT INTO logs VALUES ('run_001', 'accuracy', 0.92)")
conn.execute("INSERT INTO logs VALUES ('run_001', 'f1_score', 0.89)")
conn.execute("INSERT INTO logs VALUES ('run_002', 'accuracy', 0.95)")

# .df() でそのままDataFrameに変換
df_result = conn.execute("""
    SELECT run_id, AVG(value) as avg_metric
    FROM logs
    GROUP BY run_id
""").df()

print(df_result)
    run_id  avg_metric
0  run_001       0.905
1  run_002       0.950

DataFrameをそのままINSERT

import pandas as pd
import duckdb

df_new = pd.DataFrame({
    "run_id": ["run_003", "run_003"],
    "metric": ["accuracy", "f1_score"],
    "value": [0.91, 0.88],
})

conn = duckdb.connect("my_database.duckdb")

# DataFrameをテーブルに直接INSERT
conn.register("tmp_df", df_new)
conn.execute("INSERT INTO logs SELECT * FROM tmp_df")
conn.unregister("tmp_df")

SQLiteではdf.to_sql()を使いますが、DuckDBではconn.register()で一時的にDataFrameをテーブルとして登録し、そのままINSERTできます。


CSVやParquetを直接クエリできる

DuckDBのもう一つの強みは、外部ファイルをインポートせずに直接SQLで読めることです。

import duckdb

# CSVを直接クエリ
result = duckdb.sql("SELECT * FROM read_csv_auto('data.csv') LIMIT 5").df()

# Parquetも同様
result = duckdb.sql("SELECT COUNT(*) FROM read_parquet('embeddings.parquet')").df()

# 複数ファイルをまとめてクエリ(glob対応)
result = duckdb.sql("SELECT * FROM read_csv_auto('logs/*.csv')").df()

ETLの前処理など、「とりあえず中身を確認したい」という場面で非常に便利です。


実用例:複数Excelを結合して学習データを作る

私が実際にDuckDBを使っていたユースケースをもとに実用例を作ってみました。

複数のExcelファイルをそれぞれDBに取り込み、IDで結合・フィルタリングして必要なデータを取り出すという処理です。

まず、各Excelを読み込んでDBに登録します。

import duckdb
import pandas as pd
import glob

conn = duckdb.connect("project.duckdb")

# メインデータ(Excel)をDBに登録
df_main = pd.read_excel("main_data.xlsx")
df_main.columns = ["id", "title", "description", "file_name", "path", "use_flag"]

conn.execute("""
    CREATE TABLE IF NOT EXISTS main_data (
        id INTEGER,
        title VARCHAR,
        description VARCHAR,
        file_name VARCHAR,
        path VARCHAR,
        use_flag VARCHAR  -- 学習対象かどうかのフラグ(例: '')
    )
""")
conn.register("tmp", df_main)
conn.execute("INSERT INTO main_data SELECT * FROM tmp")
conn.unregister("tmp")

# 別ソースのメタデータ(複数Excel)をまとめてDBに登録
dfs = [pd.read_excel(f) for f in glob.glob("metadata/*.xlsx")]
df_meta = pd.concat(dfs, ignore_index=True)
df_meta.columns = ["item_id", "category", "summary"]

conn.execute("""
    CREATE TABLE IF NOT EXISTS metadata (
        item_id VARCHAR,
        category VARCHAR,
        summary VARCHAR
    )
""")
conn.register("tmp", df_meta)
conn.execute("INSERT INTO metadata SELECT * FROM tmp")
conn.unregister("tmp")

次に、SQLで結合・フィルタリングして必要なデータだけを取り出します。

# use_flagが'〇'のデータに絞り、item_idで結合して学習データを作成
df_train = conn.execute("""
    SELECT
        d.id,
        d.title,
        d.description,
        d.file_name,
        m.category,
        m.summary
    FROM main_data d
    JOIN metadata m
        ON d.id = m.item_id
    WHERE d.use_flag = ''
      AND d.file_name IS NOT NULL
    ORDER BY d.id
""").df()

print(df_train.shape)
df_train.to_csv("train_data.csv", index=False)

この程度の処理であればPandasでも問題ないですが、より複雑な条件で紐づけや絞り込みをやろうと思うとコードがかなり散らばってしまいます。
SQLで書くと処理の意図がひと目でわかりますし、あとから「このフラグも追加で絞りたい」といった要件変更にも WHERE 句を足すなどで簡単に対応できるのが便利です。


まとめ

ユースケース おすすめ
Webアプリのデータ永続化(トランザクション重視) SQLite
データ分析・集計・Pandas連携 DuckDB
CSVやParquetの探索的分析 DuckDB
RAGや実験ログの管理 DuckDB
Pythonだけで完結させたい(pip不要) SQLite

MLやAI開発の文脈では、Pandasとの相性の良さやCSVの直接クエリなど、DuckDBが活きる場面がかなり多いと感じています。

「SQLiteは知ってるけど重い処理だとちょっと辛い」「DataFrameとDBをいったりきたりするのが面倒」という方は、ぜひDuckDBを試してみてください。


参考リンク

2
2
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
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?