SQL Server 저장 프로 시저에 배열을 전달하는 방법


293

SQL Server 저장 프로 시저에 배열을 전달하는 방법은 무엇입니까?

예를 들어 직원 목록이 있습니다. 이 목록을 테이블로 사용하고 다른 테이블과 조인하고 싶습니다. 그러나 직원 목록은 C #에서 매개 변수로 전달해야합니다.


선생님이 링크는 당신을 도울 것입니다 희망 SQL 서버 SP에 목록 / 배열을 전달
패트릭 최

답변:


437

SQL Server 2008 이상

먼저, 데이터베이스에서 다음 두 오브젝트를 작성하십시오.

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

이제 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
}

SQL Server 2005

SQL Server 2005를 사용하는 경우에도 XML보다 분할 기능을 권장합니다. 먼저 함수를 작성하십시오.

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

이제 저장 프로시 저는 다음과 같습니다.

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

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

그리고 C # 코드에서는 목록을 다음과 같이 전달하면됩니다 '1,2,3,12'...


테이블 값 매개 변수를 통과하는 방법은 솔루션을 사용하는 솔루션의 유지 관리를 단순화하고 XML 및 문자열 분할을 포함한 다른 구현에 비해 성능이 향상되는 경우가 많습니다.

입력이 명확하게 정의되어 있으며 (구분자가 쉼표 또는 세미콜론인지 추측 할 필요가 없음) 저장 프로 시저의 코드를 검사하지 않으면 명확하지 않은 다른 처리 기능에 종속되지 않습니다.

UDT 대신 사용자 정의 XML 스키마와 관련된 솔루션과 비교할 때 비슷한 단계가 필요하지만 경험상 관리, 유지 관리 및 읽기가 훨씬 간단한 코드입니다.

많은 솔루션에서 많은 저장 프로 시저에 재사용하는 이러한 UDT (사용자 정의 유형) 중 하나 또는 몇 개만 필요할 수 있습니다. 이 예제와 마찬가지로 일반적인 요구 사항은 ID 포인터 목록을 통과하는 것입니다. 함수 이름은 해당 ID가 나타내는 컨텍스트를 설명하고 형식 이름은 일반이어야합니다.


3
나는 테이블 매개 변수 아이디어를 좋아합니다. 그 가치가있는 것에 대해서는 분리자가 SplitInts () 함수 호출에 전달되어야합니다.
Drammy

쉼표로 구분 된 문자열에만 액세스 할 수있는 경우 표 매개 변수를 사용하는 방법
bdwain

@bdwain은 목적을 잃을 것입니다-TVP에 넣으려면 split 함수를 사용하여 행으로 나누어야합니다. 응용 프로그램 코드에서 분리하십시오.
Aaron Bertrand

1
@AaronBertrand는 답변 주셔서 감사합니다, 나는 실제로 그것을 알아 냈습니다. 괄호 안에 하위 선택을 사용해야합니다 SELECT [colA] FROM [MyTable] WHERE [Id] IN (SELECT [Id] FROM @ListOfIds).
JaKXz

3
@ th1rdey3 암시 적으로 선택 사항입니다. stackoverflow.com/a/18926590/61305
Aaron Bertrand

44

내 경험을 바탕으로 employeeID에서 구분 된 표현식을 작성하면이 문제에 대한 까다 롭고 멋진 솔루션이 있습니다. 당신은 같은 문자열 식을 작성해야 ';123;434;365;'하는- 123, 434그리고 365일부 employeeIDs이다. 아래 프로 시저를 호출하고이 표현식을 전달하면 원하는 레코드를 가져올 수 있습니다. "다른 테이블"을이 쿼리에 쉽게 조인 할 수 있습니다. 이 솔루션은 모든 버전의 SQL Server에 적합합니다. 또한 테이블 변수 또는 임시 테이블을 사용하는 것과 비교하여 매우 빠르고 최적화 된 솔루션입니다.

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

좋은! 나는 int 키를 필터링하는이 접근법을 정말로 좋아합니다! +1
MDV2000

@ MDV2000 thanks :) 문자열 키에서 int 열을 varchar로 캐스팅하지 않기 때문에 성능도 우수합니다.
Hamed Nazaktabar

나는 게임에 늦었지만 이것은 매우 영리하다! 내 문제에 잘 작동합니다.
user441058

이것은 놀랍다! 감사합니다
Omar Ruder

26

저장 프로 시저에 테이블 반환 매개 변수를 사용하십시오.

