Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/php/269.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Php Laravel、数据表、具有关系计数的列_Php_Laravel_Datatables_Eloquent_Laravel Datatables - Fatal编程技术网

Php Laravel、数据表、具有关系计数的列

Php Laravel、数据表、具有关系计数的列,php,laravel,datatables,eloquent,laravel-datatables,Php,Laravel,Datatables,Eloquent,Laravel Datatables,我有两个模型,用户和培训,它们之间有多对多的关系。我正在使用这个包来显示一个包含所有用户的表。检索查询结果并创建Datatables表的data controller方法如下所示: public function getData() { $users = User::select(array('users.id', 'users.full_name', 'users.email', 'users.business_unit', 'users.position_id'))

我有两个模型,用户和培训,它们之间有多对多的关系。我正在使用这个包来显示一个包含所有用户的表。检索查询结果并创建Datatables表的data controller方法如下所示:

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();
}

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

我将使用中提供的约定设置数据库表和雄辩的模型。在您的示例中,有三个表

训练 培训用户 使用者 你的模特看起来像这样

class Training {

    public function users() {
        return $this->belongsToMany('User');    
    }

}

class User {

    public function trainings() {
        return $this->belongsToMany('Training');    
    }

}
然后,您可以使用Eloquent获得用户列表并加载他们的培训

// Get all users and eager load their trainings
$users = User::with('trainings')->get();
如果要计算每个用户的培训次数,只需迭代$users并计算培训数组的大小

foreach ( $users as $v ) {
    $numberOfTrainings = sizeof($v->trainings); 
}
或者,您也可以简单地用纯SQL实现。请注意,我下面的示例假设您遵循Laravel的命名表和列的约定

SELECT 
    u.*, COUNT(p.user_id) AS number_of_trainings 
FROM 
    users u 
JOIN 
    training_user p ON u.id = p.user_id 
GROUP BY 
    u.id

现在您有了两种计算关系数的方法,您可以使用任何方法将该值存储到某个位置。请记住,如果将该数字作为值存储在用户表中,则每次用户创建/更新/删除培训时都需要更新该数字,反之亦然

我将使用上提供的约定设置您的DB表和雄辩的模型。在您的示例中,有三个表

训练 培训用户 使用者 你的模特看起来像这样

class Training {

    public function users() {
        return $this->belongsToMany('User');    
    }

}

class User {

    public function trainings() {
        return $this->belongsToMany('Training');    
    }

}
然后,您可以使用Eloquent获得用户列表并加载他们的培训

// Get all users and eager load their trainings
$users = User::with('trainings')->get();
如果要计算每个用户的培训次数,只需迭代$users并计算培训数组的大小

foreach ( $users as $v ) {
    $numberOfTrainings = sizeof($v->trainings); 
}
或者,您也可以简单地用纯SQL实现。请注意,我下面的示例假设您遵循Laravel的命名表和列的约定

SELECT 
    u.*, COUNT(p.user_id) AS number_of_trainings 
FROM 
    users u 
JOIN 
    training_user p ON u.id = p.user_id 
GROUP BY 
    u.id

现在您有了两种计算关系数的方法,您可以使用任何方法将该值存储到某个位置。请记住,如果将该数字作为值存储在用户表中,则每次用户创建/更新/删除培训时都需要更新该数字,反之亦然

暴力的方法是尝试一个用户::selectRaw。。。它有一个内置子查询,用于获取用户的培训次数,并将其作为字段公开

然而,有一种更为内在的方法可以做到这一点。您可以立即加载关系以避免n+1查询,并使用DataTables add_column方法添加计数。假设您的关系名为“培训”:

add_列中的列的名称应与加载的关系的名称相同。如果出于某种原因使用不同的名称,则需要确保删除关系列,以便将其从数据数组中删除。例如:

    return \Datatables::of($users)
        ->add_column('trainings_count', function($user) {
            return $user->trainings->count();
        })
        ->remove_column('id')
        ->remove_column('trainings')
        ->make();
编辑 不幸的是,如果您想在count字段上下单,则需要蛮力方法。包通过调用传递给of方法的Builder对象上的->orderBy进行排序,因此查询本身需要排序的字段

然而,即使您需要执行一些原始SQL,它也可以变得更干净一些。您可以添加一个模型范围,该范围将添加关系的计数。例如,将以下方法添加到用户模型中:

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

将该范围添加到用户模型后,getData函数将变为:

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();
}
如果希望计数字段具有不同的名称,可以将字段名称作为第二个参数传入selectRelatedCount范围,例如selectRelatedCount'trainings'、'training\u count'

编辑2 上述scopeSelectRelatedCount方法存在几个问题

首先,对$relation->getQualifiedParentKeyName的调用将只对hasOne/hasMany关系起作用。这是将该方法定义为公共的唯一关系。所有其他关系都将此方法定义为受保护。因此,将此作用域与非hasOne/hasMany的关系一起使用会引发Illumb\Database\Query\Builder::getQualifiedParentKeyName异常

其次,SQL生成的计数并非适用于所有关系。同样,它对于hasOne/hasMany也可以很好地工作,但是手动生成的SQL对于多对多关系belongtomany根本不起作用

然而,我确实找到了解决这两个问题的办法。在查看关系代码以确定异常的原因后,我发现Laravel已经提供了一个公共方法来为关系生成count 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

    // 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));
}
编辑3 此更新允许您将闭包传递给将修改计数子查询的作用域 添加到选择字段的y

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);
}
使用更新的范围,您可以使用闭包修改计数查询:

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();
}

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

