Reputation: 17928
I have the following association: Artist has_many Songs
. Hence, I can get an artist's songs by doing:
artist.songs
However, I would like to get only the songs' genre:
artist.songs.pluck(:genre)
But, this genre may appear multiple times in the results; I would only like to get unique genre values. Unfortunately, pluck
isn't of any help here, since it returns an array, and calling uniq
on it won't tweak the ActiveRecord query, but the plain Array#uniq
.
I can do it like this:
artist.songs.select(:genre).uniq.pluck(:genre)
but I feel there must be a better way.
P.S.: From some minimal benchmarks, however, pluck + Array#uniq
seems a bit faster than select + uniq + pluck
.
Upvotes: 3
Views: 6773
Reputation: 121
This is in addition to the other answers. If you want to select unique rows based on a particular column, you can also use
artist.pluck('distinct on (col1) col1, col2, col3')
which is basically
select distinct on (col1) col1, col2, col3 from artist
you can also do this with select
artist.select('distinct on (col1) col1, col2, col3')
pluck gives array of values, where as select gives array of records.
Upvotes: 0
Reputation: 28305
Note that as of Rails 5, Relation#uniq
is deprecated; you should use Relation#distinct
instead.
I recently came across this same issue myself. Ideally, I would like the following code to work - but it does not generate SQL to fetch distinct values only:
artist.songs.distinct.pluck(:genre)
As a workaround however, you can instead do:
Song.where(artist: artist).distinct.pluck(:genre)
This will generate the following SQL:
SELECT DISTINCT "songs"."genre" FROM "songs" WHERE "songs"."artist_id" = 123
For convenience, you could also consider adding this as a model method such as:
class Artist < ApplicationRecord
def genres
Song.where(artist: self).distinct.pluck(:genre)
end
end
This achieves optimal performance, as the query is entirely in SQL - there are no ruby operations such as Array#map
or Array#uniq
being used.
Upvotes: 2
Reputation: 3633
I do it like this.
artist.songs.pluck('DISTINCT genre')
I am still in search of better way. I feel this is cleaner than select(:col).uniq.pluck(:col)
Upvotes: 0
Reputation: 464
If using the artist's songs
association, you can select distinct
on genre
, then map over the results to just return the strings:
artist.songs.select('distinct genre').map(&:genre)
# or...
artist.songs.select(:genre).uniq.map(&:genre) # uniq or distinct work
resulting query:
(0.2ms) SELECT distinct genre FROM "songs" WHERE "songs"."artist_id" = ? [["artist_id", 1]]
You could also use uniq if calling on the Song model directly while narrowing to the artist:
Song.where(artist: artist).uniq.pluck(:genre)
resulting query:
(0.2ms) SELECT DISTINCT "songs"."genre" FROM "songs" WHERE "songs"."artist_id" = 1
Both are equally and efficient and do the uniqueness operation in SQL and not in Ruby.
Upvotes: 6
Reputation: 1260
Model.uniq.pluck(:genre)
this generates SQl query SELECT DISTINCT
rather than querying .uniq to an array again.
Upvotes: 0