CakePHP联接未显示N-to-M查询的所有数据

时间:2016-02-17 08:29:43

标签: cakephp join cakephp-3.0

我想要做的是一个简单的N-to-M连接,就像你从SQL中知道的那样。 表格的结构如下:

CREATE TABLE `groups` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `name` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `firstname` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    `lastname` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    `username` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    `password` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
    `image` varchar(255) COLLATE utf8_unicode_ci NULL DEFAULT NULL,
    `role_id` int(11) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

CREATE TABLE `groups_users` (
    `id` int(11) NOT NULL AUTO_INCREMENT,
    `group_id` int(11) NOT NULL,
    `user_id` int(11) NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

现在查询时,我希望包含用户与组相关的所有日期的行。 所以在我的理解中,我必须转到UserController并执行以下两个左连接(用户分组到group_users和groups_users)

$query2 = $this->Users->find();
$query2->join([
    'a' => [
        'table' => 'groups_users',
        'type' => 'LEFT',
        'conditions' => [
            'a.user_id' => 'users.id',
            'users.id' => 1
        ]
    ],
    'b' => [
        'table' => 'groups',
        'type' => 'LEFT',
        'conditions' => [
            'b.id' => 'a.group_id'
        ]
    ]
]);

当然这是我的测试数据:

INSERT INTO `customytest`.`users` (`id`, `firstname`, `lastname`, `username`, `password`, `image`, `role_id`) VALUES (NULL, 'nm1', 'fn1', 'ln1', 'doesnt matter', NULL, '');
INSERT INTO `customytest`.`users` (`id`, `firstname`, `lastname`, `username`, `password`, `image`, `role_id`) VALUES (NULL, 'nm2', 'fn2', 'ln2', 'doesnt matter', NULL, '');
INSERT INTO `customytest`.`users` (`id`, `firstname`, `lastname`, `username`, `password`, `image`, `role_id`) VALUES (NULL, 'nm3', 'fn3', 'ln3', 'doesnt matter', NULL, '');

INSERT INTO `customytest`.`groups` (`id`, `name`) VALUES (NULL, 'group1');

INSERT INTO `customytest`.`groups_users` (`id`, `group_id`, `user_id`) VALUES (NULL, 1, 1);
INSERT INTO `customytest`.`groups_users` (`id`, `group_id`, `user_id`) VALUES (NULL, 1, 2);
INSERT INTO `customytest`.`groups_users` (`id`, `group_id`, `user_id`) VALUES (NULL, 1, 3);

因此,我只获取用户的数据,而不是相关的组数据:

{"id":1,"firstname":"nm1","lastname":"fn1","username":"ln1","password":"doesnt matter","image":null,"role_id":0},
{"id":2,"firstname":"nm2","lastname":"fn2","username":"ln2","password":"doesnt matter","image":null,"role_id":0},
{"id":3,"firstname":"nm3","lastname":"fn3","username":"ln3","password":"doesnt matter","image":null,"role_id":0}

1 个答案:

答案 0 :(得分:1)

您正在使用高级ORM,因此我建议您使用适合您的匹配和拼接功能。具体而言,Query::contain(),这将加入相关数据,并正确地保存结果,以便您最终得到合理的结果集和非重复数据。

这几乎是CakePHP 101,所以我建议在进入试用和错误模式之前将文档稍微研究一下。鉴于您已正确设置关联,您的查询将减少为

$this->Users
    ->find()
    ->contain('Groups')
    ->where([
        'Users.id' => 1
    ]);

另见

当然也可以使用手动连接,但理想情况下,这对于非常复杂的查询和类似边缘情况应该是必需的。但是,为了完整起见,使用手动连接的查询的设置应该更像是这样:

$this->Users
    ->find()
    ->select($this->Users)
    ->select(['b.id', 'b.name'])
    ->join([
        'a' => [
            'table' => 'groups_users',
            'type' => 'LEFT',
            'conditions' => [
                'a.user_id = users.id'
            ]
        ],
        'b' => [
            'table' => 'groups',
            'type' => 'LEFT',
            'conditions' => [
                'b.id = a.group_id'
            ]
        ]
    ])
    ->where([
        'Users.id' => 1
    ]);

使用手动连接时,您必须指定应选择的字段。由于您没有遵循命名约定,因此必须逐个指定它们。如果您遵循约定,并且存在适当的关联(这使得手动连接的使用变得多余),您可以使用select($this->Users->Groups) - 连接看起来像:

'GroupsUsers' => [
    'table' => 'groups_users',
    'type' => 'LEFT',
    'conditions' => [
        'GroupsUsers.user_id = Users.id'
    ]
],
'Groups' => [
    'table' => 'groups',
    'type' => 'LEFT',
    'conditions' => [
        'Groups.id = GroupsUsers.group_id'
    ]
]

另请注意不使用键/值条件,这是必需的,否则您需要与字符串进行比较,例如a.user_id = 'users.id',这当然不起作用。此外,如果您计划将内容限制为特定用户,则不需要为连接表添加其他条件,因为用户ID比较已根据需要过滤了内容。

最后但并非最不重要的一点是,当用户在多个组中时,您可能会以这种方式收到重复数据,这是您不应该手动执行此类操作的另一个原因,Query::contain()将处理事情的方式更优雅。

另见