ManyToMany Doctrine join 必须 return 一行
ManyToMany Doctrine join must return one row
假设你在 Doctrine 中有一个经典的 ManyToMany 关系。例如,official documentation 中描述的那个:
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
private $groups;
// ...
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** @Entity */
class Group
{
/**
* @ORM\Column(type="string")
*/
private $type;
}
为了简化示例,只有 2 种可能的组类型:A 或 B。一个用户不能属于具有相同类型的 2 个组。因此,每个用户可以属于 0 到 2 个组。
现在假设我想 return 每个用户一行(不使用 getArrayResults()
方法进行水合作用,如下所示:
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
| 2 | A | 10 | B | 21 |
----------------------------------------------------------------------------
| 3 | A | 11 | NULL | NULL |
----------------------------------------------------------------------------
如何在 DQL 中翻译该结果?我可以用 innerJoin()
方法得到它是所有用户都有一个组。但如果不是这样,一个leftJoin()
查询return多于2行。
这种查询的主要问题是由于 class 用户和组之间的非系统性 link。如果每个用户 link 编到每个组,一个简单的 DQL 查询将起作用:
$qb->select('u.id, gA.type, gA.id, gB.type, gB.id')
->from('User', 'u')
->innerJoin('u.groups', 'gA', 'WITH', 'gA.type = "A"')
->innerJoin('u.groups', 'gB', 'WITH', 'gB.type = "B"')
但在上面给出的示例中,结果将是因为用户 #3 没有 link 组 #B :
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
| 2 | A | 10 | B | 21 |
----------------------------------------------------------------------------
通过使用 leftJoin 子句,结果将是最大的 table 超过 3 条记录(上例中为 5 条)。
所以解决方案是查询中没有 where 子句是使用 combine MAX 和 groupBy :
$qb->select('u.id, gA.type, max(gA.id), gB.type, max(gB.id)')
->from('User', 'u')
->innerJoin('u.groups', 'gA', 'WITH', 'gA.type = "A"')
->innerJoin('u.groups', 'gB', 'WITH', 'gB.type = "B"')
->groupBy('u.id')
这将给出预期的结果:
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
| 2 | A | 10 | B | 21 |
----------------------------------------------------------------------------
| 3 | A | 11 | NULL | NULL |
----------------------------------------------------------------------------
如果在查询中应用 where 子句,可以避免这种情况:
$qb->select('u.id, gA.type, gA.id, gB.type, gB.id')
->from('User', 'u')
->innerJoin('u.groups', 'gA', 'WITH', 'gA.type = "A"')
->innerJoin('u.groups', 'gB', 'WITH', 'gB.type = "B"')
->where('gA.type = 10 AND gB.type = 20)
给出以下结果:
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
假设你在 Doctrine 中有一个经典的 ManyToMany 关系。例如,official documentation 中描述的那个:
/** @Entity */
class User
{
// ...
/**
* Many Users have Many Groups.
* @ManyToMany(targetEntity="Group")
* @JoinTable(name="users_groups",
* joinColumns={@JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={@JoinColumn(name="group_id", referencedColumnName="id")}
* )
*/
private $groups;
// ...
public function __construct() {
$this->groups = new \Doctrine\Common\Collections\ArrayCollection();
}
}
/** @Entity */
class Group
{
/**
* @ORM\Column(type="string")
*/
private $type;
}
为了简化示例,只有 2 种可能的组类型:A 或 B。一个用户不能属于具有相同类型的 2 个组。因此,每个用户可以属于 0 到 2 个组。
现在假设我想 return 每个用户一行(不使用 getArrayResults()
方法进行水合作用,如下所示:
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
| 2 | A | 10 | B | 21 |
----------------------------------------------------------------------------
| 3 | A | 11 | NULL | NULL |
----------------------------------------------------------------------------
如何在 DQL 中翻译该结果?我可以用 innerJoin()
方法得到它是所有用户都有一个组。但如果不是这样,一个leftJoin()
查询return多于2行。
这种查询的主要问题是由于 class 用户和组之间的非系统性 link。如果每个用户 link 编到每个组,一个简单的 DQL 查询将起作用:
$qb->select('u.id, gA.type, gA.id, gB.type, gB.id')
->from('User', 'u')
->innerJoin('u.groups', 'gA', 'WITH', 'gA.type = "A"')
->innerJoin('u.groups', 'gB', 'WITH', 'gB.type = "B"')
但在上面给出的示例中,结果将是因为用户 #3 没有 link 组 #B :
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
| 2 | A | 10 | B | 21 |
----------------------------------------------------------------------------
通过使用 leftJoin 子句,结果将是最大的 table 超过 3 条记录(上例中为 5 条)。
所以解决方案是查询中没有 where 子句是使用 combine MAX 和 groupBy :
$qb->select('u.id, gA.type, max(gA.id), gB.type, max(gB.id)')
->from('User', 'u')
->innerJoin('u.groups', 'gA', 'WITH', 'gA.type = "A"')
->innerJoin('u.groups', 'gB', 'WITH', 'gB.type = "B"')
->groupBy('u.id')
这将给出预期的结果:
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------
| 2 | A | 10 | B | 21 |
----------------------------------------------------------------------------
| 3 | A | 11 | NULL | NULL |
----------------------------------------------------------------------------
如果在查询中应用 where 子句,可以避免这种情况:
$qb->select('u.id, gA.type, gA.id, gB.type, gB.id')
->from('User', 'u')
->innerJoin('u.groups', 'gA', 'WITH', 'gA.type = "A"')
->innerJoin('u.groups', 'gB', 'WITH', 'gB.type = "B"')
->where('gA.type = 10 AND gB.type = 20)
给出以下结果:
----------------------------------------------------------------------------
| user.id | group_A.type | group_A.id | group_B.type | group_B.id |
----------------------------------------------------------------------------
| 1 | A | 10 | B | 20 |
----------------------------------------------------------------------------