以编程方式使用某些存储过程时,我应该始终使用动态 sql 吗?
Should I always use dynamic sql when programatically use some stored procedure?
我有一个存储过程,可以获取table中的记录数,其中@tableName
是存储过程的参数。我们称它为 FastCount
:
SELECT OBJECT_NAME(object_id), SUM(row_count) AS rows
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID(@tableName)
AND index_id < 2
GROUP BY OBJECT_NAME(object_id);
现在,假设我有 50 个 table,例如 data_1950、data_1951、.....data_2000。我写了一个batch,查询每个table的记录数,放到一个临时的table中。它就像一个魅力
CREATE TABLE #Temp
(
TableName varchar(30),
RecordsCount int
)
DECLARE @sql as varchar(max)
DECLARE @yearN as int = 1950
DECLARE @tbName as sysname
WHILE @yearN <= 2000
BEGIN
SET @tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,@yearN))
SET @sql = N'Exec [dbo].FastCount @tableName=' + @tbName
INSERT INTO #Temp
EXEC (@sql)
SET @yearN = @yearN + 1
END
SELECT * FROM #Temp
DROP TABLE #Temp
但是,如果我替换动态 SQL 字符串部分
SET @sql = N'Exec [dbo].FastCount @tableName=' + @tbName
INSERT INTO #Temp
EXEC (@sql)
直接调用
INSERT INTO #Temp
EXEC [dbo].FastCount @tableName = @tbName
然后整个批次就不行了...
所以我不明白为什么...在以编程方式使用存储过程时,我是否应该始终使用动态 SQL 字符串和 exec(@sql)
。非常感谢您抽出时间来看。
一般情况下,您应该避免使用动态 SQL,并且您应该避免授予执行动态 SQL 的权限,除非绝对必要。这是出于性能和安全原因。
处理这种情况的典型方法是使用 partitioned view:
CREATE VIEW DataView
AS
SELECT '1950' TableName, * FROM Data_1950
UNION ALL
SELECT '1951' TableName, * FROM Data_1951
UNION ALL
SELECT '1952' TableName, * FROM Data_1952
UNION ALL
SELECT '1953' TableName, * FROM Data_1953
UNION ALL
SELECT '1954' TableName, * FROM Data_1954
UNION ALL
SELECT '1955' TableName, * FROM Data_1955
(继续添加 select 语句,直到涵盖所有 table 语句。)
现在要获得 table 计数,您需要做的就是执行此操作:
SELECT TableName, COUNT(*) RecordCount
FROM DataView
GROUP BY TableName
是不是简单多了?
好的,这是您在原始问题中提出的两个场景中发生的情况。 (是的,现实是可能有更好的方法来实现你的最终结果,但让我们看看你提出的实际问题......为什么你的 INSERT / EXEC 的行为不同,这取决于你如何打电话).
首先,您声明了变量,它将包含您的 table 姓名:
DECLARE @tbName as sysname
然后你的循环块会逐渐增加年份数,以生成不同的 table 名称。循环块本身并没有什么错误,所以让我们看一个使用 table 名称之一的示例,看看 WHILE 块中发生了什么。以第一个table名字为例,就是[dbo].data_1950.
您的声明:
set @tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,@yearN))
最终采用字符串“[dbo].data_1950” - 它来自将“[dbo].data_”与年份数字(在本例中为 1950)连接转换为字符串(varchar) - 并将其传递给 QUOTENAME() 函数。 QUOTENAME() 函数接受它的输入和第二个参数,这是输入应该用引号引起来的字符(如果没有传递第二个参数,则默认为 [])。因此,如果我们随后将 @tbName 变量转换为字符串,它将显示为:
[[dbo].data_1950]
现在我们可以看到 SQL 处理“sysname”数据类型的时髦方式。 (事实上,当您进一步阅读时,也许问题主要与“sysname”数据类型无关,但无论如何,请从中删除您想要的内容)。老实说,“sysname”本身是一种有点古怪的数据类型,除非绝对必要,否则我倾向于避开它。但无论如何,关于您所看到的问题的详细信息。
第 1 步 - 我创建了您的存储过程的一个版本,但我包含了一个语句,该语句将输出传入的 @tableName 参数的值。这让我们有机会看到什么SQL是在两种不同的场景下做的,然后解释为什么结果不同。
CREATE PROC [dbo].FastCount
(
@tableName varchar(100)
)
AS
BEGIN
PRINT @tableName;
SELECT OBJECT_NAME(object_id), SUM(row_count) AS rows
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID(@tableName)
AND index_id < 2
GROUP BY OBJECT_NAME(object_id);
END
第 2 步 - 我们的第一个场景是执行动态 SQL.
set @tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,@yearN))
set @sql = N'Exec [dbo].FastCount @tableName=' + @tbName
Insert Into #Temp Exec(@sql)
现在,我们知道@tbName 变量包含
[[dbo].data_1950]
因此我们可以推断出@sql变量包含
Exec [dbo].FastCount @tableName=[[dbo].data_1950]
所以这实际上是由 Exec(@sql) 命令执行的语句。
当这 运行s 时,我们查看 PRINT 命令的输出,我们看到
[dbo].data_1950
并且我们看到了查询的结果(table 名称和行数)。这当然是有道理的,因为我们的table名称是“data_1950”,而table的模式是“dbo”,所以SELECT语句得到行数将按预期工作。
第 3 步 - 运行 直接执行 EXEC 命令,不使用 @sql 变量,即
Insert Into #Temp Exec [dbo].FastCount @tableName = @tbName
现在,当我们查看执行“FastCount”存储过程的 PRINT 命令的输出时,我们看到
[[dbo].data_1950]
当然,现在这不会产生我们预期的结果,因为我们告诉 SQL 查找名为“[dbo].table 的行数。data_1950"(在没有特定模式的情况下,SQL 将只采用默认模式。在这种情况下,使用 [dbo] 模式,我们会告诉 SQL 获取名为 [dbo].[[dbo].data_1950] 的 table 中的行计数 - 这显然不是 table 名称)。
您应该看到明显的区别 - 在一种情况下,传递到存储中的参数值是对 table 名称的“正确”引用,而在另一种情况下则不是。
作为最后一步,让我们看看如何执行“非动态”SQL,以获得我们需要的结果。在这种情况下,不需要 QUOTENAME() 函数:
set @tbName = N'[dbo].data_' + Convert(nvarchar,@yearN)
Insert Into #Temp Exec [dbo].FastCount @tableName = @tbName
当我们以这种方式 运行 时,我们从 PRINT 命令中看到了预期的输出 ([dbo].data_1950),并且我们看到了预期的查询结果(包含 table 名称和行数)。
我能准确解释一下这种行为吗?呃,不一定......也许其他人能够具体解释发生了什么,以及为什么。我唯一的解释是,当 EXEC() 语句传递动态 sql (即 @sql 变量)时,它首先解释整个字符串并剥离标识符(在这种情况下,周围的 [ ] ... 它是根据什么做出那个决定的,我不知道)。与非动态执行相反,@tbName 值 ([[dbo].data_1950]) 只是作为参数直接传递,没有修改(因此导致我们意想不到的最终结果锯)。
希望这些信息对您有用(或者至少对将来的其他人有用!)。
我有一个存储过程,可以获取table中的记录数,其中@tableName
是存储过程的参数。我们称它为 FastCount
:
SELECT OBJECT_NAME(object_id), SUM(row_count) AS rows
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID(@tableName)
AND index_id < 2
GROUP BY OBJECT_NAME(object_id);
现在,假设我有 50 个 table,例如 data_1950、data_1951、.....data_2000。我写了一个batch,查询每个table的记录数,放到一个临时的table中。它就像一个魅力
CREATE TABLE #Temp
(
TableName varchar(30),
RecordsCount int
)
DECLARE @sql as varchar(max)
DECLARE @yearN as int = 1950
DECLARE @tbName as sysname
WHILE @yearN <= 2000
BEGIN
SET @tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,@yearN))
SET @sql = N'Exec [dbo].FastCount @tableName=' + @tbName
INSERT INTO #Temp
EXEC (@sql)
SET @yearN = @yearN + 1
END
SELECT * FROM #Temp
DROP TABLE #Temp
但是,如果我替换动态 SQL 字符串部分
SET @sql = N'Exec [dbo].FastCount @tableName=' + @tbName
INSERT INTO #Temp
EXEC (@sql)
直接调用
INSERT INTO #Temp
EXEC [dbo].FastCount @tableName = @tbName
然后整个批次就不行了...
所以我不明白为什么...在以编程方式使用存储过程时,我是否应该始终使用动态 SQL 字符串和 exec(@sql)
。非常感谢您抽出时间来看。
一般情况下,您应该避免使用动态 SQL,并且您应该避免授予执行动态 SQL 的权限,除非绝对必要。这是出于性能和安全原因。
处理这种情况的典型方法是使用 partitioned view:
CREATE VIEW DataView
AS
SELECT '1950' TableName, * FROM Data_1950
UNION ALL
SELECT '1951' TableName, * FROM Data_1951
UNION ALL
SELECT '1952' TableName, * FROM Data_1952
UNION ALL
SELECT '1953' TableName, * FROM Data_1953
UNION ALL
SELECT '1954' TableName, * FROM Data_1954
UNION ALL
SELECT '1955' TableName, * FROM Data_1955
(继续添加 select 语句,直到涵盖所有 table 语句。)
现在要获得 table 计数,您需要做的就是执行此操作:
SELECT TableName, COUNT(*) RecordCount
FROM DataView
GROUP BY TableName
是不是简单多了?
好的,这是您在原始问题中提出的两个场景中发生的情况。 (是的,现实是可能有更好的方法来实现你的最终结果,但让我们看看你提出的实际问题......为什么你的 INSERT / EXEC 的行为不同,这取决于你如何打电话).
首先,您声明了变量,它将包含您的 table 姓名:
DECLARE @tbName as sysname
然后你的循环块会逐渐增加年份数,以生成不同的 table 名称。循环块本身并没有什么错误,所以让我们看一个使用 table 名称之一的示例,看看 WHILE 块中发生了什么。以第一个table名字为例,就是[dbo].data_1950.
您的声明:
set @tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,@yearN))
最终采用字符串“[dbo].data_1950” - 它来自将“[dbo].data_”与年份数字(在本例中为 1950)连接转换为字符串(varchar) - 并将其传递给 QUOTENAME() 函数。 QUOTENAME() 函数接受它的输入和第二个参数,这是输入应该用引号引起来的字符(如果没有传递第二个参数,则默认为 [])。因此,如果我们随后将 @tbName 变量转换为字符串,它将显示为:
[[dbo].data_1950]
现在我们可以看到 SQL 处理“sysname”数据类型的时髦方式。 (事实上,当您进一步阅读时,也许问题主要与“sysname”数据类型无关,但无论如何,请从中删除您想要的内容)。老实说,“sysname”本身是一种有点古怪的数据类型,除非绝对必要,否则我倾向于避开它。但无论如何,关于您所看到的问题的详细信息。
第 1 步 - 我创建了您的存储过程的一个版本,但我包含了一个语句,该语句将输出传入的 @tableName 参数的值。这让我们有机会看到什么SQL是在两种不同的场景下做的,然后解释为什么结果不同。
CREATE PROC [dbo].FastCount
(
@tableName varchar(100)
)
AS
BEGIN
PRINT @tableName;
SELECT OBJECT_NAME(object_id), SUM(row_count) AS rows
FROM sys.dm_db_partition_stats
WHERE object_id = OBJECT_ID(@tableName)
AND index_id < 2
GROUP BY OBJECT_NAME(object_id);
END
第 2 步 - 我们的第一个场景是执行动态 SQL.
set @tbName = QUOTEName(N'[dbo].data_' + Convert(varchar,@yearN))
set @sql = N'Exec [dbo].FastCount @tableName=' + @tbName
Insert Into #Temp Exec(@sql)
现在,我们知道@tbName 变量包含
[[dbo].data_1950]
因此我们可以推断出@sql变量包含
Exec [dbo].FastCount @tableName=[[dbo].data_1950]
所以这实际上是由 Exec(@sql) 命令执行的语句。
当这 运行s 时,我们查看 PRINT 命令的输出,我们看到
[dbo].data_1950
并且我们看到了查询的结果(table 名称和行数)。这当然是有道理的,因为我们的table名称是“data_1950”,而table的模式是“dbo”,所以SELECT语句得到行数将按预期工作。
第 3 步 - 运行 直接执行 EXEC 命令,不使用 @sql 变量,即
Insert Into #Temp Exec [dbo].FastCount @tableName = @tbName
现在,当我们查看执行“FastCount”存储过程的 PRINT 命令的输出时,我们看到
[[dbo].data_1950]
当然,现在这不会产生我们预期的结果,因为我们告诉 SQL 查找名为“[dbo].table 的行数。data_1950"(在没有特定模式的情况下,SQL 将只采用默认模式。在这种情况下,使用 [dbo] 模式,我们会告诉 SQL 获取名为 [dbo].[[dbo].data_1950] 的 table 中的行计数 - 这显然不是 table 名称)。
您应该看到明显的区别 - 在一种情况下,传递到存储中的参数值是对 table 名称的“正确”引用,而在另一种情况下则不是。
作为最后一步,让我们看看如何执行“非动态”SQL,以获得我们需要的结果。在这种情况下,不需要 QUOTENAME() 函数:
set @tbName = N'[dbo].data_' + Convert(nvarchar,@yearN)
Insert Into #Temp Exec [dbo].FastCount @tableName = @tbName
当我们以这种方式 运行 时,我们从 PRINT 命令中看到了预期的输出 ([dbo].data_1950),并且我们看到了预期的查询结果(包含 table 名称和行数)。
我能准确解释一下这种行为吗?呃,不一定......也许其他人能够具体解释发生了什么,以及为什么。我唯一的解释是,当 EXEC() 语句传递动态 sql (即 @sql 变量)时,它首先解释整个字符串并剥离标识符(在这种情况下,周围的 [ ] ... 它是根据什么做出那个决定的,我不知道)。与非动态执行相反,@tbName 值 ([[dbo].data_1950]) 只是作为参数直接传递,没有修改(因此导致我们意想不到的最终结果锯)。
希望这些信息对您有用(或者至少对将来的其他人有用!)。