【问题标题】:Tricky Rails 5 ActiveRecord "where" query involving multiple associated models and an "or" operatorTricky Rails 5 ActiveRecord“where”查询涉及多个关联模型和“or”运算符
【发布时间】:2017-07-12 20:56:12
【问题描述】:

我有一个 PurchaseOrder 模型,它与 Line Item 模型具有 has_many 关系。 PurchaseOrder 和 Line Item 模型都与 Delivery 模型具有 has_many 关系。 Delivery 模型具有“expected_arrival”属性。我需要获取满足以下任一条件的所有采购订单:

a) 订单的交货具有昨天或更早的“expected_arrival”属性 b) 订单的任何订单项的“expected_arrival”属性为昨天或更早

我曾计划使用 Rails 5 or 运算符,但在将 orjoinsincludes 结合使用时,Rails 中显然存在一个已知错误:

https://github.com/rails/rails/issues/24055

然后我尝试使用grouphaving,如下所示:

PurchaseOrder.left_joins(:deliveries, {line_items: :deliveries})
  .group("purchase_orders.id")
  .having("MAX(line_items.deliveries.expected_arrival) <= ? 
    OR MAX(deliveries.expected_arrival) <= ?", 
  Time.now, 
  Time.now)`

这产生了以下错误:

ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR:  invalid reference to FROM-clause entry for table "deliveries"

LINE 1: ..." IS NULL GROUP BY purchase_orders.id HAVING (MAX(line_items... ^ 提示:表“deliveries”有一个条目,但不能从查询的这一部分引用。 `

Rails 告诉我我无法以这种方式访问​​交付表,这很奇怪,因为以下每个查询都是自己工作的:

PurchaseOrder.left_joins(:deliveries).group("purchase_orders.id").having("MAX(deliveries.expected_arrival) <= ?", Time.now)`

PurchaseOrder.left_joins({line_items: :deliveries}).group("purchase_orders.id").having("MAX(deliveries.expected_arrival) <= ?", Time.now)`

我想避免对数据库进行调用,这就是我最初想使用or 查询的原因。如您所见,在最后两个查询中,我只是引用deliveries.expected_arrival 而不是line_items.deliveries.expected_arrival。我假设这是 Rails 有问题的地方,这意味着我需要以某种方式给 line_items.deliveries 起别名。

这是一个正确的假设吗?如果是这样,我会怎么做?如果不是,那么构建此查询的正确方法是什么?

【问题讨论】:

  • 想要this answer 帮助
  • 我更喜欢 Rails 命令而不是普通 SQL。
  • Delivery 模型是否与 PurchaseOrder/LineItem 具有多态关联,还是每个模型都有一个外键?
  • @MichaelGorman Deliveries 和 PurchaseOrders 之间以及 Deliveries 和 LineItems 之间存在多对多关系(即连接表)。
  • 这使它有点粘,但仍然可行。我会更新我的答案以反映这一点

标签: sql ruby-on-rails activerecord ruby-on-rails-5


【解决方案1】:

试试看。

PurchaseOrder.includes(:deliveries, line_items: :deliveries)
  .group("purchase_orders.id")
  .where("MAX(line_items.deliveries.expected_arrival) <= ? 
    OR MAX(deliveries.expected_arrival) <= ?", 
  Time.now, 
  Time.now)

【讨论】:

  • 这不正是我在最初的问题中发布的内容吗?唯一的区别似乎是 includesleft_joins
【解决方案2】:

据我所知,Rails 没有内置的连接方法,可以对同一个表进行多次连接,但 sql 有。从this answer我们看到可以通过在joins语句中添加一个参数来命名joins来完成。

SELECT i.name as name, v1.value as value_1, v2.value as value_2 
  FROM item i
       INNER JOIN item_value iv ON iv.item = i.id
       INNER JOIN property p ON iv.property = p.id
       LEFT JOIN value v1 ON p.name = 'prop1' AND v1.id = iv.value
       LEFT JOIn value v2 ON p.name = 'prop2' AND v2.id = iv.value

Rails 确实允许您将 sql 作为连接语句的一部分,如 this answer 中所见

对于这个用例

PurchaseOrder
  .joins("LEFT JOIN deliveries_purchase_orders on purchase_orders.id = deliveries_purchase_orders.purchase_order_id)
  .joins("LEFT JOIN deliveries order_deliveries ON deliveries.id = deliveries_purchase_orders.delivery_id")
  .left_joins(:line_items)
  .joins("LEFT JOIN deliveries_line_items on line_items.id = deliveries_line_items.line_item_id)
  .joins("LEFT JOIN deliveries item_deliveries ON deliveries.id = deliveries_line_items.delivery_id")
  .group("purchase_orders.id")
  .having("MAX(item_deliveries.expected_arrival) <= ? 
    OR MAX(order_deliveries.expected_arrival) <= ?", 
  Time.now, 
  Time.now)`

如果有一种轨道式的方法,它可能不会那么干净,但它应该能让你前进。

编辑:

这都是用 PostgreSQL 编写的,对于其他风格的 SQL,格式会略有不同

替代解决方案。

由于此请求执行 5 个连接请求,因此最好在 PurchaseOrderLineItem 中添加一个字段以表示最新的预期交付日期,并在创建/更新过滤器后使用 Rails 来维护它们。将处理负载从读取转移到写入。

【讨论】:

  • AS 语句丢失是有意还是无意?我尝试了您的确切代码(顺便说一句缺少一些右引号)和以下代码(pastebin.com/V8rQ9SNj),但我不断收到如下错误:ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "deliveries_purchase_orders" LINE 1: ...ries AS order_deliveries ON order_deliveries.id = deliveries...
  • 好吧,看起来可能只是连接表名不是deliveries_purchase_orders。连接表的名称是什么?
  • 看起来连接表名称是问题的一部分。我得到了以下语句,它有效:PurchaseOrder.joins("LEFT JOIN delivery_purchase_orders on purchase_orders.id = delivery_purchase_orders.purchase_order_id").joins("LEFT JOIN deliveries AS order_deliveries ON order_deliveries.id = delivery_purchase_orders.delivery_id").left_joins(:line_items) 但是当我连接以下内容时它失败了:.joins("LEFT JOIN delivery_line_items on line_items.id = delivery_line_items.line_item_id")
  • 错误是:ActiveRecord::StatementInvalid: PG::UndefinedTable: ERROR: missing FROM-clause entry for table "line_items" LINE 1: ...ders.delivery_id LEFT JOIN delivery_line_items on line_items... ^ (注意向上箭头指向line_items)。我猜这个问题是由left_joins(:line_items)引起的。
  • 你能看到这种情况下生成的完整sql吗?
猜你喜欢
  • 2015-12-21
  • 2023-03-11
  • 1970-01-01
  • 2018-09-13
  • 1970-01-01
  • 1970-01-01
  • 2011-05-11
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多