Упорядочение по релевантности в базе данных возможно и гораздо более эффективно, чем использование метода сортировки в Ruby. Предполагая следующую структуру модели и соответствующую базовую структуру таблицы SQL:
class Product < ActiveRecord::Base
has_many :product_taggings
has_many :product_tags, :through => :product_taggings
end
class ProductTags < ActiveRecord::Base
has_many :product_taggings
has_many :products, :through => :product_taggings
end
class ProductTaggings < ActiveRecord::Base
belongs_to :product
belongs_to :product_tags
end
Запрос на актуальность в MySQL будет выглядеть примерно так:
SELECT
`product_id`
,COUNT(*) AS relevance
FROM
`product_taggings` AS ptj
LEFT JOIN
`products` AS p
ON p.`id` = ptj.`product_id`
LEFT JOIN
`product_tags` AS pt
ON pt.`id` = ptj.`product_tag_id`
WHERE
pt.`name` IN ('Tag 1', 'Tag 2')
GROUP BY
`product_id`
Если у меня есть следующие продукты и связанные теги:
Product 1 -> Tag 3
Product 2 -> Tag 1, Tag 2
Product 3 -> Tag 1, Tag 3
Тогда предложение WHERE
сверху должно дать мне ответ:
product_id | relevance
----------------------
2 | 2
3 | 1
* Product 1 is not included since there were no matches.
Given that the user is performing a filtered search,
this behavior is probably fine. There's a way to get
Product 1 into the results with 0 relevance if
necessary.
То, что вы сделали, - это создали небольшой набор результатов, который может выступать в качестве своего рода встроенной таблицы соединений. Чтобы прикрепить оценку релевантности к каждой строке запроса из таблицы products
, используйте этот запрос как подзапрос следующим образом:
SELECT *
FROM
`products` AS p
,(SELECT
`product_id`
,COUNT(*) AS relevance
FROM
`product_taggings` AS ptj
LEFT JOIN
`products` AS p
ON p.`id` = ptj.`product_id`
LEFT JOIN
`product_tags` AS pt
ON pt.`id` = ptj.`product_tag_id`
WHERE
pt.`name` IN ('Tag 1', 'Tag 2')
GROUP BY `product_id`
) AS r
WHERE
p.`id` = r.`product_id`
ORDER BY
r.`relevance` DESC
У вас будет набор результатов, содержащий поля из вашей таблицы products
и дополнительный столбец релевантности в конце, который затем будет использоваться в предложении ORDER BY
.
Вам нужно будет написать метод, который заполнит этот запрос желаемым списком pt.name IN
. Обязательно очистите этот список , прежде чем подключать его к запросу, иначе вы откроете себя для возможного внедрения SQL.
Возьмите результат вашего метода сборки запроса и выполните его через Product.find_by_sql(my_relevance_sql)
, чтобы предварительно отсортировать ваши модели по релевантности непосредственно из БД.
Очевидным недостатком является то, что вы вводите специфичную для СУБД зависимость в свой код Rails (и рискуете ввести SQL, если не будете осторожны). Если вы не используете MySQL, возможно, потребуется изменить синтаксис. Однако он должен работать намного быстрее, особенно на огромном наборе результатов, чем использование Ruby sort
для результатов. Кроме того, добавление предложения LIMIT
даст вам поддержку пагинации при необходимости.