ActiveRecord объединяется - возвращает только точные совпадения - PullRequest
2 голосов
/ 08 мая 2020

У меня есть три модели: Outfit, Product и объединенная модель 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

Вышеупомянутое возвращает любую одежду, содержащую продукты, которые я ищу, даже если это не точное совпадение. Я хотел бы, чтобы он возвращал только ту одежду, которая точно соответствует.

Пример. Предположим, у нас есть следующие наряды, состоящие из следующих товаров:

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] на мой запрос он вернет outfit_1 и outfit_3 - я бы хотел, чтобы он возвращал только outfit_3, что является точным совпадением.

UPDATE (Подробнее)

Вызов запроса с массивом из трех продуктов дает следующий запрос:

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 ) 

1 Ответ

2 голосов
/ 08 мая 2020

Давайте сначала посмотрим, почему это происходит. Вы используете следующий сценарий:

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 таблица:

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 записями, когда вы группируете и подсчитываете записи, вы получите результирующее значение 3 для продукта 1. Это означает, что текущий запрос будет проверять только минимальное количество предоставленных продуктов (то есть убедитесь, что все предоставленные продукты присутствуют).

Чтобы также удалить записи, в которых продуктов больше, чем предоставленных, вам необходимо добавить второй счет. При этом учитываются товары без указанного выше ограничения.

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

Будет выбрана вся одежда, в которой есть хотя бы все предоставленные товары, и будет подсчитано их общее количество товаров.

...