C #에서 전달하면 SqlDb.Structured 데이터 형식의 매개 변수가 추가됩니다.

여기를 참조하십시오 : http://msdn.microsoft.com/en-us/library/bb675163.aspx

예:

// 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();
}

17

XML 매개 변수로 전달해야합니다.

편집 : 내 프로젝트의 빠른 코드로 아이디어를 얻을 수 있습니다.

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)

그런 다음 임시 테이블을 사용하여 테이블과 조인 할 수 있습니다. 데이터 무결성을 유지하기 위해 arrayOfUlong을 내장 XML 스키마로 정의했지만 반드시 그럴 필요는 없습니다. 항상 XML을 오래 사용할 수 있도록하는 빠른 코드가 있으므로 사용하는 것이 좋습니다.

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

많은 행이있을 때 테이블 변수를 사용하는 것이 좋지 않다고 생각 했습니까? 대신 임시 (#table) 테이블을 사용하는 것이 더 좋지 않습니까?
ganders

@ ganders : 그 반대의 경우도 마찬가지입니다.
abatishchev

14

배열 의 크기복잡성 과 같은 컨텍스트는 항상 중요 합니다. 중소 규모의 목록의 경우 여기에 게시 된 몇 가지 답변은 괜찮지 만 몇 가지 설명이 필요합니다.

  • 구분 된 목록을 분할하는 경우 SQLCLR 기반 분할기가 가장 빠릅니다. 직접 작성하고 싶거나 CLR 함수 의 무료 SQL # 라이브러리 (내가 작성했지만 String_Split 함수 등은 완전히 무료입니다)를 다운로드 할 수 있습니다.
  • XML 기반 배열을 분할하는 것은 빠를 있지만 요소 기반 XML이 아닌 속성 기반 XML을 사용해야합니다 (여기서 답변에 표시된 유일한 유형이지만 @AaronBertrand의 XML 예제는 코드에서 text()XML 함수 XML을 사용하여 목록을 분할하는 방법에 대한 자세한 내용 (예 : 성능 분석) 은 Phil Factor의 "XML을 사용하여 목록을 SQL Server의 매개 변수로 전달" 을 참조하십시오.
  • 데이터가 proc에 스트리밍되고 미리 분석되고 강력한 형식의 테이블 변수로 표시되므로 TVP를 사용하는 것이 좋습니다 (최소한 SQL Server 2008 이상을 사용한다고 가정). 그러나 대부분의 경우 모든 데이터를 저장 DataTable한다는 것은 데이터가 원본 컬렉션에서 복사 될 때 데이터를 메모리에 복제하는 것을 의미합니다. 따라서 DataTableTVP를 전달 하는 방법을 사용하면 더 큰 데이터 세트에 대해 잘 작동하지 않습니다 (즉, 잘 확장되지 않음).
  • XML은 간단한 구분 된 Int 또는 문자열 목록과 달리 TVP와 마찬가지로 1 차원 이상의 배열을 처리 할 수 ​​있습니다. 그러나 DataTableTVP 방법 과 마찬가지로 XML은 XML 문서의 오버 헤드를 추가로 고려해야하기 때문에 메모리의 데이터 크기를 두 배 이상 확장 할 수 있으므로 확장 성이 떨어집니다.

이 모든 것을 말하지만, 사용중인 데이터가 크거나 아직 크지 않지만 꾸준히 증가하는 경우 IEnumerableTVP 방법은 데이터와 같은 DataTable방법 으로 SQL Server로 데이터를 스트리밍 할 때 최선의 선택 이지만 BUT은 그렇지 않습니다. 다른 방법과 달리 메모리에 컬렉션을 복제해야합니다. 이 답변에 SQL 및 C # 코드의 예를 게시했습니다.

저장 프로 시저 T-SQL에 사전 전달


6

SQL Server에서는 배열을 지원하지 않지만 컬렉션을 저장된 proc에 전달할 수있는 몇 가지 방법이 있습니다.

  1. datatable을 사용함으로써
  2. XML을 사용하여 컬렉션을 XML 형식으로 변환 한 다음 저장 프로 시저에 입력으로 전달

아래 링크가 도움이 될 수 있습니다

저장 프로 시저에 컬렉션 전달


5

새로운 테이블 유형을 만드는 번거 로움없이 배열을 SQL Server에 전달하는 방법에 대한 모든 예제와 답변을 검색했습니다.이 linK를 찾을 때까지 아래는 내 프로젝트에 적용하는 방법입니다.

다음 코드는 Array를 Parameter로 가져 와서 --array의 값을 다른 테이블에 삽입합니다

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 

즐기 셨으면 좋겠습니다


2
@zaitsman : CLEANEST가 가장 적합하거나 가장 적합한 것은 아닙니다. "깨끗한"코드를 얻기 위해 종종 유연성 및 / 또는 "적절한"복잡성 및 / 또는 성능을 포기합니다. 이 답변은 "확인"이지만 소규모 데이터 세트에만 해당됩니다. 들어오는 배열 @s이 CSV이면 간단히 분할하는 것이 더 빠릅니다 (예 : INSERT INTO ... SELECT FROM SplitFunction). XML로 변환하는 것은 CLR보다 느리고 속성 기반 XML은 훨씬 빠릅니다. 그리고 이것은 간단한 목록이지만 XML이나 TVP로 전달하면 복잡한 배열도 처리 할 수 ​​있습니다. 간단한 일회성을 피함으로써 얻는 것이 무엇인지 확실하지 않습니다 CREATE TYPE ... AS TABLE.
Solomon Rutzky

5

도움이 될 것입니다. :) 다음 단계를 따르십시오.

  1. 쿼리 디자이너를 엽니 다
  2. 복사 다음 코드를 그대로 붙여 넣으면 String을 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. 다음 스토어드 프로 시저 작성

     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. 이 SP 실행이 exec sp_DeleteId '1,2,3,12'문자열을 사용 하여 삭제하려는 ID의 문자열입니다.

  5. C #에서 배열을 문자열로 변환하여 저장 프로 시저 매개 변수로 전달합니다

    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 ;

이렇게하면 여러 행이 삭제됩니다.


이 쉼표로 구분 된 구문 분석 기능을 사용했습니다. 실행 계획을 확인하는 경우 작은 데이터 세트에서 작동합니다. 큰 데이터 세트에서 문제가 발생하고 저장 프로 시저에서 여러 csv 목록을
사용해야하는 위치

2

이것을 알아내는 데 오랜 시간이 걸렸으므로 누군가가 필요로하는 경우 ...

이것은 Aaron의 답변에서 SQL 2005 방법과 SplitInts 함수를 사용합니다 (항상 쉼표를 사용하기 때문에 delim 매개 변수를 제거했습니다). SQL 2008을 사용하고 있지만 유형이 지정된 데이터 세트 (XSD, TableAdapters)에서 작동하는 것을 원했고 문자열 매개 변수가 작동한다는 것을 알고 있습니다.

나는 그의 기능이 "(1,2,3)"형 절에서 작동하도록하려고했고, 직설적 인 운이 없었습니다. 그래서 임시 테이블을 먼저 만든 다음 "where in"대신 내부 조인을 수행했습니다. 다음은 사용법 예입니다. 제 경우에는 특정 성분을 포함하지 않는 요리법 목록을 원했습니다.

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)

