关联模型字段取别名查询不出数据的处理方法

需求

file
查询这样的有父子层次关系的菜单数据,例如id=2的数据有子数据,id=3,4,5,6,16,因为后面这些数据的parent_id都是2

比较笨的办法

先查询每一条数据,然后再遍历查询parent_id = 每一条数据的id,最后数组拼接。这种方法不仅笨,查询次数还多,完全没必要。

模型自关联

laravel自带的ORM是个神器,针对这种有关系的数据,完全可以使用关系模型,既简单又实用,由于这里只有一个数据表,关系也存在于同一张表,所以可以直接使用自关联,将两个关系定义在同一个Model里面:

    ......
    public function parent() {
        return $this->hasOne($this, 'id', 'parent_id');
    }

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

关系定义里面的参数也可以这样写:

    ......
    public function parent() {
        return $this->hasOne(get_class($this), $this->getKeyName(), 'parent_id');
    }

    public function children() {
        return $this->hasMany(get_class($this), 'parent_id', $this->getKeyName());
    }

查询包含子菜单的数据

        return Admin_menu::with(['children' => function($query){
                $query->select('id', 'title', 'parent_id');
            }])
            ->select('id', 'title', 'parent_id')
            ->get();

file

同理查询包含父菜单的数据,将with参数'children'换成'parent'即可。

关联键取别名查询不出数据

        return Admin_menu::with(['children' => function($query){
                $query->select('id', 'title', 'parent_id');
            }])
            ->select('id as MainId', 'title', 'parent_id')
            ->get();

file

分析sql语句

    \DB::connection()->enableQueryLog(); // 开启查询日志
    $menus =  Admin_menu::with(['children' => function($query){
        $query->select('id', 'title', 'parent_id');
    }])
    ->select('id as MainId', 'title', 'parent_id')
    ->get();
    foreach (\DB::getQueryLog() as $sql) {
        dump($sql['query']);
    }

file
打印sql语句,不取别名的情况下,第二条查询,bindings应该是有值的数组,也就是 where in ()是有值可以查询的;给id取了别名后会发现,binding变成null,where in(null)也就查不到数据。

这里我的猜想是,where in (array)这里的array是依赖主键的名称的,在关联查询的时候,已经定义了id = [3,4,5,6...],但是我们最后给id取了别名,变成MaindId,所以找不到名为id的数组。
如果真是这样,我们试着再给它加上id,让它能够找到名为id的数组

    \DB::connection()->enableQueryLog(); // 开启查询日志
    $menus =  Admin_menu::with(['children' => function($query){
        $query->select('id', 'title', 'parent_id');
    }])
    ->select('id', 'id as MainId', 'title', 'parent_id')
    ->get();
    foreach (\DB::getQueryLog() as $sql) {
        dump($sql['query']);
    }

file
这里可以看到bingdings已经不再是null了。

总结

虽然以上取别名问题所在只是我的猜想,但大致可以得出结论:依赖关联主键localKey的查询,不能缺少相应的字段,也就是说select应该包含对应localKey,如果要取别名应该额外添加,形如:

select('id', 'id as MainId', 'title', 'parent_id', 'parent_id as extraId')

附加另外一种处理数据格式方法

另外写一个私有的格式处理方法transformer,取别名就交给这个方法来转换

    public function test() {
        $menus = Admin_menu::with(['parent' => function($query){
                $query->select('id', 'title', 'parent_id');
            }])
            ->select('id', 'title', 'parent_id')
            ->get();
        return $this->transformer($menus);
    }

    protected function transformer($items) {
        $data = [];
        foreach ($items ?? [] as $item) {
            $data[] = [
                'mainId' => $item->id,
                'title' => $item->title,
                'parent_id' => $item->parent_id,
                'children' => $item->children,
            ];
        }
        return $data;
    }
本作品采用《CC 协议》,转载必须注明作者和本文链接
zgxxx
《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
讨论数量: 2

怎么感觉好别扭,提前返回怎么样?
if(!$items) return $data;

 protected function transformer($items) {
        $data = [];
        foreach ($items ?? [] as $item) {
            $data[] = [
                'mainId' => $item->id,
                'title' => $item->title,
                'parent_id' => $item->parent_id,
                'children' => $item->children,
            ];
        }
        return $data;
    }
5年前 评论

其实内部绑定就是这样的,必须依赖主键。orm不可能那么智能

5年前 评论

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!