CakePHP 3 - Count many-to-many Association

家住魔仙堡 提交于 2019-12-25 16:39:53

问题


I have a database with Rounds and Users. Rounds belongsToMany Users and Users belongsToMany Rounds, so a many-to-many relation. A join table rounds_users was added to do this. EDIT: Used the incorrect phrase here. I meant 'belongsToMany' instead of 'hasMany'

Now I want to retrieve a list of Rounds, together with the number of linked Users per round.

In case of a one-to-many relation something like the following would work:

$rounds = $this->Rounds->find()
       ->contain(['Users' => function ($q) 
        {
            return $q->select(['Users.id', 'number' => 'COUNT(Users.round_id)'])
                    ->group(['Users.round_id']);
        }
        ]);

...According to Count in contain Cakephp 3

However, in a many-to-many relation Users.round_id does not exist. So, what could work instead?

Note: Several questions like this have been asked already, but few about CakePHP 3.x, so I still wanted to give this a try.

Note 2: I can get around this by using the PHP function count, though I'd rather do it more elegantly

EDIT: As suggested below, manually joining seems to do the trick:

$rounds_new = $this->Rounds->find()
        ->select($this->Rounds)
        ->select(['user_count' => 'COUNT(Rounds.id)'])
        ->leftJoinWith('Users')
        ->group('Rounds.id');

...With one problem! Rounds without Users still get a user_count equal to 1. What could be the problem?


回答1:


Join in the association

As already mentioned in the comments, you can always join in associations instead of containing them (kinda like in your SUM() on ManyToMany question).

The reason why you retrieve a count of 1 for rounds that do not have any associated users, is that you are counting on the wrong table. Counting on Rounds will of course result in a count of at least 1, as there will always be at least 1 round, the round for which no associated users exist.

So long story short, count on Users instead:

$rounds = $this->Rounds
    ->find()
    ->select($this->Rounds)
    ->select(function (\Cake\ORM\Query $query) {
        return [
            'user_count' => $query->func()->count('Users.id')
        ];
    })
    ->leftJoinWith('Users')
    ->group('Rounds.id');

Counter cache for belongsToMany associations

There's also the counter cache behavior, it works for belongsToMany associations too, as long as they are set up to use a concrete join table class, which can be configured via the association configurations through option:

class RoundsTable extends Table
{
    public function initialize(array $config)
    {
        $this->belongsToMany('Users', [
            'through' => 'RoundsUsers'
        ]);
    }
}

class UsersTable extends Table
{
    public function initialize(array $config)
    {
        $this->belongsToMany('Rounds', [
            'through' => 'RoundsUsers'
        ]);
    }
}

The counter cache would be set up in the join table class, in this example RoundsUsersTable, which would have two belongsTo associations, one to Rounds and one to Users:

class RoundsUsersTable extends Table
{
    public function initialize(array $config)
    {
        $this->belongsTo('Rounds');
        $this->belongsTo('Users');

        $this->addBehavior('CounterCache', [
            'Rounds' => ['user_count']
        ]);
    }
}

Count in containment

If you would have actually explicitly created hasMany associations (instead of belongsToMany), then you would have a one Rounds to many RoundsUsers relation, ie you could still use the linked "count in contain" example via RoundsUsers.

However, that would leave you with a structure where the count would be placed in a nested entity, which in turn would be missing for rounds that do not have any associated users. I would imagine that in most situations this kind of structure would require reformatting, so it's probably not the best solution. However, for the sake of completion, here's an example:

$rounds = $this->Rounds
    ->find()
    ->contain(['RoundsUsers' => function (\Cake\ORM\Query $query) {
        return $query
            ->select(function (\Cake\ORM\Query $query) {
                return [
                    'RoundsUsers.round_id',
                    'number' => $query->func()->count('RoundsUsers.round_id')
                ];
            })
            ->group(['RoundsUsers.round_id']);
        }]
    );

See also

  • Cookbook > Database Access & ORM > Query Builder > Filtering by Associated Data
  • Cookbook > Database Access & ORM > Query Builder > Returning the Total Count of Records
  • Cookbook > Database Access & ORM > Behaviors > CounterCache
  • Cookbook > Database Access & ORM > Associations - Linking Tables Together > BelongsToMany Associations > Using the ‘through’ Option


来源:https://stackoverflow.com/questions/42254790/cakephp-3-count-many-to-many-association

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!