Unlimited Plugins, WordPress themes, videos & courses! Unlimited asset downloads! From $16.50/m
Advertisement
  1. Code
  2. Ruby on Rails

Abfragen in Rails, Teil 2

by
Length:LongLanguages:

German (Deutsch) translation by Valentina (you can also view the original English article)

In diesem zweiten Artikel werden wir uns etwas eingehender mit Active Record-Abfragen in Rails befassen. Falls Sie noch nicht mit SQL vertraut sind, füge ich Beispiele hinzu, die so einfach sind, dass Sie sie begleiten und die Syntax im Laufe der Zeit ein wenig übernehmen können.

Abgesehen davon wäre es auf jeden Fall hilfreich, wenn Sie ein kurzes SQL-Tutorial durcharbeiten, bevor Sie zurückkehren, um weiterzulesen. Nehmen Sie sich ansonsten Zeit, um die von uns verwendeten SQL-Abfragen zu verstehen, und ich hoffe, dass es sich am Ende dieser Serie nicht mehr einschüchternd anfühlt.

Das meiste davon ist wirklich unkompliziert, aber die Syntax ist etwas seltsam, wenn Sie gerade erst mit dem Codieren begonnen haben - insbesondere in Ruby. Bleiben Sie dran, es ist keine Raketenwissenschaft!

Themen

  • Beinhaltet und Eifriges Laden
  • Tabellen verbinden
  • Eifriges Laden
  • Geltungsbereich
  • Aggregationen
  • Dynamische Finder
  • Spezifische Felder
  • Benutzerdefiniertes SQL

Beinhaltet und Eifriges Laden

Diese Abfragen enthalten mehr als eine Datenbanktabelle, mit der Sie arbeiten können, und sind möglicherweise die wichtigsten, die Sie aus diesem Artikel entfernen sollten. Es läuft darauf hinaus: Anstatt mehrere Abfragen nach Informationen durchzuführen, die über mehrere Tabellen verteilt sind, includes den Versuch, diese auf ein Minimum zu beschränken. Das Schlüsselkonzept dahinter heißt „eifriges Laden“ und bedeutet, dass wir zugehörige Objekte laden, wenn wir einen Fund durchführen.

Wenn wir dies tun, indem wir eine Sammlung von Objekten durchlaufen und dann versuchen, von einer anderen Tabelle aus auf die zugehörigen Datensätze zuzugreifen, stoßen wir auf ein Problem, das als "N + 1-Abfrageproblem" bezeichnet wird. Beispielsweise würden wir für jeden agent.handler in einer Sammlung von Agenten separate Abfragen für beide Agenten und ihre Handler auslösen. Das müssen wir vermeiden, da dies überhaupt nicht skaliert. Stattdessen machen wir Folgendes:

Rails

Wenn wir jetzt eine solche Sammlung von Agenten durchlaufen - ohne die Anzahl der vorerst zurückgegebenen Datensätze zu beschränken -, werden wir am Ende zwei Abfragen anstelle von möglicherweise einer Unmenge haben.

SQL

Dieser eine Agent in der Liste verfügt über zwei Handler. Wenn wir nun das Agentenobjekt nach seinen Handlern fragen, müssen keine zusätzlichen Datenbankabfragen ausgelöst werden. Wir können natürlich noch einen Schritt weiter gehen und eifrig mehrere zugehörige Tabellendatensätze laden. Wenn wir aus irgendeinem Grund nicht nur Handler, sondern auch die damit verbundenen Missionen des Agenten laden müssten, könnten wir solche includes verwenden.

Rails

Einfach! Seien Sie vorsichtig, wenn Sie Singular- und Pluralversionen für die Includes verwenden. Sie hängen von Ihren Modellzuordnungen ab. Eine has_many-Assoziation verwendet den Plural, während ein belongs_to oder ein has_one natürlich die Singularversion benötigt. Bei Bedarf können Sie auch eine where-Klausel zum Angeben zusätzlicher Bedingungen verwenden. Die bevorzugte Methode zum Angeben von Bedingungen für zugehörige Tabellen, die eifrig geladen werden sollen, ist die Verwendung von joins.

Beachten Sie beim eifrigen Laden, dass die hinzugefügten Daten vollständig an Active Record zurückgesendet werden, wodurch wiederum Ruby-Objekte mit diesen Attributen erstellt werden. Dies steht im Gegensatz zum „einfachen“ Zusammenfügen der Daten, bei dem Sie ein virtuelles Ergebnis erhalten, das Sie beispielsweise für Berechnungen verwenden können, und das weniger Speicher verbraucht als Includes.

Tabellen verbinden

