概要
の続きです。
前回、Ecto
マイグレーション〜スキーマ作成を行って、Elixir側からデータをMySQLに渡しました。
本記事では、ElixirでMySQLのCRUD操作(データ作成・読み出し・更新・削除)を実行していきます。
- 本記事シリーズにおける実行環境等については、シリーズ #1 の記事をご参照ください。
(事前準備)データベース再作成
事前準備として、前回つくったデータベースを削除して、あたらしく作り直します。
$ mix ecto.drop
Compiling 1 file (.ex)
The database for Friendsmysql.Repo has been dropped
$ mix ecto.create
The database for Friendsmysql.Repo has been created
$ mix ecto.migrate
13:36:23.224 [info] == Running 20200521003013 Friendsmysql.Repo.Migrations.CreatePeople.change/0 forward
13:36:23.226 [info] create table people
13:36:23.241 [info] == Migrated 20200521003013 in 0.0s
13:36:23.265 [info] == Running 20200522004701 Friendsmysql.Repo.Migrations.ApplyNotnullToNames.change/0 forward
13:36:23.265 [info] alter table people
13:36:23.278 [info] == Migrated 20200522004701 in 0.0s
- 結果確認(MySQL)
mysql> desc people;
+------------+---------------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------------------+------+-----+---------+----------------+
| id | bigint(20) unsigned | NO | PRI | NULL | auto_increment |
| first_name | varchar(255) | NO | | NULL | |
| last_name | varchar(255) | NO | | NULL | |
| age | int(11) | YES | | NULL | |
+------------+---------------------+------+-----+---------+----------------+
4 rows in set (0.00 sec)
mysql> select * from people;
Empty set (0.00 sec)
作りなおしたデータベースと、からっぽのpeople
テーブルが準備できました。
CRUD
それでは、IEx($ iex -S mix
)でCRUD操作を実行していきます。
Create(データ作成)
Repo.insert()
はじめに、データの作成(Create)です。
以下の計4レコードを用意します。
iex(1)> people = [
...(1)> %Friendsmysql.Person{first_name: "Ryan", last_name: "Bigg", age: 28},
...(1)> %Friendsmysql.Person{first_name: "John", last_name: "Smith", age: 27},
...(1)> %Friendsmysql.Person{first_name: "Jane", last_name: "Smith", age: 26},
...(1)> %Friendsmysql.Person{first_name: "im", last_name: "miolab", age: 28},
...(1)> ]
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: 28,
first_name: "Ryan",
id: nil,
last_name: "Bigg"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: 27,
first_name: "John",
id: nil,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: 26,
first_name: "Jane",
id: nil,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: 28,
first_name: "im",
id: nil,
last_name: "miolab"
}
]
Enum.each
でinsert()
していきます。
iex(2)> Enum.each(people, fn(p) -> Friendsmysql.Repo.insert(p) end)
13:45:40.520 [debug] QUERY OK db=5.9ms decode=1.3ms queue=7.0ms idle=1457.6ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [28, "Ryan", "Bigg"]
13:45:40.528 [debug] QUERY OK db=1.3ms queue=4.8ms idle=1475.8ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [27, "John", "Smith"]
13:45:40.531 [debug] QUERY OK db=0.4ms queue=1.8ms idle=1482.1ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [26, "Jane", "Smith"]
:ok
13:45:40.534 [debug] QUERY OK db=2.3ms queue=1.0ms idle=1484.5ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [28, "im", "miolab"]
people
テーブルにINSERT
できました。
ちなみに、前回の記事みたいに1レコードだけをシンプルにinsert()
実行するには、以下の通りです。
iex(3)> iiii = %Friendsmysql.Person{first_name: "iiii", last_name: "mimimi", age: 18}
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:built, "people">,
age: 18,
first_name: "iiii",
id: nil,
last_name: "mimimi"
}
iex(4)> Friendsmysql.Repo.insert(iiii)
13:46:54.648 [debug] QUERY OK db=2.5ms queue=3.0ms idle=1575.2ms
INSERT INTO `people` (`age`,`first_name`,`last_name`) VALUES (?,?,?) [18, "iiii", "mimimi"]
{:ok,
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 18,
first_name: "iiii",
id: 5,
last_name: "mimimi"
}}
- 結果確認(MySQL)
mysql> select * from people;
+----+------------+-----------+------+
| id | first_name | last_name | age |
+----+------------+-----------+------+
| 1 | Ryan | Bigg | 28 |
| 2 | John | Smith | 27 |
| 3 | Jane | Smith | 26 |
| 4 | im | miolab | 28 |
| 5 | iiii | mimimi | 18 |
+----+------------+-----------+------+
5 rows in set (0.02 sec)
Create
操作ができました。
Read(データ読み出し)
つづいて、登録データの読み出し・抽出(Read)を実行していきます。
全レコード 取得
Repo.all()
iex(5)> Friendsmysql.Person |> Friendsmysql.Repo.all
13:51:56.586 [debug] QUERY OK source="people" db=0.3ms queue=5.9ms idle=1513.0ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 []
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "im",
id: 4,
last_name: "miolab"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 18,
first_name: "iiii",
id: 5,
last_name: "mimimi"
}
]
レコード全件を読み出しました。
(SELECT * FROM table
相当)
id
を指定してレコード取得
Repo.get_by()
iex(6)> Friendsmysql.Person |> Friendsmysql.Repo.get(3)
13:53:31.176 [debug] QUERY OK source="people" db=6.2ms queue=0.3ms idle=1102.8ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`id` = ?) [3]
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
}
id: 3
に該当するレコードを抽出しました。
カラム名とフィールドに基づいてレコードを取得
Repo.where()
iex(7)> Friendsmysql.Person |> Friendsmysql.Repo.get_by(first_name: "im")
13:54:58.257 [debug] QUERY OK source="people" db=1.8ms queue=2.0ms idle=1185.3ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = ?) ["im"]
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "im",
id: 4,
last_name: "miolab"
}
iex(8)> Friendsmysql.Person |> Friendsmysql.Repo.get_by(age: 18)
13:56:15.218 [debug] QUERY OK source="people" db=1.9ms queue=6.1ms idle=1269.7ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`age` = ?) [18]
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 18,
first_name: "iiii",
id: 5,
last_name: "mimimi"
}
-
first_name: im
に該当するレコードを1件 -
age: 18
に該当するレコードを1件
それぞれ抽出しました。
Ecto.Queryでフィルタリング抽出
もう少しつっこんだ条件抽出のクエリを書いてみます。
require Ecto.Query
で事前準備をします。
iex(9)> require Ecto.Query
Ecto.Query
カラム名とフィールドに基づいて重複レコードを取得
それでは、Ecto.Query
でまず重複するレコードを抽出します。
具体例として、last_name
がSmith
さんが2人いるので、こちらのお二方を抽出することとします。
なお、先ほどのget_by()
ではレコード1件の抽出しかできないため、以下のようにEcto.Query
でのオペレーションが必要となります。
iex(10)> Friendsmysql.Person |> Ecto.Query.where(last_name: "Smith") |> Friendsmysql.Repo.all
14:00:10.899 [debug] QUERY OK source="people" db=0.2ms queue=5.0ms idle=1827.0ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`last_name` = 'Smith') []
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
}
]
もしくは、
iex(11)> Ecto.Query.from(p in Friendsmysql.Person, where: p.last_name == "Smith") |> Friendsmysql.Repo.all
14:00:57.013 [debug] QUERY OK source="people" db=5.5ms idle=1940.3ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`last_name` = 'Smith') []
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
}
]
2人のSmithさんを抽出しました。
絞り込み抽出
複数の抽出条件を組み合わせ実行します。
クエリをパイプライン演算子で繋いでいきます。
last_name
条件抽出クエリに、first_name
条件抽出クエリをつないで、AND
検索っぽくします。
iex(12)> Friendsmysql.Person |> Ecto.Query.where(last_name: "Smith") |> Ecto.Query.where(age: 27) |> Friendsmysql.Repo.all
14:02:44.259 [debug] QUERY OK source="people" db=0.6ms queue=3.9ms idle=1184.8ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`last_name` = 'Smith') AND (p0.`age` = 27) []
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
}
]
last_name
がSmithで、かつage
が27の人を抽出しました。
その他、抽出いろいろ
適当にいくつか例を実行します。
参考のため、それぞれに、相当するSQLを添えています。
- 事前準備
iex(13)> import Ecto.Query, only: [from: 2]
Ecto.Query
-
全レコードの
age
平均値を抽出
(SELECT AVG(age) FROM people
)
iex(14)> query_avg = from p in "people",
...(14)> select: avg(p.age)
#Ecto.Query<from p0 in "people", select: avg(p0.age)>
iex(15)> Friendsmysql.Repo.all(query_avg)
14:14:19.725 [debug] QUERY OK source="people" db=18.2ms queue=1.8ms idle=1914.1ms
SELECT avg(p0.`age`) FROM `people` AS p0 []
[#Decimal<25.4000>]
-
age > 20
の人のfirst_name
を抽出
(SELECT first_name FROM people WHERE age > 20
)
iex(16)> query_over_twenty = from p in "people",
...(16)> where: p.age > 20,
...(16)> select: p.first_name
#Ecto.Query<from p0 in "people", where: p0.age > 20, select: p0.first_name>
iex(17)> Friendsmysql.Repo.all(query_over_twenty)
14:17:21.281 [debug] QUERY OK source="people" db=1.1ms queue=3.6ms idle=1491.4ms
SELECT p0.`first_name` FROM `people` AS p0 WHERE (p0.`age` > 20) []
["Ryan", "John", "Jane", "im"]
-
age > 26
の人をage降順
で並びかえて抽出
(SELECT id, first_name, age FROM people WHERE age > 26 ORDER BY age DESC
)
iex(18)> query_over_twentysix = from p in "people",
...(18)> where: p.age > 26,
...(18)> order_by: [desc: :age],
...(18)> select: [:id, :first_name, :age]
#Ecto.Query<from p0 in "people", where: p0.age > 26, order_by: [desc: p0.age],
select: [:id, :first_name, :age]>
iex(19)> Friendsmysql.Repo.all(query_over_twentysix)
14:19:08.225 [debug] QUERY OK source="people" db=4.1ms queue=3.9ms idle=1431.1ms
SELECT p0.`id`, p0.`first_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`age` > 26) ORDER BY p0.`age` DESC []
[
%{age: 28, first_name: "Ryan", id: 1},
%{age: 28, first_name: "im", id: 4},
%{age: 27, first_name: "John", id: 2}
]
ほんの一例ですが、いずれも問題なく抽出実行できました。
この他にも、以下公式リファレンスにEcto.Query
の抽出もろもろについて詳細がありますので、ご覧いただき試してみられると良いかとおもいます。
- Hexdocs
(補足)Ecto.Query
内での変数使用の際は^
が必要
変数をクエリ内で展開する際には、^
(ピン演算子)が必要になります。
iex(20)> first_name_im = "im"
"im"
iex(21)> Friendsmysql.Person |> Ecto.Query.where(first_name: ^first_name_im) |> Friendsmysql.Repo.all
14:24:47.240 [debug] QUERY OK source="people" db=2.0ms idle=1170.0ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = ?) ["im"]
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "im",
id: 4,
last_name: "miolab"
}
]
first_name_im
で束縛した文字列im
をEcto.Query
で展開する際、適用変数を^first_name_im
の形にします。
Update(データ更新)
CRUD
のつづきで、次はデータの更新を実行します。
Repo.update()
iex(1)> require Ecto.Query
Ecto.Query
iex(2)> ryan = Friendsmysql.Person |> Ecto.Query.where(first_name: "Ryan") |> Friendsmysql.Repo.one
14:28:40.395 [debug] QUERY OK source="people" db=0.3ms decode=1.0ms queue=2.7ms idle=1582.4ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = 'Ryan') []
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}
上記で準備したRyanさんのデータage: 28
を、29
に更新してみます。
iex(3)> change_age_twentynine = Friendsmysql.Person.changeset(ryan, %{age: 29})
#Ecto.Changeset<
action: nil,
changes: %{age: 29},
errors: [],
data: #Friendsmysql.Person<>,
valid?: true
>
iex(4)> Friendsmysql.Repo.update(change_age_twentynine)
14:31:55.745 [debug] QUERY OK db=7.2ms queue=4.1ms idle=1929.9ms
UPDATE `people` SET `age` = ? WHERE `id` = ? [29, 1]
{:ok,
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}}
結果を確認します。
iex(5)> Friendsmysql.Person |> Friendsmysql.Repo.get_by(first_name: "Ryan")
14:32:16.250 [debug] QUERY OK source="people" db=0.5ms queue=0.8ms idle=1442.8ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 WHERE (p0.`first_name` = ?) ["Ryan"]
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 29,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}
age: 29
に更新がされています。
Delete(データ削除)
CRUD
の最後に、データの削除を実行します。
Repo.delete()
先ほどのIEx
で実行したUpdate
のつづきに、コードを書いていきます。
Ryanさん レコードを、ごめんなさいですが削除します。
iex(6)> Friendsmysql.Repo.delete(ryan)
14:35:11.201 [debug] QUERY OK db=4.8ms queue=4.8ms idle=1386.5ms
DELETE FROM `people` WHERE `id` = ? [1]
{:ok,
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:deleted, "people">,
age: 28,
first_name: "Ryan",
id: 1,
last_name: "Bigg"
}}
削除できているか全件抽出して確認します。
iex(7)> Friendsmysql.Person |> Friendsmysql.Repo.all
14:35:52.982 [debug] QUERY OK source="people" db=0.4ms queue=2.0ms idle=173.7ms
SELECT p0.`id`, p0.`first_name`, p0.`last_name`, p0.`age` FROM `people` AS p0 []
[
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 27,
first_name: "John",
id: 2,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 26,
first_name: "Jane",
id: 3,
last_name: "Smith"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 28,
first_name: "im",
id: 4,
last_name: "miolab"
},
%Friendsmysql.Person{
__meta__: #Ecto.Schema.Metadata<:loaded, "people">,
age: 18,
first_name: "iiii",
id: 5,
last_name: "mimimi"
}
]
Ryanさんのレコードが削除されています。
終わり
IEx
でElixir(Ecto)によるMySQLのCRUD操作(データ作成・読み出し・更新・削除)を実行しました。
- Ectoはドキュメントも詳しく、パイプラインでガシガシ書けて良い感じなので、個人的にも色々もっと調べてみようと思ってます。
- もしかしたら続きを何かしら書くかもしれません。