8 Stimmen

Schienen: Alle Artikel mit den Tags x AND y AND z erhalten

Ich habe zwei Modelle: Item y Tag . Beide haben ein Attribut name. Ich möchte Elemente finden, die mit mehreren Tags versehen sind.

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

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

Mit einer Liste von Tag-IDs kann ich ganz einfach die Liste der mit einem Tag versehenen Artikel ermitteln ou das andere:

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

Wenn tag_ids es {1,4} dann erhalte ich alle Bilder, die mit 1 oder 4 oder beidem gekennzeichnet sind.

Ich möchte jetzt wissen, wie ich die Bilder, die mit beiden markiert sind, bekomme - 1 UND 4.

Ich kann mir gar nicht vorstellen, wie viel SQL hier erforderlich ist.

13voto

elektronaut Punkte 2527

Sie können dieses Problem lösen, indem Sie die Ergebnisse gruppieren und die Anzahl überprüfen:

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

3voto

Arthur Punkte 171

Kleines Update : Heute können wir (inspiriert von elektronaut) verwenden:

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

Es ist nicht viel anders, aber es funktioniert hier gut.

2voto

kikito Punkte 49986

Ich habe eine Sache zu Elektronauts ansonsten wunderbarer Antwort hinzuzufügen: Es wird nicht auf PostgreSQL funktionieren.

In meinem realen Beispiel schließt der Item.all-Aufruf andere Tabellen ein, so dass die Auswahl wie folgt aussieht:

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

PostgreSQLs GROUP BY erfordert, dass alle Felder bei einer Auswahl verwendet werden, um dort aufgenommen zu werden. Ich musste also alle Felder, die im vorherigen Select verwendet wurden, in die GROUP BY-Klausel aufnehmen.

Und trotzdem hat es nicht funktioniert; ich bin mir nicht sicher, warum.

Ich habe mich für eine einfachere, hässlichere Lösung entschieden. Es erfordert zwei db-Anfragen. Einer von ihnen wird verwendet, um ids zurückzugeben, die als eine Bedingung verwendet werden.

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]
    }
  }

Dann kann ich das tun:

  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
  )

Es ist hakelig, aber es funktioniert, und die Hakeligkeit ist lokal begrenzt.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X