Das Verknüpfen von Tabellen ist ein weiteres Werkzeug, mit dem Sie vermeiden können, zu viele unnötige Abfragen über die Pipeline zu senden. Ein häufiges Szenario besteht darin, zwei Tabellen mit einer einzigen Abfrage zu verbinden, die eine Art kombinierten Datensatz zurückgibt. joins ist nur eine weitere Finder-Methode von Active Record, mit dem Sie - in SQL-Begriffen - JOIN-Tabellen. Diese Abfragen können Datensätze zurückgeben, die aus mehreren Tabellen kombiniert wurden, und Sie erhalten eine virtuelle Tabelle, die Datensätze aus diesen Tabellen kombiniert. Dies ist ziemlich radikal, wenn man das mit dem Auslösen aller Arten von Abfragen für jede Tabelle vergleicht. Es gibt verschiedene Arten von Datenüberlappungen, die mit diesem Ansatz erzielt werden können.

Der innere Join ist der Standardmodus für joins. Dies entspricht allen Ergebnissen, die einer bestimmten ID und ihrer Darstellung als Fremdschlüssel aus einem anderen Objekt oder einer anderen Tabelle entsprechen. Im folgenden Beispiel einfach ausgedrückt: Geben Sie mir alle Missionen, bei denen die Missions-id als mission_id in der Tabelle eines Agenten angezeigt wird. "agents"."mission_id" = "missionen"."id". Innere Verknüpfungen schließen nicht vorhandene Beziehungen aus.

Rails

SQL

Wir passen also Missionen und ihre Begleitagenten an - in einer einzigen Abfrage! Sicher, wir könnten zuerst die Missionen bekommen, sie einzeln durchlaufen und nach ihren Agenten fragen. Aber dann würden wir zu unserem schrecklichen „N + 1-Abfrageproblem“ zurückkehren. Nein danke!

Das Schöne an diesem Ansatz ist auch, dass wir keine inneren Fälle mit inneren Verknüpfungen erhalten. Es werden nur Datensätze zurückgegeben, deren IDs mit Fremdschlüsseln in zugeordneten Tabellen übereinstimmen. Wenn wir zum Beispiel Missionen finden müssen, denen Agenten fehlen, brauchen wir stattdessen einen äußeren Join. Da dies derzeit das Schreiben Ihres eigenen OUTER JOIN SQL umfasst, werden wir dies im letzten Artikel untersuchen. Zurück zu Standardverknüpfungen können Sie natürlich auch mehrere zugeordnete Tabellen verknüpfen.

Rails

Und Sie können einige where-Klauseln hinzufügen, um Ihre Finder noch genauer anzugeben. Im Folgenden suchen wir nur nach Missionen, die von James Bond ausgeführt werden, und nur nach den Agenten, die zur Mission 'Moonraker' im zweiten Beispiel gehören.

SQL

Rails

SQL

Bei joins müssen Sie auch auf die Verwendung Ihrer Modellassoziationen im Singular und im Plural achten. Da meine Mission-Klasse has_many :agents, können wir den Plural verwenden. Auf der anderen Seite funktioniert für die Agent-Klasse belongs_to :mission, nur die Singular-Version, ohne in die Luft zu jagen. Wichtiges kleines Detail: Der where-Teil ist einfacher. Da Sie in der Tabelle nach mehreren Zeilen suchen, die eine bestimmte Bedingung erfüllen, ist die Pluralform immer sinnvoll.

Geltungsbereich

Bereiche sind eine praktische Möglichkeit, allgemeine Abfragebedürfnisse in eigene benannte Methoden zu extrahieren. Auf diese Weise sind sie etwas einfacher weiterzugeben und möglicherweise auch leichter zu verstehen, wenn andere mit Ihrem Code arbeiten müssen oder wenn Sie bestimmte Abfragen in Zukunft erneut prüfen müssen. Sie können sie für einzelne Modelle definieren, aber auch für ihre Zuordnungen verwenden.

Der Himmel ist wirklich die Grenze - joinsincludes, und where sind alle Freiwild! Da Bereiche auch ActiveRecord::Relations-Objekte zurückgeben, können Sie sie verketten und ohne zu zögern andere Bereiche darüber aufrufen. Das Extrahieren solcher Bereiche und das Verketten dieser Bereiche für komplexere Abfragen ist sehr praktisch und macht längere Bereiche umso lesbarer. Bereiche werden über die Syntax „Stabby Lambda“ definiert:

Rails

SQL

Wie Sie dem obigen Beispiel entnehmen können, ist es viel schöner, James Bond zu finden, wenn Sie nur Zielfernrohre miteinander verketten können. Auf diese Weise können Sie verschiedene Abfragen mischen und abgleichen und gleichzeitig trocken bleiben. Wenn Sie Bereiche über Verbände benötigen, stehen diese ebenfalls zur Verfügung:

Sie können das default_scope auch neu definieren, wenn Sie sich etwas wie Mission.all ansehen.

