On Friday 29 May 2009, [email protected] wrote:

> leider habe ich ein Problem mit ActiveRecord.
> Hier ein kurzes Beispiel:
>
> competitors (id, name,score)
> has_won_against(id, competitor1,competitor2,price)
>
> Nun möchte ich gerne, die alle Wettbewerber Selektieren, die
> - Die mind 2x angetreten sind
> - Niemals verloren haben
> - und in ihrer Karriere schon mehr als 1000 verdient haben.
>
> Ein SQL-Query dazu würde etwa so aussehen:
> SELECT DISTINCT c.id,c.name,c.score FROM (
>       SELECT id,name,score,sum(h.price),count(h.id)
>       FROM (has_won_against as h) JOIN (competitors as c) on c.id =
> h.competitor1 WHERE c.id not IN (SELECT competitors2 FROM competitors
> as c1) GROUP_BY h.competitor1
>       HAVING sum(h.price) > 1000 AND cound(h.id) > 1
> )

Ich würde zur Klarheit folgende Änderung vornehmen:

matches(id, winner_id, loser_id, price)

Dann

class Competitor < ActiveRecord::Base
  named_scope :winners, :select => %{
    competitors.*,
    (SELECT COUNT(*) from matches
     WHERE matches.winner_id = competitors.id) AS won_match_count,
    (SELECT SUM(price) from matches
     WHERE matches.winner_id = competitors.id) AS earning,
    (SELECT COUNT(*) from matches
     WHERE matches.loser_id = competitors.id) AS lost_match_count},
    :conditions => %{
      won_match_count > 2 AND
      lost_match_count = 0 AND
      earning > 1000}
end

Ich bin sicher, dass da noch Fehler drin sind und dass es besser geht. 
PostgreSQL hat keine Schwierigkeiten mit Sub-SELECTs in der Selectliste, 
ich weiß nicht, wie es bei MySQL aussieht.

> Diese Abfrage möchte ich nun gerne mit ActiveRecord umsetzen. Mein
> Problem ist, dass ich nicht weiß, wie ich den inneren Query umsehen
> soll. Da ein competitor an vielen hbtm-Beziehungen hängt, wird das
> SELECT und der JOIN-Teil von ActiveRecord gebaut und die Beziehungen
> werden über :include geladen.

Da kann ich nicht ganz folgen. Wie dem auch sei, du darfst SQL 
verwenden!

> Wie setze ich das am besten mit ActiveRecord um?

Du könntest die schwierigen Teile in die Datenbank verschieben und 
hinter Views verstecken[*]. Empfehlen würde ich aber eine Kombination 
aus Counter Caches und Association Callbacks:

In einer Migration zu Competitor die Spalten won_matches_count, 
lost_matches_count und earnings hinzufügen.

class Competitor < ActiveRecord::Base
  has_many :won_matches, :class_name => 'Match',
    :foreign_key => :winner_id, :counter_cache => 'won_matches_count',
    :after_add => :accumulate_earnings
  has_many :lost_matches, :class_name => 'Match',
    :foreign_key => :loser_id, :counter_cache => 'lost_matches_count'

  def accumulate_earnings(match)
    self.earnings += match.price
  end

  named_scope :winners, :conditions =>
    %{won_matches_count > 2 AND
      lost_matches_count = 0 AND
      earnings > 1000}
end

Wenn du weißt, dass niemand an den Models vorbei die Datenbank ändert, 
die Caches also nicht inkonsistent werden können, dann ist das die 
einfachste Lösung. Wenn diese Voraussetzung nicht erfüllt ist, nimm 
Datenbank-Views.

Michael

[*] http://www.schuerig.de/michael/pres/kreative-assoziationen/
-- 
Michael Schuerig
mailto:[email protected]
http://www.schuerig.de/michael/

_______________________________________________
rubyonrails-ug mailing list
[email protected]
http://mailman.headflash.com/listinfo/rubyonrails-ug

Antwort per Email an