在不使用动态 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 找到了一个条目”,那是什么时候它变得非常丑陋。
我有一个包含以下语句的程序:
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 找到了一个条目”,那是什么时候它变得非常丑陋。