ChatGPT解决这个技术问题 Extra ChatGPT

从存储过程的结果集中选择列

我有一个返回 80 列和 300 行的存储过程。我想编写一个获取其中 2 个列的选择。就像是

SELECT col1, col2 FROM EXEC MyStoredProc 'param1', 'param2'

当我使用上述语法时,我得到了错误:

“无效的列名”。

我知道最简单的解决方案是更改存储过程,但我没有编写它,我无法更改它。

有什么办法可以做我想做的事吗?

我可以制作一个临时表来放入结果,但是因为有 80 列,所以我需要制作一个 80 列的临时表才能获得 2 列。我想避免追踪所有返回的列。

我尝试按照 Mark 的建议使用 WITH SprocResults AS ....,但在关键字“EXEC”附近出现 2 个错误语法不正确。 ')' 附近的语法不正确。

我尝试声明一个表变量,但出现以下错误插入错误:列名或提供的值的数量与表定义不匹配

如果我尝试 SELECT * FROM EXEC MyStoredProc 'param1', 'param2' 我得到错误:关键字 'exec' 附近的语法不正确。

出于好奇,这个查询是否有效: SELECT * FROM EXEC MyStoredProc 'param1', 'param2' 如果可以,它会在结果集中显示哪些列名,您可以在选择列表中使用这些列名吗?
我从来没有找到答案。
好吧,你从来没有回答过一个非常重要的问题!你问的是什么 SQL 平台? MySQL、Microsoft SQL Server、Oracle 等。在我看来,它就像是 SQL Server,但您需要告诉人们,否则他们无法可靠地回答您的问题。
嗯,它必须是 MS-SQL。 EXEC 不是 MySQL 关键字(MySQL 等效项是 prepared statements)。虽然我想知道 MySQL 的答案,但下面的答案是针对 T-SQL 的。重新标记。
我从来没有找到答案

M
Matthew

你可以拆分查询吗?将存储的 proc 结果插入到表变量或临时表中。然后,从表变量中选择 2 列。

