Comment passer un tableau dans une procédure stockée SQL Server?
Par exemple, j'ai une liste d'employés. Je veux utiliser cette liste comme une table et la joindre à une autre table. Mais la liste des employés doit être passée en paramètre à partir de C #.
Tout d'abord, dans votre base de données, créez les deux objets suivants:
CREATE TYPE dbo.IDList
AS TABLE
(
ID INT
);
GO
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List AS dbo.IDList READONLY
AS
BEGIN
SET NOCOUNT ON;
SELECT ID FROM @List;
END
GO
Maintenant dans votre code C #:
// Obtain your list of ids to send, this is just an example call to a helper utility function
int[] employeeIds = GetEmployeeIds();
DataTable tvp = new DataTable();
tvp.Columns.Add(new DataColumn("ID", typeof(int)));
// populate DataTable from your List here
foreach(var id in employeeIds)
tvp.Rows.Add(id);
using (conn)
{
SqlCommand cmd = new SqlCommand("dbo.DoSomethingWithEmployees", conn);
cmd.CommandType = CommandType.StoredProcedure;
SqlParameter tvparam = cmd.Parameters.AddWithValue("@List", tvp);
// these next lines are important to map the C# DataTable object to the correct SQL User Defined Type
tvparam.SqlDbType = SqlDbType.Structured;
tvparam.TypeName = "dbo.IDList";
// execute query, consume results, etc. here
}
Si vous utilisez SQL Server 2005, je recommanderais quand même une fonction divisée par rapport à XML. Tout d'abord, créez une fonction:
CREATE FUNCTION dbo.SplitInts
(
@List VARCHAR(MAX),
@Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM
( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
FROM ( SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(@List, @Delimiter, '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
WHERE Item IS NOT NULL
);
GO
Maintenant, votre procédure stockée peut simplement être:
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ',');
END
GO
Et dans votre code C #, il vous suffit de passer la liste sous la forme '1,2,3,12'
...
Je trouve que la méthode consistant à passer par des paramètres de valeur table simplifie la facilité de maintenance d'une solution qui l'utilise et offre souvent des performances accrues par rapport à d'autres implémentations, notamment XML et le fractionnement de chaînes.
Les entrées sont clairement définies (personne ne doit deviner si le délimiteur est une virgule ou un point-virgule) et nous ne dépendons pas d'autres fonctions de traitement qui ne sont pas évidentes sans inspecter le code de la procédure stockée.
Comparé aux solutions impliquant un schéma XML défini par l'utilisateur plutôt que des UDT, cela implique un nombre similaire d'étapes, mais d'après mon expérience, le code est beaucoup plus simple à gérer, à gérer et à lire.
Dans de nombreuses solutions, il se peut que vous n'ayez besoin que d'un ou de quelques-uns de ces types UDT (types définis par l'utilisateur) que vous réutiliserez pour de nombreuses procédures stockées. Comme dans cet exemple, l'exigence courante consiste à passer par une liste de pointeurs d'ID, le nom de la fonction décrit le contexte que ces ID doivent représenter, le nom du type doit être générique.
D'après mon expérience, en créant une expression délimitée à partir des ID employé, il existe une solution délicate et agréable à ce problème. Vous ne devez créer qu'une expression de chaîne telle que ';123;434;365;'
dans-laquelle 123
, 434
et 365
sont des ID d'employé. En appelant la procédure ci-dessous et en lui passant cette expression, vous pouvez récupérer vos enregistrements souhaités. Vous pouvez facilement joindre la "autre table" à cette requête. Cette solution convient à toutes les versions de SQL Server. En outre, en comparaison avec l'utilisation d'une variable de table ou d'une table temporaire, c'est une solution très rapide et optimisée.
CREATE PROCEDURE dbo.DoSomethingOnSomeEmployees @List AS varchar(max)
AS
BEGIN
SELECT EmployeeID
FROM EmployeesTable
-- inner join AnotherTable on ...
where @List like '%;'+cast(employeeID as varchar(20))+';%'
END
GO
Utilisez un paramètre table pour votre procédure stockée.
Lorsque vous le transmettez à partir de C #, vous ajoutez le paramètre avec le type de données SqlDb.Structured.
Voir ici: http://msdn.Microsoft.com/en-us/library/bb675163.aspx
Exemple:
// Assumes connection is an open SqlConnection object.
using (connection)
{
// Create a DataTable with the modified rows.
DataTable addedCategories =
CategoriesDataTable.GetChanges(DataRowState.Added);
// Configure the SqlCommand and SqlParameter.
SqlCommand insertCommand = new SqlCommand(
"usp_InsertCategories", connection);
insertCommand.CommandType = CommandType.StoredProcedure;
SqlParameter tvpParam = insertCommand.Parameters.AddWithValue(
"@tvpNewCategories", addedCategories);
tvpParam.SqlDbType = SqlDbType.Structured;
// Execute the command.
insertCommand.ExecuteNonQuery();
}
Vous devez le transmettre en tant que paramètre XML.
Edit: code rapide de mon projet pour vous donner une idée:
CREATE PROCEDURE [dbo].[GetArrivalsReport]
@DateTimeFrom AS DATETIME,
@DateTimeTo AS DATETIME,
@HostIds AS XML(xsdArrayOfULong)
AS
BEGIN
DECLARE @hosts TABLE (HostId BIGINT)
INSERT INTO @hosts
SELECT arrayOfUlong.HostId.value('.','bigint') data
FROM @HostIds.nodes('/arrayOfUlong/u') as arrayOfUlong(HostId)
Vous pouvez ensuite utiliser la table temporaire pour rejoindre vos tables. Nous avons défini arrayOfUlong comme un schéma XML intégré pour préserver l'intégrité des données, mais vous n'avez pas à le faire. Je vous recommande de l'utiliser, voici donc un code rapide pour vous assurer que vous obtenez toujours un XML avec longs.
IF NOT EXISTS (SELECT * FROM sys.xml_schema_collections WHERE name = 'xsdArrayOfULong')
BEGIN
CREATE XML SCHEMA COLLECTION [dbo].[xsdArrayOfULong]
AS N'<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="arrayOfUlong">
<xs:complexType>
<xs:sequence>
<xs:element maxOccurs="unbounded"
name="u"
type="xs:unsignedLong" />
</xs:sequence>
</xs:complexType>
</xs:element>
</xs:schema>';
END
GO
Le contexte est toujours important, comme la taille et la complexité du tableau. . Pour les listes de taille petite à moyenne, plusieurs des réponses affichées ici conviennent, bien que des éclaircissements soient nécessaires:
text()
. Pour plus d'informations (par exemple, l'analyse des performances) sur l'utilisation de XML pour scinder des listes, consultez "Using XML passer des listes en tant que paramètres dans SQL Server " par Phil Factor.DataTable
signifie dupliquer les données en mémoire à mesure qu'elles sont copiées à partir de la collection d'origine. Par conséquent, l’utilisation de la méthode DataTable
de transmission des TVP ne fonctionne pas bien pour des ensembles de données plus volumineux (c’est-à-dire que l’échelle n’est pas adaptée).DataTable
TVP, XML ne s'adapte pas correctement car il double la taille des données en mémoire, car il doit en outre prendre en compte les frais généraux du document XML.Cela dit, SI les données que vous utilisez sont volumineuses ou ne le sont pas encore, mais que leur croissance est constante, la méthode IEnumerable
TVP est le meilleur choix car elle transmet les données à SQL Server (comme la méthode DataTable
méthode), MAIS ne nécessite aucune duplication de la collection en mémoire (contrairement à toutes les autres méthodes). J'ai posté un exemple de code SQL et C # dans cette réponse:
Il n’existe pas de support pour array dans le serveur SQL, mais il existe plusieurs façons de transmettre la collection à un proc stocké.
Le lien ci-dessous peut vous aider
Cela vous aidera. :) Suivez les prochaines étapes,
Copier Collez le code suivant tel quel, il créera la fonction qui convertit la chaîne en int
CREATE FUNCTION dbo.SplitInts
(
@List VARCHAR(MAX),
@Delimiter VARCHAR(255)
)
RETURNS TABLE
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM
( SELECT Item = x.i.value('(./text())[1]', 'varchar(max)')
FROM ( SELECT [XML] = CONVERT(XML, '<i>'
+ REPLACE(@List, @Delimiter, '</i><i>') + '</i>').query('.')
) AS a CROSS APPLY [XML].nodes('i') AS x(i) ) AS y
WHERE Item IS NOT NULL
);
GO
Créer la procédure stockée suivante
CREATE PROCEDURE dbo.sp_DeleteMultipleId
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
DELETE FROM TableName WHERE Id IN( SELECT Id = Item FROM dbo.SplitInts(@List, ','));
END
GO
Exécutez ceci SP Avec exec sp_DeleteId '1,2,3,12'
c'est une chaîne d'identifiants que vous souhaitez supprimer,
Vous convertissez votre tableau en chaîne en C # et le transmettez en tant que paramètre de procédure stockée
int[] intarray = { 1, 2, 3, 4, 5 };
string[] result = intarray.Select(x=>x.ToString()).ToArray();
SqlCommand command = new SqlCommand();
command.Connection = connection;
command.CommandText = "sp_DeleteMultipleId";
command.CommandType = CommandType.StoredProcedure;
command.Parameters.Add("@Id",SqlDbType.VARCHAR).Value=result ;
Cela supprimera plusieurs lignes, Tous les meilleurs
J'ai cherché dans tous les exemples et réponses sur la façon de passer un tableau sur un serveur SQL sans avoir à créer de nouveau type de table, jusqu'à ce que je trouve ceci linK , voici comment je l'ai appliqué à mon projet:
--Le code suivant va obtenir un tableau en tant que paramètre et insérer les valeurs de ce --array dans une autre table
Create Procedure Proc1
@UserId int, //just an Id param
@s nvarchar(max) //this is the array your going to pass from C# code to your Sproc
AS
declare @xml xml
set @xml = N'<root><r>' + replace(@s,',','</r><r>') + '</r></root>'
Insert into UserRole (UserID,RoleID)
select
@UserId [UserId], t.value('.','varchar(max)') as [RoleId]
from @xml.nodes('//root/r') as a(t)
END
Je espère que vous l'apprécierez
J'ai mis beaucoup de temps à comprendre cela, alors au cas où quelqu'un en aurait besoin ...
Ceci est basé sur la méthode SQL 2005 dans la réponse d'Aaron et sur l'utilisation de sa fonction SplitInts (je viens de supprimer le paramètre delim puisque j'utiliserai toujours des virgules). J'utilise SQL 2008 mais je voulais quelque chose qui fonctionne avec les ensembles de données typés (XSD, TableAdapters) et je sais que les paramètres de chaîne fonctionnent avec ceux-ci.
J'essayais de faire en sorte que sa fonction fonctionne dans une clause de type "where in (1,2,3)" et je n'ai pas de chance en toute simplicité. J'ai donc d'abord créé une table temporaire, puis une jointure interne au lieu du "où". Voici mon exemple d'utilisation. Dans mon cas, je voulais obtenir une liste de recettes ne contenant pas certains ingrédients:
CREATE PROCEDURE dbo.SOExample1
(
@excludeIngredientsString varchar(MAX) = ''
)
AS
/* Convert string to table of ints */
DECLARE @excludeIngredients TABLE (ID int)
insert into @excludeIngredients
select ID = Item from dbo.SplitInts(@excludeIngredientsString)
/* Select recipies that don't contain any ingredients in our excluded table */
SELECT r.Name, r.Slug
FROM Recipes AS r LEFT OUTER JOIN
RecipeIngredients as ri inner join
@excludeIngredients as ei on ri.IngredientID = ei.ID
ON r.ID = ri.RecipeID
WHERE (ri.RecipeID IS NULL)