🦆

DuckDBが 1.2.0になったので機能を試したり調べたりしてみた

2025/02/19に公開

まえがき

Duckdbが1.20バージョンにアップデートされました。🙌
色々追加されたらしいのでとりあえず公式のブログ上の追加機能をいくつか試してみました。

参照元

https://duckdb.org/2025/02/05/announcing-duckdb-120.html

今回のアップデートのコードネーム

本リリースのコードネームは「Histrionicus」
北アメリカ、グリーンランド、アイスランド、東ロシアの寒い地域の川に住んでる
ハーレクインカモ(学名: Histrionicus histrionicus)...ということらしいです。
日本名だとシノリガモで冬になると北海道とか東北地方の沿岸部に飛んでくるらしいです。
画像を見ましたが自分は西の方にずっと住んでいるのでぱっと見では見たことない見た目でした。🦆

Pythonコードで共通部分

以下のコードでpython上でduckdbを使えるようにしてます。(いつものやつです。)

import duckdb

DuckDBのインメモリデータベースを作成
con = duckdb.connect(database=':memory:')

①RANDOM()のアップデートでより多くの種類のランダムな数を出力

今回のアップデートでは32bitでのランダム出力から64bitでの出力に変更されたらしいです。
なので
32ビットの最大値: 4294967295種類
が限界だったのが
64ビットの最大値: 18446744073709551615種類
になった?ということかと思い以下のコードを試しました。

とりあえず50 億個の乱数を生成する出力させてみました。(10分くらいかかるので注意)


# 大量のランダム数を生成 (例: 100万個)
query = """
SELECT 
    COUNT(DISTINCT random_value) AS unique_count,
    MIN(random_value) AS min_value,
    MAX(random_value) AS max_value
FROM (
    SELECT CAST(RANDOM() * (2 ** 63) AS BIGINT) AS random_value
    FROM range(1, 5000000000)
) AS subquery
"""

# クエリを実行して結果を取得
result = con.sql(query)
print(result)

max_32bit = (2 ** 32) - 1
print(f"32ビットの最大値: {max_32bit}")
max_64bit = (2 ** 64) - 1
print(f"64ビットの最大値: {max_64bit}")


"""
# 結果
┌──────────────┬────────────┬─────────────────────┐
│ unique_count │ min_value  │      max_value      │
│    int64     │   int64    │        int64        │
├──────────────┼────────────┼─────────────────────┤
│   4999999093 │ 3023944772 │ 9223372031664709632 │
└──────────────┴────────────┴─────────────────────┘

32ビットの最大値: 4294967295
64ビットの最大値: 18446744073709551615

"""

出力を見ると確かにユニーク値が32bitの上限の約43億種類を超えてるみたいです。
(これがアップデートの意味なのかかちょっと自信ないです。)

②mapでの返却値がリストから単一の値

SELECT map(['k'], ['v'])['k'];

kとvの辞書(マップ)を作ってキーのkで呼ぶので結果はvになります。
こののコードを走らせると、以前はこのような結果となってたみたいです。

旧:

['v']

アップデートでは以下のようになりました。
新:

┌────────────────────────────────────────────────────────┐
│ "map"(main.list_value('k'), main.list_value('v'))['k'] │
│                        varchar                         │
├────────────────────────────────────────────────────────┤
│ v                                                      │
└────────────────────────────────────────────────────────┘

これは今までmapを使用していた人にとっては結構大きな変更かもしれないですね。
リストから単一の文字列になっているので...

③既存のテーブルに主キーを追加する。

もうすでに作っているテーブルに主キーを追加できるらしいです!

data = pd.DataFrame({
    "id": [1, 2, 3, 4, 5],
    "name": ["Alice", "Bob", "Charlie", "David", "Eve"],
    "age": [25, 30, 35, 40, 45]
})

# テーブル作成(最初は主キーなし)
con.sql("CREATE OR REPLACE TABLE tbl AS SELECT * FROM data")

result_df = con.sql("SELECT * FROM tbl")

print(result_df)

con.sql("ALTER TABLE tbl ADD PRIMARY KEY (id);")

con.sql("INSERT INTO tbl VALUES (5, 'Frank', 50)")

