Laravel,Datatables,列关系数 [英] Laravel, Datatables, column with relations count

查看:1240
本文介绍了Laravel,Datatables,列关系数的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有两个模型,用户培训许多到许多他们之间的关系。我正在使用 Laravel Datatables 软件包显示所有用户的表格。这是数据控制器方法(检索查询结果并创建数据表)的方式如下所示:

  public function getData )
{
$ users = User :: select(array('users.id','users.full_name','users.email','users.business_unit','users.position_id') )
- > where('users.is_active','=',1);

返回\Datatables ::($ users)
- > remove_column('id')
- > make();
}

如何将列添加到创建的表中,显示总数每个用户的关系(即每个用户有多少培训有)?

解决方案

强力的方式是尝试一个 User :: selectRaw(...)它有一个内置的子查询,以获取用户的培训计数并将其作为一个字段。



然而,有一种更内置的方法来做到这一点。您可以加载关系(以避免n + 1查询),并使用DataTables add_column 方法添加到计数中。假设你的关系被命名为培训

  public function getData() 
$ users = User :: with('trainings') - > select(array('users.id','users.full_name','users.email','users.business_unit','users。 position_id'))
- > where('users.is_active','=',1);

return \Datatables :: of($ users)
- > add_column('trainings',function($ user){
return $ user-> trainings- > count();
})
- > remove_column('id')
- > make();
}

add_column 应该与加载的关系名称相同。如果由于某种原因使用其他名称,那么您需要确保删除关系列,以便从数据数组中移除。例如:

 返回\Datatables ::($ users)
- > add_column('trainings_count' ,$($ user){
return $ user-> trainings-> count();
})
- > remove_column('id')
- > ; remove_column('trainings')
- > make();



编辑



不幸的是,如果你想要在计数字段中进行排序,您将需要强力方法。该包通过调用 - > orderBy() Builder 对象传递给 of()方法,所以查询本身需要订购的字段。



然而,即使你需要做一些原始SQL,可以做一点清洁。您可以添加一个模型范围,将添加关系的计数。例如,将以下方法添加到您的用户模型中:



注意:以下功能仅适用于hasOne / hasMany关系。请参阅编辑2 以获取更新的功能,以处理所有关系。

  public function scopeSelectRelatedCount($ query,$ relationName,$ fieldName = null)
{
$ relation = $ this-> $ relationName(); // ex:$ this-> trainings()
$ related = $ relation-> getRelated(); // ex:Training
$ parentKey = $ relation-> getQualifiedParentKeyName(); // ex:users.id
$ relatedKey = $ relation-> getForeignKey(); // ex:trainings.user_id
$ fieldName = $ fieldName?:$ relationName; // ex:trainings

//构建查询以获取相关记录的计数
// ex:从训练中选择count(*),其中trainings.id = users.id
$ subQuery = $ related-> select(DB :: raw('count(*)')) - > whereRaw($ relatedKey。'='。$ parentKey);

//构建添加到查询中的选择文本
// ex :(从trainings.id = users.id的培训中选择count(*))作为培训
$ select ='('。$ subQuery-> toSql()。')as'。 $ fieldName;

//添加select到查询
return $ query-> addSelect(DB :: raw($ select));
}

