Laravel query builder for recursive results? E.g.

2019-02-07 12:26发布

So I have data structured like this:

id|parent_id|name
1 |null     |foo
2 |1        |bar
3 |2        |baz

So basically foo->bar->baz. I'm stumped on how to use laravel's query builder to get rows for a child row, then its ancestors (until parent_id == null). Can this be done with laravel? I've done a little research and Postgres has RECURSIVE while MySQL doesn't (Postgres recursive query to update values of a field while traversing parent_id).

I believe MySQL has something similar: How to do the Recursive SELECT query in MySQL?

But how would I implement this in Laravel?

My starting code is basically using a query scope, but I'm just not getting it right:

Model::select('name')->getParent(3); //get baz and the ancestors of baz
protected function scopeGetParent($id) {
  $parent = Model::where('id', '=', $id);
  return $query->getParent($parent->parent_id);
}

The desired result I want is:

name
baz
bar
foo

Any ideas?

3条回答
神经病院院长
2楼-- · 2019-02-07 12:49

I modified tiffanyhwang solution and turned it into a non-static method and included a attribute accessor to make it easier to get results.

public function ancestors()
{
    $ancestors = $this->where('id', '=', $this->parent_id)->get();

    while ($ancestors->last() && $ancestors->last()->parent_id !== null)
    {
        $parent = $this->where('id', '=', $ancestors->last()->parent_id)->get();
        $ancestors = $ancestors->merge($parent);
    }

    return $ancestors;
}

and accessor to retrieve a collection of ancestors from model attribute

public function getAncestorsAttribute()
{
    return $this->ancestors();
    // or like this, if you want it the other way around
    // return $this->ancestors()->reverse();
}

so now you can get ancestors like this:

$ancestors = $model->ancestors;

and since its a Collection, you can now easily do for example this:

echo $model->ancestors->implode('title',', ');
查看更多
Viruses.
3楼-- · 2019-02-07 12:57

An other way could be to use the etrepat/baum package, it's a Laravel implementation of the Nested set model. It's using an ordered tree that is faster and use non-recursive queries. While your data structured like this :

root
  |_ Child 1
    |_ Child 1.1
    |_ Child 1.2
  |_ Child 2
    |_ Child 2.1
    |_ Child 2.2

There are structured like this in nested set model :

 ___________________________________________________________________
|  Root                                                             |
|    ____________________________    ____________________________   |
|   |  Child 1                  |   |  Child 2                  |   |
|   |   __________   _________  |   |   __________   _________  |   |
|   |  |  C 1.1  |  |  C 1.2 |  |   |  |  C 2.1  |  |  C 2.2 |  |   |
1   2  3_________4  5________6  7   8  9_________10 11_______12 13  14
|   |___________________________|   |___________________________|   |
|___________________________________________________________________|

And inserting nodes is easy as :

$child1 = $root->children()->create(['name' => 'Child 1']);
查看更多
该账号已被封号
4楼-- · 2019-02-07 13:02

So after fiddling around with the merge() method for the Collections class:

public static function ancestors($id)
{
    $ancestors = Model::where('id', '=', $id)->get();

    while ($ancestors->last()->parent_id !== null)
    {
      $parent = Model::where('id', '=', $ancestors->last()->parent_id)->get();
      $ancestors = $ancestors->merge($parent);
    }

    return $ancestors;
}

That will produce what I needed, however I believe it can be more cleaner so please feel free to edit it!

查看更多
登录 后发表回答