为什么这两个 Slick 查询不等价?
Why are these two Slick queries not equivalent?
作为 的结果,我有了这个有效的查询构造函数
val q = Users.filter(_.id === userId) join People on {
case (u, p) => u.personId === p.id
} joinLeft Addresses on {
case ((u, p), a) => p.addressId === a.id
} joinLeft Businesses on {
case (((u, p), a), b) => p.businessId === b.id
} joinLeft Addresses on {
case ((((u, p), a), b), ba) => b.flatMap(_.addressId) === ba.id
} map {
case ((((u, p), a), b), ba) => (p, a, b, ba)
}
我认为这个是等效的,但不起作用:
val q = Users.filter(_.id === userId) join People joinLeft Addresses joinLeft Businesses joinLeft Addresses on {
case ((((u, p), a), b), ba) =>
u.personId === p.id &&
p.addressId === a.flatMap(_.id) &&
p.businessId === b.flatMap(_.id) &&
b.flatMap(_.addressId) === ba.id
} map {
case ((((u, p), a), b), ba) => (p, a, b, ba)
}
第二个似乎返回了一个配置文件列表,其中包含的配置文件比目标配置文件多。
为什么它们不一样?
"equivalent"SQL(IE这个构造的目标)是:
select p.*, a1.*, b.*, a2.* from Users u
innerJoin People p on (u.personId == p.id)
leftJoin Addresses a1 on (p.addressId == a1.id)
leftJoin Businesses b on (p.businessId == b.id)
leftJoin Addresses a2 on ( b.addressId == a2.id)
这里的问题是,slick uses a LiteralNode(true) as the default join condition. 所以第二个查询的结果如下所示:
select p.*, a1.*, b.*, a2.*
from Users u
join People p on 1 = 1
left join Addresses a1 on 1 = 1
left join Businesses b on 1 = 1
left join Addresses a2 on u.personId = p.id
and p.addressId = a1.id
and p.businessId = b.id
and b.addressId = a2.id
如你所见,所有被期望成为每个加入的table的加入条件的条件,实际上是最后一个加入的加入条件的一部分。
要了解这将如何影响最终结果,让我们将问题简化如下:
create temporary table A (id int primary key);
insert into A values (1), (2);
select a1.id, a2.id, a3.id
from A a1
join A a2 on 1 = 1
left join A a3 on a1.id = a2.id
and a2.id = a3.id;
在第一次连接时,a1 和 a2 由始终为真的条件连接,导致临时结果:
(1, 1)
(1, 2)
(2, 1)
(2, 2)
现在让我们考虑第二个连接。我们将 a1.id = a2.id
作为连接条件的一部分,但请记住,连接条件用于决定如何从 table a3 中检索行,而不是过滤第一个连接的中间结果。而我们这里做的是left join,所以会额外生成a3行的NULL,即使不满足join条件。最终结果将是:
(1, 1, 1)
(1, 2, NULL)
(2, 1, NULL)
(2, 2, 2)
因此,您预计会看到更多意想不到的结果,最后左边连接的列 table 为 NULL。
作为
val q = Users.filter(_.id === userId) join People on {
case (u, p) => u.personId === p.id
} joinLeft Addresses on {
case ((u, p), a) => p.addressId === a.id
} joinLeft Businesses on {
case (((u, p), a), b) => p.businessId === b.id
} joinLeft Addresses on {
case ((((u, p), a), b), ba) => b.flatMap(_.addressId) === ba.id
} map {
case ((((u, p), a), b), ba) => (p, a, b, ba)
}
我认为这个是等效的,但不起作用:
val q = Users.filter(_.id === userId) join People joinLeft Addresses joinLeft Businesses joinLeft Addresses on {
case ((((u, p), a), b), ba) =>
u.personId === p.id &&
p.addressId === a.flatMap(_.id) &&
p.businessId === b.flatMap(_.id) &&
b.flatMap(_.addressId) === ba.id
} map {
case ((((u, p), a), b), ba) => (p, a, b, ba)
}
第二个似乎返回了一个配置文件列表,其中包含的配置文件比目标配置文件多。
为什么它们不一样?
"equivalent"SQL(IE这个构造的目标)是:
select p.*, a1.*, b.*, a2.* from Users u
innerJoin People p on (u.personId == p.id)
leftJoin Addresses a1 on (p.addressId == a1.id)
leftJoin Businesses b on (p.businessId == b.id)
leftJoin Addresses a2 on ( b.addressId == a2.id)
这里的问题是,slick uses a LiteralNode(true) as the default join condition. 所以第二个查询的结果如下所示:
select p.*, a1.*, b.*, a2.*
from Users u
join People p on 1 = 1
left join Addresses a1 on 1 = 1
left join Businesses b on 1 = 1
left join Addresses a2 on u.personId = p.id
and p.addressId = a1.id
and p.businessId = b.id
and b.addressId = a2.id
如你所见,所有被期望成为每个加入的table的加入条件的条件,实际上是最后一个加入的加入条件的一部分。
要了解这将如何影响最终结果,让我们将问题简化如下:
create temporary table A (id int primary key);
insert into A values (1), (2);
select a1.id, a2.id, a3.id
from A a1
join A a2 on 1 = 1
left join A a3 on a1.id = a2.id
and a2.id = a3.id;
在第一次连接时,a1 和 a2 由始终为真的条件连接,导致临时结果:
(1, 1)
(1, 2)
(2, 1)
(2, 2)
现在让我们考虑第二个连接。我们将 a1.id = a2.id
作为连接条件的一部分,但请记住,连接条件用于决定如何从 table a3 中检索行,而不是过滤第一个连接的中间结果。而我们这里做的是left join,所以会额外生成a3行的NULL,即使不满足join条件。最终结果将是:
(1, 1, 1)
(1, 2, NULL)
(2, 1, NULL)
(2, 2, 2)
因此,您预计会看到更多意想不到的结果,最后左边连接的列 table 为 NULL。