Declare @tablevar table(col1 col1Type,..
insert into @tablevar(col1,..) exec MyStoredProc 'param1', 'param2'

SELECT col1, col2 FROM @tablevar

当您不知道表定义时,它也不起作用
不知道那种类型。它们的实现与临时表相同吗?或者它是否严格存在于内存中?
如果临时表中提供的列数与存储过程输出中的列数相同,则此方法可以正常工作。查伯特。
L
Lance McNearney

这是一个非常好的文档的链接,解释了解决问题的所有不同方法(尽管其中很多方法无法使用,因为您无法修改现有的存储过程。)

How to Share Data Between Stored Procedures

Gulzar 的答案会起作用(它记录在上面的链接中)但是编写起来会很麻烦(您需要在 @tablevar(col1,...) 语句中指定所有 80 个列名。并且在将来如果将列添加到架构或更改输出,则需要在您的代码中更新它,否则会出错。


我认为该链接中的 OPENQUERY 建议更接近 OP 正在寻找的内容。
t
takrl
CREATE TABLE #Result
(
  ID int,  Name varchar(500), Revenue money
)
INSERT #Result EXEC RevenueByAdvertiser '1/1/10', '2/1/10'
SELECT * FROM #Result ORDER BY Name
DROP TABLE #Result

来源:
http://stevesmithblog.com/blog/select-from-a-stored-procedure/


@LawfulHacker 圣抽烟。 2014 年你在 SQL Server 2000 上做什么?
拥有遗留系统的大公司:D
C
Community

这对我有用:(即我只需要 sp_help_job 返回的 30 多列中的 2 列)

SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, 
  'EXEC msdb.dbo.sp_help_job @job_name = ''My Job'', @job_aspect = ''JOB''');  

在此之前,我需要运行它:

sp_serveroption 'MYSERVER', 'DATA ACCESS', TRUE;

....更新 sys.servers 表。 (即在 OPENQUERY 中使用自引用似乎默认情况下被禁用。)

对于我的简单要求,我没有遇到兰斯出色链接的OPENQUERY section中描述的任何问题。

Rossini,如果您需要动态设置这些输入参数,那么使用 OPENQUERY 会变得更加繁琐:

DECLARE @innerSql varchar(1000);
DECLARE @outerSql varchar(1000);

-- Set up the original stored proc definition.
SET @innerSql = 
'EXEC msdb.dbo.sp_help_job @job_name = '''+@param1+''', @job_aspect = N'''+@param2+'''' ;

-- Handle quotes.
SET @innerSql = REPLACE(@innerSql, '''', '''''');

-- Set up the OPENQUERY definition.
SET @outerSql = 
'SELECT name, current_execution_status 
FROM OPENQUERY (MYSERVER, ''' + @innerSql + ''');';

-- Execute.
EXEC (@outerSql);

我不确定使用 sp_serveroption 直接更新现有的 sys.servers 自引用与使用 sp_addlinkedserver(如 Lance 的链接中所述)创建重复/别名之间的区别(如果有的话)。

注意 1:我更喜欢 OPENQUERY 而不是 OPENROWSET,因为 OPENQUERY 不需要 proc 中的连接字符串定义。

注意 2:说了这么多:通常我只会使用 INSERT ... EXEC :) 是的,这是 10 分钟的额外打字,但如果我能帮助它,我不想乱搞:(a)引号内的引号引号,和 (b) sys 表,和/或偷偷摸摸的自引用链接服务器设置(即,对于这些,我需要向我们全能的 DBA 辩护:)

但是在这种情况下,我不能使用 INSERT ... EXEC 构造,因为 sp_help_job 已经在使用一个。 (“不能嵌套 INSERT EXEC 语句。”)


我之前在 dynamic-sql-that-generated-dynamic-sql-that-generated-dynamic-sql 中连续有 13 个单引号 ...
我需要检查工作是否完成。 “不能嵌套 INSERT EXEC 语句”。我恨你微软。
B
Brannon

(假设 SQL Server)

在 T-SQL 中处理存储过程结果的唯一方法是使用 INSERT INTO ... EXEC 语法。这使您可以选择插入临时表或表变量,然后从那里选择您需要的数据。


这需要知道表定义。没用。
J
JasonMArcher

为此,您首先要创建一个如下所示的 #test_table

create table #test_table(
    col1 int,
    col2 int,
   .
   .
   .
    col80 int
)

现在执行程序并将值放入 #test_table

insert into #test_table
EXEC MyStoredProc 'param1', 'param2'

现在您从 #test_table 获取值:

select col1,col2....,col80 from #test_table

创建临时表而不是表变量有优势吗?
我在stackoverflow上找到的最佳解决方案! :)
如果我只需要来自其他存储过程的一列怎么办?
b
bluish

了解为什么这如此困难可能会有所帮助。存储过程可能只返回文本(打印'text'),或者可能返回多个表,或者根本不返回表。

所以像 SELECT * FROM (exec sp_tables) Table1 这样的东西不起作用


如果发生这种情况,SQL Server 可以自由地引发错误。例如,如果我编写一个返回多个值的子查询。是的,它可能会发生,但实际上它不会。即使确实如此:引发错误也不难。
d
dyatchenko

如果您能够修改存储过程,您可以轻松地将所需的列定义作为参数并使用自动创建的临时表:

CREATE PROCEDURE sp_GetDiffDataExample
      @columnsStatement NVARCHAR(MAX) -- required columns statement (e.g. "field1, field2")
AS
BEGIN
    DECLARE @query NVARCHAR(MAX)
    SET @query = N'SELECT ' + @columnsStatement + N' INTO ##TempTable FROM dbo.TestTable'
    EXEC sp_executeSql @query
    SELECT * FROM ##TempTable
    DROP TABLE ##TempTable
END

在这种情况下,您不需要手动创建临时表 - 它是自动创建的。希望这可以帮助。


小心使用##tables,因为它们在会话之间共享
您可以在 stackoverflow.com/a/34081438/352820 中找到 # 和 ## 表之间差异的简短说明
这容易发生 SQL 注入吗?
T
Taryn

一个快速的技巧是添加一个新参数 '@Column_Name' 并让调用函数定义要检索的列名。在存储过程的返回部分中,您将有 if/else 语句并仅返回指定的列,或者如果为空 - 则返回全部。

CREATE PROCEDURE [dbo].[MySproc]
        @Column_Name AS VARCHAR(50)
AS
BEGIN
    IF (@Column_Name = 'ColumnName1')
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1'
        END
    ELSE
        BEGIN
            SELECT @ColumnItem1 as 'ColumnName1', @ColumnItem2 as 'ColumnName2', @ColumnItem3 as 'ColumnName3'
        END
END

s
sqluser

正如问题中提到的,在执行存储过程之前很难定义 80 列的临时表。

因此,解决此问题的另一种方法是根据存储过程结果集填充表。

SELECT * INTO #temp FROM OPENROWSET('SQLNCLI', 'Server=localhost;Trusted_Connection=yes;'
                                   ,'EXEC MyStoredProc')

如果您遇到任何错误,您需要通过执行以下查询来启用临时分布式查询。

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

要使用两个参数执行 sp_configure 以更改配置选项或运行 RECONFIGURE 语句,您必须被授予 ALTER SETTINGS 服务器级权限

现在您可以从生成的表中选择您的特定列

SELECT col1, col2
FROM #temp

S
ShawnFeatherly

如果您这样做是为了手动验证数据,您可以使用 LINQPad 执行此操作。

在 LinqPad 中创建与数据库的连接,然后创建类似于以下内容的 C# 语句:

DataTable table = MyStoredProc (param1, param2).Tables[0];
(from row in table.AsEnumerable()
 select new
 {
  Col1 = row.Field<string>("col1"),
  Col2 = row.Field<string>("col2"),
 }).Dump();

参考http://www.global-webnet.net/blogengine/post/2008/09/10/LINQPAD-Using-Stored-Procedures-Accessing-a-DataSet.aspx


A
Alex T

对于 SQL Server,我发现这很好用:

创建一个临时表(或永久表,并不重要),并对存储过程执行插入语句。 SP 的结果集应该与表中的列匹配,否则会出错。

这是一个例子:

DECLARE @temp TABLE (firstname NVARCHAR(30), lastname nvarchar(50));

INSERT INTO @temp EXEC dbo.GetPersonName @param1,@param2;
-- assumption is that dbo.GetPersonName returns a table with firstname / lastname columns

SELECT * FROM @temp;

而已!


为此,需要创建表定义的副本。有什么办法可以避免吗?
S
SelvirK

尝试这个

use mydatabase
create procedure sp_onetwothree as
select 1 as '1', 2 as '2', 3 as '3'
go
SELECT a.[1], a.[2]
FROM OPENROWSET('SQLOLEDB','myserver';'sa';'mysapass',
    'exec mydatabase.dbo.sp_onetwothree') AS a
GO

大声笑 - 他没有。他将其编码到存储过程的调用中,而不用通过网络嗅探访问数据库就可以更容易地获得它。
从 Github 中取出它也很容易。
H
Humayoun_Kabir

我知道从 sp 执行并插入临时表或表变量是一种选择,但我认为这不是您的要求。根据您的要求,以下查询语句应该可以工作:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);uid=test;pwd=test'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

如果您有受信任的连接,请使用以下查询语句:

Declare @sql nvarchar(max)
Set @sql='SELECT   col1, col2 FROM OPENROWSET(''SQLNCLI'', ''Server=(local);Trusted_Connection=yes;'',
     ''EXEC MyStoredProc ''''param1'''', ''''param2'''''')'
 Exec(@sql)

如果您在运行上述语句时遇到错误,则只需在下面运行此语句:

sp_configure 'Show Advanced Options', 1
GO
RECONFIGURE
GO
sp_configure 'Ad Hoc Distributed Queries', 1
GO
RECONFIGURE
GO

我希望这将帮助那些将面临这种类似问题的人。如果有人想尝试使用如下所示的临时表或表变量,但在这种情况下,您应该知道您的 sp 返回了多少列,那么您应该在临时表或表变量中创建那么多列:

--for table variable 
Declare @t table(col1 col1Type, col2 col2Type)
insert into @t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM @t

--for temp table
create table #t(col1 col1Type, col2 col2Type)
insert into #t exec MyStoredProc 'param1', 'param2'
SELECT col1, col2 FROM #t

L
Lemiarty

这是一个简单的答案:

SELECT ColA, ColB
FROM OPENROWSET('SQLNCLI','server=localhost;trusted_connection=yes;','exec schema.procedurename')

SQLNCLI 是本机 SQL 客户端,“localhost”将使其利用您正在执行该过程的服务器。

无需构建临时表或任何其他爵士乐。


“SQL Server 阻止了对组件 'Ad Hoc Distributed Queries' 的 STATEMENT 'OpenRowset/OpenDatasource' 的访问,因为该组件作为该服务器安全配置的一部分被关闭。”
N
Nilesh Umaretiya

创建一个动态视图并从中获取结果.......

CREATE PROCEDURE dbo.usp_userwise_columns_value
(
    @userid BIGINT
)
AS 
BEGIN
        DECLARE @maincmd NVARCHAR(max);
        DECLARE @columnlist NVARCHAR(max);
        DECLARE @columnname VARCHAR(150);
        DECLARE @nickname VARCHAR(50);

        SET @maincmd = '';
        SET @columnname = '';
        SET @columnlist = '';
        SET @nickname = '';

        DECLARE CUR_COLUMNLIST CURSOR FAST_FORWARD
        FOR
            SELECT columnname , nickname
            FROM dbo.v_userwise_columns 
            WHERE userid = @userid

        OPEN CUR_COLUMNLIST
        IF @@ERROR <> 0
            BEGIN
                ROLLBACK
                RETURN
            END   

        FETCH NEXT FROM CUR_COLUMNLIST
        INTO @columnname, @nickname

        WHILE @@FETCH_STATUS = 0
            BEGIN
                SET @columnlist = @columnlist + @columnname + ','

                FETCH NEXT FROM CUR_COLUMNLIST
                INTO @columnname, @nickname
            END
        CLOSE CUR_COLUMNLIST
        DEALLOCATE CUR_COLUMNLIST  

        IF NOT EXISTS (SELECT * FROM sys.views WHERE name = 'v_userwise_columns_value')
            BEGIN
                SET @maincmd = 'CREATE VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END
        ELSE
            BEGIN
                SET @maincmd = 'ALTER VIEW dbo.v_userwise_columns_value AS SELECT sjoid, CONVERT(BIGINT, ' + CONVERT(VARCHAR(10), @userid) + ') as userid , ' 
                            + CHAR(39) + @nickname + CHAR(39) + ' as nickname, ' 
                            + @columnlist + ' compcode FROM dbo.SJOTran '
            END

        --PRINT @maincmd
        EXECUTE sp_executesql @maincmd
END

-----------------------------------------------
SELECT * FROM dbo.v_userwise_columns_value

M
Martijn Tromp

如果您只需要这样做一次,最简单的方法是:

在导入和导出向导中导出到 excel,然后将此 excel 导入表中。


创建存储过程的重点是可重用性。你的回答完全相反。
为了反驳 deutschZuid,在原始帖子中,他没有提到他是否要重用它,或者他是否只是试图查看存储过程的结果。马丁是对的,如果他只需要做一次,这可能是最简单的方法。
E
Emil

对于拥有 SQL 2012 或更高版本的任何人,我都能够使用非动态且每次输出相同列的存储过程来完成此操作。

一般的想法是我构建动态查询来创建、插入、选择和删除临时表,并在它全部生成后执行它。我通过第一个 retrieving column names and types from the stored procedure 动态生成临时表。

注意:如果您愿意/能够更新 SP 或更改配置并使用 OPENROWSET,则有更好、更通用的解决方案可以使用更少的代码行。如果您没有其他方法,请使用以下方法。

DECLARE @spName VARCHAR(MAX) = 'MyStoredProc'
DECLARE @tempTableName VARCHAR(MAX) = '#tempTable'

-- might need to update this if your param value is a string and you need to escape quotes
DECLARE @insertCommand VARCHAR(MAX) = 'INSERT INTO ' + @tempTableName + ' EXEC MyStoredProc @param=value'

DECLARE @createTableCommand VARCHAR(MAX)

-- update this to select the columns you want
DECLARE @selectCommand VARCHAR(MAX) = 'SELECT col1, col2 FROM ' + @tempTableName

DECLARE @dropCommand VARCHAR(MAX) = 'DROP TABLE ' + @tempTableName

-- Generate command to create temp table
SELECT @createTableCommand = 'CREATE TABLE ' + @tempTableName + ' (' +
    STUFF
    (
        (
            SELECT ', ' + CONCAT('[', name, ']', ' ', system_type_name)
            FROM sys.dm_exec_describe_first_result_set_for_object
            (
              OBJECT_ID(@spName), 
              NULL
            )
            FOR XML PATH('')
        )
        ,1
        ,1
        ,''
    ) + ')'

EXEC( @createTableCommand + ' '+ @insertCommand + ' ' + @selectCommand + ' ' + @dropCommand)

A
Andrew

我会剪切并粘贴原始 SP 并删除除您想要的 2 之外的所有列。或者。我会带回结果集,将其映射到适当的业务对象,然后 LINQ 出两列。


人们不这样做。这将违反 DRY 原则。当事情发生变化时,而不是如果,您现在需要在所有位置追踪并输入您的更改。