3

I have two models, Project and Category, which have a many-to-many relationship between them. The Project model is very simple:

class Project < ActiveRecord::Base
  has_and_belongs_to_many :categories

  scope :in_categories, lambda { |categories|
    joins(:categories).
    where("categories.id in (?)", categories.collect(&:to_i))
  }
end

The :in_categories scope takes an array of Category IDs (as strings), so using this scope I can get back every project that belongs to at least one of the categories passed in.

But what I'm actually trying to do is filter (a better name would be :has_categories). I want to just get the projects that belong to all of the categories passed in. So if I pass in ["1", "3", "4"] I only want to get the projects that belong to all of the categories.

4

2 に答える 2

1

あなたが説明していることを行うために、SQLには2つの一般的な解決策があります。

自己結合:

SELECT ...
FROM Projects p
JOIN Categories c1 ON c1.project_id = p.id
JOIN Categories c3 ON c3.project_id = p.id
JOIN Categories c4 ON c4.project_id = p.id
WHERE (c1.id, c3.id, c4.id) = (1, 3, 4);

タプルを比較するために構文を使用していることに注意してください。これは次と同等です。

WHERE c1.id = 1 AND c3.id = 3 AND c4.id = 4;

一般に、カバリング インデックスがある場合、自己結合ソリューションは非常に優れたパフォーマンスを発揮します。おそらくCategories.(project_id,id)正しいインデックスになりますが、念のために EXPLAIN で SQL を分析してください。

この方法の欠点は、4 つの異なるカテゴリに一致するプロジェクトを検索する場合、4 つの結合が必要になることです。5 つのカテゴリに 5 つの結合など。

グループ化:

SELECT ...
FROM Projects p
JOIN Categories cc ON c.project_id = p.id
WHERE c.id IN (1, 3, 4)
GROUP BY p.id
HAVING COUNT(*) = 3;

MySQL を使用している場合 (使用していると思います)、ほとんどの GROUP BY クエリは一時テーブルを呼び出すため、パフォーマンスが低下します。

これらの SQL ソリューションの 1 つを同等の Rails ActiveRecord API に適応させるための演習として残しておきます。

于 2010-07-14T07:03:03.347 に答える
1

ActiveRecord では次のようにします。

scope :has_categories, lambda { |categories|
  joins(:categories).
  where("categories.id in (?)", categories.collect(&:to_i)).
  group("projects.id HAVING COUNT(projects.id) = #{categories.count}")
}
于 2010-07-14T07:43:26.857 に答える