Return Single Field with Rails Query
Rails 6 introduced ActiveRecord::Relation#pick
.
pick(:foo)
is equivalent to limit(1).pluck(:foo).first
.
A lot of people referring to .pluck
. It does have a caveat, it returns an array of the columns for records found.
If column is unique, like id
, it's not an issue, DB will send to Active Record same content, 1 record 1 column, using limit(1)
or not (using it is useless, and most certainly it will be discarded by DB query optimizer).
But if column is not unique, it could in fact be not the best performance. Because, first
is called after AR executes DB query, returning [0]
of all the results brought from DB. limit
, is called before that execution and it's sent to DB as part of the query., filtering the results.
Consider the following table and setup:
> Log
=> Log(id: integer, user_id, integer, event_type: integer, timestamp: datetime)
> user_newest_logs = Log.where(user_id: 1).order(timestamp: :desc)
Now, let's get the last event date time for the user_id = 1:
1) this is the nicest way. DB returns one record with 1 column for timestamp
> user_newest_logs.limit(1).pluck(:timestamp).first
SELECT timestamp FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC LIMIT 1
=> Fri, 09 Aug 2019 23:00:00 UTC +00:00
2) DB returns one record with all columns
> user_newest_logs.first
SELECT * FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC LIMIT 1
=> #<Log:0x00111111>
id: 920821839,
user_id: 1,
event_type: 1,
timestamp: Fri, 09 Aug 2019 23:00:00 UTC +00:00
> user_newest_logs.first.timestamp
SELECT * FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC LIMIT 1
=> Fri, 09 Aug 2019 23:00:00 UTC +00:00
3) DB returns the list of timestamps for N records
> user_newest_logs.pluck(:timestamp)
SELECT timestamp FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC
=> [Fri, 09 Aug 2019 23:00:00 UTC +00:00,
Fri, 09 Aug 2019 22:00:00 UTC +00:00,
Fri, 09 Aug 2019 21:00:00 UTC +00:00,
Fri, 09 Aug 2019 20:00:00 UTC +00:00,
...
]
> user_newest_logs.pluck(:timestamp).first
SELECT timestamp FROM logs WHERE logs.user_id = 1 ORDER BY logs.timestamp DESC
=> Fri, 09 Aug 2019 23:00:00 UTC +00:00
> user_newest_logs.pluck(:timestamp).count # NOT PRETTY HUH?!
=> 1523
So, if you do pluck(:column).first
can be actually worst than just find_by.column
.
User.where(:id => user_id).pluck(:user_name).first
Should do what you're trying to do.
pluck "accepts a column name as argument and returns an array of values of the specified column with the corresponding data type"