从具有多个结果集的存储过程中检索数据

时间:2013-11-19 21:54:17

标签: sql-server stored-procedures

鉴于SQL Server中有多个select语句的存储过程,有没有办法在调用过程时单独使用这些结果?

例如:

alter procedure dbo.GetSomething
as
begin
    select * from dbo.Person;
    select * from dbo.Car;
end;

在.NET中,如果我调用此proc,我可以使用SqlDataReader在两个结果集之间移动,这样我就可以轻松检索所有人和汽车。但是在SQL中,当我直接执行proc时,我得到两个结果集。

如果我打电话:

insert @myTempTable
    exec dbo.GetSomething;

然后它会出错,因为列定义不匹配。如果有可能Person和Car有相同的列,它将两者连接在一起,@ myTempTable从两个表中获取所有记录,这显然也不好。

我可以定义代表两个结果集的新自定义类型,并创建那些输出参数而不是多个select语句,但我想知道是否有更好的方法 - 某种方式将结果拉入临时表,或循环结果或其他内容。

修改

实际上,在仔细观察之后,即使输出表参数也无法解决这个问题 - 它们只是只读,而且在SQL 2012中仍然如此。(Connect ticket asking for this to be added

10 个答案:

答案 0 :(得分:8)

String myConnString  = "User ID="username";password="password";Initial Catalog=pubs;Data Source=Server";
SqlConnection myConnection = new SqlConnection(myConnString);
SqlCommand myCommand = new SqlCommand();
SqlDataReader myReader ;

myCommand.CommandType = CommandType.StoredProcedure;
myCommand.Connection = myConnection;
myCommand.CommandText = "MyProc";

try
{
    myConnection.Open();
    myReader = myCommand.ExecuteReader();

    while (myReader.Read())
    {
        //Write logic to process data for the first result.   
        }

    myReader.NextResult();
    while (myReader.Read())
    {
        //Write logic to process data for the second result.
    }
}

答案 1 :(得分:4)

在TSQL领域,你被困住了。

这是我用过一次的一种技巧(有些人可能称之为半hacky)。

/*  START TSQL CODE */

/*  Stored Procedure Definition */

Use Northwind
GO


IF EXISTS 
    (
    SELECT * FROM INFORMATION_SCHEMA.ROUTINES
    WHERE ROUTINE_TYPE = N'PROCEDURE' and ROUTINE_SCHEMA = N'dbo' and ROUTINE_NAME = N'uspOrderDetailsByCustomerId'  
    )
BEGIN
    DROP PROCEDURE [dbo].[uspOrderDetailsByCustomerId]
END


GO

CREATE Procedure dbo.uspOrderDetailsByCustomerId
(
  @CustomerID nchar(5)
, @ResultSetIndicator smallint = 0
)
AS

BEGIN

    SET NOCOUNT ON



    /* ResultSet #1 */

    if (@ResultSetIndicator = 0 OR @ResultSetIndicator = 1)
    BEGIN 
        SELECT 
            c.CustomerID, c.CompanyName /*,c.ContactName,c.ContactTitle,c.[Address],c.City,c.Region,c.PostalCode,c.Country ,c.Phone,c.Fax */
        FROM 
            Customers c 
            JOIN Orders o ON c.CustomerID = o.CustomerID 
        WHERE 
            c.CustomerID = @CustomerID
    END


    /* */
    /* ResultSet #2 */ 

    if (@ResultSetIndicator = 0 OR @ResultSetIndicator = 2)
    BEGIN 

        SELECT o.OrderID,o.CustomerID /* ,o.EmployeeID,o.OrderDate,o.RequiredDate,o.ShippedDate,o.ShipVia ,o.Freight,o.ShipName,o.ShipAddress,o.OrderID,o.CustomerID,o.EmployeeID,o.OrderDate  */
        FROM 
            Orders o 
         WHERE 
            o.CustomerID = @CustomerID
        ORDER BY 
            o.CustomerID , o.OrderID 

    END


    /* */
    /* ResultSet #3 */

    if (@ResultSetIndicator = 0 OR @ResultSetIndicator = 3)
    BEGIN 
         SELECT od.OrderID,od.ProductID /* ,od.UnitPrice,od.Quantity,od.Discount  */
         FROM 
            [Order Details] od 
         WHERE 
            exists (select null from dbo.Orders  innerOrds where innerOrds.OrderID = od.OrderID and innerOrds.CustomerID = @CustomerID )
         ORDER BY 
            od.OrderID 

    END

    SET NOCOUNT OFF


END

GO 
/* Get everything */


exec dbo.uspOrderDetailsByCustomerId 'ALFKI'




    IF OBJECT_ID('tempdb..#TempCustomer') IS NOT NULL
    begin
            drop table #TempCustomer
    end


    CREATE TABLE #TempCustomer
    ( 
      [CustomerID] nchar(5)
    , [CompanyName] nvarchar(40)
    )

INSERT INTO #TempCustomer ( [CustomerID] , [CompanyName])
exec dbo.uspOrderDetailsByCustomerId 'ALFKI' , 1

Select * from #TempCustomer



    IF OBJECT_ID('tempdb..#TempOrders') IS NOT NULL
    begin
            drop table #TempOrders
    end


    CREATE TABLE #TempOrders
    ( 
        OrderID int
      , [CustomerID] nchar(5)

    )

INSERT INTO #TempOrders ( OrderID , [CustomerID] )
exec dbo.uspOrderDetailsByCustomerId 'ALFKI' , 2

Select * from #TempOrders






    IF OBJECT_ID('tempdb..#TempOrderDetails') IS NOT NULL
    begin
            drop table #TempOrderDetails
    end


    CREATE TABLE #TempOrderDetails
    ( 
        OrderID int
      , [ProductID] int

    )

INSERT INTO #TempOrderDetails ( OrderID , [ProductID] )
exec dbo.uspOrderDetailsByCustomerId 'ALFKI' , 3

Select * from #TempOrderDetails


    IF OBJECT_ID('tempdb..#TempOrderDetails') IS NOT NULL
    begin
            drop table #TempOrders
    end


    IF OBJECT_ID('tempdb..#TempOrders') IS NOT NULL
    begin
            drop table #TempOrders
    end



    IF OBJECT_ID('tempdb..#TempCustomer') IS NOT NULL
    begin
            drop table #TempCustomer
    end

答案 2 :(得分:4)

虽然在T-SQL中似乎不支持这种情况,但如果您使用CLR存储过程是一个选项,那么您应该能够使用首选的.Net语言创建一个使用{{{ 1}}方法前进到所需的结果集,然后通过SqlDataReader.NextResult()方法返回SqlDataReader。您只需要将SQL执行并将所需的结果集作为参数传递给此proc。

这将允许您按原样使用proc,而不修改它以发送回全部或仅一个结果集。

答案 3 :(得分:3)

似乎没有一个简单的方法可以做到这一点,没有黑客或主要的范式转变。看起来最好的方法就是将原始过程拆分出来并最终获得比以前更多的过程:

旧方式:

create procedure dbo.GetSomething
as
begin
    select * from dbo.Person;
    select * from dbo.Car;
end;

新方式:

create procedure dbo.GetPeople
as
begin
    select * from dbo.Person;
end;

create procedure dbo.GetCars
as
begin
    select * from dbo.Car;
end;

-- This gives the same result as before
create procedure dbo.GetSomething
as
begin
    exec dbo.GetPeople;
    exec dbo.GetCars;
end;

然后,当我处于不同的过程并且需要两个结果集时,我只需要一次调用它们。

答案 4 :(得分:2)

您可以将多个结果集以xml的形式放入表

因此,当您想要访问所有这些结果时,您将这些结果集列解析为表格形式

答案 5 :(得分:1)

明镜。在写答案之前阅读整个问题! :-P

如果您尝试使用TSQL中的结果,则需要使用某种方法将结果分开。将结果写入Temp表可能是您最好的选择,因为您不需要依赖排列的列(或不是,视情况而定)并且可以以“自然”方式处理SQL Server的数据。 E.g。

create proc test_something
as begin
    select a, b into temp1 from table1
    select b, c into temp2 from table2
end
go

exec dbo.test_something()

select * from temp1
select * from temp2

答案 6 :(得分:1)

Would passing a parameter to the sp do the trick
----------------------
CREATE PROCEDURE  dostuff @parm1 int
AS

BEGIN
Declare @resultset Int
Set @resultset = @parm1

--0 = Select ranks
--1 = Select suits
--other - Select ALL

If @resultset = 0 
 SELECT [rank] FROM [ranks]
 Else If @resultset = 1
 SELECT [suit] FROM [suits]
 Else 
 SELECT * FROM [suits]
 cross join   [ranks] 
END
GO

 declare @mytemptbl table (rank text)
 insert @mytemptbl
  exec dostuff 0

 select * from @mytemptbl

答案 7 :(得分:0)

创建一个SqlDataAdapter,将其SelectCommand设置为执行SP“ GetSomething”,然后使用数据适配器填充DataSet。数据集将包含与您从SP返回记录集的“选择”语句一样多的数据表。

这是您的代码的样子:

System.Data.SqlClient.SqlDataAdapter da = new System.Data.SqlClient.SqlDataAdapter();
System.Data.SqlClient.SqlCommand cmd = new System.Data.SqlClient.SqlCommand();
cmd.Connection = myConnectionObject;
cmd.CommandType = CommandType.StoredProcedure;
cmd.CommandText = "GetSomething";
da.SelectCommand = cmd;

System.Data.DataSet ds = new DataSet();
da.Fill(ds);
// at this point, the (ds) object contains DataTables created from the recordsets returned by the SP
DataTable dt0 = ds.Tables[0];
DataTable dt1 = ds.Tables[1];

// note that dt0 corresponds to the FIRST recordset returned by the SP, etc.

答案 8 :(得分:0)

我知道我参加这个聚会有点晚了,但是添加它只是为了帮助遇到此话题并需要其他选择的其他人。

如果您有此选项,我建议您按照Joe Enos的接受答案中的指示将过程调用分开,但如果没有,则此链接中的信息可能是您的选择。

>

https://khalidabuhakmeh.com/entity-framework-6-multiple-result-sets-with-stored-procedures

这是我处理无法拆分的过程调用的方法,该过程返回了6个不同的查询结果。我采用的方法主要基于该文章中的信息,并且使此操作非常容易且可测试。

答案 9 :(得分:0)

一种可用的方法是反转进程并让存储过程接受 temporary tables 的名称,并使用 dynamic SQL 填充具有所需结果的临时表。

当 SP 返回时,调用者可以访问临时表中包含的数据。

-- local temporary tables avoid leaking to other sessions
create table #fast_cars (name varchar(max), top_speed float);
create table #awesome_people (name varchar(max), age int);

-- invoked SPs can access local temporary tables in scope
exec GetAmazingThings @name='%Wonder%'
    ,@cars='#fast_cars'
    ,@people='#awesome_people'

-- use 'returned' data
select name, top_speed from #fast_cars;
select name, age from #awesome_people;

接受表名会减少对哪些表受到影响的“神奇知识”,因为名称是明确提供的。它还允许在保持隔离的同时收集多次调用的结果,包括在嵌套期间。

存储过程可能看起来有点像..

create procedure GetAmazingThings
    @name varchar(100),
    -- output table names..
    ,@cars varchar(100)
    ,@people varchar(100)
as
    set @cars = quotename(@cars); -- bobby is not welcome
    declare @sql nvarchar(max);

    declare #_cars (name varchar(max), top_speed float);

    -- perform queries into local temp tables
    -- (this could also be done as the dynamic SQL to avoid a copy)
    insert into #_cars (name, top_speed)
    select Name, max(LapSpeed)
    from TonkaToys
    where Name like @name and VehicleType = 'car'
    group by Name;

    if patindex('[[]#%', @cars) > 0 -- given temp table
    begin
        -- copy result to supplied temp table
        set @sql = concat(N'
insert into ', @cars, ' (name, top_speed)
select name, top_speed
from #_cars
');
        exec sp_executesql @sql;
    end
    else
    begin
        -- just select result
        select name, top_speed from #cars
    end

    -- ditto for @people query/results
go

注意事项:

  • 临时表之间至少有一个(可能更多)数据副本。
  • 如果动态 SQL 与主查询隔离,则在 SP 中会更清晰一些。首先查询本地临时表,然后使用动态 SQL 将其复制到提供的临时表中。
  • 如果使用全局临时表,SP 可以创建所需的表结果集。然而,这虽然方便,但可能会出现问题,因为全局临时表在会话之间共享。
  • 参数还可用于控制 SP“返回”的内容,例如。跳过查询,或选择作为结果集而不是写入临时表。