はじめに
pluck
とselect
の使いどころについて整理した学習記録です。どちらも特定カラムを取得するメソッドですが、返り値や使える機能、パフォーマンスに違いがあります。
比較のまとめ
項目 | pluck |
select |
---|---|---|
返り値の型 | 配列(Array) | ActiveRecord::Relation |
モデルメソッド利用 | 不可 | 可能 |
メモリ効率 | 高い(軽量) | やや低い(オブジェクト生成あり) |
サブクエリ相性 | △ 配列展開による IN 句 が巨大化しやすい |
◎ SQLとして組み込める |
返り値の型
pluck
- 配列を返す
irb(main):001:0> roles = User.pluck(:role)
(1.2ms) SELECT `users`.`role` FROM `users`
=> ["admin", "editor", "viewer"]
irb(main):002:0> roles.class
=> Array
irb(main):003:0> roles.first.class
=> String
select
- 指定カラムのみを持つActiveRecord::Relationを返す
irb(main):001:0> users = User.select(:role)
User Load (1.5ms) SELECT `users`.`role` FROM `users`
=>
[#<User:0x00007f8c8d7021c0 id: nil, role: "admin">, #<User:0x00007f8c8d702120 id: nil, role: "editor">]
irb(main):002:0> users.class
=> User::ActiveRecord_Relation
irb(main):003:0> users.first.class
=> User(id: integer, name: string, email: string, role: string, created_at: datetime, updated_at: datetime)
モデルメソッドの利用
app/models/user.rb
class User < ApplicationRecord
def admin?
role == "admin"
end
end
pluck
- 値(String)を返すためモデルメソッドは使えない
irb(main):001:0> role = User.pluck(:role).first
(2.4ms) SELECT `users`.`role` FROM `users`
=> "admin"
irb(main):002:0> role.admin?
(irb):2:in `<main>': undefined method `admin?' for "admin":String (NoMethodError)
select
- モデルインスタンスを返すためモデルメソッドが使える
irb(main):003:0> user = User.select(:role).first
User Load (1.5ms) SELECT `users`.`role` FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: nil, role: "admin">
irb(main):004:0> user.admin?
=> true
サブクエリとの相性
pluck
- SQLの
IN
句に配列展開される - 件数が多いと
IN
句が巨大化しパフォーマンス低下
irb(main):004:0> ids = User.where(role: "admin").pluck(:id)
irb(main):005:0> posts = Post.where(user_id: ids)
irb(main):006:0> posts.to_sql
=> "SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1, 2, 3, ..., 9999)"
select
- サブクエリとして組み込まれる
- DB側で処理され効率的
irb(main):001:0> subquery = User.select(:id).where(role: "admin")
irb(main):002:0> posts = Post.where(user_id: subquery)
irb(main):003:0> posts.to_sql
=> "SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (SELECT `users`.`id` FROM `users` WHERE `users`.`role` = 'admin')"
おわりに
- 値だけ取得したい場合は
pluck
が軽量で高速 - モデルの機能を使いたい場合やSQLに組み込みたい場合は
select
- 目的に応じて使い分けることが重要
select 利用時の注意点
1. 取得していないカラムにはアクセスできない
irb(main):001:0> user = User.select(:role).first
User Load (2.6ms) SELECT `users`.`role` FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
=> #<User id: nil, role: "admin">
irb(main):002:0> user.email
ActiveModel::MissingAttributeError: missing attribute: email
2. includes
と併用する場合は外部キーを含める
irb(main):001:0> user = User.select(:id, :name).includes(:posts).first
User Load (1.4ms) SELECT `users`.`id`, `users`.`name` FROM `users` ORDER BY `users`.`id` ASC LIMIT 1
Post Load (1.0ms) SELECT `posts`.* FROM `posts` WHERE `posts`.`user_id` IN (1)
=> #<User id: 1, name: "Hanako">
irb(main):002:0> user.posts
=> [#<Post id: 101, title: "Hello World", user_id: 1>, ...]