11

各プレイリストの最初の曲をプレイリストの配列に結合しようとしていますが、効率的な解決策を見つけるのにかなり苦労しています。

私は次のモデルを持っています:

class Playlist < ActiveRecord::Base
  belongs_to :user
  has_many :playlist_songs
  has_many :songs, :through => :playlist_songs
end

class PlaylistSong < ActiveRecord::Base
  belongs_to :playlist
  belongs_to :song
end

class Song < ActiveRecord::Base
  has_many :playlist_songs
  has_many :playlists, :through => :playlist_songs
end

私はこれを取得したいと思います:

playlist_name  |  song_name
----------------------------
chill          |  baby
fun            |  bffs

結合を介してこれを行う効率的な方法を見つけるのにかなり苦労しています。

更新* ***

Shane Andrade は私を正しい方向に導いてくれましたが、私はまだ欲しいものを正確に得ることができません.

これは私が得ることができた限りです:

playlists = Playlist.where('id in (1,2,3)')

playlists.joins(:playlist_songs)
         .group('playlists.id')
         .select('MIN(songs.id) as song_id, playlists.name as playlist_name')

これは私に与えます:

playlist_name  |  song_id
---------------------------
chill          |  1

これは近いですが、最初の曲 (ID による) の名前が必要です。

4

10 に答える 10

12

Postgresqlを使用していると仮定します

Playlist.
  select("DISTINCT ON(playlists.id) playlists.id, 
          songs.id song_id, 
          playlists.name, 
          songs.name first_song_name").
  joins(:songs).
  order("id, song_id").
  map do |pl|
    [pl.id, pl.name, pl.first_song_name]
  end
于 2013-03-14T22:01:59.777 に答える
2

上記の結合で行っていることは、特定の名前と特定の曲を持つすべてのプレイリストを見つけたい場合に行うことです。各プレイリストからプレイリスト名と最初の曲名を収集するには、次のようにします。

Playlist.includes(:songs).all.collect{|play_list| [playlist.name, playlist.songs.first.name]}

これは、この形式で配列を返します[[playlist_name, first song_name],[another_playlist_name, first_song_name]]

于 2013-03-08T20:21:35.397 に答える
2

この問題は、「最初」の定義をより厳密にすることで改善されると思います。モデルにpositionフィールドを追加することをお勧めします。PlaylistSongその時点で、次のことを簡単に実行できます。

Playlist.joins(:playlist_song).joins(:song).where(:position => 1)
于 2013-03-12T00:26:30.290 に答える
0

試す:

PlaylistSong.includes(:song, :playlist).
find(PlaylistSong.group("playlist_id").pluck(:id)).each do |ps|
  puts "Playlist: #{ps.playlist.name}, Song: #{ps.song.name}"
end

(0.3ms)  SELECT id FROM `playlist_songs` GROUP BY playlist_id
PlaylistSong Load (0.2ms)  SELECT `playlist_songs`.* FROM `playlist_songs` WHERE `playlist_songs`.`id` IN (1, 4, 7)
Song Load (0.2ms)  SELECT `songs`.* FROM `songs` WHERE `songs`.`id` IN (1, 4, 7)
Playlist Load (0.2ms)  SELECT `playlists`.* FROM `playlists` WHERE `playlists`.`id` IN (1, 2, 3)

Playlist: Dubstep, Song: Dubstep song 1
Playlist: Top Rated, Song: Top Rated song 1
Playlist: Last Played, Song: Last Played song 1

このソリューションにはいくつかの利点があります。

  • 4 つの select ステートメントに制限
  • すべてのプレイリストの曲をロードしない - データベース側に集約
  • すべての曲をロードしない - データベース側の ID によるフィルタリング

MySQL でテスト済み。

これにより、空のプレイリストは表示されません。また、プレイリスト数が 1000 を超えると、一部の DB で問題が発生する可能性があります

于 2013-03-12T08:02:09.720 に答える
0

反対側から曲を取得するだけです:

Song
  .joins( :playlist )
  .where( playlists: {id: [1,2,3]} )
  .first

positionただし、@Dave S. が示唆したように、明示的に要求しない限り、SQL はレコードが返される順序を保証しないため、順序 (列など) を明示的に指定しない限り、プレイリストの「最初の」曲はランダムです。.

編集

申し訳ありませんが、私はあなたの質問を読み違えました。さすがにポジション欄は必要だと思います。

Song
  .joins( :playlist )
  .where( playlists: {id: [1,2,3]}, songs: {position: 1} )

位置列がまったく必要ない場合は、いつでもプレイリスト ID で曲をグループ化することができますが、そうする必要がselect("songs.*, playlist_songs.*")あり、「最初の」曲は依然としてランダムです。別のオプションは、RANK ウィンドウ関数を使用することですが、すべての RDBMS でサポートされているわけではありません (私が知っている限りでは、postgres と sql サーバーがサポートしています)。

于 2013-03-12T08:22:54.070 に答える
0

事実上、プレイリストに関連付けられた最初の曲を呼び出す has_one 関連付けを作成できます

class PlayList < ActiveRecord::Base
  has_one :playlist_cover, class_name: 'Song', foreign_key: :playlist_id
end

次に、この関連付けを使用します。

Playlist.joins(:playlist_cover)

更新:結合テーブルが表示されませんでした。

結合テーブルがある場合は、:throughオプションを使用できますhas_one

class PlayList < ActiveRecord::Base
  has_one :playlist_song_cover
  has_one :playlist_cover, through: :playlist_song_cover, source: :song
end
于 2013-03-12T08:24:07.917 に答える
0

これを行う最善の方法は、内部クエリを使用して最初のアイテムを取得してから結合することだと思います。

テストされていませんが、これが基本的な考え方です。

# gnerate the sql query that selects the first item per playlist
inner_query = Song.group('playlist_id').select('MIN(id) as id, playlist_id').to_sql

@playlists = Playlist
              .joins("INNER JOIN (#{inner_query}) as first_songs ON first_songs.playlist_id = playlist.id")
              .joins("INNER JOIN songs on songs.id = first_songs.id")

songs次に、曲名が必要なため、テーブルに再び参加します。songRailsが最後の結合でフィールドを選択するほど賢いかどうかはわかりません。selectそうでない場合は、最後に何かを選択する a を含める必要があるかもしれませんplaylists.*, songs.*

于 2013-03-12T00:20:06.213 に答える
0
Playlyst.joins(:playlist_songs).group('playlists.name').minimum('songs.name').to_a

それがうまくいくことを願っています:)

