Come passare un array in una procedura memorizzata di SQL Server?
Ad esempio, ho un elenco di dipendenti. Voglio usare questo elenco come tabella e unirlo a un'altra tabella. Ma l'elenco dei dipendenti deve essere passato come parametro da C #.
Come passare un array in una procedura memorizzata di SQL Server?
Ad esempio, ho un elenco di dipendenti. Voglio usare questo elenco come tabella e unirlo a un'altra tabella. Ma l'elenco dei dipendenti deve essere passato come parametro da C #.
Risposte:
Innanzitutto, nel database, creare i seguenti due oggetti:
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
Ora nel tuo codice 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
}
Se si utilizza SQL Server 2005, consiglierei comunque una funzione suddivisa su XML. Innanzitutto, crea una funzione:
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
Ora la procedura memorizzata può essere solo:
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ',');
END
GO
E nel tuo codice C # devi solo passare l'elenco come '1,2,3,12'
...
Trovo che il metodo per passare attraverso i parametri con valori di tabella semplifichi la manutenibilità di una soluzione che lo utilizza e spesso ha prestazioni migliorate rispetto ad altre implementazioni tra cui XML e suddivisione delle stringhe.
Gli input sono chiaramente definiti (nessuno deve indovinare se il delimitatore è una virgola o un punto e virgola) e non abbiamo dipendenze da altre funzioni di elaborazione che non sono ovvie senza ispezionare il codice per la procedura memorizzata.
Rispetto alle soluzioni che coinvolgono lo schema XML definito dall'utente anziché gli UDT, ciò comporta un numero simile di passaggi, ma nella mia esperienza è un codice molto più semplice da gestire, mantenere e leggere.
In molte soluzioni potrebbe essere necessario solo uno o alcuni di questi UDT (tipi definiti dall'utente) che si possono riutilizzare per molte procedure memorizzate. Come in questo esempio, il requisito comune è passare attraverso un elenco di puntatori ID, il nome della funzione descrive il contesto che tali ID dovrebbero rappresentare, il nome del tipo dovrebbe essere generico.
SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds)
.
Sulla base della mia esperienza, creando un'espressione delimitata dagli impiegatiID, esiste una soluzione delicata e piacevole per questo problema. Dovresti solo creare un'espressione stringa come ';123;434;365;'
in-quale 123
, 434
e 365
sono alcuni impiegatiID. Chiamando la procedura seguente e passando ad essa questa espressione, è possibile recuperare i record desiderati. Puoi facilmente unire l '"altra tabella" a questa query. Questa soluzione è adatta a tutte le versioni di SQL Server. Inoltre, rispetto all'utilizzo di variabili di tabella o tabelle temporanee, è una soluzione molto più veloce e ottimizzata.
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
Utilizzare un parametro con valori di tabella per la procedura memorizzata.
Quando lo passi da C #, aggiungi il parametro con il tipo di dati di SqlDb.Structured.
Vedi qui: http://msdn.microsoft.com/en-us/library/bb675163.aspx
Esempio:
// 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();
}
Devi passarlo come parametro XML.
Modifica: codice rapido dal mio progetto per darti un'idea:
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)
Quindi puoi utilizzare la tabella temporanea per unirti ai tuoi tavoli. Abbiamo definito arrayOfUlong come uno schema XML incorporato per mantenere l'integrità dei dati, ma non è necessario farlo. Ti consiglierei di usarlo, quindi ecco un codice rapido per assicurarti di ottenere sempre un XML con long.
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
Il contesto è sempre importante, come le dimensioni e la complessità dell'array. Per gli elenchi di dimensioni medio-piccole, molte delle risposte pubblicate qui vanno bene, anche se alcuni chiarimenti dovrebbero essere fatti:
text()
Funzione XML Per ulteriori informazioni (ad es. Analisi delle prestazioni) sull'uso di XML per dividere gli elenchi, consultare "Utilizzo di XML per passare elenchi come parametri in SQL Server" di Phil Factor.DataTable
significa duplicare i dati in memoria mentre vengono copiati dalla raccolta originale. Quindi usando ilDataTable
metodo di trasmissione dei TVP non funziona bene per gruppi di dati più grandi (ovvero non si adatta bene).DataTable
metodo TVP, XML non si adatta bene in quanto raddoppia la dimensione dei dati in memoria in quanto deve inoltre tenere conto dell'overhead del documento XML.Detto questo, SE i dati che stai utilizzando sono grandi o non molto grandi ma in costante crescita, il IEnumerable
metodo TVP è la scelta migliore in quanto trasmette i dati a SQL Server (come ilDataTable
metodo), MA NON richiedere qualsiasi duplicazione della raccolta in memoria (diversamente da qualsiasi altro metodo). Ho pubblicato un esempio del codice SQL e C # in questa risposta:
Non c'è supporto per l'array nel server sql ma ci sono diversi modi con cui è possibile passare la raccolta a un proc memorizzato.
Il seguente link può aiutarti
Ho cercato tutti gli esempi e le risposte su come passare qualsiasi array al server sql senza il fastidio di creare un nuovo tipo di tabella, fino a quando non ho trovato questo linK , di seguito è riportato come l'ho applicato al mio progetto:
- Il codice seguente otterrà un array come parametro e inserirà i valori di quell'array in un'altra tabella
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
Spero ti sia piaciuto
@s
è CSV, sarebbe più veloce dividerlo semplicemente (ovvero INSERT INTO ... SELECT FROM SplitFunction). La conversione in XML è più lenta di CLR e l'XML basato sugli attributi è comunque molto più veloce. E questo è un semplice elenco ma passare in XML o TVP può anche gestire array complessi. Non sono sicuro di ciò che si ottiene evitando un semplice, una volta CREATE TYPE ... AS TABLE
.
Questo ti aiuterà. :) Segui i passaggi successivi,
Copia Incolla il seguente codice così com'è, creerà la funzione che converte la stringa in 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
Creare la seguente procedura memorizzata
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
Esegui questo SP Usando exec sp_DeleteId '1,2,3,12'
questa è una stringa di ID che vuoi eliminare,
Converti l'array in stringa in C # e lo passi come parametro Stored Procedure
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 ;
Questo eliminerà più righe, tutto il meglio
Mi ci è voluto molto tempo per capirlo, quindi nel caso qualcuno ne avesse bisogno ...
Questo si basa sul metodo SQL 2005 nella risposta di Aaron e sull'uso della sua funzione SplitInts (ho appena rimosso il parametro delim dal momento che userò sempre le virgole). Sto usando SQL 2008 ma volevo qualcosa che funzioni con set di dati digitati (XSD, TableAdapters) e so che i parametri di stringa funzionano con quelli.
Stavo cercando di far funzionare la sua funzione in una clausola di tipo "where in (1,2,3)", senza avere fortuna nel modo diretto. Quindi ho creato prima una tabella temporanea, quindi ho fatto un join interno anziché "where in". Ecco il mio esempio di utilizzo, nel mio caso volevo ottenere un elenco di ricette che non contengono determinati ingredienti:
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)
Come altri hanno già notato, un modo per farlo è convertire l'array in una stringa e quindi dividere la stringa all'interno di SQL Server.
A partire da SQL Server 2016, esiste un modo integrato per dividere le stringhe chiamato
STRING_SPLIT ()
Restituisce un set di righe che è possibile inserire nella tabella temporanea (o tabella reale).
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')
produrrebbe:
valore ----- 123 456 789 246 22 33 44 55 66
Se vuoi diventare più elaborato:
DECLARE @tt TABLE (
thenumber int
)
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
INSERT INTO @tt
SELECT value FROM STRING_SPLIT(@str, ';')
SELECT * FROM @tt
ORDER BY thenumber
ti darebbe gli stessi risultati di cui sopra (tranne che il nome della colonna è "thenumber"), ma ordinato. È possibile utilizzare la variabile di tabella come qualsiasi altra tabella, quindi è possibile unirla facilmente con altre tabelle nel DB, se lo si desidera.
Si noti che l'installazione di SQL Server deve essere al livello di compatibilità 130 o superiore per STRING_SPLIT()
poter riconoscere la funzione. Puoi verificare il tuo livello di compatibilità con la seguente query:
SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';
Molte lingue (incluso C #) hanno una funzione "join" che puoi usare per creare una stringa da un array.
int[] myarray = {22, 33, 44};
string sqlparam = string.Join(";", myarray);
Quindi si passa sqlparam
come parametro alla procedura memorizzata sopra.
CREATE TYPE dumyTable
AS TABLE
(
RateCodeId int,
RateLowerRange int,
RateHigherRange int,
RateRangeValue int
);
GO
CREATE PROCEDURE spInsertRateRanges
@dt AS dumyTable READONLY
AS
BEGIN
SET NOCOUNT ON;
INSERT tblRateCodeRange(RateCodeId,RateLowerRange,RateHigherRange,RateRangeValue)
SELECT *
FROM @dt
END