2026年10月21日、分析用のデータベースDuckDBの2.0.0が公開されました。1.0から続いてきた1.x系から、初めてメジャーバージョンが上がったことになります。
本体では、表に行が追加されたときなどに自動で処理を動かすトリガーに対応しました。Python向けのパッケージも同時に2.0.0になり、Python 3.11以上が必要になったほか、いくつかの使い方が変わっています。
DuckDBをpandasやPolarsと一緒に使っている方は、上げるだけでエラーになる箇所があるかもしれません。今回は、1.5.6と2.0.0で同じコードを動かしながら、何が変わったのかを見ていきます。
まずはDuckDBとはどんなものか¶
最初に、DuckDBを知らない方のために、どんな道具なのかを簡単に紹介します。DuckDBは、分析のための集計を得意とするデータベースです。
いちばんの特徴は、サーバーを立てずに、Pythonのプログラムの中でそのまま動くことです。pip install duckdbで入れるだけで、CSVやParquetのファイル、pandasのDataFrameに対して、SQLで集計を書けます。
pandasのDataFrameにSQLを書いてみる¶
実際に、pandasのDataFrameをSQLで集計してみましょう。変数の名前をそのまま表の名前として書けるのが、DuckDBの便利なところです。
import duckdb
import pandas as pd
df = pd.DataFrame({
"item": ["りんご", "みかん", "りんご", "ぶどう", "みかん"],
"price": [120, 80, 130, 300, 90],
})
# pandasのDataFrameに、そのままSQLを書ける
print(duckdb.sql("""
SELECT item, COUNT(*) AS n, AVG(price) AS avg_price
FROM df
GROUP BY item
ORDER BY avg_price DESC
""").fetchall())
DuckDB 2.0.0とpandas 3.0.6を入れた仮想環境で、手元のPython 3.11で実行した結果です。
[('ぶどう', 1, 300.0), ('りんご', 2, 125.0), ('みかん', 2, 85.0)]
pandasの書き方に慣れていない方でも、SQLを知っていればすぐに集計できます。pandasそのものについては、こちらで紹介しています。【関連記事】Pythonのpandasとは?
2.0で何が変わったのかを整理しよう¶
続いて、2.0.0の主な変更を表にまとめます。内容は、DuckDB本体と、Python向けパッケージduckdb-pythonの、GitHubにある2.0.0のリリースノートにもとづいています。
| 分類 | 主な変更 |
|---|---|
| SQLの新機能 | トリガー、JSONを書き換える関数、NEAREST BYという結合など |
| SQLの動きの変更 | 正規表現の演算子がPostgreSQLと同じ動きに |
| 速度 | ParquetやCSVの非同期の読み書き |
| Pythonの対応範囲 | Python 3.10のサポートを終了し、3.11以上が必要に |
| Pythonの使い方 | 一部のメソッドの削除、文字列や小数の扱いの変更 |
PyPIの記録によると、duckdbの2.0.0は2026年10月21日に公開されています。日本時間では、21日の夜にあたります。
リリースノートによると、2.0.0にはCyanopteraという愛称が付いています。南北アメリカの西部にいる鴨の名前から取ったものだと説明されています。
SQLでトリガーが使えるようになった¶
本体の目玉の1つが、トリガーへの対応です。トリガーとは、表に行が追加されたり、更新や削除をされたりしたときに、自動で別の処理を動かす仕組みのことです。
リリースノートには、AFTER INSERTやAFTER DELETEなどの、行ごとに動くトリガーへの対応が並んでいます。
1.5.6と2.0.0で動かしてみた¶
実際に試してみましょう。注文の表に行が追加されるたびに、記録用の表へメッセージを残すトリガーを作ります。
import duckdb
con = duckdb.connect()
con.sql("CREATE TABLE orders (id INTEGER, item VARCHAR, price INTEGER)")
con.sql("CREATE TABLE logs (msg VARCHAR)")
# ordersに行が追加されるたびに、logsへ記録を残すトリガー
con.sql("""
CREATE TRIGGER add_log AFTER INSERT ON orders
FOR EACH ROW
INSERT INTO logs VALUES ('追加: ' || NEW.item || ' ' || NEW.price || '円')
""")
con.sql("INSERT INTO orders VALUES (1, 'りんご', 120)")
con.sql("INSERT INTO orders VALUES (2, 'みかん', 80)")
print(con.sql("SELECT * FROM logs").fetchall())
1.5.6と2.0.0を、それぞれ新しい仮想環境に入れて実行した結果です。1.5.6のエラーは、最後の行だけを載せています。
DuckDB 1.5.6
_duckdb.ParserException: Parser Error: syntax error at or near "TRIGGER"
DuckDB 2.0.0
[('追加: りんご 120円',), ('追加: みかん 80円',)]
1.5.6では、TRIGGERという書き方そのものが受け付けられませんでした。2.0.0では、2回の追加に合わせて、記録が2行自動で残っています。
NEW.itemのように書くと、追加された行の値を使えます。記録や集計の更新を、書き忘れなく自動で行えるのがトリガーの嬉しいところです。
JSONを書き換える関数と、正規表現の変更¶
SQLの関数にも、いくつか変更が入っています。ここでは、初心者にも関係しそうな2つを紹介します。
json_set()などが加わった¶
リリースノートによると、JSONの値を書き換えるjson_set()、json_insert()、json_replace()、json_remove()の4つの関数が加わりました。2.0.0で試すと、次のようになりました。
import duckdb
print(duckdb.sql("""
SELECT json_set('{"name": "taro"}'::JSON, '$.age', 20::JSON)
""").fetchall())
[('{"name":"taro","age":20}',)]
元のJSONに、ageという項目が加わりました。手元で試したところ、型を付けずに数値を渡すと、合う関数がないというエラーになったので、::JSONのように型を明示しておくと確実です。
JSONの基本については、こちらで紹介しています。【関連記事】PythonでJSONを扱う方法
正規表現の~が、部分一致になった¶
もう1つは、正規表現の演算子の変更です。リリースノートには、正規表現の演算子をPostgreSQLの動きに合わせたと書かれています。
SELECT 'tokyo' ~ 'ky'、つまりtokyoにkyが含まれるかを、2つの版で比べました。
| 版 | 結果 | 意味 |
|---|---|---|
| 1.5.6 | False | 文字列全体がパターンと一致するかを見ていた |
| 2.0.0 | True | パターンを含むかを見るように |
エラーを出さずに、結果だけが変わる変更なので、~を使ったSQLがある方は特に注意してください。2.0.0には、regex_match_operator_semanticsという設定もあり、fullにすると、手元では1.5.6と同じ結果に戻りました。
Pythonからの使い方で変わったこと¶
ここからは、Python向けパッケージの変更です。リリースノートの冒頭には、Python 3.11以上に対応し、3.10のサポートを終えたと書かれています。
主要なライブラリが3.10を外していく流れは、先日の記事でもまとめました。DuckDBもその流れに加わった形です。【関連記事】Python 3.10のサポート終了から2週間、主要ライブラリの対応状況
同じコードで、結果がこう変わった¶
使い方の変更を、まとめて試してみました。
from decimal import Decimal
import duckdb
con = duckdb.connect()
con.sql("CREATE TABLE users (name VARCHAR, city VARCHAR)")
con.sql("INSERT INTO users VALUES ('taro', 'tokyo'), ('hanako', 'osaka')")
# 1. 指数つきのDecimalを渡す
print(con.execute("SELECT ?::VARCHAR", [Decimal("1E+2")]).fetchall())
# 2. 列と文字列を比べる
rel = con.table("users")
try:
print(rel.filter(duckdb.ColumnExpression("city") == "tokyo").fetchall())
except Exception as e:
print(type(e).__name__)
# 3. リレーションのfetchone()
try:
print(rel.fetchone())
except Exception as e:
print(type(e).__name__)
1.5.6と2.0.0で実行した結果です。
DuckDB 1.5.6
[('1.00',)]
BinderException
('taro', 'tokyo')
DuckDB 2.0.0
[('100',)]
[('taro', 'tokyo')]
AttributeError
3つとも、結果が変わりました。それぞれ順番に見ていきましょう。
3つの変更の中身¶
1つ目は小数です。リリースノートによると、Decimal("1E+2")は2.0.0では100として届き、1.5では1.00になっていたと説明されています。
2つ目は、文字列との比較です。1.5.6では、"tokyo"という文字列を列の名前と解釈し、そんな列はないというエラーになっていました。
2.0.0では、ただの文字列はただの値として扱うようになり、自然に書いたとおりに動きます。
3つ目は、リレーションと呼ばれる、con.table()やcon.sql()が返すオブジェクトのfetchone()です。2.0.0では削除され、エラーの文面で、代わりにrel.limit(1).fetchall()を使うよう案内されました。
Arrowへの書き出し方も変わった¶
リリースノートによると、con.execute()の結果に付いていたfetch_arrow_table()やpl()なども削除されました。代わりに、con.sql(query).to_arrow_table()のように書くよう案内されています。
手元でも、1.5.6ではfetch_arrow_table()は非推奨の警告を出しつつ動きましたが、2.0.0ではAttributeErrorになりました。警告が出ていたものが、予告どおり取り除かれたわけです。
Polarsと組み合わせている方は、こちらの記事もあわせてどうぞ。【関連記事】Polars 2.0がリリース
速さとデータベースファイルの互換性¶
リリースノートには、速さについての数字も載っています。代表的なものを表にまとめます。
| 項目 | 1.5と比べた時間 |
|---|---|
| 50万行の数値をnumpyへ書き出す | 0.31倍 |
| 同じくpandasへ | 0.43倍 |
| 同じくArrowへ | 0.24倍 |
| 297個のベンチマーク全体の幾何平均 | 0.74倍 |
数字が小さいほど速いという意味です。リリースノートによると、297個のうち161個が速く、77個は遅く、48個は変わらなかったそうです。
一方で、遅くなったものも正直に書かれています。connect()は8.4ミリ秒から16.0ミリ秒になり、その理由は、データベースが既定で最大256個の非同期I/O用のスレッドを立ち上げるようになったためと説明されています。
古い版では、新しいファイルを開けない¶
データベースをファイルに保存して使っている方は、互換性も気になりますよね。手元で、1.5.6で作ったファイルを2.0.0で開くと、問題なく読めました。
ところが逆に、2.0.0で作ったファイルを1.5.6で開くと、新しい版で作られたファイルなので読めない、というIOExceptionになりました。チームの中で版がそろっていないと、片方の人だけファイルを開けないということが起こりえます。
上げる前にやっておきたいこと¶
最後に、DuckDBを2.0へ上げる前に確かめておきたいことを整理します。リリースノートの移行の手引きを参考に、手元で試した結果も加えています。
| 順番 | 確認すること |
|---|---|
| 1 | Python 3.11以上を使っているか |
| 2 | execute()の結果でfetch_arrow_table()やpl()を使っていないか |
| 3 | リレーションでfetchone()やfetchmany()を使っていないか |
| 4 | SQLの~を、全体一致のつもりで使っていないか |
| 5 | データベースファイルを、古い版の人と共有していないか |
私は10年ほどエンジニアとして開発に関わってきましたが、集計用のツールを上げたら一部の数字だけが変わっていて、原因を探すのに丸一日かかったことがあります。更新の前後で、代表的な集計の結果を見比べる習慣をつけておくと安心です。
DuckDBに限らず、データベースのライブラリはrequirements.txtなどで版を固定しておくのが基本です。PythonからSQLを扱う別の方法としては、SQLAlchemyもあります。【関連記事】PythonのSQLAlchemyとは?
まとめ¶
2026年10月21日、DuckDB 2.0.0が公開されました。SQLではトリガーやJSONを書き換える関数が加わり、Python向けパッケージはPython 3.11以上が必要になって、一部のメソッドの削除や値の扱いの変更が入っています。
サーバーなしでSQLの集計ができる手軽さはそのままに、できることが一段と増えました。上げる前に、上の確認ポイントを一度見直してみてください。
ここまでお読みいただきありがとうございました。