2010-06-15 32 views
8

我有两个模型:ItemTag。两者都有一个名称属性。我想查找带有几个标签的项目。rails:获取所有标记为x和y AND z的项目

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 4.

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

+0

我有同样的问题......真是巧合! – fjuan 2010-06-15 14:45:29

+0

我相信,如果一个项目标有“both”标签,那么代码也会给你“重复”,这可能不好(使用:group =>'items.id'使它不会返回重复项)。 – rogerdpack 2012-06-04 22:13:22

回答

13

您可以通过分组结果,并检查计数解决这个问题:

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

这简单而优雅。这对我帮助很大。谢谢! – kikito 2010-06-16 07:59:35

2

我有一件事情要添加到elektronaut是另有精彩的回答:它不会在PostgreSQL的工作。

在我的真实例子中,Item.all调用包括其他表;所以选择这个样子的:

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

PostgreSQL的GROUP BY要求所有在选择使用领域被列入那里。所以我必须在GROUP BY子句中包含上一次选择使用的所有字段。

仍然没有工作;我不知道为什么。

我最终做了一个更简单,更丑陋的事情。它需要两个数据库请求。其中一个用于返回用作条件的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 
) 

哈克,但它的工作原理,以及hackyness本地化。

3

小更新: 今天,我们可以使用(通过elektronaut启发):

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

这不是很大的不同,由它工作得很好这里。

+0

这是一个很好的解决方案! +1(只要确保你有一个tags.label索引)。 – 2015-09-22 04:07:11

相关问题