如何编写 Rails 查找器方法,其中 has_many 项的 none 具有非零字段?
How do I write a Rails finder method where none of the has_many items has a non-nil field?
我正在使用 Rails 5. 我有以下型号 ...
class Order < ApplicationRecord
...
has_many :line_items, :dependent => :destroy
LineItem 模型有一个属性,"discount_applied." 我想 return 所有订单,其中 "discount_applied" 字段不为零的订单项的实例为零。如何编写这样的查找器方法?
一个可能的代码
Order.includes(:line_items).where.not(line_items: {discount_applied: nil})
我建议熟悉查询方法的 AR 文档。
更新
这似乎比我最初更感兴趣。而且更复杂,所以我无法给你一个工作代码。但我会研究使用 LineItem.group(order_id).having(discount_applied: nil)
的解决方案,它应该为您提供 line_items 的集合,然后将其用作子查询以查找相关订单。
首先,这实际上取决于您是想使用纯粹的 Arel 方法还是使用 SQL 是否合适。前者仅在您打算构建库时才建议使用前者,但如果您正在构建应用程序则不必要手动查询可能是您最少的麻烦)。
假设使用 SQL 没问题,应该适用于几乎所有数据库的最简单的解决方案是:
Order.where("(SELECT COUNT(*) FROM line_items WHERE line_items.order_id = orders.id AND line_items.discount_applied IS NULL) = 0")
这应该也适用于几乎所有地方(并且有更多的 Arel 和更少的手动 SQL):
Order.left_joins(:line_items).where(line_items: { discount_applied: nil }).group("orders.id").having("COUNT(line_items.id) = 0")
根据您的特定 DBMS(更具体地说:其各自的查询优化器),一个或另一个可能性能更高。
希望对您有所帮助。
如果我没理解错的话,您想要获取 none 订单项(如果有)应用了折扣的所有订单。
使用 ActiveRecord
获取这些订单的一种方法如下:
Order.distinct.left_outer_joins(:line_items).where(line_items: { discount_applied: nil })
以下是其工作原理的简要说明:
- 该解决方案使用
left_outer_joins
,假设您不会访问每个订单的行项目。您也可以使用 left_joins
,这是一个别名。
- 如果您需要为每个
Order
实例实例化订单项,请将 .eager_load(:line_items)
添加到链中,这将防止对每个订单 (N+1) 执行额外查询,即,执行order.line_items.each
在视图中。
- 使用
distinct
对确保结果中仅包含一次订单至关重要。
更新
我之前的解决方案只是检查 discount_applied IS NULL
至少一个订单项,而不是所有订单项。以下查询应该 return 您需要的订单。
Order.left_joins(:line_items).group(:id).having("COUNT(line_items.discount_applied) = ?", 0)
这是怎么回事:
- 该解决方案仍需要使用左外部联接 (
orders LEFT OUTER JOIN line_items
),以便包含没有任何关联项目的订单。
- 将行项目分组以获得单个
Order
对象,而不管它有多少项目 (GROUP BY recipes.id
)。
- 它计算每个订单的折扣订单项数量,仅选择应用了零折扣的订单项 (
HAVING (COUNT(line_items.discount_applied) = 0)
)。
希望对您有所帮助。
效率不高,但我认为它可以解决您的问题:
orders = Order.includes(:line_items).select do |order|
order.line_items.all? { |line_item| line_item.discount_applied.nil? }
end
更新:
我们可以从输出结果中排除所有订单项都应用了折扣的订单,而不是查找其所有订单项都没有折扣的订单。这可以通过 where 子句中的 subquery 来完成:
# Find all ids of orders which have line items with a discount applied:
excluded_ids = LineItem.select(:order_id)
.where.not(discount_applied: nil)
.distinct.map(&:order_id)
# exclude those ids from all orders:
Order.where.not(id: excluded_ids)
您可以将它们组合在一个查找器方法中:
Order.where.not(id: LineItem
.select(:order_id)
.where.not(discount_applied: nil))
希望对您有所帮助
如果你想要所有 discount_applied 为 nil 的记录,那么:
Order.includes(:line_items).where.not(line_items: {discount_applied: nil})
(使用include避免n+1问题)
或
Order.joins(:line_items).where.not(line_items: {discount_applied: nil})
这是您问题的解决方案
order_ids = Order.joins(:line_items).where.not(line_items: {discount_applied: nil}).pluck(:id)
orders = Order.where.not(id: order_ids)
第一个查询将 return 个 Orders
的 ID,其中至少一个 line_item
具有 discount_applied
。第二个查询将 return 所有 orders
其中 line_item
具有 discount_applied
.
的零个实例
您无法使用经典的 rails left_joins 有效地执行此操作,但是 sql 左连接是为处理这些情况而构建的
Order.joins("LEFT JOIN line_items AS li ON li.order_id = orders.id
AND li.discount_applied IS NOT NULL")
.where("li.id IS NULL")
一个简单的内部联接将 return 所有订单,与所有 line_items、
联接
但如果此订单没有 line_items,则忽略该订单(如 false where)
使用左连接,如果没有找到 line_items,sql 会将其连接到一个空条目以保留它
所以我们离开加入了我们不想要的line_items,并找到所有加入空line_items的订单
并避免使用 where(id: pluck(:id))
或 having("COUNT(*) = 0")
的所有代码,这会在那天杀死您的数据库
我会使用 SQL 的 NOT EXISTS
特性,它至少在 MySQL 和 PostgreSQL
中可用
它应该是这样的
class Order
has_many :line_items
scope :without_discounts, -> {
where("NOT EXISTS (?)", line_items.where("discount_applied is not null")
}
end
我正在使用 Rails 5. 我有以下型号 ...
class Order < ApplicationRecord
...
has_many :line_items, :dependent => :destroy
LineItem 模型有一个属性,"discount_applied." 我想 return 所有订单,其中 "discount_applied" 字段不为零的订单项的实例为零。如何编写这样的查找器方法?
一个可能的代码
Order.includes(:line_items).where.not(line_items: {discount_applied: nil})
我建议熟悉查询方法的 AR 文档。
更新
这似乎比我最初更感兴趣。而且更复杂,所以我无法给你一个工作代码。但我会研究使用 LineItem.group(order_id).having(discount_applied: nil)
的解决方案,它应该为您提供 line_items 的集合,然后将其用作子查询以查找相关订单。
首先,这实际上取决于您是想使用纯粹的 Arel 方法还是使用 SQL 是否合适。前者仅在您打算构建库时才建议使用前者,但如果您正在构建应用程序则不必要手动查询可能是您最少的麻烦)。
假设使用 SQL 没问题,应该适用于几乎所有数据库的最简单的解决方案是:
Order.where("(SELECT COUNT(*) FROM line_items WHERE line_items.order_id = orders.id AND line_items.discount_applied IS NULL) = 0")
这应该也适用于几乎所有地方(并且有更多的 Arel 和更少的手动 SQL):
Order.left_joins(:line_items).where(line_items: { discount_applied: nil }).group("orders.id").having("COUNT(line_items.id) = 0")
根据您的特定 DBMS(更具体地说:其各自的查询优化器),一个或另一个可能性能更高。
希望对您有所帮助。
如果我没理解错的话,您想要获取 none 订单项(如果有)应用了折扣的所有订单。
使用 ActiveRecord
获取这些订单的一种方法如下:
Order.distinct.left_outer_joins(:line_items).where(line_items: { discount_applied: nil })
以下是其工作原理的简要说明:
- 该解决方案使用
left_outer_joins
,假设您不会访问每个订单的行项目。您也可以使用left_joins
,这是一个别名。 - 如果您需要为每个
Order
实例实例化订单项,请将.eager_load(:line_items)
添加到链中,这将防止对每个订单 (N+1) 执行额外查询,即,执行order.line_items.each
在视图中。 - 使用
distinct
对确保结果中仅包含一次订单至关重要。
更新
我之前的解决方案只是检查 discount_applied IS NULL
至少一个订单项,而不是所有订单项。以下查询应该 return 您需要的订单。
Order.left_joins(:line_items).group(:id).having("COUNT(line_items.discount_applied) = ?", 0)
这是怎么回事:
- 该解决方案仍需要使用左外部联接 (
orders LEFT OUTER JOIN line_items
),以便包含没有任何关联项目的订单。 - 将行项目分组以获得单个
Order
对象,而不管它有多少项目 (GROUP BY recipes.id
)。 - 它计算每个订单的折扣订单项数量,仅选择应用了零折扣的订单项 (
HAVING (COUNT(line_items.discount_applied) = 0)
)。
希望对您有所帮助。
效率不高,但我认为它可以解决您的问题:
orders = Order.includes(:line_items).select do |order|
order.line_items.all? { |line_item| line_item.discount_applied.nil? }
end
更新:
我们可以从输出结果中排除所有订单项都应用了折扣的订单,而不是查找其所有订单项都没有折扣的订单。这可以通过 where 子句中的 subquery 来完成:
# Find all ids of orders which have line items with a discount applied:
excluded_ids = LineItem.select(:order_id)
.where.not(discount_applied: nil)
.distinct.map(&:order_id)
# exclude those ids from all orders:
Order.where.not(id: excluded_ids)
您可以将它们组合在一个查找器方法中:
Order.where.not(id: LineItem
.select(:order_id)
.where.not(discount_applied: nil))
希望对您有所帮助
如果你想要所有 discount_applied 为 nil 的记录,那么:
Order.includes(:line_items).where.not(line_items: {discount_applied: nil})
(使用include避免n+1问题) 或
Order.joins(:line_items).where.not(line_items: {discount_applied: nil})
这是您问题的解决方案
order_ids = Order.joins(:line_items).where.not(line_items: {discount_applied: nil}).pluck(:id)
orders = Order.where.not(id: order_ids)
第一个查询将 return 个 Orders
的 ID,其中至少一个 line_item
具有 discount_applied
。第二个查询将 return 所有 orders
其中 line_item
具有 discount_applied
.
您无法使用经典的 rails left_joins 有效地执行此操作,但是 sql 左连接是为处理这些情况而构建的
Order.joins("LEFT JOIN line_items AS li ON li.order_id = orders.id
AND li.discount_applied IS NOT NULL")
.where("li.id IS NULL")
一个简单的内部联接将 return 所有订单,与所有 line_items、
联接
但如果此订单没有 line_items,则忽略该订单(如 false where)
使用左连接,如果没有找到 line_items,sql 会将其连接到一个空条目以保留它
所以我们离开加入了我们不想要的line_items,并找到所有加入空line_items的订单
并避免使用 where(id: pluck(:id))
或 having("COUNT(*) = 0")
的所有代码,这会在那天杀死您的数据库
我会使用 SQL 的 NOT EXISTS
特性,它至少在 MySQL 和 PostgreSQL
它应该是这样的
class Order
has_many :line_items
scope :without_discounts, -> {
where("NOT EXISTS (?)", line_items.where("discount_applied is not null")
}
end