14
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 1 year has passed since last update.

はじめに

Elixirを楽しんでいますか:bangbang::bangbang::bangbang:
楽しんでいますよね:bangbang::bangbang::bangbang:

この記事はRepo.aggregateを取り上げます。
ひとことで言うと、集計するです。

題材

Ectoは、Elixir製のデータベースライブラリです。
EctoGetting 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_namelast_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はスキーマモジュールと呼ばれるコードです。

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ファイル名の先頭は、作成日時によって変わります。

priv/repo/migrations/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を編集します。

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:bangbang::bangbang::bangbang:

14
1
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
14
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?