简介
有时候,您可以故意使用标量子查询来检查是否找到了不止一行,而不是连接。例如,您可能需要此查询来查找某些person
行的国籍。
select p.name,
c.iso
from person p
join person_country_map pcm
on p.id = pcm.person
join country c
on pcm.country = c.id
where p.id in (1, 2, 3)
现在,假设person_country_map
不是功能映射。给定的人可能会映射到多个国家/地区 - 因此联接可能会找到多个行。或者实际上,一个人可能根本不在映射表中,至少就任何数据库约束而言都是如此。
但是对于这个特定的查询我碰巧知道我要查询的人只有一个国家。这是我基于我的代码的假设。但是我想在可能的情况下检查这个假设 - 这样如果出现问题并且我最终试图对有多个国家的人进行此查询,或者没有国家映射行,它将会死亡。
最多添加一行的安全检查
要检查多行,可以将连接重写为标量子查询:
select p.name,
(
select c.iso
from person_country_map pcm
join country c
on pc.country = c.id
where pcm.person = p.id
) as iso
from person p
where p.id in (1, 2, 3)
如果查询的人映射到两个或更多国家/地区,DBMS将发出错误。它不会为同一个人返回多个条目,因为直接连接会。因此,即使在将任何行返回到应用程序之前,我也可以更轻松地知道正在检查此错误情况。作为一个细心的程序员,我当然可以检查应用程序。
是否可以对 没有 行进行安全检查?
但是如果一个人的person_country_map中没有行怎么办?在这种情况下,标量子查询将返回null,使其大致相当于左连接。
(为了论证,假设从person_country_map.country到country.id的外键和country.id上的唯一索引,以便特定连接将始终成功并找到一个国家行。)
我的问题
我是否可以通过某种方式在SQL中表达我想要的结果?一个普通的标量子查询是“零或一”。我想能够说
select 42, (select exactly one x from t where id = 55)
如果子查询不返回行,则在运行时查询失败。当然,上面的语法是虚构的,我相信它不会那么容易。
我使用的是MSSQL 2008 R2,实际上这段代码存放在存储过程中,因此我可以根据需要使用TSQL。 (显然普通的声明性SQL更可取,因为它也可用于视图定义。)当然,我可以进行exists
检查,或者我可以在TSQL变量中选择一个值然后显式检查它是否为null,等等。我甚至可以将结果选择到临时表中,然后在该表上构建唯一索引作为检查。但是,没有更多可读和优雅的方法来标记我的假设,即子查询只返回一行,并且DBMS检查了该假设吗?
答案 0 :(得分:0)
你正在努力实现这一目标
您肯定需要在person.id上与person_country_map.person
建立FK关系您对person_country_map.person有唯一约束,或者您没有? 如果您没有唯一约束,则可以为同一person_country_map.person创建多条记录。
如果您想知道您是否有任何重复
select pcm.person
from person_country_map pcm
group by pcm.person
having count(*) > 1
如果有多个,那么你只需要确定哪一个
select p.name,
min(c.iso)
from person p
join person_country_map pcm
on p.id = pcm.person
join country c
on pcm.country = c.id
where p.id in (1, 2, 3)
group by p.name
答案 1 :(得分:0)
在MSSQL中,如果第一个参数为null,则isnull
仅计算其第二个参数。所以一般来说你可以说
select isnull(x, 0/0)
给出一个查询,如果非null则返回x
,如果返回则返回null。将其应用于标量子查询,
select 42, isnull((select x from t where id = 55), 0/0)
将保证select x
子查询只找到一行。如果不止一个,DBMS本身就会产生错误;如果没有行,则触发除零。
将此应用于原始示例会导致代码
select p.name,
-- Get the unique country code of this person.
-- Although the database constraints do not guarantee it in general,
-- for this particular query we expect exactly one row. Check that.
--
isnull((
select c.iso
from person_country_map pcm
join country c
on pc.country = c.id
where pcm.person = p.id
), 0/0) as iso
from person p
where p.id in (1, 2, 3)
要获得更好的错误消息,您可以使用转换失败而不是除以零:
select 42, isnull((select x from t where id = 55), convert(int, 'No row found'))
如果您从子查询中获取的值本身不是convert
,则需要进一步int
恶作剧。