Objekte und Beziehungen
Wie im täglichen Leben stehen auch bei der programmatischen Nachbildung der Wirklichkeit Objekte oft in einer bestimmten Beziehung zu einander. Dabei unterscheidet man zwischen 1:1, 1:n und m:n Beziehungen.
Neulich wollte ich in einer kleinen Rails Web-Applikation (siehe aktuelle Literatur) eine Ansicht einer 1:n Beziehung zweier Objekte in Listenform erstellen. Die Anforderung war allerdings, die Liste nach der Anzahl der Kinder zu sortieren und dabei die Objekte zuerst anzuzeigen, die keine Kinder haben.
[ad#vert-banner]
Damit die Ergebnismenge des SELECT Statements auch die Eltern Objekte enthält, die keine Kinder besitzen, muss ein LEFT JOIN verwendet werden. Grundsätzlich ist das zwar etwas mehr Aufwand für die Datenbank, aber dennoch schaffbar, ohne eine Ewigkeit warten zu müssen. Mit Hilfe von Indizierungen lässt sich die ganze Sache auch hinreichend beschleunigen. Das folgende SQL Statement erfüllt diese erste Anforderung:
SELECT p.name, c.name
FROM parents AS p
LEFT JOIN children AS c
ON p.id=c.parent_id
Allerdings fehlt hier die Sortierung. Das zweite Problem ist, dass das Feld c.name für alle kinderlosen Eltern NULL ist, und somit nicht gezählt werden kann. Folgendes Statement behebt zwar das Problem, aber die Ausführung braucht bei großen Tabellen Jahre.
SELECT IF(c.id IS NULL,0,COUNT(*)) AS kids, p.name
FROM parents AS p
LEFT JOIN children AS c ON p.id=c.parent_id
ORDER BY kids
GROUP BY p.id
Die lange Ausführungsdauer hat vor allem einen Grund: Die Sortierung geschieht auf einer Spalte, die effektiv nicht existiert, sondern erst zur Laufzeit mit Hilfe der Count Funktion erzeugt wird. Das heißt es muss erst ein kompletter Join über beide Tabellen durchgeführt werden, ehe eine Sortierung stattfinden kann. Auch eine Limitierung der Ergebnisliste mit Hilfe von LIMIT ändert daran nichts, denn die würde erst nach der Sortierung durchgeführt.
Um diese lange Wartezeiten zu vermeiden, habe ich mir folgendes überlegt: Man baut in die Tabelle der Elternklasse eine Spalte ein, die die Anzahl der Kinder speichert. Nun muss “nur noch” beim hinzufügen und löschen eines Kindes diese Spalte gepflegt werden. Das heißt, meine Applikation ist für die Datenintegrität zuständig.
Zurück zu Rails, denn da ist dieses Funktionalität bereits an Board. Getreu dem Motto “Konvention über Konfiguration”, erzeugt man lediglich die Spalte in der Tabelle der Elternklasse. Diese trägt den sprechenden Namen <kindertabelle>_count also in diesem falls children_count. In der Kinderklasse wird nun noch deklariert, dass der Zähler automatisch aktualisiert wird.
class Child < ActiveRecord::Base
belongs_to :parent, :counter_cache => true
end
Und siehe da, nun lässt sich eine normaler Finder verwenden, um den View aufzubauen. Komplett ohne JOIN!
@parents = Parent.find(
:all,
:limit => 20,
ffset => @parent_pages.current.to_sql[1],
rder => "children_count ASC, parent ASC")
Da jetzt nur noch real existierende Spalten für die Sortierung verwendet werden und eine effektive Limitierung der Ergebnismenge möglich ist, dauert die Anfrage nur noch wenige ms.
Thx Rails!

Thank you for sharing!