当使用有多个关联来管理一系列标记时,根据所选标签的数量对集合进行排序/排序的最有效方法是什么。
例如:
ProductTags
在这种情况下,是否可以使用cache_counter或类似的东西?我不认为使用sort
是最好的选择。我是否正确地认为在实际数据库上使用order
通常比sort
更快?
澄清/更新
如果上面的内容混淆了,很抱歉。基本上,我所追求的是更接近排序的相关性。例如,用户可能选择标记1、2和4。如果产品具有与其相关的所有树标记,我希望该产品先列出。第二个产品可能只有标签1和4,依此类推。我几乎可以肯定,这将不得不使用sort
和order
,但我想知道是否有人找到了一种更有效的方法。
发布于 2012-01-27 10:15:13
与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
列表填充该查询。在将列表插入查询之前,一定要对列表中的进行净化,否则您将打开可能的注入。
获取查询组装方法的结果,并通过Product.find_by_sql(my_relevance_sql)
运行它,直接从DB中按相关性对模型进行预排序。
明显的缺点是,您在Rails代码中引入了一个特定于DBMS的依赖项(如果您不小心的话,就会面临SQL注入的风险)。如果不使用MySQL,则可能需要调整语法。但是,它的执行速度应该比在结果上使用Rubysort
快得多,特别是在一个庞大的结果集中。此外,如果需要,添加LIMIT
子句将为您提供分页支持。
发布于 2012-12-03 14:01:28
基于Ryan的出色回答,我想要一种可以使用acts-as-taggable-on
和类似插件(称为tags
/taggings
的表)的方法,最后得到如下结果:
def Product.find_by_tag_list(tag_list)
tag_list_sql = "'" + tag_list.join("','") + "'"
Product.find_by_sql("SELECT * FROM products, (SELECT taggable_id, COUNT(*) AS relevance FROM taggings LEFT JOIN tags ON tags.id = taggings.tag_id WHERE tags.name IN (" + tag_list_sql + ") GROUP BY taggable_id) AS r WHERE products.id = r.taggable_id ORDER BY r.relevance DESC;")
end
为了获得相关产品的列表,我可以这样做:
Product.find_by_tag_list(my_product.tag_list)
https://stackoverflow.com/questions/9028190
复制相似问题