SQL

Aggregationen

Dieser Abschnitt ist in Bezug auf das Verständnis nicht so weit fortgeschritten, aber Sie werden sie häufig in Szenarien benötigen, die als etwas fortgeschrittener angesehen werden können als Ihr durchschnittlicher Finder - wie .all, .first, .find_by_id oder was auch immer. Das Filtern auf der Grundlage grundlegender Berechnungen ist beispielsweise höchstwahrscheinlich etwas, mit dem Neulinge nicht sofort Kontakt aufnehmen. Was sehen wir genau hier?

  • sum
  • count
  • minimum
  • maximum
  • average

Einfach peasy, oder? Das Coole ist, dass wir, anstatt eine zurückgegebene Sammlung von Objekten zu durchlaufen, um diese Berechnungen durchzuführen, Active Record all diese Arbeiten für uns erledigen lassen und diese Ergebnisse mit den Abfragen zurückgeben können - vorzugsweise in einer Abfrage. Schön, was?

  • count

Rails

SQL

  • average

Rails

SQL

Da wir jetzt wissen, wie wir joins verwenden können, können wir noch einen Schritt weiter gehen und nur nach dem Durchschnitt der Gadgets fragen, die die Agenten beispielsweise für eine bestimmte Mission haben.

Rails

SQL

Das Gruppieren dieser durchschnittlichen Anzahl von Gadgets nach Missionsnamen wird zu diesem Zeitpunkt trivial. Weitere Informationen zur Gruppierung finden Sie unten:

Rails

SQL

  • sum

Rails

SQL

  • maximum

Rails

SQL

  • minimum

Rails

SQL

Beachtung!

Bei all diesen Aggregationsmethoden können Sie andere Dinge nicht verketten - sie sind terminal. Die Reihenfolge ist wichtig, um Berechnungen durchzuführen. Wir erhalten von diesen Vorgängen kein ActiveRecord::Relation-Objekt zurück, wodurch die Musik an diesem Punkt stoppt. Stattdessen erhalten wir einen Hash oder Zahlen. Die folgenden Beispiele funktionieren nicht:

Rails

Gruppiert

Wenn Sie möchten, dass die Berechnungen in logische Gruppen unterteilt und sortiert werden, sollten Sie eine GROUP-Klausel verwenden und dies nicht in Ruby tun. Damit meine ich, dass Sie vermeiden sollten, über eine Gruppe zu iterieren, die möglicherweise Tonnen von Abfragen erzeugt.

Rails

SQL

In diesem Beispiel werden alle Agenten gefunden, die einer bestimmten Mission zugeordnet sind, und es wird ein Hash mit der berechneten durchschnittlichen Anzahl von Gadgets als Werten zurückgegeben - in einer einzigen Abfrage! Jep! Das gilt natürlich auch für die anderen Berechnungen. In diesem Fall ist es wirklich sinnvoller, SQL die Arbeit machen zu lassen. Die Anzahl der Abfragen, die wir für diese Aggregationen auslösen, ist einfach zu wichtig.

Dynamische Finder

Mit Active Record können Sie für jedes Attribut in Ihren Modellen, z. B. name, e-mail-address, favorite-gadget usw., gut lesbare Finder-Methoden verwenden, die dynamisch für Sie erstellt werden. Klingt kryptisch, ich weiß, aber es bedeutet nichts anderes als find_by_id oder find_by_favorite_gadget. Der Teil find_by ist Standard, und Active Record gibt nur den Namen des Attributs für Sie ein. Sie können sogar eine ! hinzufügen, wenn Sie möchten, dass dieser Finder einen Fehler auslöst, wenn nichts gefunden werden kann. Der kranke Teil ist, dass Sie diese dynamischen Finder-Methoden sogar miteinander verketten können. Genau wie dieser:

Rails

SQL

Natürlich können Sie damit verrückt werden, aber ich denke, es verliert seinen Charme und seine Nützlichkeit, wenn Sie über zwei Attribute hinausgehen:

Rails

SQL

In diesem Beispiel ist es trotzdem schön zu sehen, wie es unter der Haube funktioniert. Jedes neue _and_ fügt einen SQL AND-Operator hinzu, um die Attribute logisch miteinander zu verknüpfen. Insgesamt ist der Hauptvorteil von dynamischen Suchern die Lesbarkeit - wenn Sie zu viele dynamische Attribute verwenden, verliert dieser Vorteil jedoch schnell. Ich benutze das selten, vielleicht meistens, wenn ich in der Konsole herumspiele, aber es ist auf jeden Fall gut zu wissen, dass Rails diesen kleinen Trick bietet.

Spezifische Felder

