Warum kann Laravel/Eloquent JOIN nicht für Eager Loading verwenden?

Lesezeit: 6 Minuten

Benutzer-Avatar
empz

<?php

class Cat extends Eloquent {

    public function user() {
        return $this->belongsTo('User');
    }
}

class User extends Eloquent {

    public function cats() {
        return $this->hasMany('Cat');
    }
}

Jetzt:

$cats = Cat::with('user')->get();

Führt 2 Abfragen aus:

select * from `cats`
select * from `users` where `users`.`id` in ('1', '2', 'x')

Warum kann es nicht einfach:

select * from cats inner join users on cats.user_id = users.id

Für diejenigen, die sagen, dass es beide ID-Spalten in der Tabelle gibt, könnte dies mit Aliasen leicht vermieden werden:

select 
    c.id as cats__id,
    c.name as cats__name,
    c.user_id as cats__user_id,
    b.id as users__id,
    b.name as users__name
from cats c
inner join users b on b.id = c.user_id

AKTUALISIEREN

Jemand wies darauf hin, dass Eloquent die Spalten der Tabellen aus den Modellen nicht kennt, aber ich denke, sie könnten eine Möglichkeit bieten, sie im Modell zu definieren, damit es Aliase verwenden und anstelle einer zusätzlichen Abfrage eine ordnungsgemäße Verknüpfung durchführen könnte.

  • @deczo Ahmmm … weil es 1 Abfrage statt 2 ist? Das user_id an Cat ist nicht nullable, every Cat gehört zu einem User… Also warum nicht inner join?

    – empz

    28. Mai 2014 um 20:26 Uhr


  • @deczo Auch dies ist kein großes Problem, wenn die ORM-Designer gesucht es anzugehen. Die Definition der Objekte könnte leicht zwischen „hat immer“ und „manchmal hat“ oder dem Abruf „auf jeden Fall mit“ und „möglicherweise mit“ unterscheiden und entsprechend innere, linke oder rechte Verknüpfung auswählen. Ich kenne Eloquent nicht, also haben sie vielleicht einfach beschlossen, die Dinge einfach zu halten.

    – IMSoP

    28. Mai 2014 um 21:09 Uhr

  • @deczo Ich glaube nicht, dass ich dir folge. Die Join-Klausel existiert aus einem bestimmten Grund, sodass Sie keine zusätzlichen Abfragen durchführen müssen, um Zeilen aus verschiedenen Tabellen abzugleichen. Wenn Sie zwischen verschiedenen Arten von Joins sprechen, wie left, right, inner… Ich denke, Sie könnten Eloquent sagen, welche je nach Beziehung im Modell verwendet werden soll. belongsTo würde bedeuten, dass es immer gehört, also kann der Fremdschlüssel nicht null sein. Sie könnten ein weiteres Like hinzufügen canBelongTo Dies würde zulassen, dass der Fremdschlüssel null ist, und aus diesem Grund würde er die linke Verknüpfung verwenden. Weiß nicht, sag nur…

    – empz

    28. Mai 2014 um 21:10 Uhr

  • Die Sache ist, dass ORM nur eine weitere Ebene ist, um das Leben einfacher zu machen. Sein Hauptzweck ist es, entwicklerfreundlich zu sein, und ich denke, das ist so einfach wie es nur sein kann. Es hat Fehler und Einschränkungen, es gibt Leistungsprobleme, auch Inkonsistenzen im Code, aber andererseits ist es wirklich eloquent und großartig für einfache Aufgaben. Allerdings gibt es kein ORM, das flexibel genug ist, um alle Anforderungen zu erfüllen, deshalb verwenden Sie (ich) ORM nicht für komplexere Aufgaben.

    – Jarek Tkaczyk

    28. Mai 2014 um 21:30 Uhr

  • @emzero, ich habe vor kurzem angefangen, Laravel auszuprobieren, und ich stehe vor genau der gleichen Frage. Ich kann nicht glauben, wie ein Entwickler sagen kann, dass eine zusätzliche Abfrage in Ordnung ist, wo Join die Arbeit erledigen würde und / oder select * ist in Ordnung

    – dav

    5. Februar 2017 um 21:28 Uhr

Meine Vermutung ist, dass dies das eifrige Laden mehrerer Eins-zu-viele-Beziehungen ermöglicht. Sagen wir zum Beispiel, wir hatten auch einen Hundetisch:

class User extends Eloquent {

    public function cats() {
        return $this->hasMany('Cat');
    }

    public function dogs() {
        return $this->hasMany('Dog');
    }
}

Jetzt wollen wir beide eifrig mit dem User laden:

$users = User::with('cats','dogs')->get();

Es gibt keinen Join, der funktionieren würde, um diese in einer einzigen Abfrage zu kombinieren. Es funktioniert jedoch, eine separate Abfrage für jedes “with” -Element durchzuführen:

select * from `users`
select * from `cats` where `user`.`id` in ('1', '2', 'x')
select * from `dogs` where `user`.`id` in ('1', '2', 'x') 

