Bir diziyi SQL Server saklı yordamına nasıl geçirilir?
Örneğin, bir çalışan listem var. Bu listeyi tablo olarak kullanmak ve başka bir tabloya katılmak istiyorum. Ancak çalışanların listesi C # 'dan parametre olarak geçirilmelidir.
Bir diziyi SQL Server saklı yordamına nasıl geçirilir?
Örneğin, bir çalışan listem var. Bu listeyi tablo olarak kullanmak ve başka bir tabloya katılmak istiyorum. Ancak çalışanların listesi C # 'dan parametre olarak geçirilmelidir.
Yanıtlar:
İlk olarak, veritabanınızda aşağıdaki iki nesneyi oluşturun:
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
Şimdi C # kodunuzda:
// 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
}
SQL Server 2005 kullanıyorsanız, yine de XML üzerinden bölünmüş bir işlev öneriyoruz. İlk olarak, bir işlev oluşturun:
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
Şimdi saklı yordam sadece olabilir:
CREATE PROCEDURE dbo.DoSomethingWithEmployees
@List VARCHAR(MAX)
AS
BEGIN
SET NOCOUNT ON;
SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ',');
END
GO
Ve C # kodunuzda sadece listeyi geçmek zorundasınız '1,2,3,12'
...
Tablo değerli parametrelerden geçme yönteminin, onu kullanan bir çözümün sürdürülebilirliğini basitleştirdiğini ve XML ve dize bölünmesi de dahil olmak üzere diğer uygulamalara kıyasla genellikle performansı artırdığını görüyorum.
Girişler açıkça tanımlanmıştır (hiç kimse sınırlayıcının virgül mü yoksa noktalı virgül mü olduğunu tahmin etmek zorunda değildir) ve saklı yordamın kodunu incelemeden açık olmayan diğer işleme işlevlerine bağımlılığımız yoktur.
UDT'ler yerine kullanıcı tanımlı XML şemasını içeren çözümlerle karşılaştırıldığında, bu benzer sayıda adım içerir, ancak benim deneyimime göre yönetilmesi, bakımı ve okunması çok daha basit bir koddur.
Birçok çözümde, saklanan birçok yordam için yeniden kullandığınız bu UDT'lerden (Kullanıcı tanımlı Türler) yalnızca bir veya birkaçına ihtiyacınız olabilir. Bu örnekte olduğu gibi, ortak gereksinim bir kimlik işaretçileri listesinden geçmektir, işlev adı bu kimliklerin hangi bağlamı temsil etmesi gerektiğini, tür adının genel olması gerektiğini açıklar.
SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds)
.
Deneyimlerime dayanarak, workerID'lerden sınırlandırılmış bir ifade oluşturarak, bu sorun için zor ve güzel bir çözüm var. Yalnızca gibi bir dize ifade oluşturmak gerekir ';123;434;365;'
ki- 123
, 434
ve 365
bazı employeeIDs bulunmaktadır. Aşağıdaki prosedürü çağırarak ve bu ifadeyi ona ileterek, istediğiniz kayıtları getirebilirsiniz. Kolayca bu sorguya "başka bir tablo" birleştirebilirsiniz. Bu çözüm, SQL sunucusunun tüm sürümleri için uygundur. Ayrıca, tablo değişkeni veya geçici tablo kullanımıyla karşılaştırıldığında, çok daha hızlı ve optimize edilmiş bir çözümdür.
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
Saklı yordamınız için tablo değerli bir parametre kullanın.
C # 'dan ilettiğinizde SqlDb.Structured veri türüne sahip parametreyi eklersiniz.
Buraya bakın: http://msdn.microsoft.com/en-us/library/bb675163.aspx
Misal:
// 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();
}
Bir XML parametresi olarak iletmeniz gerekir.
Düzenleme: size bir fikir vermek için projemden hızlı kod:
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)
Ardından, tablolarınızla birleştirmek için geçici tabloyu kullanabilirsiniz. ArrayOfUlong'u, veri bütünlüğünü korumak için yerleşik bir XML şeması olarak tanımladık, ancak bunu yapmak zorunda değilsiniz. Bu yüzden her zaman uzun bir XML almak emin olmak için hızlı bir kod kullanmanızı öneririz.
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
Dizinin boyutu ve karmaşıklığı gibi bağlam her zaman önemlidir . Küçük ve orta boy listeler için, burada yayınlanan cevapların birçoğu gayet iyi, ancak bazı açıklamalar yapılması gerekiyor:
text()
XML işlevi Listeleri bölmek için XML kullanma hakkında daha fazla bilgi (yani performans analizi) için Phil Factor tarafından "SQL Server'da listeleri parametre olarak geçirmek için XML kullanma" konusuna bakın .DataTable
, orijinal koleksiyondan kopyalanırken verilerin bellekteki çoğaltılması anlamına gelir. Bu nedenle DataTable
, TVP'lere geçme yöntemini kullanmak daha büyük veri setleri için iyi çalışmaz (yani iyi ölçeklenmez).DataTable
TVP yöntemi gibi , XML, ek olarak XML belgesinin ek yükünü de hesaba katması gerektiği için bellekteki veri boyutunu iki katından daha iyi ölçeklemez.Tüm bunlarla birlikte, kullandığınız veriler büyükse veya çok büyük değilse, ancak sürekli olarak büyüyorsa, IEnumerable
TVP yöntemi verileri SQL Server'a ( DataTable
yöntem gibi ) aktardığı için en iyi seçimdir , ancak koleksiyonun bellekte kopyalanmasını gerektirir (diğer yöntemlerin aksine). Bu cevapta SQL ve C # kodunun bir örneğini yayınladım:
Sql sunucusunda dizi için destek yoktur, ancak koleksiyonu depolanan bir proc'a geçirmenin birkaç yolu vardır.
Aşağıdaki bağlantı size yardımcı olabilir
Bütün aracılığıyla örnekler ve ben bu bulana kadar, yeni Tablo türü oluşturarak uğraşmadan sql sunucuya herhangi dizi geçirilecek cevaplarını aradım linki aşağıda benim projeye uyguladı nasıl:
- Aşağıdaki kod, Parametre olarak bir Dizi alacak ve bu --array'ın değerlerini başka bir tabloya ekleyecektir
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
Umarım tadını çıkarırsın
@s
CSV ise, bunu basitçe bölmek daha hızlı olur (örneğin, INSERT INTO ... SELECT FROM SplitFunction). XML'ye dönüştürmek CLR'den daha yavaştır ve özniteliğe dayalı XML yine de çok daha hızlıdır. Ve bu basit bir liste henüz XML veya TVP geçmek de karmaşık diziler işleyebilir. Basit, bir kerelik kaçınarak ne kazanıldığından emin değilim CREATE TYPE ... AS TABLE
.
Bu sana yardım edecek. :) Sonraki adımları izleyin,
Kopyala Aşağıdaki kodu olduğu gibi yapıştırdığınızda, String'i Int'ye dönüştüren İşlev oluşturulur
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
Aşağıdaki saklı yordamı oluşturun
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
Bu SP'yi yürütün Bu exec sp_DeleteId '1,2,3,12'
, silmek istediğiniz bir dizi Kimlik,
Dizinizi C # 'da dizeye dönüştürür ve bir Saklı Yordam parametresi olarak iletirsiniz
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 ;
Bu, birden çok satırı siler, En iyisi
Bunu çözmem çok uzun sürdü, bu yüzden birinin ihtiyacı olması durumunda ...
Bu, Aaron'un cevabındaki SQL 2005 yöntemine ve SplitInts işlevini kullanıyor (her zaman virgül kullanacağımdan beri delim parametresini kaldırdım). SQL 2008 kullanıyorum ama yazılan veri kümeleri (XSD, TableAdapters) ile çalışan bir şey istedim ve dize params bunlarla çalışmak biliyorum.
Ben onun işlevini bir "nerede (1,2,3)" tipi yan tümcesinde çalışmak için almaya çalışıyordum ve düz ileri yolu şanssız. Bu yüzden önce bir geçici tablo oluşturdum ve sonra "nerede" yerine bir iç birleşim yaptım. İşte benim örnek kullanım, benim durumumda belirli maddeler içermeyen tariflerin bir listesini almak istedim:
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)
Diğerlerinin yukarıda belirttiği gibi, bunu yapmanın bir yolu dizinizi bir dizeye dönüştürmek ve sonra dizeyi SQL Server içinde bölmektir.
SQL Server 2016'dan itibaren, dizeleri ayırmak için yerleşik bir yol var.
STRING_SPLIT ()
Geçici tablonuza (veya gerçek tablonuza) ekleyebileceğiniz bir dizi satır döndürür.
DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')
verim verecek:
değer ----- 123 456 789 246 22 33 44 55 66
Daha meraklı olmak istiyorsanız:
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
yukarıdakiyle aynı sonuçları verir (sütun adı "thenumber" dır), ancak sıralanır. Tablo değişkenini herhangi bir tablo gibi kullanabilirsiniz, böylece isterseniz DB'deki diğer tablolarla kolayca birleştirebilirsiniz.
STRING_SPLIT()
Fonksiyonun tanınabilmesi için SQL Server kurulumunuzun uyumluluk seviyesi 130 veya daha yüksek olması gerektiğini unutmayın. Uyumluluk düzeyinizi aşağıdaki sorgu ile kontrol edebilirsiniz:
SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';
Çoğu dilde (C # dahil) bir diziden dize oluşturmak için kullanabileceğiniz bir "birleştirme" işlevi vardır.
int[] myarray = {22, 33, 44};
string sqlparam = string.Join(";", myarray);
Ardından sqlparam
parametreniz olarak yukarıdaki saklı yordama geçersiniz .
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