在我正在处理的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')
问题是志愿者到目前为止可能没有任何约定,在这种情况下,查询完全忽略了那些志愿者。即使他们没有任何约定,我怎样才能让所有志愿者归还?
答案 0 :(得分: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'
移动到join子句。
LEFT JOIN
使用另一个accounts
,如果它也可能丢失。但似乎您确实要检查user_type
和activation_state
。
答案 1 :(得分:1)
您需要not exists
才能执行此操作。这种语法在SQL上有点沉重,也许有更好的方法来实现它。
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]) }