通过关联匹配has_many最快的订购方式?

时间:2012-01-27 02:13:36

标签: ruby-on-rails ruby-on-rails-3

当使用多个关联来管理严重的标签时,按照所选标签的数量对集合进行排序/排序的最有效方法是什么。

例如:

  • 产品可以通过ProductTags获得许多标签
  • 当用户选择标签时,我想按照每个产品所选标签的数量来订购产品。

在这种情况下是否可以使用cache_counter或类似的东西?我不相信使用sort是最好的选择。我是否认为在实际数据库上使用order通常比sort更快?

澄清/更新

很抱歉,如果上述情况令人困惑。基本上我所追求的更接近于通过相关性排序。例如,用户可以选择标签1,2和4.如果产品具有与之关联的所有树标签,我希望首先列出该产品。第二个产品可能只有标签1&等等。我几乎肯定这必须使用sortorder,但是想知道是否有人找到了更有效的方法。

2 个答案:

答案 0 :(得分:0)

在数据库中按相关性排序既可能也比在Ruby中使用sort方法更有效。假设以下模型结构和适当的基础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代码中引入了特定于DBMS的依赖关系(如果您不小心,则会冒SQL注入风险)。如果您不使用MySQL,则可能需要调整语法。但是,与在结果上使用Ruby sort相比,它应该执行得更快,特别是在结果集上。此外,如果需要,添加LIMIT子句将为您提供分页支持。

答案 1 :(得分:0)

在Ryan的优秀答案的基础上,我想要一个可以使用acts-as-taggable-on的方法和类似的插件(名为tags / taggings的表),最后得到这个:< / p>

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)