ActiveRecord 连接 - Return 仅完全匹配

ActiveRecord joins - Return only exact matches

我有三个模型 OutfitProduct 和一个连接模型 OutfitProduct(Outfit 通过 OutfitProducts 有很多产品)。

我想查找仅包含完全匹配产品的服装。

到目前为止我有这个

def by_exact_products(products)
  joins(outfit_products: :product)
    .where(outfit_products: { product: products })
    .group("outfits.id")
    .having('count(outfits.id) = ?', products.size)
end

以上 return 包含我正在搜索的产品的任何服装,即使它不是完全匹配的。我希望 return 只有完全匹配的服装。

示例: 假设我们有以下由以下产品组成的服装:

outfit_1.products = [product_1, product_2, product_3, product_4]
outfit_2.products = [product_1, product_2]
outfit_3.products = [product_1, product_2, product_3]

如果我将 [product_1, product_2, product_3] 传递给我的查询,它将 return outfit_1outfit_3 - 我只希望它 return outfit_3 这是完全匹配。

更新(更多信息)

使用包含三个产品的数组调用查询会生成以下查询:

SELECT "outfits".* 
FROM   "outfits" 
       INNER JOIN "outfit_products" 
               ON "outfit_products"."outfit_id" = "outfits"."id" 
       INNER JOIN "products" 
               ON "products"."id" = "outfit_products"."product_id" 
WHERE  "outfit_products"."product_id" IN ( 18337, 6089, 6224 ) 
GROUP  BY outfits.id 
HAVING ( Count(outfits.id) = 3 ) 

我们先来看看为什么会这样。您使用以下场景:

outfit_1.products = [product_1, product_2, product_3, product_4]
outfit_2.products = [product_1, product_2]
outfit_3.products = [product_1, product_2, product_3]

这将有以下 outfit_products table:

outfit_id | product_id
----------|-----------
        1 |          1
        1 |          2
        1 |          3
        1 |          4
        2 |          1
        2 |          2
        3 |          1
        3 |          2
        3 |          3

当您添加限制时:

WHERE "outfit_products"."product_id" IN ( 1, 2, 3 )

它将消除行:

outfit_id | product_id
----------|-----------
        1 |          4

产品 1 保留 3 条记录,当您对记录进行分组和计数时,产品 1 的结果值为 3。这意味着当前查询将仅检查提供的最少产品(也就是确保所有提供的产品都存在)。

要同时消除产品多于所提供产品的记录,您必须添加第二个计数。没有上述限制的产品算在哪。

def by_exact_products(products)
  # all outfits that have at least all products
  with_all_products = joins(outfit_products: :product)
                      .where(outfit_products: { product: products })
                      .group("outfits.id")
                      .having('count(outfits.id) = ?', products.size)

  # all outfits that have exactly all products
  joins(outfit_products: :product)
    .where(id: with_all_products.select(:id))
    .group("outfits.id")
    .having('count(outfits.id) = ?', products.size)
end

这将select所有至少拥有所有提供的产品的服装,并统计他们的产品总数。