2016-09-20 2 views
2

'IN'절의 매개 변수에 대한 SQL 제한을 피하기 위해 Dapper를 사용하여 임시 테이블에 삽입 할 ID가 들어있는 List가 있습니다.Dapper를 사용하여 IN 절에서 2100 개가 넘는 값을 사용하려면 어떻게해야합니까?

public IList<int> LoadAnimalTypeIdsFromAnimalIds(IList<int> animalIds) 
{ 
    using (var db = new SqlConnection(this.connectionString)) 
    { 
     return db.Query<int>(
      @"SELECT a.animalID   
      FROM 
      dbo.animalTypes [at] 
      INNER JOIN animals [a] on a.animalTypeId = at.animalTypeId 
      INNER JOIN edibleAnimals e on e.animalID = a.animalID 
      WHERE 
      at.animalId in @animalIds", new { animalIds }).ToList(); 
    } 
} 

내가 해결해야 할 문제가 2100 개 이상의 ID를 다음 animalIds 목록에있을 때 나는 SQL 오류가 있다는 것입니다 "들어오는 요청도 있습니다

그래서 현재 내 코드는 다음과 같습니다 많은 매개 변수. 서버는 최대 2100 개의 매개 변수를 지원합니다. "

그래서 이제 메서드에 전달 된 animalIds로 채워진 임시 테이블을 만들고 싶습니다. 그러면 임시 테이블의 동물 테이블에 가입하고 거대한 "IN"절을 사용하지 않아도됩니다.

다양한 구문 조합을 시도했지만 어디에도 없습니다.

public IList<int> LoadAnimalTypeIdsFromAnimalIds(IList<int> animalIds) 
{ 
    using (var db = new SqlConnection(this.connectionString)) 
    { 
     db.Execute(@"SELECT INTO #tempAnmialIds @animalIds"); 

     return db.Query<int>(
      @"SELECT a.animalID   
      FROM 
      dbo.animalTypes [at] 
      INNER JOIN animals [a] on a.animalTypeId = at.animalTypeId 
      INNER JOIN edibleAnimals e on e.animalID = a.animalID 
      INNER JOIN #tempAnmialIds tmp on tmp.animalID = a.animalID).ToList(); 
    } 
} 

내가 ID의 목록과 함께 작업 SELECT INTO를 얻을 수 없습니다 : 내가 지금있는 곳 이입니다. 내가 잘못된 길로 가고 있을지도 모르지만 아마 "IN"절의 한계를 피하는 더 좋은 방법이있을 것입니다.

나는 animalID의 들어오는 목록을 1000 개의 블록으로 나눌 수 있다는 점에서 백업 솔루션을 가지고 있지만 큰 "IN"절이 성능에 영향을 미치고 임시 테이블에 가입하는 것이 더 효율적이며 그것은 또한 내가 돈을 의미합니다; 1000의 블록으로 ID를 일괄 처리하기 위해 별도의 '분할'코드가 필요하지 않습니다.

+0

'INSERT INTO'문은 어디에 있습니까? –

+0

2100 값은 매우 큰 숫자입니다. 적절한 인덱싱을 사용하여 테이블에 저장해야합니다. 이 가치들은 어디서 비롯된 것입니까? 그것들은 질의의 결과입니까? 왜 SELECT 문에 포함시키지 않겠습니까? 아니면 외부 소스 (예 : CSV 파일)에서 왔습니까? 그런 다음 가장 좋은 방법은 파일을 스테이징 테이블로 가져온 다음 해당 테이블에 결합하는 것입니다. –

+0

이들은 CSV –

답변

4

좋아, 다음은 원하는 버전입니다. SP/TVP를 사용하여 만든 첫 번째 대답은 다른 개념을 사용하므로 별도의 대답으로 추가하고 있습니다. 테스트하려면

public IList<int> LoadAnimalTypeIdsFromAnimalIds(IList<int> animalIds) 
{ 
    using (var db = new SqlConnection(this.connectionString)) 
    { 
    // This Open() call is vital! If you don't open the connection, Dapper will 
    // open/close it automagically, which means that you'll loose the created 
    // temp table directly after the statement completes. 
    db.Open(); 

    // This temp table is created having a primary key. So make sure you don't pass 
    // any duplicate IDs 
    db.Execute("CREATE TABLE #tempAnimalIds(animalId int not null primary key);"); 
    while (animalIds.Any()) 
    { 
     // Build the statements to insert the Ids. For this, we need to split animalIDs 
     // into chunks of 1000, as this flavour of INSERT INTO is limited to 1000 values 
     // at a time. 
     var ids2Insert = animalIds.Take(1000); 
     animalIds = animalIds.Skip(1000).ToList(); 

     StringBuilder stmt = new StringBuilder("INSERT INTO #tempAnimalIds VALUES ("); 
     stmt.Append(string.Join("),(", ids2Insert)); 
     stmt.Append(");"); 

     db.Execute(stmt.ToString()); 
    } 

    return db.Query<int>(@"SELECT animalID FROM #tempAnimalIds").ToList(); 
    } 
} 

:

당신은 그냥 원래 무엇인지에 select 문을 개정 할 필요가
var ids = LoadAnimalTypeIdsFromAnimalIds(Enumerable.Range(1, 2500).ToList()); 

. 내 환경에서 모든 테이블을 보유하고 있지 않기 때문에 필자는 임시 테이블에서 선택한 방식대로 작동하는지 확인했습니다.

주의 할 점은, 주석을 참조하십시오

  • 열기 시작 부분에 연결을, 그렇지 않으면 임시 테이블이 사라질 것 날씬한 자동으로 테이블을 생성 바로 후 연결을 종료 한 후.
  • INSERT INTO의 특정 향미료는 한 번에 개까지 1000 개의 값으로 제한되므로 전달 된 ID는 개의 청크로 분할해야합니다.
  • 임시 테이블의 기본 키가 허용하지 않으므로 중복 키를 전달하지 마십시오. 나는이 이전에 비해 수행하는 방법을 잘 모르는

    public IList<int> LoadAnimalTypeIdsFromAnimalIdsV2(IList<int> animalIds) 
    { 
        // This creates an IEnumerable of an anonymous type containing an Id property. This seems 
        // to be necessary to be able to grab the Id by it's name via Dapper. 
        var namedIDs = animalIds.Select(i => new {Id = i}); 
        using (var db = new SqlConnection(this.connectionString)) 
        { 
        // This is vital! If you don't open the connection, Dapper will open/close it 
        // automagically, which means that you'll loose the created temp table directly 
        // after the statement completes. 
        db.Open(); 
    
        // This temp table is created having a primary key. So make sure you don't pass 
        // any duplicate IDs 
        db.Execute("CREATE TABLE #tempAnimalIds(animalId int not null primary key);"); 
    
        // Using one of Dapper's convenient features, the INSERT becomes: 
        db.Execute("INSERT INTO #tempAnimalIds VALUES(@Id);", namedIDs); 
    
        return db.Query<int>(@"SELECT animalID FROM #tempAnimalIds").ToList(); 
        } 
    } 
    

    :

편집 그것은 단정 한 것 같다

도이 일을 할 것입니다 집합 기반 작업을 지원합니다 버전 (즉, 각각 1000, 1000, 500 값을 가진 3 개의 삽입 대신 2500 개의 단일 삽입). 그러나 의사는 비동기, MARS 및 파이프 라이닝과 함께 사용하면 성능이 향상 될 것이라고 제안합니다.

+0

이 버전은 색인 생성과 통계 때문에 더 잘 작동 할 것이다. TVP에는 색인이없고 최적화 프로그램은 하나의 행만 포함한다고 가정합니다. 'IN' 절이 TVP보다 빠릅니다. –

+0

@PanagiotisKanavos 언제나 인덱스 나 PK로 테이블 유형을 생성하면 TVP에 활용 될 것이라고 생각했습니다. 그렇지 않은가요? – takrl

+0

[SQL Server는 통계를 유지 관리하지 않습니다.] (https://msdn.microsoft.com/en-us/library/bb510489.aspx # Anchor_1) TVP의 경우 카디널리티 예상치가 낮아집니다. 즉, 옵티마이 저는 몇 개의 고유 한 값이 있는지 알지 못합니다. 이로 인해 기본 키가 있더라도 비효율적 인 실행 계획이 발생할 수 있습니다. > 2100 행이면 벤치마킹이 충분합니다. 2100 개 이상의 행을 가지고 있지만 소스 데이터를 가져오고 마사지하기 위해 적절한 스테이징 테이블을 사용하고 싶습니다. –

0

예에서 볼 수있는 것은 animalIds의 목록이 실제로 쿼리에 전달되는 방법입니다 #tempAnimalIDs 테이블에 삽입됩니다.

테이블 값 매개 변수가있는 저장 프로 시저를 사용하여 임시 테이블을 사용하지 않고이를 수행하는 방법이 있습니다.

SQL :

CREATE TYPE [dbo].[udtKeys] AS TABLE([i] [int] NOT NULL) 
GO 
SET ANSI_NULLS ON 
GO 
SET QUOTED_IDENTIFIER ON 
GO 
CREATE PROCEDURE [dbo].[myProc](@data as dbo.udtKeys readonly)AS 
BEGIN 
    select i from @data; 
END 
GO 

이 단 하나의 INT의 i라는 이름의 열 및 해당 유형의 매개 변수를 예상하는 저장 프로 시저를 포함 udtKeys라는 사용자 정의 테이블 형식을 생성합니다. proc은 전달한 ID를 선택하는 대신 다른 작업을 수행하지만 다른 테이블을 조인 할 수는 있습니다. 구문과 관련된 힌트는 see here입니다.

C 번호 :

var dataTable = new DataTable(); 
dataTable.Columns.Add("i", typeof(int)); 
foreach (var animalId in animalIds) 
    dataTable.Rows.Add(animalId); 
using(SqlConnection conn = new SqlConnection("connectionString goes here")) 
{ 
    var r=conn.Query("myProc", new {data=dataTable},commandType: CommandType.StoredProcedure); 
    // r contains your results 
} 

절차 내에서 매개 변수는 DataTable을 전달하여 채워 도착 및 DataTable에의 구조는 사용자가 만든 테이블 형식의 하나와 일치해야합니다.

실제로 2100 개 이상의 값을 전달해야하는 경우 성능을 높이기 위해 테이블 ​​유형을 인덱싱하는 것이 좋습니다. 이 같은 중복 된 키를 전달하지 않는 경우에 당신은 실제로 그것을 기본 키를 제공 할 수 있습니다 :

당신은 또한 당신과 같이, 이것을 실행하는 데이터베이스 사용자 유형에 대한 실행 권한을 할당해야 할 수 있습니다
CREATE TYPE [dbo].[udtKeys] AS TABLE(
    [i] [int] NOT NULL, 
    PRIMARY KEY CLUSTERED 
    (
     [i] ASC 
    )WITH (IGNORE_DUP_KEY = OFF) 
) 
GO 

:

GRANT EXEC ON TYPE::[dbo].[udtKeys] TO [User] 
GO 

herehere를 참조하십시오.

+0

고맙습니다. SP를 만들거나 데이터베이스를 변경하려고하지 않습니다. 나는 당신이 제공 한 두 링크가 의미하는 바를 잘 모르겠습니다. 실행 권한에 문제가 없습니다. 가장 큰 문제는 Dapper를 사용하여 일반 INT 값 목록을 테이블에 삽입하는 방법을 구성하는 것입니다. –

+0

@ChrisB 두 링크는 ​​SP 경로를 내려 가서 유형에 대한 exec 권한을 부여해야 할 수도 있다는 사실을 보여줍니다 당신이 그것을 모르는 경우에 명백하지 않은 창조하십시오. 대부분의 사람들은 SP를 실행할 수있는 권한으로 충분하다고 가정합니다. – takrl

+0

아, 알았어. 알았어. 알았어. 알았어. –

관련 문제