2009-08-04 10 views
0

Haben Sie Addresse s und List s mit vielen-zu-viele-Beziehung, wie unten gezeigt.Gibt es eine ActiveRecord-Methode, um diese SQL-Abfrage auszuführen?

Manchmal müssen alle List s ein Address ist nicht in.

Mit der find_by_sql Abfrage angezeigt, und es funktioniert super. Aber gibt es eine Möglichkeit, dies zu tun, ohne direktes SQL zu verwenden?

class List 
    has_many :address_list_memberships 
    has_many :addresses, :through => :address_list_memberships 
end 


class Address 
    has_many :address_list_memberships, :dependent => :destroy 
    has_many :lists, :through => :address_list_memberships 

    # Lists that this Address is not in 
    def Address.lists_not_in(address_id) 
    sql = %Q| 
SELECT 
    l.* 
FROM 
    lists l 
WHERE 
    l.id 
NOT IN 
(
    SELECT 
    l.id 
    FROM 
    addresses a, lists l, address_list_memberships alm 
    WHERE 
    a.id = alm.address_id AND l.id = alm.list_id 
    AND 
    a.id = #{address_id} 
) 
| 
    List.find_by_sql(sql) 
    end 
end 
+1

Haben Sie Indizes für Ihre Datenbanktabellen? Ich benutze: Joins auf großen Tabellen bei der Arbeit und ich brauche normalerweise nicht 30 Sekunden, um eine Abfrage auszuführen. Sie sollten sicherstellen, dass Sie Indizes für Ihre Fremdschlüssel haben. – nitecoder

+0

Indizes werden so viel schneller machen. Ich habe meine Antwort bearbeitet, um Code für sie hinzuzufügen. –

+0

-1. Bitte wählen Sie eine Antwort aus den Antworten, anstatt die Antworten manuell auf Ihre Frage zu bearbeiten. –

Antwort

3

ich tun würde dies als Rahmen in List

class List 
    named_scope :without_address, lambda { |address_id| { :joins => 'inner join address_list_memberships alm on alm.list_id = lists.id', :conditions => ['alm.address_id <> ?', address_id]}} 
end 

Jetzt können Sie rufen List.without_address (4), und Sie Bereiche auf der Oberseite, dass anrufen können.

Wie Matchu weist darauf hin, können Sie es tun, ohne die SQL beitreten auszuschreiben:

class List 
    named_scope :without_address, lambda { |address_id| { :joins => :address_list_memberships, :conditions => ['address_list_memberships.address_id <> ?', address_id]}} 
end 

Und stellen Sie sicher, dass Ihre Join-Tabelle Indizes hat!

In einer Migration:

add_index "address_list_memberships", "address_id" 
add_index "address_list_memberships", "list_id" 

Für andere Möglichkeiten, wie Sie die named_scope formatieren können, Sam Saffron des Kern sehen: http://gist.github.com/162489

+1

Müssen Sie den Join explizit ausschreiben, oder kann Rails ihn selbst schreiben, wenn Sie nur address_list_memberships ersetzen? Oder verpasse ich hier etwas? – Matchu

+1

Und erlaubt unendliche Argumente und nur mit dem ersten ein Stil-Ding, oder bin ich wieder völlig etwas übersehen?Mein erster Gedanke wäre, einfach Lambda {| Adresse | } – Matchu

+0

Rails API scheint so zu sagen, solange es nur eine grundlegende Zuordnung ist. Und da eine Liste has_many address_list_memberships hat, sollte es gut funktionieren. (http://api.rubyonrails.org/classes/ActiveRecord/Base.html#M002263) – Matchu

1
WHERE (address_list_memberships.address_id <> 13896) 

wird mit 21.849 Adressen in einer Datenbank teuer sein und 1483 Listen.

Flip Ihrer Logik:

def lists_not_in 
    List.all - self.lists 
end 

Auf diese Weise kann nur von einer anderen Stelle ein Array subtrahiert jeden Datensatz in der Datenbank zu überprüfen, ob es in der Liste ist.

1

Sie erhalten nicht die Flexibilität, die Sie mit Direct SQL von ActiveRecord erhalten, insbesondere wird es Ihnen nicht möglich sein, die not in-Klausel im aktiven Datensatz zu erstellen.

Wenn Sie ein bisschen mehr Kontrolle bekommen möchten, können Sie versuchen Sequel http://sequel.rubyforge.org/ oder einfach nur Handarbeit zu verwenden.

Hinweis, die Lösung, die Sie haben, ist riskant, weil Sie eine SQL-Injektion zulassen. (a.id = #{address_id})

Verwandte Themen