1

私はモデルを持っUserていGiftます。ユーザーは他のユーザーにギフトを送ることができます。どのユーザーがギフトを受け取ったかを示すリレーショナル テーブルがあります。一方、ユーザーは、School無料または有料の に属します。

特定の種類の学校 (無料または有料) で先週ギフトを受け取ったユーザーの数を知りたいです。

できます:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:gift_recipients).flatten.uniq.count.

または、先週ギフトを送信したユーザーの数を知りたいです。これは機能します:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id).uniq.count.

過去 1 週間に何人のユーザーがギフトを送信または受信したかを知りたい場合は、次のようにします。

(Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:gift_recipients).flatten + Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id)).uniq.count

これはすべて正常に機能しますが、データベースが十分に大きい場合、これは非常に遅くなります。必要に応じて生の SQL を使用して、より効率的にするための提案はありますか?

"gifts" 
  user_id (integer) 
  school_id (integer) 
  created_at (datetime) 
  updated_at (datetime) 
"gift_recipients" is a table like 
  gift_id | recipient_id,
4

2 に答える 2

1

すべての結果をメモリにロードし、それらを ActiveRecord の配列内でフィルタリングする collect() を使用してこれを行うことは望ましくありません。データとサーバーのサイズによっては、利用可能なすべてのメモリをリーク/使用する可能性があるため、これは遅くて危険です。

スキーマを投稿したら、これを SQL でクエリ/集計するのを手伝うことができます。これは正しい方法です。

たとえば、次の代わりに:

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).collect(&:user_id).uniq.count

以下を使用する必要があります。

Gift.joins(:schools).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).count('distinct user_id')

...これは、すべてのオブジェクトを返してメモリ内でカウントするのではなく、SQL で個別の user_ids をカウントして結果を返します。

于 2012-09-19T16:00:37.693 に答える
0

この古い投稿を見て、いくつかコメントしたいと思いました: Winfield が言ったように

Gift.joins(:school).where("created_at >= ? AND schools.free_school = ?", Time.now.beggining_of_week, true).count('distinct user_id')

これを行う良い方法です。私はするだろう

Gift.joins(:school).count('distinct user_id', :conditions => ["gifts.created_at >= ? AND free_school = ?", Time.now.beginning_of_week, true])

しかし、これは私の目にはより良いという理由だけで、個人的なことですが、両方がまったく同じ SQL クエリを生成することを確認できます。書く必要があることに注意してください

gifts.created_at 

列名の場合、両方のテーブルにこの名前の列があるため、あいまいさを避けるため

free_school

これはギフト テーブルの列名ではないため、あいまいさはありません。私が行っていた最初のクエリについて

Gift.joins(:school).where("created_at >= ? AND schools.free_school = ?", Time.now.beginning_of_week, true).collect(&:user_id).uniq.count

これは厄介です。これはよりよく機能します

Gift.joins(:school).count("distinct user_id", :conditions => ["gifts.created_at >= ? AND free_school = ?", Time.now.beginning_of_week, true])

これにより、ギフトを思い出してルビーでフィルタリングするという問題が回避されます。

ここまでは目新しいものはありません。ここで重要な点は、私の問題は、先週ギフトを送受信したユーザーの数を計算することでした。このために、私は次のことを思いつきました

  senders_ids = Gift.joins(:school).find(:all, :select => 'distinct user_id', :conditions => ['gifts.created_at >= ? AND free_school = ?', Time.now.beginning_of_week, type]).map {|g| g.user_id}
  receivers_ids = Gift.joins(:school).find(:all, :select => 'distinct rec.recipient_id', :conditions => ['gifts.created_at >= ? AND free_school = ?', Time.now.beginning_of_week, type], :joins => "INNER JOIN gifts_recipients as rec on rec.gift_id = gifts.id").map {|g| g.recipient_id}
  (senders_ids + receivers_ids).uniq.count

これを行うためのより良い方法が存在すると確信しています。つまり、単一の SQL クエリでこの数値を正確に返すことですが、少なくとも結果は id (受信者の場合は recipient_id) のみを含むオブジェクトの配列であり、すべてのオブジェクトをメモリに。これは、私のようなレールを介したSQLクエリの初心者にとって役立つことを望んでいます:)。

于 2012-09-22T00:48:59.653 に答える