4

そのようなコードがあります(PublicActivity gem&Squeelを使用)

  def index
    @activities = Activity.limit(20).order { created_at.desc }
    @one = @activities.where{trackable_type == 'Post'}.includes(trackable: [:author, :project])
    @two = @activities.where{trackable_type == 'Project'}.includes trackable: [:owner]
    @activities = @one + @two
  end

ただし、8 つの SQL 要求が作成されます。

 SELECT "activities".* FROM "activities" WHERE "activities"."trackable_type" = 'Post' ORDER BY "activities"."created_at" DESC LIMIT 20

      SELECT "posts".* FROM "posts" WHERE "posts"."id" IN (800, 799, 798, 797, 796, 795, 794, 793, 792, 791, 790, 789, 788, 787, 786, 785, 784, 783, 782, 781)

      SELECT "users".* FROM "users" WHERE "users"."id" IN (880, 879, 878, 877, 876, 875, 874, 873, 872, 871, 869, 868, 867, 866, 865, 864, 863, 862, 861, 860)

      SELECT "projects".* FROM "projects" WHERE "projects"."id" IN (80, 79)

      SELECT "activities".* FROM "activities" WHERE "activities"."trackable_type" = 'Project' ORDER BY "activities"."created_at" DESC LIMIT 20

      SELECT "projects".* FROM "projects" WHERE "projects"."id" IN (80, 79, 78, 77, 76, 75, 74, 73, 72, 71, 70, 69, 68, 67, 66, 65, 64, 63, 62, 61)

     SELECT "users".* FROM "users" WHERE "users"."id" IN (870, 859, 848, 837, 826, 815, 804, 793, 782, 771, 760, 749, 738, 727, 716, 705, 694, 683, 672, 661)
  1. 活動リクエストは参加していません
  2. 一部のユーザー (投稿所有者とプロジェクト所有者) が 2 回読み込まれます
  3. 一部のプロジェクトは 2 回読み込まれます
  4. @activities は配列です。Rails のリレーション マージ メソッド ( を除く+) は、上記のコードでは機能しません。

それを最適化するためのアイデアはありますか?

4

5 に答える 5

2

Rails-4 以外の非スクイール ソリューションは次のとおりです。

def index
  @activities = Activity.limit(20).order("created_at desc")
  @one = @activities.where(trackable_type: 'Post')   .joins(trackable: [:author, :project]).includes(trackable: [:author, :project])
  @two = @activities.where(trackable_type: 'Project').joins(trackable: [:owner])           .includes(trackable: [:owner])
  @activities = @one + @two
end

との組み合わせは奇妙joinsincludes見えますが、私のテストでは驚くほどうまく機能します。

ただし、これにより、1 つではなく 2 つのクエリに削減されます。そして、@activities は引き続き配列になります。しかし、このアプローチを squeel で使用すると、それも解決されるかもしれません。残念ながら、私はスクイールを使用していないため、テストできません。

編集:これがポリモーフィックな関連付けに関するものであるという点を完全に見逃しました。上記は強制的に機能します

AR が提供するものを使用したい場合は、少しハックですが、関連するプロジェクトと投稿を読み取り専用で定義できます。

belongs_to :project, read_only: true, foreign_key: :trackable_id
belongs_to :post,    read_only: true, foreign_key: :trackable_id

これらの場合、前述の熱心な負荷を強制する方法が機能するはずです。条件はまだ必要であるため、これらのwhere関連付けは適切なアクティビティでのみ呼び出されます。

def index
  @activities = Activity.limit(20).order("created_at desc")
  @one = @activities.where(trackable_type: 'Post')   .joins(post: [:author, :project]).includes(post: [:author, :project])
  @two = @activities.where(trackable_type: 'Project').joins(project: [:owner])        .includes(project: [:owner])
  @activities = @one + @two
end

それはきれいな解決策ではなく、誤って設定されないようにアソシエーションを attr_protected にする必要があります(ポリモーフィズムが壊れると思います)が、私のテストではうまくいくようです。

于 2013-10-22T20:24:59.373 に答える
1

limit(20)句のために、少なくとも 2 つの AR クエリ呼び出し (現在のように) が必要になると思います。あなたのクエリは現在、最大 20 件の投稿と最大 20 件のプロジェクトを提供しているため、単一のクエリで両方のアクティビティ タイプに対して集計制限を行うと、意図した結果が得られません。

