如何基于参数左联接has_many引用的第一个上一个/下一个实例

How to left-join the first previous/next instance of a has_many reference based on a parameter

提问人:Noxx 提问时间:11/12/2023 最后编辑:Noxx 更新时间:11/12/2023 访问量:42

问:

使用 PostgreSQL 15.1 在 Ruby 3.2 上运行 Rails 7。

首先,我想举一个抽象的例子:

猫:

编号 名字
1 米风
2 蒂芬妮

玩具:

编号 cat_id
1 1 1
2 1 5
3 2 2

我要解决的问题是:

基于一个尚未保存的玩具及其伟大之处,我想得到一个清单,列出所有猫和它们的玩具,这些玩具略低于或大于我想要创造的玩具。

如果给一个新玩具,结果应该是:greatness=4

cat_id toy_prev.伟大 toy_next.伟大
1 1 5
2 2

要求:

  • 两个玩具都应该“左连接”,因为它们不需要存在。
  • 每只猫只有一排
  • 我需要完全访问模型,包括猫和两个玩具,而不仅仅是属性。greatness

我以后喜欢使用的伪代码是这样的:

Cats.with_toys_by(4).each do |cat|
  [cat.id, cat.toy_prev.greatness, cat.toy_next.greatness]
end

到目前为止,我已经尝试过:

  1. 最简单的解决方案是迭代并在这个循环中,调用以获取左侧和右侧。这是非常不高性能的,因为这会触发 N+2 查询。Cat.all@cat.toys.where("greatness < ?", 4).order(greatness: :desc).first()@cat.toys.where("greatness > ?", 4).order(greatness: :asc).first()
  2. 与条件一起使用不起作用,因为这不能直接在主查询的 .has_onewhere()joins

事情,我在想:

如果我用纯 SQL 编写它,我会在两个方向上左联接表两次:Toy

SELECT cats.*, t1.* FROM cats
  -- makes sure the requirement matches
  LEFT JOIN toys t1 ON (cats.id = t1.cat_id AND t1.greatness < 4)
  -- filter out ones which would have toys with a greater greatness, a.k.a. get the first one
  LEFT JOIN toys t2 ON (cats.id = t1.cat_id AND t1.greatness < t2.greatness)
  -- same for the other side
  LEFT JOIN toys t3 ON (cats.id = t3.cat_id AND t3.greatness > 4)
  LEFT JOIN toys t4 ON (cats.id = t4.cat_id AND t3.greatness > t4.greatness)
WHERE
  t2.id IS NULL AND t4.id IS NULL

t1将是 ,具有较低伟大的那个 - 并且将是.toy_prevt3toy_next

使用将是一种选择,我还没有使用它,我不太喜欢这个想法,原因如下:Arel

  • Arel是一个私有 API,不建议公开使用。
  • 当尝试在返回的哈希值和模型实例之间进行映射时,它似乎很难。进一步过滤或同时使用 ORM,例如 ,似乎不受支持,至少如果不仅用于 -子句。ArelCats.where(..).with_toys_by(4).limit(5)Arelwhere

有没有直接使用官方ORM的“干净”解决方案来解决我的问题? 还是我需要回退到?Arel

SQL Ruby-on-Rails arel

评论

0赞 dbugger 11/13/2023
您可以使用原始 sql -- 只需在 SQL 视图中创建它 -- 然后基于 SQL 视图创建活动记录模型。然后,您可以像查询任何其他模型一样查询它。

答: 暂无答案