如何将存储过程中的宽结果集插入到临时 table 或 table 变量中?
How to insert wide result set from stored procedure into a temporary table or table variable?
我正在尝试使用我发现的各种方法将存储过程的输出保存到临时 table 或 table 变量中。
我试过声明一个临时 table 然后做:
INSERT INTO #temptable EXEC storedprocedurename
我 运行 遇到了一个问题,因为存储过程在结果集中有大约 550 列。我无法创建那么宽的临时 table,因为最大行大小超过 8060 字节。
不幸的是,我没有编辑存储过程的能力,但我真的只需要访问这些列中的大约 200 个。有没有办法只获取我想要的列并将它们粘贴在 table 变量或临时 table 中以绕过大小问题,或者我是否将整行结果粘贴在 table?
我也调查了 OPENROWSET
,但我使用的 SQL 服务器不允许启用 ADHOC 查询,也不会启用。在这一点上我没有想法,因为我唯一知道要做的就是克隆存储过程并删除大约 1/2 我不需要的列,但是如果源代码,这可能是一个维护问题存储过程不断变化,我对此一无所知。
您可以使用游标将数据发送到 temp table,并限制 cols 的数量,直到它到达末尾。这可能会影响性能。
我找不到在 INSERT ... EXEC
语句中只插入部分列的方法。这意味着 temp table 应该包含所有 550 列。但是,您可以尝试避免将所有返回的数据插入到这些列中。
第一个选项。 INSTEAD OF
触发器。我从来没有用过,所以我不确定它是否适用于 temp tables。它应该与正常的 tables 一起工作。主要思想 - 拦截触发器中的 INSERT
操作,并用 NULLs
.
替换超过 8060 字节限制且不需要的 long 值
第二个选项。对于那些不需要的长 varchar
列,在 CREATE
temp table 语句中将它们定义为 varchar(1)
。主要思想 - 将不需要的长 varchar
值截断为 1 个字符,希望最后一行小于 8060 字节。如果您尝试这种方法,您很可能会看到此错误消息:String or binary data would be truncated.
要抑制此错误消息并强制服务器截断数据:SET ansi_warnings OFF
.
您是否尝试过 OPENQUERY? I do not believe it has the same requirements as OPENROWSET
. You would create a "loopback" Linked Server and use that in the call to OPENQUERY
. There are some restrictions on OPENQUERY
, though, in that it does not like Dynamic SQL or anything else that is a restriction of sp_describe_first_result_set,它在内部使用。
EXEC sp_addlinkedserver
@server = N'Loopback',
@srvproduct = N'',
@provider = N'SQLNCLI',
@datasrc = N'<your server name>';
然后尝试:
SELECT * FROM OPENQUERY(Loopback, N'EXEC sp_who2'); -- this gets an error
SELECT * FROM OPENQUERY(Loopback, N'EXEC sp_who'); -- this works
如果 OPENQUERY
不是一个选项,那么这仍然可以通过 SQLCLR 轻松完成。您有两个主要选择:
SQLCLR 存储过程: 这是更简单的方法,因为没有与现有存储过程相关的限制(即动态 SQL、副作用函数、DML、SET
语句等)。您可以使用内部上下文连接(即附加到您已经在的同一会话,这在程序集标记为 SAFE
时有效),执行存储过程,并使用 [= 循环显示结果18=],使用 SqlContext.Pipe.SendResultsRow
将每一行发送回调用者(即将行流回)。您将使用 SqlDataRecord
创建一个结果集结构,并且只创建您想要的列。然后,您从 SqlDataReader 设置这些列中的每一个,SqlDataReader 有许多从未访问过的列并不重要。
SQLCLR Table-Valued Function: 这种方法的好处是实际上能够发出一个 SELECT
反对操作,以便您可以添加 WHERE
条件等。您仍然会使用 SqlCommand
/ SqlDataReader
,就像 SQLCLR 存储过程一样。但是,有一些限制,就像 T-SQL 函数一样。
- 如果您当前的存储过程没有违反任何创建 T-SQL 函数的规则(即动态 SQL、副作用函数、DML、
SET
语句等),然后您可以在使用内部上下文连接时从 SQLCLR 函数只读执行存储过程(即附加到您已经在的同一个会话,这在程序集被标记时有效作为 SAFE
)。但是,您将无法将行流回,而是需要将它们存储在内存中(类似于在 T-SQL 多语句 TVF 中使用 Table 变量)并释放过程结束时的结果。
- 如果您当前的存储过程确实违反了这些规则中的任何一条(并且执行
SET NOCOUNT ON;
将是违反的,因为它是 SET
语句),那么您仍然可以这样做,但是您会需要使用常规/外部连接,因为上下文连接不起作用。此选项确实具有能够使用 yield return
流回行的好处,但它也有两个缺点:不是同一会话的一部分(因此无法看到本地临时表或CONTEXT_INFO
),并要求将程序集标记为 EXTERNAL_ACCESS
(但这 不 要求将数据库设置为 TRUSTWORTHY ON
:您只需签署程序集,从该 DLL / 程序集在 master
中创建一个非对称密钥,从该非对称密钥创建一个登录名,最后授予该登录名 EXTERNAL ACCESS ASSEMBLY
权限)。
并且看到您正在访问的 SQL 服务器不允许 Ad Hoc Distributed Queries
,如果 DBA 说 SQLCLR 也由于 [=70 而无法启用=] 风险,那么请将他们的注意力转移到我在 SQL Server Central 上写的关于 SQLCLR 的一系列文章:Stairway to SQLCLR(需要免费注册才能查看该站点上的内容)。在前 4 篇文章中,我通过几个示例展示了 SQLCLR 的安全性,尤其是当程序集被标记为 SAFE
(涵盖上述 3 个选项中的 2 个)时。
我正在尝试使用我发现的各种方法将存储过程的输出保存到临时 table 或 table 变量中。
我试过声明一个临时 table 然后做:
INSERT INTO #temptable EXEC storedprocedurename
我 运行 遇到了一个问题,因为存储过程在结果集中有大约 550 列。我无法创建那么宽的临时 table,因为最大行大小超过 8060 字节。
不幸的是,我没有编辑存储过程的能力,但我真的只需要访问这些列中的大约 200 个。有没有办法只获取我想要的列并将它们粘贴在 table 变量或临时 table 中以绕过大小问题,或者我是否将整行结果粘贴在 table?
我也调查了 OPENROWSET
,但我使用的 SQL 服务器不允许启用 ADHOC 查询,也不会启用。在这一点上我没有想法,因为我唯一知道要做的就是克隆存储过程并删除大约 1/2 我不需要的列,但是如果源代码,这可能是一个维护问题存储过程不断变化,我对此一无所知。
您可以使用游标将数据发送到 temp table,并限制 cols 的数量,直到它到达末尾。这可能会影响性能。
我找不到在 INSERT ... EXEC
语句中只插入部分列的方法。这意味着 temp table 应该包含所有 550 列。但是,您可以尝试避免将所有返回的数据插入到这些列中。
第一个选项。 INSTEAD OF
触发器。我从来没有用过,所以我不确定它是否适用于 temp tables。它应该与正常的 tables 一起工作。主要思想 - 拦截触发器中的 INSERT
操作,并用 NULLs
.
第二个选项。对于那些不需要的长 varchar
列,在 CREATE
temp table 语句中将它们定义为 varchar(1)
。主要思想 - 将不需要的长 varchar
值截断为 1 个字符,希望最后一行小于 8060 字节。如果您尝试这种方法,您很可能会看到此错误消息:String or binary data would be truncated.
要抑制此错误消息并强制服务器截断数据:SET ansi_warnings OFF
.
您是否尝试过 OPENQUERY? I do not believe it has the same requirements as OPENROWSET
. You would create a "loopback" Linked Server and use that in the call to OPENQUERY
. There are some restrictions on OPENQUERY
, though, in that it does not like Dynamic SQL or anything else that is a restriction of sp_describe_first_result_set,它在内部使用。
EXEC sp_addlinkedserver
@server = N'Loopback',
@srvproduct = N'',
@provider = N'SQLNCLI',
@datasrc = N'<your server name>';
然后尝试:
SELECT * FROM OPENQUERY(Loopback, N'EXEC sp_who2'); -- this gets an error
SELECT * FROM OPENQUERY(Loopback, N'EXEC sp_who'); -- this works
如果 OPENQUERY
不是一个选项,那么这仍然可以通过 SQLCLR 轻松完成。您有两个主要选择:
SQLCLR 存储过程: 这是更简单的方法,因为没有与现有存储过程相关的限制(即动态 SQL、副作用函数、DML、
SET
语句等)。您可以使用内部上下文连接(即附加到您已经在的同一会话,这在程序集标记为SAFE
时有效),执行存储过程,并使用 [= 循环显示结果18=],使用SqlContext.Pipe.SendResultsRow
将每一行发送回调用者(即将行流回)。您将使用SqlDataRecord
创建一个结果集结构,并且只创建您想要的列。然后,您从 SqlDataReader 设置这些列中的每一个,SqlDataReader 有许多从未访问过的列并不重要。SQLCLR Table-Valued Function: 这种方法的好处是实际上能够发出一个
SELECT
反对操作,以便您可以添加WHERE
条件等。您仍然会使用SqlCommand
/SqlDataReader
,就像 SQLCLR 存储过程一样。但是,有一些限制,就像 T-SQL 函数一样。- 如果您当前的存储过程没有违反任何创建 T-SQL 函数的规则(即动态 SQL、副作用函数、DML、
SET
语句等),然后您可以在使用内部上下文连接时从 SQLCLR 函数只读执行存储过程(即附加到您已经在的同一个会话,这在程序集被标记时有效作为SAFE
)。但是,您将无法将行流回,而是需要将它们存储在内存中(类似于在 T-SQL 多语句 TVF 中使用 Table 变量)并释放过程结束时的结果。 - 如果您当前的存储过程确实违反了这些规则中的任何一条(并且执行
SET NOCOUNT ON;
将是违反的,因为它是SET
语句),那么您仍然可以这样做,但是您会需要使用常规/外部连接,因为上下文连接不起作用。此选项确实具有能够使用yield return
流回行的好处,但它也有两个缺点:不是同一会话的一部分(因此无法看到本地临时表或CONTEXT_INFO
),并要求将程序集标记为EXTERNAL_ACCESS
(但这 不 要求将数据库设置为TRUSTWORTHY ON
:您只需签署程序集,从该 DLL / 程序集在master
中创建一个非对称密钥,从该非对称密钥创建一个登录名,最后授予该登录名EXTERNAL ACCESS ASSEMBLY
权限)。
- 如果您当前的存储过程没有违反任何创建 T-SQL 函数的规则(即动态 SQL、副作用函数、DML、
并且看到您正在访问的 SQL 服务器不允许 Ad Hoc Distributed Queries
,如果 DBA 说 SQLCLR 也由于 [=70 而无法启用=] 风险,那么请将他们的注意力转移到我在 SQL Server Central 上写的关于 SQLCLR 的一系列文章:Stairway to SQLCLR(需要免费注册才能查看该站点上的内容)。在前 4 篇文章中,我通过几个示例展示了 SQLCLR 的安全性,尤其是当程序集被标记为 SAFE
(涵盖上述 3 个选项中的 2 个)时。