このようにすでに作っているtblに主キーを追加した後に、
すでにある5番のidを持つデータを入れると...

con.sql("INSERT INTO tbl VALUES (5, 'Frank', 50)")
duckdb.duckdb.ConstraintException: Constraint Error: Duplicate key "id: 5" violates primary key constraint.

というように同じ値が重複してはいけない主キーの規則で弾かれます。

あと以前は以下のように一度消したレコードのidと同じidにデータを挿入することはできなかったらしいのですがそれも改善され現在ではできるようになってます。

con.sql("""
DELETE FROM tbl WHERE id = 1;
""")
con.sql("""
INSERT INTO tbl VALUES (1, 'Dareka',100);
""")
print(con.sql("SELECT * FROM tbl"))

以下のように挿入が成功してます。

┌───────┬─────────┬───────┐
│  id   │  name   │  age  │
│ int64 │ varchar │ int64 │
├───────┼─────────┼───────┤
│     2 │ Bob     │    30 │
│     3 │ Charlie │    35 │
│     4 │ David   │    40 │
│     5 │ Eve     │    45 │
│     1 │ Dareka  │   100 │
└───────┴─────────┴───────┘

④🦆🦆🦆

なんとcsvを読み込む際の区切り文字として🦆を使用できるようになりました(?)
これはどういうことかというと区切り文字として4バイトのモノ(🦆は4バイトらしいです)を使用できるようになったということみたいです。
なので以下のようなデータの入ったcsvを読み、

a🦆b
hello🦆world

以下のコードを走らせると...

print(con.sql("SELECT * FROM read_csv('csv_file1.csv',sep = '🦆')"))
┌─────────┬─────────┐
│    a    │    b    │
│ varchar │ varchar │
├─────────┼─────────┤
│ hello   │ world   │
└─────────┴─────────┘

こういう感じで綺麗に分割されます。

なおcsvを

a🇯🇵b
hello🇯🇵world

のようにして、

print(con.sql("SELECT * FROM read_csv('csv_file1.csv',sep = '🇯🇵')"))

とすると以下のエラーが出ます。

duckdb.duckdb.InvalidInputException: Invalid Input Error: The delimiter option cannot exceed a size of 4 bytes.

これは🇯🇵が4バイト超えてるから区切り文字として使えないよ、ということみたいです。(6バイトらしい)
絵文字のバイト数とか考えたことなかったのでおもしろいなあと思いました。

あと原文で絵文字がemojiって英語で書いてあるの地味にすごいなと思いました。
調べてみると、1999年にNTTドコモのケータイのiモードで使われたのが初だったらしいですね。
ガラケー時代から絵文字普通に使ってましたが知らなかった。。
2011年に世界標準化されてemojiとして広まったそうです。

⑤strict_mode で RFC 4180 に沿うようなcsvのみ読み込めるようにする。

csvでもいろいろなcsvがあって、

1,2,3
a,b,c,d,e,f,g
h
i,j,k,l

こういうテキトーなcsvもありますがアップデートでは厳格なCSVの標準規格(RFC 4180)
でないと読み込まないようになったようです。
なのでこの規格に合わないcsvも読みたい時は、

FROM read_csv('rfc_4180-defiant.csv', strict_mode = false);

とすれば読めます。
ただ信頼できるデータソースとして使いたいときにとりあえず雑なcsvをここで弾いておけるのはいいかもしれません。

⑥SQLの列名やテーブル名のエイリアスがASを使用せずに先頭でつけられるようになった。

普通SQLは以下のようにASで別名をつけます。

SELECT 
    some_long_and_winding_expression AS e1,
    t2.a_column_name AS e2
FROM
    long_schema.some_long_table_name AS t1,
    short_s.tbl AS t2;

今回のアップデートで以下のように列名をつけられるようになったみたいです。(確かに見やすい)

SELECT 
    e1: some_long_and_winding_expression,
    e2: t2.a_column_name 
FROM
    t1: long_schema.some_long_table_name,
    t2: short_s.tbl;

他にも色々アップデートがありますし、また近日のブログで詳細についても書かれるらしいので楽しみです。🙌

Discussion