これを得ました :

Product.includes(:vendors).group('products.id').collect{|product| [product.title, product.vendors.first.name]}
  Product Load (0.5ms)  SELECT "products".* FROM "products" GROUP BY products.id
  Brand Load (0.5ms)  SELECT "brands".* FROM "brands" WHERE "brands"."product_id" IN (1, 2, 3)
  Vendor Load (0.4ms)  SELECT "vendors".* FROM "vendors" WHERE "vendors"."id" IN (2, 3, 1, 4)
 => [["Computer", "Dell"], ["Smartphone", "Apple"], ["Screen", "Apple"]] 
2.0.0p0 :120 > Product.joins(:vendors).group('products.title').minimum('vendors.name').to_a
   (0.6ms)  SELECT MIN(vendors.name) AS minimum_vendors_name, products.title AS products_title FROM "products" INNER JOIN "brands" ON "brands"."product_id" = "products"."id" INNER JOIN "vendors" ON "vendors"."id" = "brands"."vendor_id" GROUP BY products.title
 => [["Computer", "Dell"], ["Screen", "Apple"], ["Smartphone", "Apple"]] 
于 2013-03-13T11:36:28.563 に答える
0

モデルにactiverecord スコープを追加して、アプリのコンテキストで SQL クエリがどのように機能するかを最適化できます。また、スコープは構成可能であるため、探しているものを簡単に取得できます。

たとえば、Song モデルでは、first_song スコープが必要な場合があります。

class Song < ActiveRecord::Base
  scope :first_song, order("id asc").limit(1)
end 

そして、あなたはこのようなことをすることができます

  playlists.songs.first_song

PlaylistSongs アソシエーション モデルまたは Playlist モデルにいくつかのスコープを追加する必要がある場合もあります。

于 2013-03-13T20:27:45.313 に答える
0

データベースにタイムスタンプがあるかどうかは言いませんでした。ただし、プレイリストに曲を追加すると、結合テーブル PlaylistSongs のレコードが作成される場合、これでうまくいくと思います。

first_song_ids = Playlist.joins(:playlist_songs).order('playlist_songs.created_at ASC').pluck(:song_id).uniq
playlist_ids = Playlist.joins(:playlist_songs).order('playlist_songs.created_at ASC').pluck(:playlist_id).uniq
playlist_names = Playlist.where(id: playlist_ids).pluck(:playlist_name)
song_names = Song.where(id: first_song_ids).pluck(:song_name)

この方法で、playlist_names と song_names がインデックスによってマッピングされるようになったと思います。次のように、playlist_names[0] の最初の曲名は song_names[0] で、playlist_names[1] の最初の曲名は song_names[1] などです。組み込みの Ruby メソッドを使用して、それらをハッシュまたは配列に非常に簡単に組み合わせることができると確信しています。

これを行うための効率的な方法を探していたことに気づきました。コメントでブロックを使用したくないと言っていましたが、効率的でオールインワンクエリを意味したかどうかはわかりません. 私はちょうどこれらすべての Rails クエリ メソッドを組み合わせることに慣れてきたところですが、おそらくここにあるものを見て、ニーズに合わせて変更し、より効率的または要約することができます。

お役に立てれば。

于 2013-03-15T00:02:16.593 に答える