рельсы: Получить все предметы с тегами х и у и г - PullRequest
8 голосов
/ 15 июня 2010

У меня есть две модели: Item и Tag. Оба имеют атрибут имени. Я хочу найти элементы, отмеченные несколькими тегами.

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

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

Учитывая список идентификаторов тегов, я достаточно легко могу получить список элементов, помеченных одним тегом или другим:

# 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 здесь нужен.

Ответы [ 3 ]

13 голосов
/ 15 июня 2010

Вы можете решить эту проблему, сгруппировав результаты и проверив количество:

Item.all(
  :conditions => ['tags.id IN (?)', tag_ids], 
  :joins      => :tags, 
  :group      => 'items.id', 
  :having     => ['COUNT(*) >= ?', tag_ids.length]
)
3 голосов
/ 17 октября 2014

Небольшое обновление: Сегодня мы можем использовать (вдохновленный электронным):

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

Не сильно отличается, здесь хорошо работает.

2 голосов
/ 18 июня 2010

У меня есть одна вещь, которую нужно добавить к отличному ответу elektronaut: он не будет работать на PostgreSQL.

В моем реальном примере вызов Item.all включает другие таблицы;таким образом, выборка выглядит следующим образом:

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

GROUP BY в PostgreSQL требует, чтобы все поля , используемые для выбора, были включены туда.Поэтому мне пришлось включить все поля, использованные в предыдущем выборе, в предложение GROUP BY.

И все же это не сработало;Я не уверен почему.

Я закончил тем, что сделал более простую и уродливую вещь.Требуется два запроса дб.Один из них используется для возврата идентификаторов, которые используются в качестве условия.

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
  )

Хаки, но это работает, и хакерство локализовано.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...