我的数据库由参加会议的跑步者组成
所以:
class Runner
has_many :attendances
has_many :attended_happenings, through: :attendances, source: :happening
end
class Attendance < ActiveRecord::Base
belongs_to :happening
belongs_to :runner
end
class Happening
has_many :attendances, dependent: :destroy
has_many :attendees, through: :attendances, source: :runner
end
我想找到在过去 9 周内not 参加任何活动的所有跑步者。在跑步者模型中,我创建了一个范围
:inactive_for_nine_weeks
:
left_joins(:attendances)
.joins("join happenings on attendances.happening_id = happenings.id")
.where("attendances.id is null")
.where("happenings.started_at > ?", 9.weeks.ago)
我想我需要
left_join
出勤率来吸引 没有 出勤率的跑步者,然后我需要定期参加活动。
虽然我知道应该有,但我没有得到任何结果,所以很明显执行得很差。
从作用域中获取计数产生的 SQL:
SELECT COUNT(*) FROM "runners" LEFT OUTER JOIN "attendances" ON "attendances"."runner_id" = "runners"."id" join happenings on attendances.happening_id = happenings.id WHERE (attendances.id is null) AND (happenings.started_at > '2023-02-27 08:54:12.460052')
任何提示将不胜感激!
这是针对您的问题的 sql 解决方案。
您首先需要交叉加入所有具有所有hapenings的跑步者以获得所有组合。
之后你用 LEFT JOIN 检查哪个跑步者正在发生,其余的必须为空
CREATE tABLe "runners"("id" int)
INSERT INTO "runners" VALUES (1), (2)
CREATE tABLe "attendances"(id int,"runner_id" int,happening_id int )
INSERT INTO "attendances" VALUES(1,1,1),(2,1,2),(3,2,1)
CREATE tABLe happenings( id int, started_at timestamp)
INSERT INTO happenings VALUEs(1, CURRENT_TIMESTAMP),(2, CURRENT_TIMESTAMP)
SELECT
COUNT(*)
FROM ("runners" CROSS JOIN happenings)
LEFT OUTER JOIN "attendances" ON "attendances"."runner_id" = "runners"."id"
AND attendances.happening_id = happenings.id
WHERE (attendances.id is null)
AND (happenings.started_at > '2023-02-27 08:54:12.460052')
计数 |
---|
1 |
ActiveRecord 中最直接的解决方案是只做一个子查询:
class Runner < ApplicationRecord
has_many :attendances
has_many :attended_happenings, through: :attendances, source: :happening
# scope is really just syntactic sugar for defining class methods
# and is really only suited for one liners as multi-line lambas are horrible for readibity
def self.inactive_since(time = 9.weeks.ago)
where.not(
id: Attendance.joins(:happening)
.where(happenings: { started_at: ...time })
.select(:runner_id)
)
end
end
Runner.inactive_since
给出以下 SQL:
SELECT "runners".* FROM "runners"
WHERE "runners"."id" NOT IN (
SELECT "attendances"."runner_id" FROM "attendances"
INNER JOIN "happenings" ON "happenings"."id" = "attendances"."happening_id"
AND "happenings"."started_at" > $1
)
您也可以使用 EXISTS 而不是 WHERE IN 作为轻微优化(在某些数据库上)。
class Runner < ApplicationRecord
def self.inactive_exists(time = 9.weeks.ago)
where.not(
Attendance.joins(:happening)
.select(1)
.where.not(Attendance.arel_table[:runner_id].eq(arel_table[:id]))
.where(happenings: { started_at: ...time })
.arel
.exists
)
end
end
SELECT "runners".* FROM "runners"
WHERE NOT (
EXISTS (
SELECT 1 FROM "attendances"
INNER JOIN "happenings" ON "happenings"."id" = "attendances"."happening_id"
WHERE "attendances"."runner_id" = "runners"."id"
AND "happenings"."started_at" < $1
)
)
如果您需要它,您可以寻求更优化的解决方案,这些解决方案不是像横向或交叉连接那样的多语言解决方案,也可以在 Postgres 上使用
COUNT(happenings.*) FILTER (WHERE started_at < ?) = 0
。但我会从子查询开始,当你到达它时穿过那座桥。