0

在我正在处理的 Rails 应用程序中,我有以下设置:

class Volunteer < AR::Base
  has_many :engagements
end

class Engagement < AR::Base
  belongs_to :volunteer
end

我正在尝试选择所有未在特定日期或日期范围之间参与的志愿者:

scope :not_engaged_on, lambda { |q| joins(:engagements).where(["engagements.date != ?", q])}

scope :not_engaged_between, lambda { |s, e| joins(:engagements).where(["engagements.date NOT BETWEEN ? AND ?", s, e]) }

我将这些范围与其他范围合并以过滤志愿者。

上述范围:not_engaged_between产生以下 SQL:

SELECT "volunteers".* FROM "volunteers" INNER JOIN "accounts" ON "accounts"."user_id" = "volunteers"."id" AND "accounts"."user_type" = 'Volunteer' INNER JOIN "engagements" ON "engagements"."volunteer_id" = "volunteers"."id" WHERE (accounts.activation_state = 'active') AND (engagements.date NOT BETWEEN '2012-07-30' AND '2012-08-04')

问题是志愿者可能到目前为止还没有任何参与,在这种情况下,查询完全忽略了这些志愿者。即使他们没有任何参与,我如何才能让所有志愿者返回?

4

2 回答 2

2

@Anthony 已经提到过NOT EXISTS。另一个选项是LEFT [OUTER] JOIN+ 检查是否存在 ( e.volunteer_id IS NULL):


SELECT v.*
FROM   volunteers       v
JOIN   accounts         a ON a.user_id = v.id
LEFT  JOIN engagements e ON e.volunteer_id = v.id
                        AND e.date BETWEEN '2012-07-30' AND '2012-08-04'
WHERE  a.user_type = 'Volunteer'
AND    a.activation_state = 'active'
AND   e.volunteer_id IS NULL;

在这种情况下,您必须将条件移动e.date BETWEEN '2012-07-30' AND '2012-08-04'到连接子句。

如果它也可能丢失,请使用另一个LEFT JOINfor 。accounts但是您似乎确实想检查user_typeand activation_state

于 2012-07-30T15:38:38.537 回答
1

你需要一个not exists来做到这一点。这种语法对 SQL 来说有点重,也许有更好的 arel 方法来做到这一点。

scope :not_engaged_on, lambda { |q| where(["NOT EXISTS (SELECT ID from engagements WHERE engagements.date = ? AND volunteer_id = volunteers.id)", q])}

scope :not_engaged_between, lambda { |s, e| where(["NOT EXISTS (SELECT ID from engagements WHERE engagements.date BETWEEN ? AND ? AND volunteer_id = volunteers.id)", s, e]) }
于 2012-07-30T15:09:13.787 回答