0

ユーザーが 3 つの異なるモデルの属性に基づいて特定のリンクを検索できる高度な検索オプションをアプリに追加しようとしています。

私のアプリは、User has_many :websitesWebsite has_many :links、およびLink has_many :stats

Railsで結合やインクルードなどを使用してSQLクエリを作成する方法は知っています、すべてではなく各リンクの最新の統計情報のみを取得したいので、行き詰まっています-これを行う最も効率的な方法がわかりません.

たとえば、ユーザーが 2 つの Web サイトを持ち、それぞれに 10 個のリンクがあり、各リンクに 100 個の統計があり、合計で 2,022 個のオブジェクトがあるとしますが、42 個のオブジェクト (リンクごとに 1 個の統計のみ) のみを検索したいとします。

データベース クエリでこれら 42 個のオブジェクトのみを取得する.where("attribute like ?", user_input)と、正しいリンクを追加して返すことができます。

アップデート

Link モデルに以下を追加しようとしました。

has_many :stats, dependent: :destroy
has_many :one_stat, class_name: "Stat", order: "id ASC", limit: 1

しかし、これはうまくいかないようです。たとえば、次のようにします。

@links = Link.includes(:one_stat).all

@links.each do |l|
  puts l.one_stat.size
end

取得する代わりに1, 1, 1...、すべての統計の数を取得します: 125, 40, 76...

limit オプションを使用して希望する結果を得ることができますか、それともうまくいきませんか?

2回目の更新

Erez のアドバイスに従ってコードを更新しましたが、まだ正しく動作していません。

has_one :latest_stat, class_name: "Stat", order: "id ASC"

@links = Link.includes(:latest_stat)

@links.each do |l|
  puts l.latest_stat.indexed
end

=> true
=> true
=> true
=> false
=> true
=> true
=> true

Link.includes(:latest_stat).where("stats.indexed = ?", false).count
=> 6

Link.includes(:latest_stat).where("stats.indexed = ?", true).count
=> 7

1 と 6 を返す必要がありますが、最新の統計だけでなく、すべての統計をチェックしています。

4

3 に答える 3

2

場合によっては、AR の抽象化を突破して SQL を実行する必要があります。ちょっとだけ。

Website has_many :links、 、 、 、 などの非常に単純な関係があるLink belongs_to :websitehas_many :statsStat belongs_to :linkます。どこにも非正規化はありません。ここで、すべてのリンクを検索し、リンクごとに最新の統計を検索するクエリを作成しますが、特定のプロパティを持つ統計 (または、特定のプロパティを持つ Web サイトまたは特定のプロパティを持つリンク) のみを対象とします。

テストされていませんが、次のようなものです:

Website
  .includes(:links => :stats)
  .where("stats.indexed" => true)
  .where("stats.id = (select max(stats2.id) 
     from stats stats2 where stats2.link_id = links.id)")

その最後のビットは、各リンクの一部である統計をサブ選択し、最大 ID を見つけます。次に、その最大 ID に一致しない統計を (上部の結合から) 除外します。クエリは Web サイトを返します。各 Web サイトにはいくつかのリンクがあり、各リンクにはstatsコレクション内の統計が 1 つしかありません。

追加情報

私はもともとこの回答をウィンドウ関数の観点から書きましたが、これはやり過ぎであることが判明しましたが、まあ、楽しいので、とにかくここでカバーする必要があると思います。上記で使用した集計関数のトリックが機能するのは、ID に基づいて使用する統計を決定しているためです。これは、結合から統計をフィルタリングするために必要なプロパティです。しかし、たとえば、ID 以外の基準でランク付けされた最初の統計のみが必要だとしましょうnumber_of_clicks。アグリゲーションが ID を追跡できなくなるため、このトリックは機能しなくなります。そこでウィンドウ関数の出番です。

繰り返しますが、完全にテストされていません:

Website
  .includes(:links => :stats)
  .where("stats.indexed" => true)
  .where(                                 
     "(stats.id, 1) in (
       select id, row_number() 
       over (partition by stats2.id order by stats2.number_of_clicks DESC)
       from stat stats2 where stats2.link_id = links.id
     )"
   )

その最後の部分は、where各リンクに一致する統計をサブ選択し、number_of_clicks昇順で並べ替えます。次に、inパーツはそれを結合からの統計に一致させます。ウィンドウ クエリは、他のデータベース プラットフォームに移植できないことに注意してください。この手法を使用して、提起した元の問題を解決することもできます (単に と交換stats2.idstats2.number_of_clicksます)。おそらくパフォーマンスが向上する可能性があり、このブログ投稿で推奨されています。

于 2013-01-23T08:02:49.193 に答える
1

私はこれを試してみます:

has_one :latest_stat, class_name: "Stat", order: "id ASC"

@links = Link.includes(:latest_stat)

@links.each do |l|
  puts l.latest_stat
end

latest_stat.sizeこれは統計オブジェクト自体であり、リレーションではないため、印刷できないことに注意してください。

于 2013-01-21T11:25:23.223 に答える
0

これはあなたが探しているものですか?

@user.websites.map { |site| site.links.map { |link| link.stats.last } }.flatten

特定のユーザーに対して、これはそのユーザーのWebサイト上のリンクの最後の統計を含む配列を返します。

于 2013-01-23T04:31:57.263 に答える