2016-01-31 4 views
3

У меня есть следующая ассоциация: Artist has_many Songs. Таким образом, я могу получить песни художника, выполнив:Pluck уникальные значения из ассоциации ActiveRecord has_many

artist.songs 

Однако, я хотел бы получить только жанр песен:

artist.songs.pluck(:genre) 

Но этот жанр может появляться несколько раз в результатах; Мне хотелось бы получить уникальные жанровые ценности. К сожалению, pluck здесь не поможет, так как он возвращает массив, а вызов uniq на нем не будет настраивать запрос ActiveRecord, а обычный Array#uniq.

я могу сделать это так:

artist.songs.select(:genre).uniq.pluck(:genre) 

, но я чувствую, что должен быть лучший способ.

P.S .: Из некоторых минимальных контрольных показателей, однако, pluck + Array#uniq кажется немного быстрее, чем select + uniq + pluck.

+0

Вы уверены, что 'uniq' не настраивает запрос ActiveRecord? –

ответ

5

При использовании songs ассоциации художника, вы можете select distinct на genre, затем сопоставьте над результатами только возвратить строки:

artist.songs.select('distinct genre').map(&:genre) 
# or... 
artist.songs.select(:genre).uniq.map(&:genre) # uniq or distinct work 

полученный запрос:

(0.2ms) SELECT distinct genre FROM "songs" WHERE "songs"."artist_id" = ? [["artist_id", 1]]

Вы также можете используйте uniq, если вы вызываете модель Song непосредственно при сужении к художнику:

Song.where(artist: artist).uniq.pluck(:genre)

полученный запрос:

(0.2ms) SELECT DISTINCT "songs"."genre" FROM "songs" WHERE "songs"."artist_id" = 1

Оба одинаково и эффективно и сделать уникальность работы в SQL, а не в Ruby.

+0

В качестве альтернативы вы можете использовать 'select + uniq + Array # map' вместо' select + uniq + pluck'. Они почти одинаковы; для большого количества связанных записей, я думаю, 'pluck' лучше, чем' Array # map'. – linkyndy

0
Model.uniq.pluck(:genre) 

это создает запрос SQL SELECT DISTINCT вместо запроса .uniq на массив снова.

+1

Я упомянул, что я использую это в ассоциации, которая не работает так же, как непосредственно на модели. – linkyndy

1

Я делаю это так.

artist.songs.pluck('DISTINCT genre') 

Я все еще ищу лучшего способа. Я считаю, что это чище, чем select(:col).uniq.pluck(:col)

1

Обратите внимание, что с Rails 5, Relation#uniq is deprecated; you should use Relation#distinct instead.

Я недавно наткнулся на эту же проблему сам.В идеале я хотел бы следующий код для работы - но он не генерирует SQL для извлечения только отдельных значений:

artist.songs.distinct.pluck(:genre) 

Как однако обходной путь, вы можете вместо этого сделать:

Song.where(artist: artist).distinct.pluck(:genre) 

Это будет генерировать следующий SQL:

SELECT DISTINCT "songs"."genre" FROM "songs" WHERE "songs"."artist_id" = 123 

Для удобства, вы также могли бы рассмотреть вопрос о включении этого в качестве способа модели, такие как:

class Artist < ApplicationRecord 
    def genres 
    Song.where(artist: self).distinct.pluck(:genre) 
    end 
end 

Это обеспечивает оптимальную производительность, так как запрос полностью находится в SQL - нет рубиновых операций, таких как Array#map или Array#uniq.

0

Это в дополнение к другим ответам. Если вы хотите, чтобы выбрать уникальные строки на основе определенного столбца, вы можете также использовать

artist.pluck('distinct on (col1) col1, col2, col3')

который в основном

select distinct on (col1) col1, col2, col3 from artist

вы также можете сделать это с помощью выбора

artist.select('distinct on (col1) col1, col2, col3')

pluck дает массив значений, где as select дает ar лучей записей.

Смежные вопросы