0

介绍

有时,您可以故意使用标量子查询来检查是否找到了不超过一行,而不是连接。例如,您可能有此查询来查找某些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 变量中选择一个值,然后显式检查它是否为空,等等。我什至可以将结果选择到一个临时表中,然后在该表上构建唯一索引作为检查。但是,是否没有更易读和更优雅的方式来标记我的假设,即子查询只返回一行,并让 DBMS 检查该假设?

4

2 回答 2

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
于 2015-03-05T18:23:34.383 回答
0

isnull在 MSSQL 中,如果第一个参数为空,它似乎只评估它的第二个参数。所以一般来说你可以说

select isnull(x, 0/0)

给出一个查询,x如果非空则返回,如果返回空则终止。将此应用于标量子查询,

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.

于 2015-03-06T18:27:11.217 回答