Obwohl diese Methode unter einigen einfachen Umständen eine zusätzliche Abfrage erzeugen kann, bietet sie die Möglichkeit, komplexere Daten eifrig zu laden, wenn die Join-Methode fehlschlägt.

Das ist meine Vermutung, warum das so ist.

  • Dies gilt für N<>M- und 1<>N-Beziehungen, aber nicht für 1<>1 oder N<>1. Selbst wenn Sie nur diese 2 implementieren, gibt es immer noch eine sehr große Verbesserung, insbesondere für große Sets. (Außerdem kann 1<>N auch mit einer zusätzlichen Abfrage gelöst werden, unabhängig von der Anzahl der Datensätze im Satz)

    – Yaron U.

    11. September 2016 um 18:45 Uhr

  • zum belongsTo Beziehungen gilt dies nicht

    – dav

    5. Februar 2017 um 21:13 Uhr

  • aber eloquent kann join für gehörtzu und eine andere Abfrage für hasMany verwenden

    – fico7489

    3. Dezember 2017 um 15:56 Uhr

  • There is no join that would work to combine these into a single query. Nun, es ist der linke Join

    – Adam

    18. September 2020 um 8:30 Uhr

Ich denke, der Join-Abfrageansatz hat einen fatalen Nachteil, wenn Sie LIMIT und/oder OFFSET verwenden möchten.

$users = User::with(‘cats’)->get() – dies gibt die folgenden 2 Abfragen aus.

select * from `users`
select * from `cats` where `user`.`id` in ('1', '2', 'x')

und es ist nicht eine einzige Abfrage wie

select * from users inner join cats on cats.user_id = users.id

aber nehmen wir an, wir müssen diesen Datensatz paginieren.

User::with(‘cats’)->paginate(10) – dies gibt die folgenden 2 Abfragen mit Limit aus.

select * from `users` limit 10
select * from `cats` where `user`.`id` in ('1', '2', 'x')

mit einem Join wird es wie sein

select * from users inner join cats on cats.user_id = users.id limit 10

Es werden 10 Datensätze abgerufen, dies bedeutet jedoch nicht 10 Benutzer, da jeder Benutzer mehrere Katzen haben kann.

Ein weiterer Grund meiner Meinung nach ist, dass eine Beziehung zwischen relationaler Datenbank und NOSQL-Datenbank mit dem getrennten Abfrageansatz einfach implementiert werden kann

Auch als vorherige Antwort ist id mehrdeutig, und Sie müssten jeder Anweisung den nicht gewünschten Tabellennamen voranstellen.

Auf der anderen Seite ist JOIN teurer als EXISTS und EXISTS ist schneller, weil es RDBMS nicht anweist, Daten abzurufen, prüfen Sie einfach, ob relevante Zeilen vorhanden sind. EXISTS wird verwendet, um einen booleschen Wert zurückzugeben, JOIN gibt eine ganz andere Tabelle zurück.

Wenn Sie der Sharding-Architektur folgen, müssen Sie aus Gründen der Skalierbarkeit die JOINs entfernen. Dies wurde von Pinterest bei der Skalierung praktiziert.
http://highscalability.com/blog/2013/4/15/scaling-pinterest-from-0-to-10s-of-billions-of-page-views-a.html

cats und users wahrscheinlich haben beide eine Spalte mit dem Namen id, wodurch Ihre vorgeschlagene Abfrage mehrdeutig bleibt. Das eifrige Laden von Laravel verwendet eine zusätzliche Abfrage, vermeidet jedoch dieses potenzielle Missgeschick.

  • Sie haben Recht. Es ist keine Möglichkeit, den Tabellennamen an zurückgegebene Spalten anzuhängen? Wie cat.id, cat.name, user.idetc?

    – empz

    28. Mai 2014 um 20:14 Uhr

  • Das ist mit Aliasen in der Abfrage trivial zu handhaben (z cat.id as cat__id), und ich würde erwarten, dass ein ORM das in Kauf nimmt.

    – IMSoP

    28. Mai 2014 um 20:15 Uhr


  • @IMSoP Genau, das würde ich auch erwarten.

    – empz

    28. Mai 2014 um 20:16 Uhr

  • @IMSoP Im Gegensatz zu einigen ORMs weiß Eloquent nicht, welche Spalten sich in den Tabellen befinden – meistens tut es dies SELECT *. Es würde nicht wissen, für welche Spalten ein Alias ​​verwendet werden soll.

    – ceejayoz

    28. Mai 2014 um 20:28 Uhr

  • @ceejayoz Siehe meinen Kommentar oben bezüglich der Rückgabe einer zusätzlichen “Trennzeichen” -Spalte für jeden Join (ein Trick, den ich häufig beim Debuggen verwende). Es wäre sicherlich möglich, ein ORM zu erstellen, das Joins verwendet, ohne die Nichtschlüsselspalten im Voraus zu kennen. Dies ist entweder eine Designentscheidung von Eloquent oder einfach eine Einschränkung, an deren Behebung sie nicht interessiert sind.

    – IMSoP

    28. Mai 2014 um 20:44 Uhr

1157650cookie-checkWarum kann Laravel/Eloquent JOIN nicht für Eager Loading verwenden?

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy