Cara meneruskan array ke prosedur tersimpan SQL Server

293

Bagaimana cara melewatkan array ke prosedur tersimpan SQL Server?

Misalnya, saya punya daftar karyawan. Saya ingin menggunakan daftar ini sebagai tabel dan bergabung dengan tabel lain. Tetapi daftar karyawan harus diberikan sebagai parameter dari C #.

Sergey
sumber
Pak harap tautan ini akan membantu Anda Melewati daftar / array ke SQL Server SP
patrick choi
Ini adalah kelas yang sama dengan Parameterize klausa SQL IN
Lukasz Szozda

Jawaban:

437

SQL Server 2008 (atau lebih baru)

Pertama, di database Anda, buat dua objek berikut:

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

Sekarang dalam kode C # Anda:

// 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

Jika Anda menggunakan SQL Server 2005, saya masih akan merekomendasikan fungsi split atas XML. Pertama, buat fungsi:

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

Sekarang prosedur tersimpan Anda bisa saja:

CREATE PROCEDURE dbo.DoSomethingWithEmployees
  @List VARCHAR(MAX)
AS
BEGIN
  SET NOCOUNT ON;

  SELECT EmployeeID = Item FROM dbo.SplitInts(@List, ','); 
END
GO

Dan dalam kode C # Anda, Anda hanya harus melewati daftar sebagai '1,2,3,12'...


Saya menemukan metode melewati tabel nilai parameter menyederhanakan rawatan dari solusi yang menggunakannya dan sering memiliki peningkatan kinerja dibandingkan dengan implementasi lain termasuk XML dan pemisah string.

Input didefinisikan dengan jelas (tidak ada yang harus menebak jika pembatas adalah koma atau semi-kolon) dan kami tidak memiliki ketergantungan pada fungsi pemrosesan lain yang tidak jelas tanpa memeriksa kode untuk prosedur yang tersimpan.

Dibandingkan dengan solusi yang melibatkan skema XML yang ditentukan pengguna dan bukan UDT, ini melibatkan sejumlah langkah yang sama tetapi dalam pengalaman saya adalah kode yang jauh lebih sederhana untuk mengelola, memelihara, dan membaca.

Dalam banyak solusi, Anda mungkin hanya memerlukan satu atau beberapa UDT ini (Jenis yang ditentukan pengguna) yang Anda gunakan kembali untuk banyak prosedur tersimpan. Seperti halnya contoh ini, persyaratan umum adalah untuk melewati daftar ID pointer, nama fungsi menjelaskan konteks apa yang harus diwakili Id, tipe nama harus generik.

Aaron Bertrand
sumber
3
Saya suka ide parameter tabel - tidak pernah memikirkan itu - tepuk tangan. Untuk apa pembatas perlu dilewatkan ke panggilan fungsi SplitInts ().
Drammy
Bagaimana saya bisa menggunakan parameter tabel jika hanya memiliki akses ke string yang dipisahkan koma
bdwain
@bdwain akan mengalahkan tujuan - Anda harus menggunakan fungsi split untuk memecahnya menjadi baris untuk dimasukkan ke TVP. Hancurkan dalam kode aplikasi Anda.
Aaron Bertrand
1
@ AaronBertrand terima kasih atas jawabannya, saya sebenarnya baru saja mengetahuinya. Saya harus menggunakan sub pilih dalam kurung: SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds).
JaKXz
3
@ th1rdey3 Mereka secara implisit opsional. stackoverflow.com/a/18926590/61305
Aaron Bertrand
44

Berdasarkan pengalaman saya, dengan membuat ekspresi terbatas dari ID karyawan, ada solusi yang rumit dan bagus untuk masalah ini. Anda hanya harus membuat ekspresi string seperti ';123;434;365;'di dalamnya 123, 434dan 365sebagian adalah ID karyawan. Dengan memanggil prosedur di bawah ini dan menyampaikan ungkapan ini kepadanya, Anda dapat mengambil catatan yang Anda inginkan. Dengan mudah Anda dapat bergabung dengan "tabel lain" ke dalam kueri ini. Solusi ini cocok di semua versi SQL server. Juga, dibandingkan dengan menggunakan variabel tabel atau tabel temp, ini adalah solusi yang sangat cepat dan dioptimalkan.

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
Hamed Nazaktabar
sumber
Bagus! Saya sangat suka pendekatan ini di mana saya memfilter pada kunci int! +1
MDV2000
@ MDV2000 terima kasih :) Pada kunci string, ini juga memiliki kinerja yang baik karena tidak memberikan kolom int ke varchar ...
Hamed Nazaktabar
Saya terlambat ke permainan, tapi ini sangat pintar! Berfungsi bagus untuk masalah saya.
user441058
Ini luar biasa! Saya pasti akan menggunakan ini, terima kasih
Omar Ruder
26

Gunakan parameter bernilai tabel untuk prosedur tersimpan Anda.

Ketika Anda meneruskannya dari C # Anda akan menambahkan parameter dengan tipe data SqlDb. Terstruktur.

Lihat disini: http://msdn.microsoft.com/en-us/library/bb675163.aspx

Contoh:

// 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();
}
Levi W
sumber
17

Anda harus meneruskannya sebagai parameter XML.

Sunting: kode cepat dari proyek saya untuk memberi Anda ide:

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)

Kemudian Anda bisa menggunakan tabel temp untuk bergabung dengan tabel Anda. Kami mendefinisikan arrayOfUlong sebagai skema XML bawaan untuk menjaga integritas data, tetapi Anda tidak harus melakukannya. Saya akan merekomendasikan menggunakannya jadi di sini adalah kode cepat untuk memastikan Anda selalu mendapatkan XML dengan panjang.

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
Fedor Hajdu
sumber
Saya pikir itu ide yang buruk untuk menggunakan variabel tabel ketika ada banyak baris? Bukankah lebih baik menggunakan tabel temp (#table) saja?
ganders
@ pedagang: Saya akan mengatakan sebaliknya.
abatishchev
14

Konteks selalu penting, seperti ukuran dan kompleksitas array. Untuk daftar kecil hingga menengah, beberapa jawaban yang diposting di sini baik-baik saja, meskipun beberapa klarifikasi harus dibuat:

  • Untuk memisahkan daftar yang dibatasi, pembagi berbasis SQLCLR adalah yang tercepat. Ada banyak contoh di sekitar jika Anda ingin menulis sendiri, atau Anda dapat mengunduh SQL # gratis perpustakaan fungsi CLR gratis (yang saya tulis, tetapi fungsi String_Split, dan banyak lainnya, sepenuhnya gratis).
  • Memisahkan array berbasis XML bisa cepat, tetapi Anda harus menggunakan XML berbasis atribut, bukan XML berbasis elemen (yang merupakan satu-satunya jenis yang ditunjukkan dalam jawaban di sini, meskipun contoh XML @ AaronBertrand adalah yang terbaik karena kodenya menggunakan text()Fungsi XML Untuk info lebih lanjut (yaitu analisis kinerja) tentang penggunaan XML untuk membagi daftar, periksa "Menggunakan XML untuk meneruskan daftar sebagai parameter dalam SQL Server" oleh Phil Factor.
  • Menggunakan TVP sangat bagus (dengan asumsi Anda menggunakan setidaknya SQL Server 2008, atau yang lebih baru) karena data dialirkan ke proc dan muncul pra-parsing dan sangat diketik sebagai variabel tabel. NAMUN, dalam kebanyakan kasus, menyimpan semua data dengan DataTablecara menggandakan data dalam memori karena disalin dari koleksi asli. Oleh karena itu menggunakan DataTablemetode melintas di TVP tidak bekerja dengan baik untuk set data yang lebih besar (yaitu tidak skala dengan baik).
  • XML, tidak seperti daftar Ints atau Strings yang dibatasi terbatas, dapat menangani lebih dari array satu dimensi, seperti halnya TVP. Tetapi juga seperti DataTablemetode TVP, XML tidak skala dengan baik karena lebih dari dua kali lipat data dalam memori karena perlu juga memperhitungkan overhead dokumen XML.

Dengan semua yang dikatakan, JIKA data yang Anda gunakan besar atau tidak terlalu besar tetapi tumbuh secara konsisten, maka IEnumerablemetode TVP adalah pilihan terbaik karena mengalirkan data ke SQL Server (seperti DataTablemetode), TAPI tidak membutuhkan duplikasi koleksi dalam memori (tidak seperti metode lainnya). Saya memposting contoh kode SQL dan C # dalam jawaban ini:

Lewati Kamus ke Prosedur Tersimpan T-SQL

Solomon Rutzky
sumber
6

Tidak ada dukungan untuk array di server sql tetapi ada beberapa cara yang Anda dapat melewati koleksi ke proc yang disimpan.

  1. Dengan menggunakan datatable
  2. Dengan menggunakan XML. Cobalah mengonversi koleksi Anda dalam format xml dan kemudian meneruskannya sebagai input ke prosedur tersimpan

Tautan di bawah ini dapat membantu Anda

meneruskan koleksi ke prosedur tersimpan

Praveen
sumber
5

Saya telah mencari-cari semua contoh dan jawaban tentang cara meneruskan array apa pun ke server sql tanpa kesulitan membuat tipe Tabel baru, sampai saya menemukan linK ini , di bawah ini adalah bagaimana saya menerapkannya pada proyek saya:

--Kode berikut akan mendapatkan Array sebagai Parameter dan memasukkan nilai-nilai itu --array ke tabel lain

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 

Berharap kamu menikmatinya

Adam
sumber
2
@zaitsman: CLEANEST bukan berarti yang terbaik atau paling tepat. Seseorang sering kali memberikan fleksibilitas dan / atau kompleksitas yang "pantas" dan / atau kinerja untuk mendapatkan kode "bersih". Jawaban di sini adalah "ok" tetapi hanya untuk set data kecil. Jika array yang masuk @sadalah CSV maka akan lebih cepat untuk hanya membagi itu (yaitu INSERT INTO ... SELECT FROM SplitFunction). Konversi ke XML lebih lambat dari CLR, dan XML berbasis atribut jauh lebih cepat. Dan ini adalah daftar sederhana namun lewat XML atau TVP juga dapat menangani array yang kompleks. Tidak yakin apa yang didapat dengan menghindari yang sederhana, satu kali CREATE TYPE ... AS TABLE.
Solomon Rutzky
5

Ini akan membantu Anda. :) Ikuti langkah selanjutnya,

  1. Buka Desainer Kueri
  2. Copy Paste kode berikut apa adanya, itu akan membuat Function yang mengubah String menjadi 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
  3. Buat prosedur tersimpan berikut

     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
  4. Jalankan SP ini Menggunakan exec sp_DeleteId '1,2,3,12'ini adalah string Id yang ingin Anda hapus,

  5. Anda mengonversi array menjadi string dalam C # dan meneruskannya sebagai parameter Prosedur Tersimpan

    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 ;

Ini akan menghapus beberapa baris, Semua yang terbaik

Charan Ghate
sumber
saya telah menggunakan fungsi parsing terpisah koma ini, itu akan bekerja untuk kumpulan data kecil, jika Anda memeriksa rencana pelaksanaannya, itu akan menyebabkan masalah pada kumpulan data besar dan di mana Anda harus beberapa daftar csv dalam prosedur tersimpan
Saboor Awan
2

Butuh waktu lama untuk memikirkan ini, jadi kalau-kalau ada yang membutuhkannya ...

Ini didasarkan pada metode SQL 2005 dalam jawaban Aaron, dan menggunakan fungsi SplitInts-nya (saya baru saja menghapus parameter delim karena saya akan selalu menggunakan koma). Saya menggunakan SQL 2008 tetapi saya menginginkan sesuatu yang berfungsi dengan dataset yang diketik (XSD, TableAdapters) dan saya tahu string params bekerja dengan itu.

Saya mencoba menjalankan fungsinya dalam klausa ketik "where in (1,2,3)", dan tidak beruntung dengan cara langsung. Jadi saya membuat tabel temp terlebih dahulu, dan kemudian melakukan inner join bukannya "where in". Berikut ini adalah contoh penggunaan saya, dalam kasus saya saya ingin mendapatkan daftar resep yang tidak mengandung bahan-bahan tertentu:

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)
Eselk
sumber
Secara umum, yang terbaik adalah tidak BERGABUNG ke Variabel Tabel, melainkan ke Tabel Temp. Tabel Variabel, secara default, hanya tampak memiliki satu baris, meskipun ada satu atau dua trik di sekitarnya (lihat artikel yang sangat bagus dan terperinci @ AaronBertrand: sqlperformance.com/2014/06/t-sql-queries/… ).
Solomon Rutzky
1

Seperti yang orang lain catat di atas, salah satu cara untuk melakukan ini adalah dengan mengubah array Anda menjadi string dan kemudian membagi string di dalam SQL Server.

Pada SQL Server 2016, ada cara bawaan untuk memisahkan string yang disebut

STRING_SPLIT ()

Ini mengembalikan satu set baris yang bisa Anda masukkan ke tabel temp (atau tabel sebenarnya) Anda.

DECLARE @str varchar(200)
SET @str = "123;456;789;246;22;33;44;55;66"
SELECT value FROM STRING_SPLIT(@str, ';')

akan menghasilkan:

nilai
-----
  123
  456
  789
  246
   22
   33
   44
   55
   66

Jika Anda ingin menjadi pelamun:

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

akan memberi Anda hasil yang sama seperti di atas (kecuali nama kolom adalah "thenumber"), tetapi diurutkan. Anda bisa menggunakan variabel tabel seperti tabel lainnya, sehingga Anda dapat dengan mudah bergabung dengan tabel lain di DB jika Anda mau.

Perhatikan bahwa penginstalan SQL Server Anda harus pada tingkat kompatibilitas 130 atau lebih tinggi agar STRING_SPLIT()fungsi dapat dikenali. Anda dapat memeriksa tingkat kompatibilitas Anda dengan pertanyaan berikut:

SELECT compatibility_level
FROM sys.databases WHERE name = 'yourdatabasename';

Sebagian besar bahasa (termasuk C #) memiliki fungsi "bergabung" yang dapat Anda gunakan untuk membuat string dari array.

int[] myarray = {22, 33, 44};
string sqlparam = string.Join(";", myarray);

Kemudian Anda meneruskan sqlparamsebagai parameter Anda ke prosedur tersimpan di atas.

Patrick Chu
sumber
0
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
Shabir Mustafa
sumber