在 CakePHP 3 中进行深层 notMatching() 关联的正确方法是什么

What´s the right way to do deep notMatching() association in CakePHP 3

当用户的 id 为 2(或其他数字)时,我试图获取 UsersTitles table 中没有相关记录的游戏,但我获取了所有游戏。

控制器:

$this->loadModel('Games');

$games = $this->Games->find()->notMatching('Titles.TitlesUsers', function ($q){
  return $q->where(['TitlesUsers.user_id'=> 2]);
})->distinct(['Games.id']);

游戏Table

class GamesTable {    
  $this->hasMany('Titles', [
      'foreignKey' => 'game_id',
      'dependent' => true
  ]);
}

标题Table

class TitlesTable {      
  $this->belongsTo('Games', ['dependent' => false]);

  $this->hasMany('TitlesUsers', [
    'classname' => 'TitlesUsers',
    'foreignKey' => 'title_id',
    'dependent' => true
  ]);    
}

TitlesUsers Table

class TitlesUsersTable {

  $this->belongsTo('Titles',['dependent'=>false]);

  $this->belongsTo('Users',['dependent'=>false]);

}

游戏table内容

id | name
---|---------
 1 | "Test A"
 2 | "Test B"
 3 | "Test C"

标题table内容

id | game_id
---|---------
 1 |       1
 2 |       1
 3 |       1
 4 |       2
 5 |       2
 6 |       2
 7 |       3
 8 |       3
 9 |       3

TitlesUsers table 内容

id | title_id | user_id
---|----------|---------
 1 |        1 |       2

生成SQL

SELECT 
  Games.id AS `Games__id`, 
  Games.name AS `Games__name`
FROM 
  games Games 
  LEFT JOIN titles Titles ON Games.id = (Titles.game_id) 
  LEFT JOIN titles_users TitlesUsers ON (
    TitlesUsers.user_id = 2 
    AND Titles.id = (TitlesUsers.title_id)
  ) 
WHERE 
  (TitlesUsers.id) IS NULL 
GROUP BY 
  Games.id

如果我使用直接相关的 table 执行 notMatching(),它会正常工作。当我试图获得更深层次的 notMatching() 关联时,问题出现了。

这是预期的结果,在您的设置中,每个游戏组有 3 个标题,并且只有一个标题分配了用户,这意味着对于所有其他标题,用户将是 NULL,例如Test A 还剩 2 个标题,其中 (TitlesUsers.id) IS NULL 为真。

您的 non-grouped 结果将如下所示:

| Games.id | Games.name | Titles.id | Titles.game_id | TitlesUsers.id | TitlesUsers.title_id | TitlesUsers.user_id |
| -------- | ---------- | --------- | -------------- | -------------- | -------------------- | ------------------- |
| 1        | Test A     | 1         | 1              | 1              | 1                    | 2                   |
| 1        | Test A     | 2         | 1              | NULL           | NULL                 | NULL                |
| 1        | Test A     | 3         | 1              | NULL           | NULL                 | NULL                |
| 2        | Test B     | 4         | 2              | NULL           | NULL                 | NULL                |
| 2        | Test B     | 5         | 2              | NULL           | NULL                 | NULL                |
| 2        | Test B     | 6         | 2              | NULL           | NULL                 | NULL                |
| 3        | Test C     | 7         | 3              | NULL           | NULL                 | NULL                |
| 3        | Test C     | 8         | 3              | NULL           | NULL                 | NULL                |
| 3        | Test C     | 9         | 3              | NULL           | NULL                 | NULL                |

可以看出,每个游戏组至少有一行TitlesUsers.idNULL

您要么需要重新考虑您的数据库架构和关联,要么如果您需要这样的架构,请更改过滤方式,因为 notMatching() 不适合它,例如排除通过针对至少存在来自特定用户的一个标题的游戏进行测试,您不想要的游戏:

$excludedGamesQuery = $this->Games
    ->find()
    ->select(['Games.id']);
    ->matching('Titles.TitlesUsers', function ($q){
        return $q->where(['TitlesUsers.user_id' => 2]);
    })
    ->group(['Games.id']);

$gamesQuery = $this->Games
    ->find()
    ->where([
        'Games.id NOT IN' => $excludedGamesQuery
    ]);