1

と の 2 つのモデルがUserありTrainingMany to manyそれらの間に関係があります。Laravel Datatablesパッケージを使用して、すべてのユーザーのテーブルを表示しています。データ コントローラー メソッド (クエリ結果を取得し、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);

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

作成されたテーブルに、各ユーザーのリレーションの総数 (つまり、各ユーザーTrainingUser持っている の数) を表示する列を追加するにはどうすればよいですか?

4

2 に答える 2

12

力ずくの方法はUser::selectRaw(...)、サブクエリが組み込まれている を試して、ユーザーのトレーニング数を取得し、それをフィールドとして公開することです。

ただし、これを行うための組み込みの方法があります。リレーションシップを熱心に読み込み (n+1 クエリを回避するため)、DataTablesadd_columnメソッドを使用してカウントを追加できます。あなたの関係が次のように命名されていると仮定します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();
}

の列の名前はadd_column、ロードされたリレーションシップと同じ名前にする必要があります。何らかの理由で別の名前を使用する場合は、データ配列から削除されるように関係列を削除する必要があります。例えば:

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

編集

残念ながら、カウント フィールドで注文する場合は、力ずくの方法が必要になります。パッケージは、メソッドに渡さ->orderBy()れたオブジェクトを呼び出すことによって順序付けを行うため、クエリ自体には順序付けするフィールドが必要です。Builderof()

ただし、生の SQL を実行する必要がありますが、もう少しきれいにすることができます。リレーションの数を追加するモデル スコープを追加できます。たとえば、次のメソッドを User モデルに追加します。

注: 次の関数は、hasOne/hasMany 関係に対してのみ機能します。Edit 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

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

そのスコープを User モデルに追加すると、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();
}

カウント フィールドに別の名前を付けたい場合は、フィールドの名前を 2 番目のパラメーターとしてselectRelatedCountスコープに渡すことができます (例: selectRelatedCount('trainings', 'training_count'))。

編集 2

scopeSelectRelatedCount()上記の方法にはいくつかの問題があります。

まず、 への呼び出し$relation->getQualifiedParentKeyName()は hasOne/hasMany 関係でのみ機能します。これは、そのメソッドが として定義されている唯一の関係publicです。他のすべての関係は、このメソッドを として定義しますprotected。したがって、このスコープを hasOne/hasMany 以外の関係で使用すると、Illuminate\Database\Query\Builder::getQualifiedParentKeyName()例外がスローされます。

第 2 に、生成されたカウント 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

    // 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

この更新により、select フィールドに追加された 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

    // 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-package datatables パッケージには、選択フィールドのサブクエリのパラメーター バインディングに関する問題があります。データは正しく返されますが、カウントは返されません (「0 件中 0 件から 0 件を表示しています」)。ここで問題を詳しく説明しました。2 つのオプションは、その問題で提供されているコードを使用して datatables パッケージを手動で更新するか、count サブクエリ内でパラメーター バインディングを使用しないことです。whereRawパラメーターのバインドを回避するために使用します。

于 2015-01-12T21:41:15.437 に答える