SQL 找到了检查一行的标量子查询

SQL scalar subquery checking a row was found

简介

有时您可以特意使用标量子查询来检查是否找到了不超过一行,而不是连接。例如,您可能有此查询来查找某些 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 不是函数映射。一个给定的人可能映射到多个国家 - 因此连接可能会找到不止一行。或者实际上,一个人可能根本不在映射 table 中,至少就任何数据库约束而言是这样。

但是对于这个特定的查询,我碰巧知道我要查询的人恰好来自一个国家。这是我的代码所基于的假设。但我想在可能的情况下检查该假设 - 这样如果出现问题并且我最终尝试为一个拥有多个国家或没有国家映射行的人执行此查询,它就会死掉。

最多为一行添加安全检查

要检查多行,您可以将连接重写为标量子查询:

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 将给出错误。它不会像直接联接那样为同一个人 return 多个条目。因此,即使在将任何行 returned 到应用程序之前,我也知道会检查此错误情况,这样我就可以安心入睡了。作为一个细心的程序员,我当然也可能会检查应用程序。

是否可以对进行安全检查 没有 找到行?

但是如果 person_country_map 中没有一个人的行怎么办?在这种情况下,标量子查询将为 return null,使其大致等同于左连接。

(为了论证,假设从 person_country_map.country 到 country.id 的外键和 country.id 上的唯一索引,这样特定的连接将始终成功并找到一个国家行.)

我的问题

有什么方法可以让我在 SQL 中表达我想要一个且恰好一个结果?普通标量子查询是 'zero or one'。我希望能够说

select 42, (select exactly one x from t where id = 55)

如果子查询没有 return 行,则查询在运行时失败。当然,上面的语法是虚构的,我相信不会那么容易。

我用的是MSSQL 2008 R2,实际上这段代码是在存储过程中,所以如果需要我可以用TSQL。 (显然普通声明 SQL 更可取,因为它也可以用在视图定义中。)当然,我可以做一个 exists 检查,或者我可以 select 一个值到 TSQL 变量,然后显式检查它是否为空,等等。我什至可以将 select 结果转换为临时 table,然后在该 table 上构建唯一索引作为检查。但是,是否没有更易读和更优雅的方式来标记我的假设,即子查询 return 恰好是一行,并让 DBMS 检查该假设?

你让事情变得更难了

你肯定需要在 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

在 MSSQL 中,isnull 似乎仅在第一个参数为空时才计算其第二个参数。所以一般来说你可以说

select isnull(x, 0/0)

给出一个查询 returns 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'))

尽管如果您从子查询中获取的值本身不是 int

,则需要进一步 convert 恶作剧