はじめに
Elixirを楽しんでいますか
楽しんでいますよね
この記事はRepo.aggregateを取り上げます。
ひとことで言うと、集計するです。
題材
Ectoは、Elixir製のデータベースライブラリです。
EctoのGetting Startedで取り上げられているpeople
テーブルを使います。
people = [
%Person{first_name: "Ryan", last_name: "Bigg", age: 28},
%Person{first_name: "John", last_name: "Smith", age: 27},
%Person{first_name: "Jane", last_name: "Smith", age: 26},
]
Enum.each(people, fn (person) -> Repo.insert(person) end)
各レコードのカラムは、first_name
とlast_name
に加えて、age
の3つのカラムがあります。
3人登録されています。
作り方は後述します。
Repo.aggregate
iex -S mix
でいろいろ楽しんでいきます。
あらかじめ以下を実行しておくとタイプ量が少なく済みます。
iex -S mix
iexが立ち上がります。
iex> alias Hello.Repo
iex> alias Hello.Friends.Person
iex> import Ecto.Query
友達何人いるのかな?
Elixirで計算
Repo.all(Person) |> Enum.count()
SQLで計算 -- Repo.aggregateを使う
Repo.aggregate(Person, :count)
こんなSQLが実行されています。
SELECT count(*) FROM "people" AS p0 []
友達の年齢を平均する
Elixirで計算
Repo.all(Person) |> Enum.map(& &1.age) |> Enum.sum |> Kernel./(Repo.all(Person) |> Enum.count())
SQLで計算 -- Repo.aggregateを使う
Repo.aggregate(Person, :avg, :age)
こんなSQLが実行されています。
SELECT avg(p0."age") FROM "people" AS p0 []
最年少はだれ?
Elixirで計算
Repo.all(Person) |> Enum.min_by(& &1.age)
SQLで計算 -- Repo.aggregateを使う
(from p in Person, where: p.age == ^Repo.aggregate(Person, :min, :age)) |> Repo.one
一人しかいないことがわかっているので、Repo.one
にしています。
複数いる場合は、例外が発生します。
Repo.all
を使うと複数人得ることができます。
Repo.aggregate(Person, :min, :age)
は外部変数扱いになるのでクエリの中で^
を付けています。
こんなSQLが実行されています。
SELECT min(p0."age") FROM "people" AS p0 []
SELECT p0."id", p0."age", p0."first_name", p0."last_name", p0."inserted_at", p0."updated_at" FROM "people" AS p0 WHERE (p0."age" = $1) [26]
最年長はだれ?
Elixirで計算
Repo.all(Person) |> Enum.max_by(& &1.age)
SQLで計算 -- Repo.aggregateを使う
(from p in Person, where: p.age == ^Repo.aggregate(Person, :max, :age)) |> Repo.one
一人しかいないことがわかっているので、Repo.one
にしています。
複数いる場合は、例外が発生します。
Repo.all
を使うと複数人得ることができます。
Repo.aggregate(Person, :max, :age)
は外部変数扱いになるのでクエリの中で^
を付けています。
こんなSQLが実行されています。
SELECT max(p0."age") FROM "people" AS p0 []
SELECT p0."id", p0."age", p0."first_name", p0."last_name", p0."inserted_at", p0."updated_at" FROM "people" AS p0 WHERE (p0."age" = $1) [28]
ひとこと
今回はデータが3件しかありませんので、どちらの方法で処理してもだいぶ広義の意味でのWebアプリケーションとしての性能、メモリ使用量に大差はないとおもいます。
データが大量になるとSQLに計算してもらったほうがよい場合が多いとおもいます。
環境構築
Phoenixは、Elixir製のWebアプリケーションフレームワークです。
Ectoは、Phoenixと必ずしも組み合わせる必要はありません。
そうではあるのですがここでは、Phoenixアプリを前提にします。
というのも、各種初期設定が楽ちんだからです。
Phoenixに詳しい方はこの節はどうぞ読み飛ばしてください。
PostgreSQL
Dockerで起動する例を示します。
docker run -d --rm -p 5432:5432 -e POSTGRES_USER=postgres -e POSTGRES_PASSWORD=postgres postgres:13
ローカルマシンにPostgreSQLをインストールして動作させる場合は、postgres
ユーザを作っておいてください。
パスワードはpostgres
が吉です。
https://db.just4fun.biz/?PostgreSQL/PostgreSQL%E3%81%A7%E3%83%A6%E3%83%BC%E3%82%B6%E3%81%AE%E3%83%AD%E3%83%BC%E3%83%AB%E3%82%92%E5%A4%89%E6%9B%B4%E3%81%99%E3%82%8B
が参考になるとおもいます。
Installation
Phoenix公式のInstallationを参考にします。
Elixir
私は、1.13.4-otp-24
を使っています。
この記事では説明しません。
Elixirをインストールしてください。
macOSをお使いの方はまずはbrew install elixir
で始めるのが早いとおもいます。
Phoenixのインストール
ターミナルでインストールを進めましょう。
mix local.hex
mix archive.install hex phx_new
[Yn]を聞かれたら、迷わずY
です。
楽しみましょう!!!
環境構築は以上です。
説明に使ったコードスニペットが実行できるプロジェクトの作成
ここからはこの記事の前半で説明につかったコードスニペットが実行できるプロジェクトを作ります。
Phoenixアプリの作成
mix phx.new hello
Fetch and install dependencies? [Yn]
には一旦n
で抜けます。
もちろん元気よくY
でもよいです。
setup
hello
ディレクトリへ移動して以下のコマンドを実行します。
cd hello
mix setup
mix setup
て何だ? という方は、mix.exs
をみてください。
以下を行ってくれます。
-
mix deps.get
: ライブラリのインストール -
mix ecto.create
: データベースの作成 -
mix ecto.migrate
: マイグレート -
mix run priv/repo/seeds.exs
: シードデータの投入
もうなんなら、mix setup
だけ覚えておけばなんとかなります。
mix phx.gen.schema
いきなり長めのコマンドで恐縮です。
mix phx.gen.schema Friends.Person people first_name:string last_name:string age:integer
このコマンドを実行すると以下のファイルができあがります!
lib/hello/friends/person.ex
はスキーマモジュールと呼ばれるコードです。
defmodule Hello.Friends.Person do
use Ecto.Schema
import Ecto.Changeset
schema "people" do
field :age, :integer
field :first_name, :string
field :last_name, :string
timestamps()
end
@doc false
def changeset(person, attrs) do
person
|> cast(attrs, [:first_name, :last_name, :age])
|> validate_required([:first_name, :last_name, :age])
end
end
20220710132740_create_people.exs
はマイグレーションファイルと呼ばれるものです。
20220710132740_create_people.exs
ファイル名の先頭は、作成日時によって変わります。
defmodule Hello.Repo.Migrations.CreatePeople do
use Ecto.Migration
def change do
create table(:people) do
add :first_name, :string
add :last_name, :string
add :age, :integer
timestamps()
end
end
end
シードデータの作成
priv/repo/seeds.exs
を編集します。
alias Hello.Repo
alias Hello.Friends.Person
people = [
Person.changeset(%Person{}, %{first_name: "Ryan", last_name: "Bigg", age: 28}),
Person.changeset(%Person{}, %{first_name: "John", last_name: "Smith", age: 27}),
Person.changeset(%Person{}, %{first_name: "Jane", last_name: "Smith", age: 26}),
]
Enum.each(people, fn (person) -> Repo.insert(person) end)
migrateとシードデータの投入
mix setup
さきほどご紹介したように、mix setup
ひとつでmigrate、シードデータの投入を行ってくれます。
【おまけ】データベースをリセットしたい
データベースをリセットしたい、そんなときは以下のコマンドを実行してください。
これもmix.exs
に定義されています。
mix ecto.reset
おわりに
この記事は、Repo.aggregateを楽しみました。
Ectoをさらに楽しむにはSQLと仲良くなるともっと楽しめるとおもいます。
Enjoy Elixir