在WHERE中重构具有两个子选择的查询

时间:2014-07-04 16:45:12

标签: ruby-on-rails postgresql activerecord ruby-on-rails-4

我在项目模型上有以下范围,以查找至少已完成80个众筹承诺的项目或已达到目标的项目:

class Project < ActiveRecord::Base
  scope :popular, -> { collecting.where("80 <= (?) OR goal <= (?)",
    Pledge.select('COUNT(*)').where("project_id = projects.id").where(paid: true),
    Pledge.select('SUM(amount)').where("project_id = projects.id").where(paid: true))
  }

  (...)
end

这很好用,并为Postres生成以下SQL:

Project.popular
# SELECT "projects".* FROM "projects"  WHERE "projects"."state" = 'collecting' AND (80 <= (SELECT COUNT(*) FROM "pledges"  WHERE (project_id = projects.id) AND "pledges"."paid" = 't') OR goal <= (SELECT SUM(amount) FROM "pledges"  WHERE (project_id = projects.id) AND "pledges"."paid" = 't'))

获得计数也很重要:

Project.popular.count
# SELECT COUNT(*) FROM "projects" WHERE "projects"."state" = 'collecting' AND (80 <= (SELECT COUNT(*) FROM "pledges" WHERE (project_id = projects.id) AND "pledges"."paid" = 't') OR goal <= (SELECT SUM(amount) FROM "pledges" WHERE (project_id = projects.id) AND "pledges"."paid" = 't'))

好的,子选择很好,但我觉得有一种更好,更有效的方法。我已尝试joinssum,但使用聚合函数的强制group会中断Project.popular.count

任何想法如何重构这个?也许只是一种用Hash表示法做where("project_id = projects.id")的方法?

2 个答案:

答案 0 :(得分:1)

我不知道这对你有什么好处,因为我无法在ActiveRecord术语中表达以下查询,但如果可以的话,它将比目前正在生成的糟糕查询有很大改进

select "projects".*, project_count, project_amount
from
    "projects"
    inner join (
        select
            id,
            count(*) as project_count,
            sum(amount) as project_amount
        from pledges
        group by id
        where paid
    ) pledges using (id)
where
    "projects"."state" = 'collecting'
    and
    (project_count >= 80 or project_amount >= goal)

答案 1 :(得分:1)

谢谢,@ Clodonaldo!通过一些小小的触摸,您的查询就像一个魅力:

select "projects".*, pledges_count, pledges_sum
from
    "projects"
    inner join (
        select
            project_id as id,
            count(*) as pledges_count,
            sum(amount) as pledges_sum
        from pledges
        where paid
        group by project_id
    ) pledges using (id)
where
    "projects"."state" = 'collecting'
    and
    (pledges_count >= 80 or pledges_sum >= goal)

不确定AR或AREL是否可以构建它。

更新:

我能想到的最短的是(我不需要计数和总和值):

Project.collecting.joins(
    "INNER JOIN (",
    Pledge.paid.group(:project_id).select("
      project_id AS id,
      COUNT(*) AS count,
      SUM(amount) AS sum
    ").to_sql,
    ") pledges USING (id)"
  ).where("count >= 80 OR sum >= goal")

以上产生:

SELECT "projects".*
FROM
    "projects"
    INNER JOIN ( 
        SELECT
            project_id AS id,
            COUNT(*) AS count,
            SUM(amount) AS sum
        FROM "pledges"
        WHERE "pledges"."paid" = 't'
        GROUP BY project_id
    ) pledges USING (id)
WHERE
    "projects"."state" = 'collecting'
    AND
    (count >= 80 OR sum >= goal)