Jak przekazać tablicę do procedury przechowywanej programu SQL Server?
Na przykład mam listę pracowników. Chcę użyć tej listy jako tabeli i połączyć ją z inną tabelą. Ale listę pracowników należy przekazać jako parametr z C #.
Jak przekazać tablicę do procedury przechowywanej programu SQL Server?
Na przykład mam listę pracowników. Chcę użyć tej listy jako tabeli i połączyć ją z inną tabelą. Ale listę pracowników należy przekazać jako parametr z C #.
Odpowiedzi:
Najpierw w swojej bazie danych utwórz następujące dwa obiekty:
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
Teraz w kodzie 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
}
Jeśli korzystasz z programu SQL Server 2005, nadal polecałbym funkcję podziału zamiast XML. Najpierw utwórz funkcję:
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
Teraz procedura składowana może być po prostu:
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ',');
END
GO
A w kodzie C # wystarczy przekazać listę jako '1,2,3,12'
...
Uważam, że metoda przekazywania parametrów wycenianych w tabeli upraszcza konserwację rozwiązania, które z niej korzysta i często ma większą wydajność w porównaniu z innymi implementacjami, w tym XML i dzielenie ciągów.
Dane wejściowe są jasno zdefiniowane (nikt nie musi zgadywać, czy separatorem jest przecinek czy średnik) i nie mamy zależności od innych funkcji przetwarzania, które nie są oczywiste bez sprawdzenia kodu procedury przechowywanej.
W porównaniu z rozwiązaniami obejmującymi zdefiniowany przez użytkownika schemat XML zamiast UDT, wymaga to podobnej liczby kroków, ale z mojego doświadczenia wynika, że kod jest o wiele prostszy w zarządzaniu, utrzymywaniu i czytaniu.
W wielu rozwiązaniach może być potrzebny tylko jeden lub kilka z tych UDT (typy zdefiniowane przez użytkownika), których ponownie używasz do wielu procedur przechowywanych. Podobnie jak w tym przykładzie, powszechnym wymogiem jest przekazywanie listy wskaźników identyfikatorów, nazwa funkcji opisuje kontekst, jaki powinny reprezentować te identyfikatory, nazwa typu powinna być ogólna.
SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds)
.
Z mojego doświadczenia wynika, że tworzenie identyfikatora oddzielonego od identyfikatora pracownika jest trudnym i przyjemnym rozwiązaniem tego problemu. Należy utworzyć tylko wyrażenie łańcuchowe, takie jak ';123;434;365;'
in-which 123
, 434
i 365
są to niektóre identyfikatory pracowników. Wywołując poniższą procedurę i przekazując do niej to wyrażenie, możesz pobrać żądane rekordy. Łatwo możesz dołączyć „kolejną tabelę” do tego zapytania. To rozwiązanie jest odpowiednie we wszystkich wersjach serwera SQL. Ponadto, w porównaniu z użyciem tabeli zmiennych lub tabeli temperatur, jest to bardzo szybsze i zoptymalizowane rozwiązanie.
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
Użyj parametru o wartościach przechowywanych w tabeli dla procedury składowanej.
Po przekazaniu go z C # dodasz parametr o typie danych SqlDb.Structured.
Zobacz tutaj: http://msdn.microsoft.com/en-us/library/bb675163.aspx
Przykład:
// 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();
}
Musisz przekazać go jako parametr XML.
Edytuj: szybki kod z mojego projektu, aby dać ci pomysł:
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)
Następnie możesz użyć tabeli tymczasowej, aby połączyć się ze swoimi tabelami. Zdefiniowaliśmy arrayOfUlong jako wbudowany schemat XML, aby zachować integralność danych, ale nie musisz tego robić. Polecam go użyć, więc oto krótki kod, aby upewnić się, że zawsze otrzymujesz XML z długimi.
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
Kontekst jest zawsze ważny, na przykład rozmiar i złożoność tablicy. W przypadku list od małych do średnich kilka opublikowanych tutaj odpowiedzi jest w porządku, choć należy wyjaśnić:
text()
Funkcja XML Aby uzyskać więcej informacji (np. Analizę wydajności) na temat używania XML do dzielenia list, zobacz „Używanie XML do przekazywania list jako parametrów w SQL Server” Phila Factora.DataTable
oznacza duplikowanie danych w pamięci podczas kopiowania z oryginalnej kolekcji. Stąd korzystanie zDataTable
metody przekazywania w TVP nie działa dobrze w przypadku większych zestawów danych (tj. Nie jest dobrze skalowane).DataTable
metoda TVP, XML nie skaluje się dobrze, ponieważ podwaja rozmiar danych w pamięci, ponieważ musi dodatkowo uwzględniać narzut dokumentu XML.Biorąc to wszystko pod uwagę, JEŻELI używane dane są duże lub jeszcze niezbyt duże, ale stale rosną, to IEnumerable
metoda TVP jest najlepszym wyborem, ponieważ przesyła dane do SQL Server (jakDataTable
metoda), ALE nie wymagają jakiegokolwiek powielenia kolekcji w pamięci (w przeciwieństwie do innych metod). W tej odpowiedzi opublikowałem przykład kodu SQL i C #:
Serwer SQL nie obsługuje tablicy, ale istnieje kilka sposobów przekazywania kolekcji do przechowywanego proc.
Poniższy link może ci pomóc
Przeszukiwałem wszystkie przykłady i odpowiedzi, jak przekazać dowolną tablicę do serwera SQL bez kłopotów z tworzeniem nowego typu tabeli, dopóki nie znalazłem tego linK , poniżej jest to, jak zastosowałem go do mojego projektu:
- Poniższy kod pobiera tablicę jako parametr i wstawia wartości tej tablicy do innej tabeli
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
Mam nadzieję, że ci się spodoba
@s
jest CSV, szybsze byłoby po prostu podzielenie tego (tj. INSERT INTO ... SELECT FROM SplitFunction). Konwersja na XML przebiega wolniej niż CLR, a XML oparty na atrybutach i tak jest znacznie szybszy. Jest to prosta lista, ale przekazywanie w XML lub TVP może również obsługiwać złożone tablice. Nie jestem pewien, co zyskasz, unikając prostego, jednorazowego działania CREATE TYPE ... AS TABLE
.
To ci pomoże. :) Wykonaj kolejne kroki,
Kopiuj Wklej następujący kod w obecnej postaci, utworzy on funkcję, która przekształci ciąg na 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
Utwórz następującą procedurę składowaną
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
Wykonaj ten SP. Używając exec sp_DeleteId '1,2,3,12'
tego jest ciąg identyfikatorów, które chcesz usunąć,
Konwertujesz tablicę na ciąg w C # i przekazujesz ją jako parametr procedury składowanej
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 ;
Spowoduje to usunięcie wielu wierszy, wszystkiego najlepszego
Zajęło mi to dużo czasu, aby to zrozumieć, więc na wypadek, gdyby ktoś tego potrzebował ...
Jest to oparte na metodzie SQL 2005 w odpowiedzi Aarona i użyciu jego funkcji SplitInts (właśnie usunąłem parametr delim, ponieważ zawsze będę używać przecinków). Korzystam z SQL 2008, ale chciałem czegoś, co będzie działać z typowymi zestawami danych (XSD, TableAdapters) i wiem, że parametry ciągów działają z nimi.
Próbowałem zmusić jego funkcję do działania w klauzuli typu „gdzie w (1,2,3)” i nie miałem szczęścia w prosty sposób. Więc najpierw stworzyłem tabelę tymczasową, a następnie wykonałem wewnętrzne połączenie zamiast „gdzie w”. Oto moje przykładowe użycie, w moim przypadku chciałem uzyskać listę przepisów, które nie zawierają niektórych składników:
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)
Jak zauważyli inni powyżej, jednym ze sposobów jest przekonwertowanie tablicy na ciąg, a następnie podzielenie ciągu wewnątrz SQL Server.
Począwszy od SQL Server 2016, istnieje wbudowany sposób na dzielenie ciągów o nazwie
STRING_SPLIT ()
Zwraca zestaw wierszy, które można wstawić do tabeli tymczasowej (lub tabeli rzeczywistej).
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')
dałoby:
wartość ----- 123 456 789 246 22 33 44 55 66
Jeśli chcesz zdobyć bardziej fantazyjne:
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
dałoby takie same wyniki jak powyżej (z wyjątkiem tego, że nazwa kolumny to „numer”), ale posortowane. Możesz używać zmiennej tabeli jak każdej innej tabeli, więc możesz łatwo połączyć ją z innymi tabelami w bazie danych, jeśli chcesz.
Należy pamiętać, że instalacja programu SQL Server musi mieć poziom zgodności 130 lub wyższy, aby STRING_SPLIT()
funkcja została rozpoznana. Możesz sprawdzić poziom zgodności za pomocą następującego zapytania:
SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';
Większość języków (w tym C #) ma funkcję „łączyć”, za pomocą której można utworzyć ciąg z tablicy.
int[] myarray = {22, 33, 44};
string sqlparam = string.Join(";", myarray);
Następnie przekazujesz sqlparam
jako parametr powyższej procedury składowanej.
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