@Splite

Как сделать такой запрос на activerecord?

Допустим, есть такие таблицы
elements
id name
1 Тест1
2 Тест2
3 Тест3

options
id name
1 Опция1
2 Опция2
3 Опция3

elements_options
element_id option_id
1 1
1 2
3 2
2 1
2 2

В RoR для этого используются связь has_and_belongs_to_many. Так вот, как мне запросить все элементы, которые, например, имеют опции с ID 1 и 2 одновременно? (в случае с моим примером это запрос должен вернуть элементы с id 1 и 2)
  • Вопрос задан
  • 774 просмотра
Решения вопроса 1
viktorvsk
@viktorvsk
Я в свое время так и не нашел ответ на этот вопрос.
Задача была похожая: интернет-магазин, товары, фильтры товаров. При фильтрации товаров нужно выбирать товар у которого, помимо прочего, есть 2 фильтра И с id=1 И с id=2, например (Когда нужно выбрать, скажем, ноутбук у которого И 8ГБ памяти И 14 дюймов диагональ)

Был вариант с фасетным поиском с помощью полнотекстовых движков (sphinx, solr, elasticsearch и т.д.), но они бы были оверхедом в той ситуации.

Сейчас уже долгое время работает вот такой метод:
def filter(fvalues_ids)
  grouped = Catalog::FilterValue.where(id: fvalues_ids.map(&:to_i)).to_group
  query = []
  grouped.each_value do |fvalues|
    ids = fvalues.map(&:id).join(', ')
    query << %(
      SELECT catalog_products.id
      FROM catalog_products
      INNER JOIN
        catalog_product_filter_values ON catalog_product_filter_values.catalog_product_id = catalog_products.id
      WHERE
        catalog_product_filter_values.catalog_filter_value_id IN (#{ids})
    )
  end

  query = query.join("\nINTERSECT\n")
  ids = ActiveRecord::Base.connection.execute(query).map{ |row| row['id'] }
  where(id: ids)
end


Так, думаю, все понятно. Разве что метод #to_group - он группирует фильтры (например, 8ГБ оперативки и 4ГБ оперативки нужно сравнивать по ИЛИ, а 8ГБ оперативки и 14 дюймов экран - по И)

Решение выглядит костыльно, но более элегантного не нашел.
Ответ написан
Пригласить эксперта
Ответы на вопрос 4
premas
@premas
Full-stack web-developer
Никогда не используйте для many-to-many связей has_and_belongs_to_many! Используйте только нотацию has_many through. Однажды вам придется добавить в вашу связующую таблицу какое-то поле. Со связью организованной через has_and_belongs_to_many вы не сможете к нему обратиться.
Ответ написан
Комментировать
POS_troi
@POS_troi
СадоМазо Админ, флудер, троль.
ModelName.where(element_id: [1,2])

Внимательно изучаем guides.rubyonrails.org/active_record_querying.html
Ответ написан
@thepry
Ruby on rails, 1С разработчик
UPDATE: а, вы уже и сами придумали :)

Вы можете использовать LEFT JOIN вместе с оператором COUNT и условием в HAVING. Товар содержит все опции из массива, если отобрать по этим опциям и количество отобранных равно длине массива.
Примерно так:
options_ids = [1, 2]
Element.includes(:options).where('options.id': options_ids).group('elements.id, options.id').having('COUNT(options.id) = ?', options_ids.size)
Ответ написан
Комментировать
@vsuhachev
Нужно использовать подзапрос, вот примерный код:
Element.where(id: Option.joins(:elements).where(id: [1,2]).uniq.pluck('elements.id'))
Ответ написан
Комментировать
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы