Laravel Rekursive Beziehungen

Lesezeit: 7 Minuten

Laravel Rekursive Beziehungen
Troncoso

Ich arbeite an einem Projekt in Laravel. Ich habe ein Kontomodell, das ein Elternteil oder Kinder haben kann, also habe ich mein Modell so eingerichtet:

public function immediateChildAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id');
}

public function parentAccount()
{
    return $this->belongsTo('Account', 'act_parent', 'act_id');
}

Das funktioniert gut. Was ich tun möchte, ist, alle Kinder unter einem bestimmten Konto zu bekommen. Aktuell mache ich das:

public function allChildAccounts()
{
    $childAccounts = $this->immediateChildAccounts;
    if (empty($childAccounts))
        return $childAccounts;

    foreach ($childAccounts as $child)
    {
        $child->load('immediateChildAccounts');
        $childAccounts = $childAccounts->merge($child->allChildAccounts());
    }

    return $childAccounts;
}

Das funktioniert auch, aber ich muss mir Sorgen machen, wenn es langsam ist. Dieses Projekt ist die Neufassung eines alten Projekts, das wir bei der Arbeit verwenden. Wir werden mehrere tausend Konten haben, die wir zu diesem neuen Projekt migrieren. Für die wenigen Testkonten, die ich habe, stellt diese Methode keine Leistungsprobleme dar.

Gibt es eine bessere Lösung? Soll ich einfach eine Rohabfrage ausführen? Tut Laravel haben Sie etwas, um damit umzugehen?

in Summe
Was ich für ein bestimmtes Konto tun möchte, ist, jedes einzelne Kinderkonto und jedes Kind seiner Kinder usw. in einer einzigen Liste / Sammlung zu erhalten. Ein Diagramm:

A -> B -> D
|--> C -> E
     |--> F 
G -> H

Wenn ich A->immediateChildAccounts() ausführe, sollte ich {B, C} erhalten
Wenn ich A->allChildAccounts() ausführe, sollte ich {B, D, C, E, F} erhalten (Reihenfolge spielt keine Rolle)

Auch hier funktioniert meine Methode, aber es scheint, als würde ich viel zu viele Abfragen durchführen.

Ich bin mir auch nicht sicher, ob es in Ordnung ist, dies hier zu fragen, aber es hängt damit zusammen. Wie bekomme ich eine Liste aller Konten, die nicht die Kinderkonten einbeziehen? Also im Grunde die Umkehrung der obigen Methode. Dies geschieht, damit ein Benutzer nicht versucht, einem Konto einen Elternteil zuzuweisen, der bereits sein Kind ist. Mit dem Diagramm von oben möchte ich (in Pseudocode):

Account::where(account_id not in (A->allChildAccounts())). Also würde ich {G, H} bekommen

Danke für jeden Einblick.

So können Sie rekursive Beziehungen verwenden:

public function childrenAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id');
}

public function allChildrenAccounts()
{
    return $this->childrenAccounts()->with('allChildrenAccounts');
}

Dann:

$account = Account::with('allChildrenAccounts')->first();

$account->allChildrenAccounts; // collection of recursively loaded children
// each of them having the same collection of children:
$account->allChildrenAccounts->first()->allChildrenAccounts; // .. and so on

So sparen Sie sich viele Rückfragen. Dadurch wird 1 Abfrage pro Verschachtelungsebene + 1 zusätzliche Abfrage ausgeführt.

Ich kann nicht garantieren, dass es für Ihre Daten effizient ist, Sie müssen es auf jeden Fall testen.


Dies gilt für kinderlose Konten:

public function scopeChildless($q)
{
   $q->has('childrenAccounts', '=', 0);
}

dann:

$childlessAccounts = Account::childless()->get();

  • Das versuche ich nicht. Ich brauche alle untergeordneten Konten in einer einzigen Sammlung. Die von mir bereitgestellte Methode tut dies, ich bin mir nur nicht sicher, wie effizient sie ist. Auch die zweite Lösung ist nicht das, wonach ich suche. Ich brauche alle Konten, die keine Kinder des Kontos sind, das die Funktion aufruft, keine kinderlosen Konten.

    – Troncoso

    30. Oktober 14 um 22:21 Uhr


  • 1 Dies ist, was Sie gerade tun, nur dass Sie db query N-mal häufiger aufrufen als mit der vorgeschlagenen Lösung. 2 formulieren Sie dann Ihre Frage um, weil es nicht das ist, was Sie geschrieben haben. Selbst jetzt nach Ihrem Kommentar ist unklar, was Sie fragen – direkte Kinder? Nachkommenschaft?

    – Jarek Tkaczyk

    30. Oktober 14 um 23:05 Uhr

  • Beeindruckend. du bist genial. Mein Code ist jetzt doppelt so schnell 🙂

    – Nurbol Alpysbayev

    24. Februar 18 um 9:34 Uhr

  • @Roark das ist auf den Punkt gebracht eloquent 😉

    – Jarek Tkaczyk

    16. März 18 um 22:09 Uhr

  • @JarekTkaczyk kannst du mir helfen, den Baum zu begrenzen? Ich möchte zB nicht mehr als 4 Ebenen auswählen.

    – Jur Gasparyan

    14. August 18 um 9:50 Uhr

1644335168 468 Laravel Rekursive Beziehungen
George

public function childrenAccounts()
{
    return $this->hasMany('Account', 'act_parent', 'act_id')->with('childrenAccounts');
}

Dieser Code gibt alle Kinderkonten zurück (wiederkehrend)

  • Dies ist sehr nützlich, wenn Sie hierarchische Daten aus demselben Modell deklarieren. Einfacher, als Sie die Beziehung benennen können children die mit den meisten UI-Elementen kompatibel ist, die sie anzeigen.

    – Yousof K.

    16. Januar 20 um 16:21 Uhr

Ich habe ein Paket erstellt, das allgemeine Tabellenausdrücke (CTE) verwendet, um rekursive Beziehungen zu implementieren: https://github.com/staudenmeir/laravel-adjacency-list

Du kannst den … benutzen descendants Beziehung, um alle untergeordneten Elemente eines Kontos rekursiv zu erhalten:

class Account extends Model
{
    use StaudenmeirLaravelAdjacencyListEloquentHasRecursiveRelationships;
}

$allChildren = Account::find($id)->descendants;

  • Ich wünschte, ich hätte das vor ein paar Monaten gefunden. Tolles Paket Jonas!

    – PW_Parsons

    27. März 20 um 11:27 Uhr

Als zukünftige Referenz:

public function parent()
{
    // recursively return all parents
    // the with() function call makes it recursive.
    // if you remove with() it only returns the direct parent
    return $this->belongsTo('AppModelsCategory', 'parent_id')->with('parent');
}

public function child()
{
    // recursively return all children
    return $this->hasOne('AppModelsCategory', 'parent_id')->with('child');
}

Dies ist für eine Category Modell, das hat id, title, parent_id. Hier ist der Code für die Datenbankmigration:

    Schema::create('categories', function (Blueprint $table) {
        $table->increments('id');
        $table->timestamps();
        $table->string('title');
        $table->integer('parent_id')->unsigned()->nullable();
        $table->foreign('parent_id')->references('id')->on('categories')->onUpdate('cascade')->onDelete('cascade');
    });

Wir machen etwas Ähnliches, aber unsere Lösung war folgende:

class Item extends Model {
  protected $with = ['children'];

  public function children() {
    $this->hasMany(AppItems::class, 'parent_id', 'id');
 }
}

  • Dadurch werden nicht alle untergeordneten untergeordneten Datensätze abgerufen.

    – Knisternder Code

    31. Januar 19 um 13:14 Uhr

  • Der einzige Unterschied zwischen der akzeptierten Antwort und meiner ist, dass die akzeptierte einen Accessor dafür definiert, während in meinem Fall die Relation eifrig geladen wird. Die akzeptierte Lösung ist allerdings flexibler in der Anwendung…

    – Meki

    4. Februar 19 um 6:42 Uhr

  • @SizzlingCode Ja, das wird es. Jeden Item Modell wird rekursiv geladen children, was eigentlich etwas zwielichtig ist, wenn es keinen Schutz gegen Wurzelzyklen gibt. Die akzeptierte Antwort ist nur eine aufgeblähte (unnötige) Version davon.

    – Stefan See

    24. November 19 um 13:49 Uhr


Laravel Rekursive Beziehungen
Kurucu

Ich mache etwas Ähnliches. Ich denke, die Antwort besteht darin, die Ausgabe zwischenzuspeichern und den Cache jedes Mal zu löschen, wenn die Datenbank aktualisiert wird (vorausgesetzt, Ihre Konten selbst ändern sich nicht viel?).

  • Dadurch werden nicht alle untergeordneten untergeordneten Datensätze abgerufen.

    – Knisternder Code

    31. Januar 19 um 13:14 Uhr

  • Der einzige Unterschied zwischen der akzeptierten Antwort und meiner ist, dass die akzeptierte einen Accessor dafür definiert, während in meinem Fall die Relation eifrig geladen wird. Die akzeptierte Lösung ist allerdings flexibler in der Anwendung…

    – Meki

    4. Februar 19 um 6:42 Uhr

  • @SizzlingCode Ja, das wird es. Jeden Item Modell wird rekursiv geladen children, was eigentlich etwas zwielichtig ist, wenn es keinen Schutz gegen Wurzelzyklen gibt. Die akzeptierte Antwort ist nur eine aufgeblähte (unnötige) Version davon.

    – Stefan See

    24. November 19 um 13:49 Uhr


picture?type=large
Alex Fichter

Ich denke, ich habe auch eine vernünftige Lösung gefunden.

class Organization extends Model
{
    public function customers()
    {
        return $this->hasMany(Customer::class, 'orgUid', 'orgUid');
    }

    public function childOrganizations()
    {
        return $this->hasMany(Organization::class, 'parentOrgUid', 'orgUid');
    }

    static function addIdToQuery($query, $org)
    {
        $query = $query->orWhere('id', $org->id);
        foreach ($org->childOrganizations as $org)
        {
            $query = Organization::addIdToQuery($query, $org);
        }
        return $query;
    }

    public function recursiveChildOrganizations()
    {
        $query = $this->childOrganizations();
        $query = Organization::addIdToQuery($query, $this);
        return $query;
    }

    public function recursiveCustomers()
    {
         $query = $this->customers();
         $childOrgUids = $this->recursiveChildOrganizations()->pluck('orgUid');
         return $query->orWhereIn('orgUid', $childOrgUids);
    }

}

Grundsätzlich beginne ich mit einer Query Builder-Beziehung und füge ihr orWhere-Bedingungen hinzu. Um alle untergeordneten Organisationen zu finden, verwende ich eine rekursive Funktion, um die Beziehungen zu durchsuchen.

Sobald ich die recursiveChildOrganizations-Beziehung habe, habe ich die einzige benötigte rekursive Funktion ausgeführt. Alle anderen Beziehungen (ich habe recursiveCustomers gezeigt, aber Sie könnten viele haben) verwenden dies.

Ich vermeide es, die Objekte an jeder möglichen Stelle zu instanziieren, da der Abfragegenerator so viel schneller ist als das Erstellen von Modellen und das Arbeiten mit Sammlungen.

Dies ist viel schneller als das Erstellen einer Sammlung und das rekursive Pushen von Mitgliedern (was meine erste Lösung war), und da jede Methode einen Abfragegenerator und keine Sammlung zurückgibt, lässt sie sich wunderbar mit Bereichen oder anderen Bedingungen stapeln, die Sie verwenden möchten.

.

824460cookie-checkLaravel Rekursive Beziehungen

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

Privacy policy