mat*_*atQ 5 ruby postgresql activerecord ruby-on-rails
我有 3 个模型,Shop,Client,Product。
一个店铺有很多客户,一个店铺有很多产品。
然后我有 2 个额外的模型,一个是ShopClient,将shop_id和分组client_id。第二个是ShopProduct,将shop_idand分组product_id。
现在我有一个接收两个参数的控制器,client_id和product_id。所以我想选择所有商店(在一个实例变量中@shops)过滤client_id和product_id没有商店重复。我怎样才能做到这一点??
我希望我说清楚了,谢谢。
ps:我使用 Postgresql 作为数据库。
小智 5
以下查询对您有用。
class Shop
has_many :shop_clients
has_many :clients, through: :shop_clients
has_many :shop_products
has_many :products, through: :shop_products
end
class Client
end
class Product
end
class ShopClient
belongs_to :shop
belongs_to :client
end
class ShopProduct
belongs_to :shop
belongs_to :product
end
@shops = Shop.joins(:clients).where(clients: {id: params[:client_id]}).merge(Shop.joins(:products).where(products: {id: params[:product_id]}))
Run Code Online (Sandbox Code Playgroud)
我觉得解决这个问题的最好方法是使用子查询。我将首先从ShopClient收集所有有效的商店 ID ,然后从ShopProduct收集所有有效的商店 ID 。然后将它们输入到Shop上的 where 查询中。这将导致一个 SQL 查询。
shop_client_ids = ShopClient.where(client_id: params[:client_id]).select(:shop_id)
shop_product_ids = ShopProduct.where(product_id: params[:product_id]).select(:shop_id)
@shops = Shop.where(id: shop_client_ids).where(id: shop_product_ids)
#=> #<ActiveRecord::Relation [#<Shop id: 1, created_at: "2018-02-14 20:22:18", updated_at: "2018-02-14 20:22:18">]>
Run Code Online (Sandbox Code Playgroud)
上面的查询会产生下面的 SQL 查询。我没有指定限制,但这可能是由于我的虚拟项目使用 SQLite 这一事实而增加的。
SELECT "shops".*
FROM "shops"
WHERE
"shops"."id" IN (
SELECT "shop_clients"."shop_id"
FROM "shop_clients"
WHERE "shop_clients"."client_id" = ?) AND
"shops"."id" IN (
SELECT "shop_products"."shop_id"
FROM "shop_products"
WHERE "shop_products"."product_id" = ?)
LIMIT ?
[["client_id", 1], ["product_id", 1], ["LIMIT", 11]]
Run Code Online (Sandbox Code Playgroud)
将两个子查询组合在一个where中不会产生正确的响应:
@shops = Shop.where(id: [shop_client_ids, shop_product_ids])
#=> #<ActiveRecord::Relation []>
Run Code Online (Sandbox Code Playgroud)
产生查询:
SELECT "shops".* FROM "shops" WHERE "shops"."id" IN (NULL, NULL) LIMIT ? [["LIMIT", 11]]
Run Code Online (Sandbox Code Playgroud)
请记住,当您在控制台中一一运行语句时,通常会产生 3 个查询。这是因为返回值使用#inspect方法让你看到结果。Rails 重写此方法来执行查询并显示结果。
您可以通过在语句后缀 . 来模拟正常应用程序的行为;nil。这确保nil返回并且不会在 where 链上调用#inspect方法,从而不执行查询并将链保留在内存中。
如果您想清理控制器,您可能需要将这些子查询移至模型方法中(受到jvillians 答案的启发)。
class Shop
# ...
def self.with_clients(*client_ids)
client_ids.flatten! # allows passing of multiple arguments or an array of arguments
where(id: ShopClient.where(client_id: client_ids).select(:shop_id))
end
# ...
end
Run Code Online (Sandbox Code Playgroud)
子查询相对于联接的优点是,如果您查询不唯一的属性,则使用联接可能最终会多次返回相同的记录。例如,假设产品的属性Product_type为'physical'或'digital'。如果你想选择所有销售数字产品的商店,你一定不要忘记在使用连接时在链上调用distinct ,否则同一家商店可能会返回多次。
但是,如果您必须查询产品中的多个属性,并且您将在模型中使用多个助手(其中每个助手joins(:products))。多个子查询可能会更慢。(假设您设置了has_many :products, through: :shop_products。)因为 Rails 将同一关联的所有连接减少为单个连接。示例:(Shop.joins(:products).joins(:products)来自多个类方法)仍将最终加入产品表一次,而子查询不会减少。
| 归档时间: |
|
| 查看次数: |
5070 次 |
| 最近记录: |