Rails 与扩展生成不正确的多对多关系 SQL
Rails Many-to-many relationship with extension generating incorrect SQL
我遇到一个问题,与 "extension" 的多对多关系生成不正确的 SQL。
class OrderItem < ApplicationRecord
belongs_to :buyer, class_name: :User
belongs_to :order
belongs_to :item, polymorphic: true
end
class User < ApplicationRecord
has_many :order_items_bought,
-> { joins(:order).where.not(orders: { state: :expired }).order(created_at: :desc) },
foreign_key: :buyer_id,
class_name: :OrderItem
has_many :videos_bought,
-> { joins(:orders).select('DISTINCT ON (videos.id) videos.*').reorder('videos.id DESC') },
through: :order_items_bought,
source: :item,
source_type: :Video do
def confirmed
where(orders: { state: :confirmed })
end
end
end
user.videos_bought.confirmed
生成此 SQL:
Video Load (47.0ms) SELECT DISTINCT ON (videos.id) videos.* FROM
"videos" INNER JOIN "order_items" "order_items_videos_join" ON
"order_items_videos_join"."item_id" = "videos"."id" AND
"order_items_videos_join"."item_type" = INNER JOIN
"orders" ON "orders"."id" = "order_items_videos_join"."order_id" INNER JOIN
"order_items" ON "videos"."id" = "order_items"."item_id" WHERE
"order_items"."buyer_id" = AND ("orders"."state" != ) AND "order_items"."item_type" = AND
"orders"."state" = ORDER BY videos.id DESC, "order_items"."created_at" DESC LIMIT
其中 returns 一些 Video
记录与未确认状态的订单相结合。我希望所有订单都有状态确认。
如果我使用原始 SQL 一切正常:
has_many :videos_bought,
-> {
joins('INNER JOIN orders ON orders.id = order_items.order_id')
.select('DISTINCT ON (videos.id) videos.*')
.reorder('videos.id DESC')
},
through: :order_items_bought,
source: :item,
source_type: :Video do
def confirmed
where(orders: { state: :confirmed })
end
end
现在 user.videos_bought.confirmed
生成这个 SQL:
Video Load (5.4ms) SELECT DISTINCT ON (videos.id) videos.* FROM
"videos" INNER JOIN "order_items" ON
"videos"."id" = "order_items"."item_id" INNER JOIN orders ON
orders.id = order_items.order_id WHERE
"order_items"."buyer_id" = AND ("orders"."state" != ) AND
"order_items"."item_type" = AND "orders"."state" = ORDER BY
videos.id DESC, "order_items"."created_at" DESC LIMIT
这看起来更简洁,因为它避免了自动生成的 order_items_videos_join
名称。它也只有 returns 个状态已确认的订单。
知道发生了什么事吗? ActiveRecord 有时会生成错误的 SQL 吗?
使用 rails 5.1.5
。升级到最新版本没有任何区别。
我希望得到解释,为什么 Rails 在第一种情况下生成 order_items_videos_join
字符串,而在第二种情况下却没有。另外,为什么第二个 SQL 查询会产生不正确的结果。如果需要,我可以使用更多代码和数据示例来编辑问题。
ActiveRecord 有时不仅会生成错误的 SQL,而且它有一些细微差别,因此在定义关系时最好从简单开始。例如,让我们重新处理您的查询以从那里获取 DISTINCT ON
。我从未见过需要使用 SQL 子句。
在链接高度定制的关联逻辑之前,让我们先看看是否有更简单的查询方法,然后检查是否有充分的理由将您的查询转化为关联。
看起来你有这样的架构:
用户
- 编号
订单
- 编号
- 状态
订单项
- 编号
- order_id
- buyer_id(这是在 OrderItem 而不是 Order 上的任何原因?)
- item_type(视频)
- item_id (videos.id)
视频
- 编号
一些花絮
- 不需要为查询条件创建关联扩展,这将在模型上形成完美的范围。见下文。
一个完美的查询可能看起来像
Video.joins(order_item: :order).
where(order_items: {
buyer_id: 123,
order: {
state: 'confirmed'
}).
# The following was part of the initial logic but
# doesn't alter the query results.
# where.not(order_items: {
# order: {state: 'expired'}
# }).
order('id desc')
这是另一种方式:
class User < ActiveRecord::Base
has_many :order_items, foreign_key: 'buyer_id'
def videos_purchased
Video.where(id: order_items.videos.confirmed.pluck(:id))
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order, class_name: 'User', foreign_key: 'buyer_id'
belongs_to :item, polymorphic: true
scope :bought, -> {where.not(orders: {state: 'cancelled'})}
scope :videos, -> {where(item_type: 'Video')}
end
class Video < ActiveRecord::Base
has_many :order_items, ...
scope :confirmed, -> {where(orders: {state: 'confirmed'})}
end
...
user = User.first()
user.videos_purchased
关于 table 和属性名称,我的语法可能有点古怪,但这应该是一个开始。
请注意,我将其从一个查询更改为两个查询。我建议 运行 在你真正注意到你有性能问题之前,即使这样你可能更容易缓存查询,而不是尝试优化复杂的连接逻辑。
我遇到一个问题,与 "extension" 的多对多关系生成不正确的 SQL。
class OrderItem < ApplicationRecord
belongs_to :buyer, class_name: :User
belongs_to :order
belongs_to :item, polymorphic: true
end
class User < ApplicationRecord
has_many :order_items_bought,
-> { joins(:order).where.not(orders: { state: :expired }).order(created_at: :desc) },
foreign_key: :buyer_id,
class_name: :OrderItem
has_many :videos_bought,
-> { joins(:orders).select('DISTINCT ON (videos.id) videos.*').reorder('videos.id DESC') },
through: :order_items_bought,
source: :item,
source_type: :Video do
def confirmed
where(orders: { state: :confirmed })
end
end
end
user.videos_bought.confirmed
生成此 SQL:
Video Load (47.0ms) SELECT DISTINCT ON (videos.id) videos.* FROM
"videos" INNER JOIN "order_items" "order_items_videos_join" ON
"order_items_videos_join"."item_id" = "videos"."id" AND
"order_items_videos_join"."item_type" = INNER JOIN
"orders" ON "orders"."id" = "order_items_videos_join"."order_id" INNER JOIN
"order_items" ON "videos"."id" = "order_items"."item_id" WHERE
"order_items"."buyer_id" = AND ("orders"."state" != ) AND "order_items"."item_type" = AND
"orders"."state" = ORDER BY videos.id DESC, "order_items"."created_at" DESC LIMIT
其中 returns 一些 Video
记录与未确认状态的订单相结合。我希望所有订单都有状态确认。
如果我使用原始 SQL 一切正常:
has_many :videos_bought,
-> {
joins('INNER JOIN orders ON orders.id = order_items.order_id')
.select('DISTINCT ON (videos.id) videos.*')
.reorder('videos.id DESC')
},
through: :order_items_bought,
source: :item,
source_type: :Video do
def confirmed
where(orders: { state: :confirmed })
end
end
现在 user.videos_bought.confirmed
生成这个 SQL:
Video Load (5.4ms) SELECT DISTINCT ON (videos.id) videos.* FROM
"videos" INNER JOIN "order_items" ON
"videos"."id" = "order_items"."item_id" INNER JOIN orders ON
orders.id = order_items.order_id WHERE
"order_items"."buyer_id" = AND ("orders"."state" != ) AND
"order_items"."item_type" = AND "orders"."state" = ORDER BY
videos.id DESC, "order_items"."created_at" DESC LIMIT
这看起来更简洁,因为它避免了自动生成的 order_items_videos_join
名称。它也只有 returns 个状态已确认的订单。
知道发生了什么事吗? ActiveRecord 有时会生成错误的 SQL 吗?
使用 rails 5.1.5
。升级到最新版本没有任何区别。
我希望得到解释,为什么 Rails 在第一种情况下生成 order_items_videos_join
字符串,而在第二种情况下却没有。另外,为什么第二个 SQL 查询会产生不正确的结果。如果需要,我可以使用更多代码和数据示例来编辑问题。
ActiveRecord 有时不仅会生成错误的 SQL,而且它有一些细微差别,因此在定义关系时最好从简单开始。例如,让我们重新处理您的查询以从那里获取 DISTINCT ON
。我从未见过需要使用 SQL 子句。
在链接高度定制的关联逻辑之前,让我们先看看是否有更简单的查询方法,然后检查是否有充分的理由将您的查询转化为关联。
看起来你有这样的架构:
用户
- 编号
订单
- 编号
- 状态
订单项
- 编号
- order_id
- buyer_id(这是在 OrderItem 而不是 Order 上的任何原因?)
- item_type(视频)
- item_id (videos.id)
视频
- 编号
一些花絮
- 不需要为查询条件创建关联扩展,这将在模型上形成完美的范围。见下文。
一个完美的查询可能看起来像
Video.joins(order_item: :order).
where(order_items: {
buyer_id: 123,
order: {
state: 'confirmed'
}).
# The following was part of the initial logic but
# doesn't alter the query results.
# where.not(order_items: {
# order: {state: 'expired'}
# }).
order('id desc')
这是另一种方式:
class User < ActiveRecord::Base
has_many :order_items, foreign_key: 'buyer_id'
def videos_purchased
Video.where(id: order_items.videos.confirmed.pluck(:id))
end
end
class OrderItem < ActiveRecord::Base
belongs_to :order, class_name: 'User', foreign_key: 'buyer_id'
belongs_to :item, polymorphic: true
scope :bought, -> {where.not(orders: {state: 'cancelled'})}
scope :videos, -> {where(item_type: 'Video')}
end
class Video < ActiveRecord::Base
has_many :order_items, ...
scope :confirmed, -> {where(orders: {state: 'confirmed'})}
end
...
user = User.first()
user.videos_purchased
关于 table 和属性名称,我的语法可能有点古怪,但这应该是一个开始。
请注意,我将其从一个查询更改为两个查询。我建议 运行 在你真正注意到你有性能问题之前,即使这样你可能更容易缓存查询,而不是尝试优化复杂的连接逻辑。