はじめに
気づけばもう9月も半分が過ぎました。
この調子だと、2025年もあっという間に終わってしまいそうで、つい
遠い目になってしまいます。
そんな慌ただしい毎日に、ちょっとした「癒し」を求めて
今回は Elixir を触ってみようと思います。
ちなみに
Elixir は、Stack Overflow 2025 survey の
Admired and Desired 部門で Rust・Gleam に次ぐ第3位 にランクインしていました!
作者もリアクションされている 2位の Gleam ってなんだ?と調べてみたら
Elixir と同じく Erlang VM(BEAM)上で動く言語 なんですね。
また、今回は触れませんが Elixir の Web Framework Phoenix も
3年連続満足度1位を記録しています🏆
Elixir/Erlang が盛り上がってますね!
さて、今回 Elixir で何をしようかなと思い
以前 Rust で触れた DuckDB を 今度は Elixir で操作してみようと思います!
環境準備
プロジェクトを作成します名前は duckdb_demo とします
> mix new duckdb_demo
* creating README.md
* creating .formatter.exs
* creating .gitignore
* creating mix.exs
* creating lib
* creating lib/duckdb_demo.ex
* creating test
* creating test/test_helper.exs
* creating test/duckdb_demo_test.exs
Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:
cd duckdb_demo
mix test
Run "mix help" for more commands.
elixir で DuckDB を操作するためのライブラリ Duckdbex を mix.exs に追加します
defp deps do
[
{:duckdbex, "~> 0.3.9"}
]
end
mix.exs に追加したら依存ライブラリを取得します
> mix deps.get
Resolving Hex dependencies...
Resolution completed in 0.031s
New:
cc_precompiler 0.1.11
duckdbex 0.3.14
elixir_make 0.9.0
* Getting duckdbex (Hex package)
* Getting cc_precompiler (Hex package)
* Getting elixir_make (Hex package)
やってみる
Duckdbex を追加できたら今回はお手軽に iex から操作します
mix.exs に追加した duckdbex を使いたいので mix.exs をロードして iex を起動します
> iex -S mix
Erlang/OTP 28 [erts-16.0.2] [source] [64-bit] [smp:10:10] [ds:10:10:10] [async-threads:1] [jit] [dtrace]
==> elixir_make
Compiling 8 files (.ex)
Generated elixir_make app
==> cc_precompiler
Compiling 3 files (.ex)
Generated cc_precompiler app
==> duckdbex
Compiling 3 files (.ex)
Generated duckdbex app
==> duckdb_demo
Compiling 1 file (.ex)
Generated duckdb_demo app
Interactive Elixir (1.18.4) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)>
では早速やってみましょう
# DBを開く (メモリ上)
iex> {:ok, db} = Duckdbex.open()
# 接続を作成
iex> {:ok, conn} = Duckdbex.connection(db)
# テーブル作成
iex> {:ok, _res} = Duckdbex.query(conn, "CREATE TABLE users(id INTEGER, name VARCHAR);")
# データ挿入
iex> {:ok, _res} = Duckdbex.query(conn, "INSERT INTO users VALUES (1, 'nakamura'), (2, 'yuzuriha');")
# データ取得
iex> {:ok, ref} = Duckdbex.query(conn, "SELECT * FROM users;")
iex> rows = Duckdbex.fetch_all(ref)
iex> IO.inspect(rows)
# => [[1, "nakamura"], [2, "yuzuriha"]]
# 繋げるとこんな書き方もできます (直接パイプ演算子で繋げれないので then/2 を挟む)
iex> rows = Duckdbex.query(conn, "SELECT * FROM users;") |> then(fn {:ok, ref} -> Duckdbex.fetch_all(ref) end)
iex> IO.inspect(rows)
# => [[1, "nakamura"], [2, "yuzuriha"]]
できましたね 🎉
(今回は手軽さを求めて DB の永続化は行なっていません)
次に CSV ファイルを読み込んでみようと思います
まずは、CVS ファイルを用意
> cat users.csv
id,name
1,nakamura
2,yuzuriha
3,tanaka
4,yamada
5,sakamoto
ではこれを Duckdbex に投入して users テーブルを作ってみます
# データベースを開く
iex> {:ok, db} = Duckdbex.open()
iex> {:ok, conn} = Duckdbex.connection(db)
# users テーブルが存在している場合は DROP しておく
iex> {:ok, _ref} = Duckdbex.query(conn, "DROP TABLE IF EXISTS users")
# CSV ファイルを読み込み、テーブルを作成
iex> {:ok, _ref} = Duckdbex.query(conn, "CREATE TABLE users AS SELECT * FROM 'users.csv';")
CSV ファイルから作成した users テーブルの中身を確認してみます
iex> {:ok, ref} = Duckdbex.query(conn, "SELECT * FROM users")
iex> rows = Duckdbex.fetch_all(ref)
iex> IO.inspect(rows)
[
[1, "nakamura"],
[2, "yuzuriha"],
[3, "tanaka"],
[4, "yamada"],
[5, "sakamoto"]
]
# iex では、`iex> rows` としても rows の内容を確認することができます
# iex> rows
# [
# [1, "nakamura"],
# [2, "yuzuriha"],
# [3, "tanaka"],
# [4, "yamada"],
# [5, "sakamoto"]
# ]
できましたね 🎉
このままテーブルから取得した情報をリストで扱ってもよいのですが
カラム名と値がペアのマップに変換してみます
users テーブルのカラム名を取得したいので PRAGMA コマンドに table_info オプションを使ってテーブルの情報を取得します
iex> {:ok, ref} = Duckdbex.query(conn, "PRAGMA table_info('users')")
iex> cols = Duckdbex.fetch_all(ref)
[
[0, "id", "BIGINT", false, nil, false],
[1, "name", "VARCHAR", false, nil, false]
]
cols からカラム名を抽出します
iex> colnames = Enum.map(cols, fn [_cid, name | _] -> name end)
["id", "name"]
users テーブルから取得したデータ(rows)とカラム名(colnames)でマップを作ります
iex> users =
...> Enum.map(rows, fn row ->
...> Enum.zip(colnames, row) |> Enum.into(%{})
...> end)
[
%{"id" => 1, "name" => "nakamura"},
%{"id" => 2, "name" => "yuzuriha"},
%{"id" => 3, "name" => "tanaka"},
%{"id" => 4, "name" => "yamada"},
%{"id" => 5, "name" => "sakamoto"}
]
作成したマップ(users)からid:2のデータを取得してみます
iex> user = Enum.find(users, & &1["id"] == 2)
%{"id" => 2, "name" => "yuzuriha"}
# これでもOK
# iex> user = Enum.find(users, fn row -> row["id"] == 2 end)
# %{"id" => 2, "name" => "yuzuriha"}
続いて name を取得してみます
iex> name = user |> Map.get("name")
"yuzuriha"
# これでもOK
# iex> name = Map.get(user, "name")
# "yuzuriha"
Elixir ではデータを左から右へ渡すことでデータの流れをわかりやすくしています。なので、
Map.get(user, “name”) よりも
user |> Map.get(”name”) の方が Elixir らしい書き方になりますね!
おわり
Elixir と DuckDB を組み合わせると、ちょっとしたデータ遊びがとても気軽にできます。
複雑なセットアップも不要で、IEx からすぐに試せるのは「癒し」そのもの。
疲れたときに、ちょっと Elixir を開いてデータをいじってみる──
そんな小さな時間が、意外と良いリフレッシュになるかもしれません。