일반적으로 테이블 변수에 참여하는 것이 아니라 임시 테이블에 참여하는 것이 가장 좋습니다. 테이블 변수는 기본적으로 하나의 행만있는 것처럼 보이지만 그 주위에는 트릭이 두 개 있습니다 (@AaronBertrand의 우수하고 자세한 기사 : sqlperformance.com/2014/06/t-sql-queries/… ).
Solomon Rutzky

1

다른 사람들이 위에서 언급했듯이 이것을 수행하는 한 가지 방법은 배열을 문자열로 변환 한 다음 SQL Server 내에서 문자열을 분할하는 것입니다.

SQL Server 2016부터는 문자열을 분리하는 기본 제공 방법이 있습니다.

STRING_SPLIT ()

임시 테이블 (또는 실제 테이블)에 삽입 할 수있는 행 집합을 반환합니다.

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

산출 :

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

더 멋진 모습을 원한다면 :

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

위와 동일한 결과를 제공하지만 (열 이름이 "숫자"인 경우는 제외) 정렬됩니다. 테이블 변수를 다른 테이블처럼 사용할 수 있으므로 원하는 경우 DB의 다른 테이블과 쉽게 조인 할 수 있습니다.

STRING_SPLIT()기능을 인식 하려면 SQL Server 설치가 호환성 수준 130 이상 이어야합니다. 다음 쿼리를 통해 호환성 수준을 확인할 수 있습니다.

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

C #을 포함한 대부분의 언어에는 배열에서 문자열을 만드는 데 사용할 수있는 "join"기능이 있습니다.

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

그런 다음 sqlparam위의 저장 프로 시저에 매개 변수로 전달 합니다.


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
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.