将该范围添加到您的User模型中,您的getData函数变为: p>

  public function getData(){
$ users = User :: select(array('users.id','users
- > selectRelatedCount('trainings')
- >其中('users.is_active','user.email','users.business_unit','users.pate_id' ,'=',1);

返回\Datatables ::($ users)
- > remove_column('id')
- > make();
}

如果您希望计数字段具有不同的名称,可以传递作为 selectRelatedCount 范围的第二个参数的字段名称(例如 selectRelatedCount('trainings','training_count'))。



编辑2



scopeSelectRelatedCount )

首先,调用 $ relation-> getQualifiedParentKeyName()只适用于hasOne / hasMany关系。这是该方法定义为 public 的唯一关系。所有其他关系将此方法定义为 protected 。因此,使用这个范围没有hasOne / hasMany的关系会抛出一个 Illuminate\Database\Query\Builder :: getQualifiedParentKeyName() exception。



其次,生成的计数SQL对于所有关系都不正确。再次,对于hasOne / hasMany它可以正常工作,但生成的手动SQL对于许多关系(belongsToMany)来说根本不起作用。



找到两个问题的解决方案。在查看关系代码以确定异常的原因之后,我发现Laravel已经提供了一种公共方法来生成一个关系的计数SQL: getRelationCountQuery()。应该适用于所有关系的更新范围方法是:

  public function scopeSelectRelatedCount($ query,$ relationName,$ fieldName = null )
{
$ relation = $ this-> $ relationName(); // ex:$ this-> trainings()
$ related = $ relation-> getRelated(); // ex:Training
$ fieldName = $ fieldName?:$ relationName; // ex:trainings

//构建查询以获取相关记录的计数
// ex:从训练中选择count(*),其中trainings.id = users.id
$ subQuery = $ relation-> getRelationCountQuery($ related-> newQuery(),$ query);

//构建添加到查询中的选择文本
// ex :(从trainings.id = users.id的培训中选择count(*))作为培训
$ select ='('。$ subQuery-> toSql()。')as'。 $ fieldName;

//添加select到查询
return $ query-> addSelect(DB :: raw($ select));
}



编辑3



此更新允许您将一个闭包传递到将修改添加到选择字段的count子查询的范围。

  public function scopeSelectRelatedCount($ query,$ relationName,$ fieldName = null,$ callback = null)
{
$ relation = $ this-> $ relationName(); // ex:$ this-> trainings()
$ related = $ relation-> getRelated(); // ex:Training
$ fieldName = $ fieldName?:$ relationName; // ex:trainings

//为count语句开始一个新的查询
$ countQuery = $ related-> newQuery();

//如果给出了一个回调闭包,使用count查询和关系调用
if($ callback instanceof Closure){
call_user_func($ callback,$ countQuery,$关系);
}

//构建查询以获取相关记录的计数
// ex:从训练中选择count(*),其中trainings.id = users.id
$ subQuery = $ relation-> getRelationCountQuery($ countQuery,$ query);

//构建添加到查询中的选择文本
// ex :(从trainings.id = users.id的培训中选择count(*))作为培训
$ select ='('。$ subQuery-> toSql()。')as'。 $ fieldName;

$ queryBindings = $ query-> getBindings();
$ countBindings = $ countQuery-> getBindings();

//如果新的计数查询具有参数绑定,则需要将
//拼接到现有的查询绑定中,正确的位置
if(!empty($ countBindings )){
//如果当前查询没有绑定,只需将当前绑定
//设置为计数查询的绑定
if(empty($ queryBindings)){
$ queryBindings = $ countBindings;
} else {
//新的计数查询绑定必须直接放在任何
之后//现有绑定的选择字段
$ fields = implode(',',$ query-> getQuery() - >列);
$ numFieldParams = 0;
//快捷方式如果没有正则表达式?在所有的领域
if(strpos($ fields,'?')!== false){
//计数字段列表中未引用的参数(?)的数量
$ paramRegex ='/(?:([\"\'])(?:\\\.|[^\1])*\1\\\\\.|[^\? ])+ /';
$ numFieldParams = preg_match_all($ paramRegex,$ fields) - 1;
}
//将当前查询绑定到计数子查询所需的绑定
array_splice($ queryBindings,$ numFieldParams,0,$ countBindings);
}
}

//添加选择到查询并更新绑定
return $ query-> addSelect(DB :: raw($ select)) - > setBindings($ queryBindings);
}

使用更新的范围,您可以使用关闭来修改计数查询:

  public function getData(){
$ users = User :: select(array('users.id','users .full_name','users.email','users.business_unit','users.position_id'))
- > selectRelatedCount('trainings','trainings',function($ query,$ relation){
return $ query
- > where($ relation-> getTable()。'is_creator',false)
- > where($ relation-> getTable() .is_speaker',false)
- > where($ relation-> getTable()。'was_absent',false);
})
- > where('users.is_active','=',1);

返回\Datatables ::($ users)
- > remove_column('id')
- > make();
}

注意:在撰写本文时,bllim / laravel4-datatables -package datatables包在选择字段中的子查询中具有参数绑定的问题。数据将被正确返回,但计数不会(显示0条目0到0)。我详细说明了这里的问题。这两个选项是使用该问题中提供的代码手动更新数据表包,或者在count子查询中不使用参数绑定。使用 whereRaw 来避免参数绑定。


I have two models, User and Training, with Many to many relationship between them. I'm using the Laravel Datatables package to display a table of all the users. This is how the data controller method (which retrieves the query results and creates a Datatables table) looks like:

public function getData()
{
    $users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
        ->where('users.is_active', '=', 1);

    return \Datatables::of($users)
        ->remove_column('id')
        ->make();
}

How can I add a column to the created table which displays the total number of relations for each user (that is, how many Trainings does each User have)?

解决方案

The brute force way would be to try a User::selectRaw(...) which has a built in subquery to get the count of trainings for the user and expose it as a field.

However, there is a more built-in way to do this. You can eager load the relationship (to avoid the n+1 queries), and use the DataTables add_column method to add in the count. Assuming your relationship is named trainings:

public function getData() {
    $users = User::with('trainings')->select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
        ->where('users.is_active', '=', 1);

    return \Datatables::of($users)
        ->add_column('trainings', function($user) {
            return $user->trainings->count();
        })
        ->remove_column('id')
        ->make();
}

The name of the column in add_column should be the same name as the loaded relationship. If you use a different name for some reason, then you need to make sure to remove the relationship column so it is removed from the data array. For example:

    return \Datatables::of($users)
        ->add_column('trainings_count', function($user) {
            return $user->trainings->count();
        })
        ->remove_column('id')
        ->remove_column('trainings')
        ->make();

Edit

Unfortunately, if you want to order on the count field, you will need the brute force method. The package does its ordering by calling ->orderBy() on the Builder object passed to the of() method, so the query itself needs the field on which to order.

However, even though you'll need to do some raw SQL, it can be made a little cleaner. You can add a model scope that will add in the count of the relations. For example, add the following method to your User model:

Note: the following function only works for hasOne/hasMany relationships. Please refer to Edit 2 below for an updated function to work on all relationships.

public function scopeSelectRelatedCount($query, $relationName, $fieldName = null)
{
    $relation = $this->$relationName(); // ex: $this->trainings()
    $related = $relation->getRelated(); // ex: Training
    $parentKey = $relation->getQualifiedParentKeyName(); // ex: users.id
    $relatedKey = $relation->getForeignKey(); // ex: trainings.user_id
    $fieldName = $fieldName ?: $relationName; // ex: trainings

    // build the query to get the count of the related records
    // ex: select count(*) from trainings where trainings.id = users.id
    $subQuery = $related->select(DB::raw('count(*)'))->whereRaw($relatedKey . ' = ' . $parentKey);

    // build the select text to add to the query
    // ex: (select count(*) from trainings where trainings.id = users.id) as trainings
    $select = '(' . $subQuery->toSql() . ') as ' . $fieldName;

    // add the select to the query
    return $query->addSelect(DB::raw($select));
}

With that scope added to your User model, your getData function becomes:

public function getData() {
    $users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
        ->selectRelatedCount('trainings')
        ->where('users.is_active', '=', 1);

    return \Datatables::of($users)
        ->remove_column('id')
        ->make();
}

If you wanted the count field to have a different name, you can pass the name of the field in as the second parameter to the selectRelatedCount scope (e.g. selectRelatedCount('trainings', 'training_count')).

Edit 2

There are a couple issues with the scopeSelectRelatedCount() method described above.

First, the call to $relation->getQualifiedParentKeyName() will only work on hasOne/hasMany relations. This is the only relationship where that method is defined as public. All the other relationships define this method as protected. Therefore, using this scope with a relationship that is not hasOne/hasMany throws an Illuminate\Database\Query\Builder::getQualifiedParentKeyName() exception.

Second, the count SQL generated is not correct for all relationships. Again, it would work fine for hasOne/hasMany, but the manual SQL generated would not work at all for a many to many relationship (belongsToMany).

I did, however, find a solution to both issues. After looking through the relationship code to determine the reason for the exception, I found Laravel already provides a public method to generate the count SQL for a relationship: getRelationCountQuery(). The updated scope method that should work for all relationships is:

public function scopeSelectRelatedCount($query, $relationName, $fieldName = null)
{
    $relation = $this->$relationName(); // ex: $this->trainings()
    $related = $relation->getRelated(); // ex: Training
    $fieldName = $fieldName ?: $relationName; // ex: trainings

    // build the query to get the count of the related records
    // ex: select count(*) from trainings where trainings.id = users.id
    $subQuery = $relation->getRelationCountQuery($related->newQuery(), $query);

    // build the select text to add to the query
    // ex: (select count(*) from trainings where trainings.id = users.id) as trainings
    $select = '(' . $subQuery->toSql() . ') as ' . $fieldName;

    // add the select to the query
    return $query->addSelect(DB::raw($select));
}

Edit 3

This update allows you to pass a closure to the scope that will modify the count subquery that is added to the select fields.

public function scopeSelectRelatedCount($query, $relationName, $fieldName = null, $callback = null)
{
    $relation = $this->$relationName(); // ex: $this->trainings()
    $related = $relation->getRelated(); // ex: Training
    $fieldName = $fieldName ?: $relationName; // ex: trainings

    // start a new query for the count statement
    $countQuery = $related->newQuery();

    // if a callback closure was given, call it with the count query and relationship
    if ($callback instanceof Closure) {
        call_user_func($callback, $countQuery, $relation);
    }

    // build the query to get the count of the related records
    // ex: select count(*) from trainings where trainings.id = users.id
    $subQuery = $relation->getRelationCountQuery($countQuery, $query);

    // build the select text to add to the query
    // ex: (select count(*) from trainings where trainings.id = users.id) as trainings
    $select = '(' . $subQuery->toSql() . ') as ' . $fieldName;

    $queryBindings = $query->getBindings();
    $countBindings = $countQuery->getBindings();

    // if the new count query has parameter bindings, they need to be spliced
    // into the existing query bindings in the correct spot
    if (!empty($countBindings)) {
        // if the current query has no bindings, just set the current bindings
        // to the bindings for the count query
        if (empty($queryBindings)) {
            $queryBindings = $countBindings;
        } else {
            // the new count query bindings must be placed directly after any
            // existing bindings for the select fields
            $fields = implode(',', $query->getQuery()->columns);
            $numFieldParams = 0;
            // shortcut the regex if no ? at all in fields
            if (strpos($fields, '?') !== false) {
                // count the number of unquoted parameters (?) in the field list
                $paramRegex = '/(?:(["\'])(?:\\\.|[^\1])*\1|\\\.|[^\?])+/';
                $numFieldParams = preg_match_all($paramRegex, $fields) - 1;
            }
            // splice into the current query bindings the bindings needed for the count subquery
            array_splice($queryBindings, $numFieldParams, 0, $countBindings);
        }
    }

    // add the select to the query and update the bindings
    return $query->addSelect(DB::raw($select))->setBindings($queryBindings);
}

With the updated scope, you can use the closure to modify the count query:

public function getData() {
    $users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))
        ->selectRelatedCount('trainings', 'trainings', function($query, $relation) {
            return $query
                ->where($relation->getTable().'.is_creator', false)
                ->where($relation->getTable().'.is_speaker', false)
                ->where($relation->getTable().'.was_absent', false);
        })
        ->where('users.is_active', '=', 1);

    return \Datatables::of($users)
        ->remove_column('id')
        ->make();
}

Note: as of this writing, the bllim/laravel4-datatables-package datatables package has an issue with parameter bindings in subqueries in the select fields. The data will be returned correctly, but the counts will not ("Showing 0 to 0 of 0 entries"). I have detailed the issue here. The two options are to manually update the datatables package with the code provided in that issue, or to not use parameter binding inside the count subquery. Use whereRaw to avoid parameter binding.

这篇关于Laravel,Datatables,列关系数的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