【问题标题】:Optimize difficult query (possibly with squeel)优化困难查询(可能使用 squeel)
【发布时间】:2013-10-19 19:06:06
【问题描述】:

有这样的代码(使用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 关系合并方法(+ 除外)不适用于上述代码。

有什么优化它的想法吗?

【问题讨论】:

  • 您始终可以在 Rails 中执行原始查询。只需构造sql字符串,然后就可以在请求端解析返回的数据集了……这比做8次查询要快得多
  • 1) 这不是轨道方式。 2)我对SQL不是很好,不知道最后应该如何查询视图:)
  • Hmmmmmmmmmmm,不同意它不是轨道方式。千篇一律的查询之外的任何内容,无论如何您都必须使用自定义 where 子句。辅助函数只是增加了一点抽象性和可移植性
  • @Chris 你能提示完成的 SQL 查询应该是什么样子吗?
  • 我的回答有帮助吗?

标签: sql ruby-on-rails activerecord rails-activerecord squeel


【解决方案1】:

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

这假设活动与帖子/项目之间存在 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
...

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

【讨论】:

  • 是的,这不是我的方式:) 无论如何感谢您的帮助!
【解决方案2】:

在 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

然后您可以轻松添加所需的包含;)

【讨论】:

  • 1.上面的代码不起作用 2. 现在我不知道 如何 这个 SQL 可以工作,所以我会去阅读有关 SQL case:)
【解决方案3】:

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

我认为您需要做的就是在查询中使用eager_load 而不是includes 来强制执行单个查询。 joinsincludespreloadeager_loadreferences 方法之间的区别很好地涵盖了 here

所以,使用 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 都可以。

【讨论】:

    【解决方案4】:

    一个非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
    

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

    不过,这会将其减少到两个查询,而不是一个。 @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 以确保它们不会被意外设置(我预计这会破坏多态性),但从我的测试来看,它似乎有效。

    【讨论】:

    • 它不起作用:Can not eagerly load the polymorphic association :trackable。我正在使用 rails 4
    • 哦,我完全错过了它是关于多态关系的事实。我的错。我不确定 Rails 的多态方法是否会急切地加载。让我建议一种不同的方法。
    【解决方案5】:

    简而言之,如果不使用 SQL,您将无法进一步优化。这就是 Rails 开展业务的方式。它不允许访问提出查询的 AR 模型之外的连接字段。因此,要获取其他表中的值,它会对每个表进行查询。

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

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

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

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

    编辑

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

    【讨论】:

    • 在对我的答案的编辑中,我指出了一种无需编写自己的 SQL 即可在两个查询(或者可能在一个查询中,使用 sqeel)中执行此操作的方法。基本上,这里遇到的问题是:trackable 是一种多态关系。但是由于在这种情况下只需要两个特定的关联变体,因此可以将它们放入代码中,从而消除多态性确实存在的所有连接问题。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-11
    • 1970-01-01
    相关资源
    最近更新 更多