Avec une procédure stockée dans SQL Server comportant plusieurs instructions select
, existe-t-il un moyen de travailler avec ces résultats séparément tout en appelant la procédure?
Par exemple:
alter procedure dbo.GetSomething
as
begin
select * from dbo.Person;
select * from dbo.Car;
end;
Dans .NET, si j'appelle ce processus, je peux utiliser une SqlDataReader
pour passer d'un jeu de résultats à un autre, afin de pouvoir récupérer facilement toutes les personnes et les voitures. En SQL cependant, quand j'exécute directement le proc, j'obtiens les deux jeux de résultats.
Si j'appelle:
insert @myTempTable
exec dbo.GetSomething;
Ensuite, il se trompe car la définition de la colonne ne correspond pas. Si, par hasard, Person et Car ont les mêmes colonnes, cela concatène les deux et @myTempTable obtient tous les enregistrements des deux tables, ce qui n'est évidemment pas bon non plus.
Je peux définir de nouveaux types personnalisés représentant les deux jeux de résultats, et définir ces paramètres de sortie au lieu d’avoir plusieurs instructions select
, mais je me demande s’il existe un meilleur moyen - d’extraire les deux résultats dans des tables temporaires ou de passer en boucle dans résultats, ou quelque chose.
MODIFIER
En fait, après une recherche plus approfondie, même les paramètres de la table de sortie ne résoudront pas ce problème - ils sont en lecture seule, et cela reste vrai dans SQL 2012. ( Connect ticket demandant l'ajout de cette information )
Il semble qu’il n’existe pas de moyen simple et efficace de procéder ainsi, sans piratage ni changement de paradigme majeur. Il semble que le meilleur moyen consiste simplement à séparer les procs d'origine et à obtenir un proc de plus qu'avant:
Ancienne façon:
create procedure dbo.GetSomething
as
begin
select * from dbo.Person;
select * from dbo.Car;
end;
Nouvelle façon:
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;
Ensuite, lorsque je suis dans un processus différent et que j'ai besoin des deux jeux de résultats, je dois les appeler un à la fois.
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.
}
}
Au pays TSQL, vous êtes bloqué.
Voici une astuce (certains peuvent appeler semi-hacky) que j'ai utilisée une fois.
/* 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
Bien que cela ne semble pas être pris en charge de manière native dans T-SQL, si l’utilisation d’une procédure stockée CLR est une option pour vous, vous devriez pouvoir créer une procédure stockée dans votre langage .Net préféré utilisant la méthode SqlDataReader.NextResult()
le résultat souhaité, puis renvoyez le SqlDataReader via la méthode SqlPipe.Send(SqlDataReader)
. Il vous suffit de passer le code SQL à exécuter et le résultat souhaité défini en tant que paramètres de ce processus.
Cela vous permettrait de travailler avec le proc tel quel, sans le modifier pour renvoyer tout ou un seul jeu de résultats.
Vous pouvez mettre plusieurs résultats sous forme de fichier xml dans une table
Ainsi, lorsque vous souhaitez accéder à tous ces résultats, vous les analysez sous forme de tableau
Der. Lire toute la question avant d'écrire une réponse! :-P
Si vous essayez d'utiliser les résultats dans TSQL Land, vous devrez utiliser un moyen de les séparer. Écrire les résultats dans des tables temporaires est peut-être votre meilleur choix, car vous n'aurez pas besoin de vous fier aux colonnes alignées (ou non, selon le cas) et pourrez traiter les données de manière "naturelle" pour SQL Server. Par exemple.
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
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
Créez un SqlDataAdapter, définissez sa SelectCommand pour exécuter le SP "GetSomething", puis utilisez l'adaptateur de données pour remplir un DataSet. Le DataSet contiendra autant de DataTable que vous avez d'instructions "select" renvoyant des jeux d'enregistrements du SP.
Voici à quoi ressemblerait votre code:
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.