Active Record bietet Ihnen die Möglichkeit, Objekte zurückzugeben, die sich etwas mehr auf die Attribute konzentrieren, die sie enthalten. Wenn nicht anders angegeben, werden bei der Abfrage normalerweise alle Felder in einer Zeile über * (SELECT "agents".*) Abgefragt. Anschließend erstellt Active Record Ruby-Objekte mit dem vollständigen Satz von Attributen. Sie können jedoch nur bestimmte Felder select, die von der Abfrage zurückgegeben werden sollen, und die Anzahl der Attribute begrenzen, die Ihre Ruby-Objekte zum "Mitführen" benötigen.

Rails

SQL

Rails

SQL

Wie Sie sehen können, haben die zurückgegebenen Objekte nur die ausgewählten Attribute und natürlich ihre IDs - das ist bei jedem Objekt gegeben. Es macht keinen Unterschied, ob Sie Zeichenfolgen wie oben oder Symbole verwenden - die Abfrage ist dieselbe.

Rails

Ein Wort der Vorsicht: Wenn Sie versuchen, auf Attribute für das Objekt zuzugreifen, das Sie in Ihren Abfragen nicht ausgewählt haben, erhalten Sie einen MissingAttributeError. Da die id ohnehin automatisch für Sie bereitgestellt wird, können Sie nach der ID fragen, ohne sie auszuwählen.

Benutzerdefiniertes SQL

Zu guter Letzt können Sie über find_by_sql Ihr eigenes benutzerdefiniertes SQL schreiben. Wenn Sie sich in Ihrem eigenen SQL-Fu sicher genug sind und einige benutzerdefinierte Aufrufe der Datenbank benötigen, kann diese Methode manchmal sehr nützlich sein. Aber das ist eine andere Geschichte. Vergessen Sie nicht, zuerst nach Active Record-Wrapper-Methoden zu suchen und das Rad nicht neu zu erfinden, wenn Rails versucht, Sie mehr als auf halber Strecke zu treffen.

Rails

Es ist nicht überraschend, dass dies zu folgenden Ergebnissen führt:

SQL

Da Bereiche und Ihre eigenen Klassenmethoden für Ihre benutzerdefinierten Finder-Anforderungen austauschbar verwendet werden können, können wir bei komplexeren SQL-Abfragen noch einen Schritt weiter gehen.

Rails

Wir können Klassenmethoden schreiben, die die SQL in einem Here-Dokument kapseln. Auf diese Weise können wir mehrzeilige Zeichenfolgen auf sehr lesbare Weise schreiben und diese SQL-Zeichenfolge dann in einer Variablen speichern, die wir wiederverwenden und an find_by_sql übergeben können. Auf diese Weise verputzen wir nicht Tonnen von Abfragecode im Methodenaufruf. Wenn Sie mehr als einen Ort haben, an dem Sie diese Abfrage verwenden können, ist sie ebenfalls trocken.

Da dies für Neulinge gedacht ist und kein SQL-Tutorial an sich ist, habe ich das Beispiel aus einem bestimmten Grund sehr minimalistisch gehalten. Die Technik für viel komplexere Abfragen ist jedoch ziemlich dieselbe. Es ist leicht vorstellbar, dass dort eine benutzerdefinierte SQL-Abfrage vorhanden ist, die sich über zehn Codezeilen hinaus erstreckt.

Gehen Sie so verrückt wie nötig - vernünftigerweise! Es kann ein Lebensretter sein. Ein Wort zur Syntax hier. Der SQL-Teil ist hier nur eine Kennung, um den Anfang und das Ende der Zeichenfolge zu markieren. Ich wette, Sie werden diese Methode nicht so sehr brauchen - hoffen wir! Es hat definitiv seinen Platz und Rails Land wäre ohne es nicht dasselbe - in den seltenen Fällen, in denen Sie unbedingt Ihr eigenes SQL damit optimieren möchten.

Abschließende Gedanken

Ich hoffe, Sie haben es etwas bequemer, Abfragen zu schreiben und das gefürchtete alte SQL zu lesen. Die meisten der in diesem Artikel behandelten Themen sind für das Schreiben von Abfragen wichtig, die sich mit komplexerer Geschäftslogik befassen. Nehmen Sie sich Zeit, um diese zu verstehen, und spielen Sie ein bisschen mit Abfragen in der Konsole.

Ich bin mir ziemlich sicher, dass Ihr Rails-Guthaben früher oder später erheblich steigt, wenn Sie an Ihren ersten realen Projekten arbeiten und Ihre eigenen benutzerdefinierten Abfragen erstellen müssen, wenn Sie das Tutorial-Land hinter sich lassen. Wenn Sie immer noch ein bisschen schüchtern sind, würde ich sagen, haben Sie einfach Spaß damit - es ist wirklich keine Raketenwissenschaft!

Advertisement
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.