暴力方式是尝试用户::selectRaw。。。它有一个内置子查询,用于获取用户的培训次数,并将其作为字段公开

然而,有一种更为内在的方法可以做到这一点。您可以立即加载关系以避免n+1查询,并使用DataTables add_column方法添加计数。假设您的关系名为“培训”:

add_列中的列的名称应与加载的关系的名称相同。如果出于某种原因使用不同的名称,则需要确保删除关系列,以便将其从数据数组中删除。例如:

    return \Datatables::of($users)
        ->add_column('trainings_count', function($user) {
            return $user->trainings->count();
        })
        ->remove_column('id')
        ->remove_column('trainings')
        ->make();
编辑 不幸的是,如果您想在count字段上下单,则需要蛮力方法。包通过调用传递给of方法的Builder对象上的->orderBy进行排序,因此查询本身需要排序的字段

然而,即使您需要执行一些原始SQL,它也可以变得更干净一些。您可以添加一个模型范围,该范围将添加关系的计数。例如,将以下方法添加到用户模型中:

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

将该范围添加到用户模型后,getData函数将变为:

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();
}
如果希望计数字段具有不同的名称,可以将字段名称作为第二个参数传入selectRelatedCount范围,例如selectRelatedCount'trainings'、'training\u count'

编辑2 上述scopeSelectRelatedCount方法存在几个问题

首先,对$relation->getQualifiedParentKeyName的调用将只对hasOne/hasMany关系起作用。这是将该方法定义为公共的唯一关系。所有其他关系都将此方法定义为受保护。因此,将此作用域与非hasOne/hasMany的关系一起使用会引发Illumb\Database\Query\Builder::getQualifiedParentKeyName异常

其次,SQL生成的计数并非适用于所有关系。同样,它对于hasOne/hasMany也可以很好地工作,但是手动生成的SQL对于多对多关系belongtomany根本不起作用

然而,我确实找到了解决这两个问题的办法。在查看关系代码以确定异常的原因后,我发现Laravel已经提供了一个公共方法来为关系生成count 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

    // 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));
}
编辑3 此更新允许您向作用域传递闭包,该作用域将修改添加到选择字段的计数子查询

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);
}
使用更新的范围,您可以使用闭包修改计数查询:

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();
}

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

Dave,谢谢你的回答,但这不是我的问题。我的模型已经按照你描述的方式建立了,我知道如何计算关系。但我的问题是它在Datatables类中不起作用。我如何将计数添加到Datatales表中?Dave,谢谢你的回答,但这不是我的问题。我的模型已经按照你描述的方式建立了,我知道如何计算关系。但我的问题是它在Datatables类中不起作用。如何将计数添加到Datatales表中?@我已根据您的评论/问题更新了答案。基本上,字段需要是select语句的一部分才能对其排序。该更新有一个解决方案。@CharStyleöМааСааааа你可能需要发布一些代码。该方法位于illumb/Database/elount/Relations/Relations对象上。我不知道你是否复制/粘贴了我的代码,但是如果你不小心用$related->getQualifiedParentKeyName而不是$relation->getQualifiedParentKeyName,我想你会得到这个错误$瑞拉
ted是模型,$relation是关系对象。@titаМааааааа用户培训方法是什么样的?您可能需要开始另一个问题,以保持所有这些都井然有序。@CharStyleааааааааа关系没有问题,但它确实帮助我找出了错误。我已经更新了我的答案的细节编辑2。除此之外,关于getData方法,我还想提到,如果您急于加载“trainings”关系,则需要使用selectRelatedCount范围的第二个参数。否则,您的“培训”关系数据将覆盖您的“培训”计数,然后“删除”列“培训”也将删除该数据。@titцццаааааћ它不应该那么糟糕。基本上,添加第三个默认为null的参数,该参数接受一个需要一个参数的闭包:生成计数的查询。然后将使用闭包为count语句添加where条件。我现在没有访问我的开发环境的权限,但我明天将发布更新的代码。@titöМаааааааа我已根据您的评论/问题更新了答案。基本上,字段需要是select语句的一部分才能对其排序。该更新有一个解决方案。@CharStyleöМааСааааа你可能需要发布一些代码。该方法位于illumb/Database/elount/Relations/Relations对象上。我不知道你是否复制/粘贴了我的代码,但是如果你不小心用$related->getQualifiedParentKeyName而不是$relation->getQualifiedParentKeyName,我想你会得到这个错误$related是模型,$relation是关系对象。@titöМааааааа用户培训方法是什么样子的?您可能需要开始另一个问题,以保持所有这些都井然有序。@CharStyleааааааааа关系没有问题,但它确实帮助我找出了错误。我已经更新了我的答案的细节编辑2。除此之外,关于getData方法,我还想提到,如果您急于加载“trainings”关系,则需要使用selectRelatedCount范围的第二个参数。否则,您的“培训”关系数据将覆盖您的“培训”计数,然后“删除”列“培训”也将删除该数据。@titцццаааааћ它不应该那么糟糕。基本上,添加第三个默认为null的参数,该参数接受一个需要一个参数的闭包:生成计数的查询。然后将使用闭包为count语句添加where条件。我现在没有权限访问我的开发环境,但我明天会发布更新的代码。