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

时间:2015-03-05 16:55:08

标签: sql-server-2008 tsql join cardinality scalar-subquery

简介

有时候,您可以故意使用标量子查询来检查是否找到了不止一行,而不是连接。例如,您可能需要此查询来查找某些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检查了该假设吗?

2 个答案:

答案 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恶作剧。