具有不同嵌套连接的作用域

问题描述 投票:0回答:2

我的数据库由参加会议的跑步者组成

所以:

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 ruby-on-rails join scope
2个回答
0
投票

这是针对您的问题的 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

小提琴


0
投票

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
。但我会从子查询开始,当你到达它时穿过那座桥。

© www.soinside.com 2019 - 2024. All rights reserved.