rails:获取标记为x AND y AND z的所有项目

时间:2010-06-15 14:39:09

标签: sql ruby-on-rails activerecord

我有两个模型:ItemTag。两者都有name属性。我想找到标有多个标签的商品。

class Item < ActiveRecord::Base
  has_many :tags
  validates_presence_of :name
end

class Tag < ActiveRecord::Base
  belongs_to :item
  validates_presence_of :name
end

鉴于标记ID列表,我可以轻松地获取标记为一个标记另一个标记的项目列表:

# Find the items tagged with one or more of the tags on tag_ids
Item.all(:conditions => ['tags.id in (?)', tag_ids], :joins => :tags)

如果tag_ids{1,4},那么我会将所有图片标记为1或4或两者。

我现在想知道如何获取标有两者的图片 - 1 AND 4。

我甚至无法想象这里需要的SQL。

3 个答案:

答案 0 :(得分:13)

您可以通过对结果进行分组并检查计数来解决此问题:

Item.all(
  :conditions => ['tags.id IN (?)', tag_ids], 
  :joins      => :tags, 
  :group      => 'items.id', 
  :having     => ['COUNT(*) >= ?', tag_ids.length]
)

答案 1 :(得分:3)

小更新: 今天,我们可以使用(灵感来自elektronaut):

Item.joins(:tags).where("tags.label in (?)", tags).group('items.id').having("COUNT(*) >= ?", tags.size)

它没有什么不同,因为它在这里运作良好。

答案 2 :(得分:2)

我有一件事要添加到elektronaut的其他很好的答案:它不适用于PostgreSQL。

在我的实例中,Item.all调用包括其他表;所以select看起来像这样:

SELECT items.id AS t0_f0, items.name as t0_f1 ..., table2.field1 as t1_f0 .. etc

PostgreSQL的GROUP BY要求将选择中使用的所有字段包含在那里。所以我必须在GROUP BY子句中包含上一个select中使用的所有字段。

但它仍然不起作用;我不确定为什么。

我最终做了一个更简单,更丑陋的事情。它需要两个db请求。其中一个用于返回id,它们用作条件。

class Item < ActiveRecord::Base

  # returns the ids of the items tagged with all tags
  # usage: Item.tagged_all(1,2,3)
  named_scope :tagged_all, lambda { |*args|
    { :select => "items.id",
      :joins => :tags,
      :group => "items.id",
      :having => ['COUNT(items.id) >= ?', args.length],
      :conditions => ["tags.id IN (?)", args]
    }
  }

然后我可以这样做:

  Item.all(
    :conditions => [
      'items.id IN (?) AND ... (other conditions) ...',
      Items.tagged_all(*tag_ids).collect(&:id),
      ... (other values for conditions) ...
    ],
    :includes => [:model2, :model3] #tags isn't needed here any more
  )

Hacky,但它有效,而且hackyness已经本地化了。