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. 一些用户(帖子所有者和项目所有者)被加载两次
  3. 一些项目被加载两次
  4. @activities 是数组。Rails 关系合并方法(除了+)不适用于上面的代码。

有什么优化它的想法吗?

4

5 回答 5

2

一个非 rails-4、非 squeel 的解决方案是:

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

joins和的组合includes看起来很奇怪,但在我的测试中,它的效果出奇的好。

不过,这会将其减少到两个查询,而不是一个。@activities 仍然是一个数组。但也许将这种方法与 squeel 一起使用也可以解决这个问题。不幸的是,我不使用 squeel 也无法对其进行测试。

编辑:我完全错过了关于多态关联的重点。以上作品强制

如果你想使用 AR 提供的东西,这有点 hacky,但你可以定义只读关联的项目和帖子:

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

在 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

limit(20)我相信由于该子句,您将需要至少两次 AR 查询调用(正如您目前所拥有的那样) 。您的查询目前最多为您提供 20 个帖子和最多 20 个项目,因此在单个查询中对两种活动类型进行聚合限制不会产生预期的结果。

我认为您需要做的就是eager_load在查询中使用而不是includes强制执行单个查询。joins, includes, preload,eager_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

没有squeel,只使用普通的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,您将无法进一步优化。这就是 Rails 开展业务的方式。它不允许访问提出查询的 AR 模型之外的连接字段。因此,要获取其他表中的值,它会对每个表进行查询。

它也不允许UNION或花哨WHERE的条件提供解决问题的其他方法。

好消息是这些查询都是高效的(假设 trackable_type 已编入索引)。如果结果的大小非常大(比如几十行),则 i/o 时间将支配 7 个简单查询和 1 个复杂查询的轻微额外开销。

即使使用 SQL,也很难在一个查询中获得您想要的所有连接结果。(可以这样做,但结果将是一个散列而不是一个 AR 实例。所以依赖代码会很丑陋。)每个表一个查询非常深入地连接到 Active Record 中。

@Mr.Yoshi 的解决方案是使用最少 SQL 的一个很好的折衷方案,但它不允许您 根据字段选择性地加载authorproject+ 。ownertrackable_type

编辑

以上对于Rails 3 都是正确的。对于@CMW 所说的Rails 4,该eager_load方法将与includes使用外部连接而不是单独的查询相同。这就是我喜欢SO的原因!我总是学到一些东西。

于 2013-10-25T16:06:45.893 回答
0

这是一个相当大的查询......从外观上看,您可以一次选择完成,但为了便于阅读,我将使用两个,一个用于项目,一个用于帖子。

这假设活动和职位/项目之间存在 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:many 关系,则如下所示:

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

编辑:当我读到这篇文章时,我意识到我有点过时了....

于 2013-10-11T19:15:23.703 回答