在不使用动态 sql 的情况下重写 select 语句

Rewrite the select statement without using dynamic sql

我有一个包含以下语句的程序:

Create procedure usp_personCountry
@inPersonId Int,
@inType varchar(100)
As
Begin

If @inType = 'Admin'
Begin
  Select Distinct p.personid, p.name, p.email, pc.country, pa.attributeId
  From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId
 Where p.personId = @inPersonId
  and pa.type     = 'Primary'
End
Else If @inType = 'Manager'
Begin 
 Select Distinct p.personid, p.name, p.email, pc.country, pa.attributeId
  From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId
  Inner Join dbo.personCountryManager pcm
    on pa.personId = pcm.personId
 Where p.personId = @inPersonId
  and pa.type     = 'Manager'
End
Else If @inType = 'User'
Begin 
 Select Distinct p.personid, p.name, p.email, pc.country, pa.attributeId
  From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId
  Inner Join dbo.personCountryUser pcm
    on pa.personId = pcm.personId
 Where p.personId = @inPersonId
  and pa.type     = 'User'
End
Else
Begin 
 Select Distinct p.personid, p.name, p.email, pc.country, pa.attributeId
  From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId
  Inner Join dbo.personCountryTeam pcm
    on pa.personId = pcm.personId
 Where p.personId = @inPersonId
  and pa.type     = 'Team'
End

End /* End of procedure */

在上面的过程中,select 语句对于所有条件都是相同的。每次我必须添加一个新列时,我必须添加所有 4 个语句,并且很可能我最终可能会在某个时候错过向其中一个语句添加列。有没有办法重写这个 sql 查询,使 select 语句只使用一次,并根据传入的类型构造查询?我想在不使用动态 sql 的情况下完成此操作。我想知道是否有办法做到这一点。谢谢!

您可以内联 if 条件,例如使用 CASE expression。但是我确实质疑 distinct 的意义。无论如何,你可以像这样少重复一些,但它可能非常浪费:

  Select /* Distinct -- why? */ p.personid, 
    p.name, p.email, pc.country, pa.attributeId
  INTO #blat From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId
 Where p.personId = @inPersonId
  and pa.type     = CASE @inType
                    WHEN 'Admin'   THEN 'Primary'
                    WHEN 'Manager' THEN 'Manager'
                    WHEN 'User'    THEN 'User'
                    ELSE                'Team' END;

IF @inType = 'Admin'
BEGIN
  SELECT * FROM #blat;
END

IF @inType = 'Manager' 
BEGIN
  SELECT * FROM #blat AS b WHERE EXISTS 
  (
    SELECT 1 FROM dbo.personCountryManager WHERE personId = b.personId
  );
END 

IF @inType = 'User' 
BEGIN
  SELECT * FROM #blat AS b WHERE EXISTS 
  (
    SELECT 1 FROM dbo.personCountryUser WHERE personId = b.personId
  );
END 

IF @inType = 'Team' 
BEGIN
  SELECT * FROM #blat AS b WHERE EXISTS 
  (
    SELECT 1 FROM dbo.personCountryTeam WHERE personId = b.personId 
  );
END 

此外,我怀疑可能是因为这些表变得越来越大,如果存在任何数据倾斜,dynamic SQL is actually a better option

DECLARE @sql nvarchar(max) = N'SELECT 
    p.personid, p.name, p.email, pc.country, pa.attributeId
  From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId';

SET @inType = CASE @inType WHEN 'Admin' THEN 'Primary' ELSE @inType END;

IF @inType <> 'Primary'
BEGIN
  SET @sql += N'
    Inner Join dbo.' + QUOTENAME(N'personCountry' + @inType) + ' pcm
      on pa.personId = pcm.personId';
END

SET @sql += N'
  Where p.personId = @inPersonId
  and pa.type     = @inType';

EXEC sys.sp_executesql @sql, 
  N'@inPersonId int, @inType varchar(100)',
    @inPersonId,
    @inType;

这可以做到,但结果将是一个非常非常复杂的查询,性能很差。如果(当)table 变大时,可能会严重消耗服务器资源。

Dynamic SQL 是一个选项,但在这里我认为它的使用是聪明的,而不是聪明的(因为“聪明”往往是“聪明”的敌人)。这些查询非常不同,我建议为每种情况创建一个单独的存储过程(@inType 值),然后直接从应用程序调用它们或使用此过程作为包装器来调用适当的存储过程。

可能会对底层 table 设计进行改进?凭我们掌握的信息无法判断。


下面是我想出的代码——它不是一个完整的解决方案。我开始讨论解决额外连接的部分,该逻辑太混乱以至于无法成为合理的查询。

--  Reset, to simplify logic
If @inType = 'Admin'
    SET @inType = 'Primary'


  Select Distinct p.personid, p.name, p.email, pc.country, pa.attributeId
  From dbo.person p
  Inner Join dbo.personCountry pc
    on p.personId = pc.personId
  Inner Join dbo.personAttribute pa
    on p.personId = pa.personId
  --  from 0 or 1 INNER JOIN to 3 left outer joins (and change alias)
  Left Outer Join dbo.personCountryManager pcm
    on pa.personId = pcm.personId
  Left Outer Join dbo.personCountryUser pcu
    on pa.personId = pcu.personId
  Left Outer Join dbo.personCountryTeam pct
    on pa.personId = pct.personId
 Where p.personId = @inPersonId
  --  Now, presumes parameter matches pa-type
  --  This is a problem if that final "else" is not always "Team"
  and pa.type = @inType

上面对参数@inType 做了一些假设,并且仍然必须考虑检查“如果@inType 是 X 并且相关的左外连接 table 找到了一个条目”,那是什么时候它变得非常丑陋。