2016-05-28 5 views
0

Anfänge, wenn ich ein Histogramm aller Items aufzubauen versuche, die einen Order Start zwischen einem gegebenen Satz von Daten basierend auf genau, was der Punkt war (:name_id) und die Frequenz dieser :name_id, war ich mit dem folgenden Code:Wie ActiveRecord Relations kombinieren? Merge funktioniert nicht?

dates = ["May 27, 2016", "May 30, 2016"] 
items = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last) 
histogram = {} 
items.pluck(:name_id).uniq.each do |name_id| 
    histogram[name_id] = items.where(name_id:name_id).count 
end 

Dieser Code funktionierte FINE.

Jetzt versuche ich jedoch, ein Histogramm zu erstellen, das expansiver ist. Ich möchte immer noch die Frequenz von :name_id über einen gewissen Zeitraum hinweg erfassen, aber jetzt möchte ich diese Zeit durch Order starten und beenden. Ich habe jedoch Probleme, die ActiveRecord Relations, die den Abfragen folgen, zu kombinieren. Insbesondere dann, wenn meine Fragen sind wie folgt:

items_a = Item.joins(:order).where("orders.start >= ?", dates.first).where("orders.start <= ?", dates.last) 
items_b = Item.joins(:order).where("orders.end >= ?", dates.first).where("orders.end <= ?", dates.last)  

Wie trete ich die 2-Abfragen, so dass mein Code unten, dass auf Abfrage wirkt Objekte noch funktioniert?

items.pluck(:name_id).each do |name_id| 
    histogram[name_id] = items.where(name_id:name_id).count 
end 

Was ich habe versucht:

+, aber das ist natürlich nicht funktioniert, weil es das Ergebnis in einem Array dreht, wo Methoden wie pluck nicht funktionieren:

(items_a + items_b).pluck(:name_id) 
=> error 

merge, das ist, was alle SO-Antworten zu sagen scheinen ... aber es funktioniert nicht für mich, weil, wie die Dokumente sagen, merge die Kreuzung herausfindet, so ist mein Ergebnis wie folgt:

items_a.count 
=> 100 
items_b.count 
=> 30 
items_a.merge(items_b) 
=> 15 

FYI derzeit habe ich affe-patched dies mit dem unten, aber es ist nicht sehr ideal. Danke für die Hilfe!

name_ids = (items_a.pluck(:name_id) + items_b.pluck(:name_id)).uniq 
name_ids.each do |name_id| 
    # from each query object, return the ids of the item objects that meet the name_id criterion 
    item_object_ids = items_a.where(name_id:name_id).pluck(:id) + items_b.where(name_id:name_id).pluck(:id) + items_c.where(name_id:name_id).pluck(:id) 
    # then check the item objects for duplicates and then count up. btw I realize that with the uniq here I'm SOMEWHAT doing an intersection of the objects, but it's nowhere near as severe... the above example where merge yielded a count of 15 is not that off from the truth, when the count should be maybe -5 from the addition of the 2 queries 
    histogram[name_id] = item_object_ids.uniq.count 
end 
+0

Welche Version von Rails verwenden Sie? Die neuesten Versionen (Vorabversionen) unterstützen 'items_a.or (items_b)'. – Raffael

+1

Eine schnelle Lösung wäre, die gesamte Bedingung mit SQL zu beschreiben: 'Item.joins (: order) .where (" (orders.start BETWEEN: min UND: max) OR (orders.end ZWISCHEN: min UND: max) " , min: dates.first, max: dates.last) ' – Raffael

+0

Als Alternative fügt das Juwel' squeel 'etwas SQL-Algebra-Gutheit zu ActiveRecord hinzu. – Raffael

Antwort

1

Sie können Ihre zwei Abfragen zu einem kombinieren:

items = Item.joins(:order).where(
    "(orders.start >= ? AND orders.start <= ?) OR (orders.end >= ? AND orders.end <= ?)", 
    dates.first, dates.last, dates.first, dates.last 

)

Dies könnte ein wenig mehr lesbar sein:

items = Item.joins(:order).where(
    "(orders.start >= :first AND orders.start <= :last) OR (orders.end >= :first AND orders.end <= :last)", 
    { first: dates.first, last: dates.last } 
) 

Rails 5 wird ein or Verfahren unterstützen das könnte das ein wenig schöner machen:

items_a = Item.joins(:order).where(
    "orders.start >= :first AND orders.start <= :last", 
    { first: dates.first, last: dates.last } 
).or(
    "orders.end >= :first AND orders.end <= :last", 
    { first: dates.first, last: dates.last } 
) 

Oder vielleicht nicht schöner in diesem Fall

0

Vielleicht wird dies etwas sauberer sein:

date_range = "May 27, 2016".to_date.."May 30, 2016".to_date 

items = Item.joins(:order).where('orders.start' => date_range).or('orders.end' => date_range) 
Verwandte Themen