単一のクエリを強制するeager_loadのではなく、クエリで使用するだけでよいと思います。、、、およびメソッドincludesの違いは、ここでうまくカバーされていますjoinsincludespreloadeager_loadreferences

したがって、AR と squeel を使用すると、次のようになります。

def index
    @activities = Activity.limit(20).order { created_at.desc }
    @one = @activities.where{trackable_type == 'Post'}.eager_loads(trackable: [:author, :project])
    @two = @activities.where{trackable_type == 'Project'}.eager_loads trackable: [:owner]
    @activities = @one + @two
end

通常の ActiveRecord 4 のみを使用して、きしみ音なしで:

def index
    @activities = Activity.limit(20).order(created_at: :desc)
    @one = @activities.where(trackable_type: 'Post').eager_loads(trackable: [:author, :project])
    @two = @activities.where(trackable_type: 'Project').eager_loads(trackable: :owner)
    @activities = @one + @two
end

squeel は必要ありません。AR 4 と Arel は問題ありませんでしたが、私の経験では多くの複雑なクエリに対して適切に機能しないため、最近プロジェクトから削除しました。

于 2013-10-21T12:24:26.267 に答える
1

SQL で単純な Switch ケースを使用する:

def index
  table_name = Activity.table_name
  @activities = Activity.where(trackable_type: ['Post', 'Project'])
                        .order("CASE #{table_name}.owner_type WHEN 'Post' THEN 'a' ELSE 'z' END, #{table_name}.created_at DESC")
end

次に、必要なインクルードを簡単に追加できます;)

于 2013-10-18T17:07:25.343 に答える
1

簡単に言えば、SQL を使用しないとこれ以上の最適化はできません。これがRailsのやり方です。クエリが設定されている AR モデルの外部にある結合フィールドへのアクセスは許可されません。したがって、他のテーブルの値を取得するには、それぞれに対してクエリを実行します。

また、問題を解決する他の方法を提供する条件を許可しUNIONたり、空想したりしません。WHERE

幸いなことに、これらのクエリはすべて効率的です (trackable_type がインデックス化されている場合)。結果のサイズがかなり大きい場合 (たとえば、数十行)、i/o 時間は、7 つの単純なクエリと 1 つの複雑なクエリのわずかな追加オーバーヘッドを支配します。

SQL を使用しても、必要なすべての結合結果を 1 つのクエリで取得することは困難です。(実行できますが、結果は AR インスタンスではなくハッシュになります。したがって、依存するコードは見苦しくなります。) テーブルごとに 1 つのクエリは、Active Record にかなり深く配線されています。

@Mr.Yoshiのソリューションは、フィールド に基づいてauthorまたはproject+を選択的にロードできないことを除いて、最小限のSQLを使用した適切な妥協案です。ownertrackable_type

編集

上記は Rails 3 の場合はすべて正しいです。Rails 4 の場合、@CMW が言うように、メソッドは個別のクエリの代わりに外部結合を使用するeager_loadのと同じことを行います。includesこれが私がSOを愛する理由です!私はいつも何かを学びます。

于 2013-10-25T16:06:45.893 に答える
0

これは非常に大きなクエリです...見た目では 1 回の選択で実行できますが、読みやすくするために、プロジェクト用と投稿用の 2 つを使用します。

これは、アクティビティと投稿/プロジェクトの間に 1 対 1 の関係があることを前提としています。これが正しくない場合、問題はサブクエリで解決できます

select * from activities a
where a.trackable_type = 'Post'
left join posts p
on p.id = a.trackable_id -- or whatever fields join these two tables
left join users u
on a.user_id = u.id --this is joining to the main table, may want to join trackable, not sure
left join projects p
on a.project_id = p.id
order by a.created_at DESC LIMIT 20

または、1 対多の関係がある場合は、次のようになります。

select * from
(   select * from activities a
    where a.trackable_type = 'Post'
    order by a.created_at DESC LIMIT 20 ) activities
left join posts p
...

編集:これを読んで、私は少し時代遅れであることに気付きました....このような大きな生のSQLクエリを使用する場合は、アプリケーションにコーディングするのではなく、データベース関数を作成する必要があると思います

于 2013-10-11T19:15:23.703 に答える