SQL IN 절 매개 변수화


1041

IN이처럼 가변 개수의 인수를 가진 절을 포함하는 쿼리를 어떻게 매개 변수화 합니까?

SELECT * FROM Tags 
WHERE Name IN ('ruby','rails','scruffy','rubyonrails')
ORDER BY Count DESC

이 쿼리에서 인수의 수는 1에서 5 사이 일 수 있습니다.

이 (또는 XML)에 전용 저장 프로 시저를 사용하지 않으려 고하지만 SQL Server 2008 과 관련하여 우아한 방법이 있다면 그 방법 을 사용할 수 있습니다.



답변:


316

내가 사용한 빠르고 더러운 기술이 있습니다.

SELECT * FROM Tags
WHERE '|ruby|rails|scruffy|rubyonrails|'
LIKE '%|' + Name + '|%'

C # 코드는 다음과 같습니다.

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
const string cmdText = "select * from tags where '|' + @tags + '|' like '%|' + Name + '|%'";

using (SqlCommand cmd = new SqlCommand(cmdText)) {
   cmd.Parameters.AddWithValue("@tags", string.Join("|", tags);
}

두 가지 경고 :

  • 성능이 끔찍합니다. LIKE "%...%"쿼리는 색인화되지 않습니다.
  • |, 공백 또는 null 태그가 없거나 작동 하지 않는지 확인하십시오.

일부 사람들이 더 깨끗하다고 ​​생각할 수있는 다른 방법이 있으므로 계속 읽으십시오.


119
그것은 매우 느릴 것입니다
Matt Rogish

13
예, 이것은 테이블 스캔입니다. 10 행에 적합하고 100,000 개에 대해 비대합니다.
Will Hartung

17
파이프가 들어있는 태그를 테스트해야합니다.
Joel Coehoorn

17
이것은 질문에 대답조차하지 않습니다. 물론 매개 변수를 추가 할 위치를 쉽게 알 수 있지만 쿼리를 매개 변수화하지 않아도 어떻게 솔루션을 수용 할 수 있습니까? 매개 변수화되어 있지 않기 때문에 @Mark Brackett보다 단순하게 보입니다.
tvanfosson

21
태그가 'ruby | rails'인 경우 어떻게됩니까? 일치 할 것입니다. 이러한 솔루션을 출시 할 때 태그에 파이프가 포함되어 있지 않은지 또는 명시 적으로 필터링해야합니다. select * from Tags where '| ruby ​​| rails | scruffy | rubyonrails |' '% |'처럼 + 이름 + '| %'AND 이름이 '%! %'와 같지 않음
AK

729

값을 매개 변수화 할 수 있습니다 .

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
string cmdText = "SELECT * FROM Tags WHERE Name IN ({0})";

string[] paramNames = tags.Select(
    (s, i) => "@tag" + i.ToString()
).ToArray();

string inClause = string.Join(", ", paramNames);
using (SqlCommand cmd = new SqlCommand(string.Format(cmdText, inClause))) {
    for(int i = 0; i < paramNames.Length; i++) {
       cmd.Parameters.AddWithValue(paramNames[i], tags[i]);
    }
}

어느 것이 당신에게 줄 것입니까?

cmd.CommandText = "SELECT * FROM Tags WHERE Name IN (@tag0, @tag1, @tag2, @tag3)"
cmd.Parameters["@tag0"] = "ruby"
cmd.Parameters["@tag1"] = "rails"
cmd.Parameters["@tag2"] = "scruffy"
cmd.Parameters["@tag3"] = "rubyonrails"

아니요, 이것은 SQL 주입에 개방되어 있지 않습니다 . CommandText에 삽입 된 유일한 텍스트는 사용자 입력을 기반으로하지 않습니다. 하드 코딩 된 "@tag"접두사와 배열의 인덱스만을 기반으로합니다. 색인은 항상 정수이며 사용자가 생성하지 않으며 안전합니다.

사용자가 입력 한 값은 여전히 ​​매개 변수에 채워져 있으므로 취약성이 없습니다.

편집하다:

주입 문제는 제쳐두고, 가변 개수의 매개 변수 (위와 같이)를 수용하기 위해 명령 텍스트를 구성하면 캐시 된 쿼리를 활용할 수있는 SQL 서버의 기능이 방해 받게됩니다. 최종 결과는 단지 술어 문자열을 SQL 자체에 삽입하는 것과는 달리 처음에는 매개 변수를 사용하는 값을 거의 잃어 버리는 것입니다.

캐시 된 쿼리 계획은 가치가 없지만 IMO는이 쿼리가 그 이점을 충분히 얻을만큼 복잡하지 않습니다. 컴파일 비용은 실행 비용에 근접하거나 초과 할 수 있지만 여전히 밀리 초입니다.

충분한 RAM이 있다면 SQL Server가 일반적인 매개 변수 수에 대한 계획을 캐시 할 것으로 예상됩니다. 나는 항상 다섯 개의 매개 변수를 추가하고 지정되지 않은 태그를 NULL로 만들 수 있다고 가정합니다. 쿼리 계획은 동일해야하지만 꽤 나쁘게 보이지만 미세 최적화의 가치가 있는지 확실하지 않습니다. 스택 오버플로-가치가있을 수 있습니다).

또한 SQL Server 7 이상 에서는 쿼리자동으로 매개 변수화 하므로 성능 관점에서 매개 변수를 사용할 필요가 없습니다. 그러나 보안 관점에서 특히 중요 합니다. 특히 이와 같은 사용자 입력 데이터 에는 중요 합니다.


2
기본적으로 "관련"질문에 대한 나의 대답과 동일하며 분명히 해석하기보다는 건설적이고 효율적이기 때문에 가장 좋은 해결책입니다 (훨씬 더 어렵습니다).
tvanfosson

49
이것이 바로 LINQ to SQL이하는 일입니다. BTW
Mark Cidade

3
@Pure : 이것의 핵심은 동적 SQL을 사용하는 경우에 취약한 SQL 인젝션을 피하는 것입니다.
Ray

4
@God of Data-예, 2100 개 이상의 태그가 필요한 경우 다른 솔루션이 필요하다고 가정합니다. 그러나 Basarb는 평균 태그 길이가 3 자 미만인 경우에만 2100에 도달 할 수있었습니다 (구분 기호도 필요하기 때문에). msdn.microsoft.com/ko-kr/library/ms143432.aspx
Mark Brackett

2
@bonCodigo-선택한 값이 배열에 있습니다. 배열을 반복하고 각 항목에 대해 매개 변수 (색인으로 접미사)를 추가하십시오.
Mark Brackett

249

SQL Server 2008의 경우 테이블 값 매개 변수를 사용할 수 있습니다 . 약간의 작업이지만 다른 방법 보다 깨끗 합니다.

먼저 유형을 만들어야합니다

CREATE TYPE dbo.TagNamesTableType AS TABLE ( Name nvarchar(50) )

그런 다음 ADO.NET 코드는 다음과 같습니다.

string[] tags = new string[] { "ruby", "rails", "scruffy", "rubyonrails" };
cmd.CommandText = "SELECT Tags.* FROM Tags JOIN @tagNames as P ON Tags.Name = P.Name";

// value must be IEnumerable<SqlDataRecord>
cmd.Parameters.AddWithValue("@tagNames", tags.AsSqlDataRecord("Name")).SqlDbType = SqlDbType.Structured;
cmd.Parameters["@tagNames"].TypeName = "dbo.TagNamesTableType";

// Extension method for converting IEnumerable<string> to IEnumerable<SqlDataRecord>
public static IEnumerable<SqlDataRecord> AsSqlDataRecord(this IEnumerable<string> values, string columnName) {
    if (values == null || !values.Any()) return null; // Annoying, but SqlClient wants null instead of 0 rows
    var firstRecord = values.First();
    var metadata = SqlMetaData.InferFromValue(firstRecord, columnName);
    return values.Select(v => 
    {
       var r = new SqlDataRecord(metadata);
       r.SetValues(v);
       return r;
    });
}

41
우리는 이것을 테스트했으며 테이블 값 매개 변수는 DOG 속도가 느립니다. 하나의 TVP를 수행하는 것보다 5 개의 쿼리를 실행하는 것이 문자 그대로 더 빠릅니다.
Jeff Atwood

4
@JeffAtwood-쿼리를 다음과 같이 바꾸려고 했습니까 SELECT * FROM tags WHERE tags.name IN (SELECT name from @tvp);? 이론 상으로는 이것이 가장 빠른 방법이어야합니다. 관련 인덱스 (예 : INCLUDE카운트가 이상적인 태그 이름의 인덱스)를 사용할 수 있으며 SQL Server는 모든 태그와 개수를 파악하기 위해 몇 번의 검색을 수행해야합니다. 계획은 어떻게 생겼습니까?
Nick Chammas

9
또한 이것을 테스트했으며 FAST AS LIGHTNING입니다 (큰 IN 문자열 구성과 비교). "매개 변수 값을 Int32 []에서 IEnumerable`1로 변환하지 못했습니다"라는 이유로 계속 매개 변수를 설정하는 데 문제가있었습니다. 어쨌든, 그것을 해결하고 여기에 pastebin.com/qHP05CXc를
Fredrik Johansson

6
@FredrikJohansson-130 개의 투표 중에서 실제로 이것을 실행하려고 시도한 유일한 달리기 일 수 있습니다! 문서를 읽는 데 실수를했으며 실제로 IEnumerable이 아니라 IEnumerable <SqlDataRecord>이 필요합니다. 코드가 업데이트되었습니다.
마크 브라켓

3
@MarkBrackett 업데이트로 훌륭합니다! 실제로이 코드는 Lucene 검색 색인을 쿼리 한 이후로 하루를 실제로 절약했으며 때로는 50.000 이상을 반환하여 SQL 서버에 대해 다시 검사 해야하는 적중을 반환합니다. 따라서 int [] (document / SQL 키)를 입력 한 다음 위 코드를 입력합니다. 이제 전체 OP는 200ms 미만이 소요됩니다. :)
Fredrik Johansson

188

원래 질문은 "쿼리를 어떻게 매개 변수화합니까?"

여기에 아니라고 말하겠습니다. 원래 질문에 하겠습니다. 다른 좋은 답변에는 이미 그에 대한 몇 가지 시위가 있습니다.

그 말로,이 대답에 플래그를 지정하고, 답장을 내리고, 대답이 아닌 것으로 표시하십시오 ... 믿는대로 행동하십시오.

내가 추천 한 답변 (및 231 명)은 Mark Brackett의 답변을 참조하십시오. 그의 대답에 주어진 접근법은 1) 바인드 변수의 효과적인 사용을 위해, 그리고 2) Sargable 술어를 허용합니다.

선택된 답변

여기서 다루고 싶은 것은 Joel Spolsky의 답변에 주어진 접근법입니다. 정답은 "선택된"답변입니다.

Joel Spolsky의 접근 방식은 영리합니다. 그리고 "정상적인"값과 NULL 및 빈 문자열과 같은 규범적인 엣지 사례를 고려하여 예측 가능한 동작과 예측 가능한 성능을 보여줍니다. 그리고 특정 응용 프로그램에 충분할 수 있습니다.

그러나이 접근법을 일반화하는 관점에서 Name열에 와일드 카드 문자가 포함 된 경우 (LIKE 술어에 의해 인식되는 경우)와 같이 모호한 모퉁이 사례를 고려 하십시오. 가장 일반적으로 사용되는 와일드 카드 문자는 %(퍼센트 기호)입니다. 자 이제 여기서 다루고 나중에 다른 경우로 넘어 갑시다.

% 문자의 일부 문제

이름 값을 고려하십시오 'pe%ter'. (여기 예제에서는 열 이름 대신 리터럴 문자열 값을 사용합니다.) 이름 값이 ''pe % ter '인 행은 다음 형식의 쿼리에 의해 반환됩니다.

select ...
 where '|peanut|butter|' like '%|' + 'pe%ter' + '|%'

그러나 검색어 순서가 반대로 바뀌면 동일한 행이 반환 되지 않습니다 .

select ...
 where '|butter|peanut|' like '%|' + 'pe%ter' + '|%'

우리가 관찰하는 행동은 이상합니다. 목록에서 검색어 순서를 변경하면 결과 집합이 변경됩니다.

우리가 원하지 않을 수도 있다는 말은 거의 없습니다. pe%ter땅콩 버터가 얼마나 좋아하든 땅콩 버터와 .

모호한 코너 케이스

(예, 이것이 모호한 경우라는 것에 동의합니다. 아마도 테스트되지 않을 것입니다. 우리는 열 값에 와일드 카드를 기대하지 않을 것입니다. 우리는 애플리케이션이 그러한 값을 저장하지 못하게 할 것이라고 가정 할 수 있습니다. 내 경험상, LIKE비교 연산자 의 오른쪽에 와일드 카드로 간주 될 문자 나 패턴을 구체적으로 허용하지 않는 데이터베이스 제약 조건은 거의 보지 못했습니다 .

구멍 패치

이 구멍을 패치하는 한 가지 방법은 %와일드 카드 문자 를 피하는 것 입니다. 연산자의 이스케이프 절에 익숙하지 않은 사용자를 위해 SQL Server 설명서 링크가 있습니다 .

select ...
 where '|peanut|butter|'
  like '%|' + 'pe\%ter' + '|%' escape '\'

이제 리터럴 %를 일치시킬 수 있습니다. 물론 열 이름이 있으면 와일드 카드를 동적으로 이스케이프해야합니다. 이 REPLACE함수를 사용하여 다음 과 같이 %문자의 발생을 찾고 각 문자 앞에 백 슬래시 문자를 삽입 할 수 있습니다 .

select ...
 where '|pe%ter|'
  like '%|' + REPLACE( 'pe%ter' ,'%','\%') + '|%' escape '\'

따라서 % 와일드 카드 관련 문제가 해결됩니다. 거의.

탈출 탈출

우리는 우리의 솔루션이 또 다른 문제를 일으킨다는 것을 알고 있습니다. 탈출 문자. 우리는 또한 이스케이프 문자 자체를 피해야합니다. 이번에는! 탈출 문자로 :

select ...
 where '|pe%t!r|'
  like '%|' + REPLACE(REPLACE( 'pe%t!r' ,'!','!!'),'%','!%') + '|%' escape '!'

밑줄도

이제 롤을 시작 했으므로 REPLACE밑줄 와일드 카드에 다른 핸들을 추가 할 수 있습니다 . 그리고 재미를 위해 이번에는 $를 이스케이프 문자로 사용합니다.

select ...
 where '|p_%t!r|'
  like '%|' + REPLACE(REPLACE(REPLACE( 'p_%t!r' ,'$','$$'),'%','$%'),'_','$_') + '|%' escape '$'

이 접근법은 SQL Server뿐만 아니라 Oracle 및 MySQL에서도 작동하므로 이스케이프 방식을 선호합니다. (일반적으로 \ 백 슬래시를 이스케이프 문자로 사용합니다. 왜냐하면 정규 표현식에서 사용하는 문자이기 때문입니다. 그러나 왜 규칙에 의해 제약을 받는가!

그 성가신 괄호

또한 SQL Server에서는 와일드 카드 문자를 대괄호로 묶어 리터럴로 취급 할 수 있습니다 []. 따라서 적어도 SQL Server에서는 아직 수정이 완료되지 않았습니다. 대괄호 쌍은 특별한 의미를 갖기 때문에이를 피해야합니다. 우리가 대괄호를 올바르게 탈출하면 적어도 대괄호 안에 하이픈 -과 캐럿 을 신경 쓸 필요가 없습니다 ^. 그리고 우리가 어떤을 남길 수 있습니다 %_ 대괄호가 빠져 안에 우리는 기본적으로 브래킷의 특별한 의미를 비활성화 한 것이기 때문에, 문자를.

일치하는 대괄호 쌍을 찾는 것이 그렇게 어렵지 않아야합니다. singleton % 및 _의 발생을 처리하는 것보다 조금 더 어렵습니다. (단일 괄호는 리터럴로 간주되어 이스케이프 처리 할 필요가 없기 때문에 모든 괄호를 이스케이프하는 것만으로는 충분하지 않습니다. 더 많은 테스트 사례를 실행하지 않고 처리 할 수있는 것보다 논리가 조금 더 흐릿 해집니다. .)

인라인 표현이 지저분해진다

SQL의 인라인 표현식이 점점 길어지고 있습니다. 우리는 아마도 그것을 작동시킬 수는 있지만 하늘은 뒤에 오는 불쌍한 영혼을 도와서 해독해야합니다. 나는 인라인 표현을 좋아하는 팬이기 때문에 여기서는 그것을 사용하지 않는 경향이 있습니다. 주된 이유는 혼란의 이유를 설명하고 이에 대해 사과하는 의견을 남기고 싶지 않기 때문입니다.

어디 기능?

자, 그것을 SQL에서 인라인 표현식으로 처리하지 않으면 가장 가까운 대안은 사용자 정의 함수입니다. 그리고 우리는 속도를 낼 수 없다는 것을 알고 있습니다 (Oracle에서와 같이 인덱스를 정의 할 수 없다면) 함수를 만들어야한다면 SQL을 호출하는 코드에서 더 잘 수행 할 수 있습니다 성명서.

이 기능은 DBMS 및 버전에 따라 동작에 약간의 차이가있을 수 있습니다. (모든 Java 개발자에게 소리를 지므로 데이터베이스 엔진을 서로 바꾸어 사용할 수 있기를 바랍니다.)

도메인 지식

열에 대한 도메인 (즉, 열에 대해 허용되는 값 집합)에 대한 전문 지식이있을 수 있습니다. 열에 저장된 값에는 백분율 기호, 밑줄 또는 대괄호가 포함되지 않는다는 우선 순위 를 알 수 있습니다. 이 경우 해당 사례에 대해 간략하게 설명합니다.

열에 저장된 값은 % 또는 _ 문자를 허용하지만 제한 조건은 값이 LIKE 비교 "안전"이되도록 정의 된 문자를 사용하여 해당 값을 이스케이프해야 할 수 있습니다. 다시, 허용되는 값 세트, 특히 어떤 문자가 이스케이프 문자로 사용되는지에 대한 간단한 설명과 Joel Spolsky의 접근 방식을 따르십시오.

그러나 전문 지식과 보증이 없다면, 모호한 모퉁이 사례를 처리하는 것이 중요하며 행동이 합리적이며 "사양에 따라"고려하는 것이 중요합니다.


요약 된 다른 문제

나는 다른 사람들이 이미 일반적으로 고려되는 다른 관심 분야 중 일부를 충분히 지적했다고 생각합니다.

  • SQL 주입 (바인드 변수를 통해 정보를 제공하지 않고 SQL 텍스트에 사용자가 제공 한 정보를 포함하여 정보를 포함하는 것). 그것을 다루는 방법 :

  • 인덱스 검색 대신 인덱스 스캔을 사용하는 옵티 마이저 계획, 와일드 카드 이스케이프를위한 표현식 또는 함수가 필요함 (표현식 또는 함수에 대한 인덱스 가능)

  • 바인드 변수 대신 리터럴 값을 사용하면 확장성에 영향을 미침


결론

나는 Joel Spolsky의 접근법을 좋아합니다. 영리하다. 그리고 작동합니다.

그러나 그것을 보자 마자 나는 잠재적 인 문제를 즉시 보았고 그것이 미끄러지는 것은 내 본성이 아닙니다. 나는 다른 사람들의 노력에 비판적임을 의미하지 않습니다. 많은 개발자들이 개인적으로 업무를 수행한다는 것을 알고 있습니다. 많은 투자를하고 많은 관심을 갖고 있기 때문입니다. 따라서 이것은 개인적인 공격이 아닙니다. 내가 여기서 식별하고있는 것은 테스트보다는 생산에서 발생하는 문제의 유형입니다.

예, 나는 원래의 질문과는 거리가 멀었습니다. 그러나 질문에 대한 "선택된"답변에서 중요한 문제로 간주되는 사항에 대해이 메모를 남길 다른 곳은 무엇입니까?


매개 변수화 된 쿼리를 사용하거나 좋아하는지 알려주시겠습니까? 이 특별한 경우에 '매개 변수화 된 쿼리 사용'의 규칙을 뛰어 넘어 원래 언어로 위생 처리하는 것이 맞습니까? 감사합니다
Luis Siquot

2
@Luis : 예, SQL 문에서 바인드 변수를 사용하는 것이 선호되며 바인드 변수를 사용할 때 성능 문제가 발생할 때만 바인드 변수를 피할 수 있습니다. 원래 문제에 대한 내 규범적인 패턴은 IN 목록에 필요한 수의 자리 표시자를 사용하여 SQL 문을 동적으로 만든 다음 각 값을 자리 표시 자 중 하나에 바인딩하는 것입니다. Mark Brackett의 답변을 참조하십시오.
spencer7593

133

매개 변수를 문자열로 전달할 수 있습니다

그래서 당신은 문자열이

DECLARE @tags

SET @tags = ruby|rails|scruffy|rubyonrails

select * from Tags 
where Name in (SELECT item from fnSplit(@tags, ‘|’))
order by Count desc

그런 다음 문자열을 1 매개 변수로 전달하면됩니다.

여기 내가 사용하는 스플릿 기능이 있습니다.

CREATE FUNCTION [dbo].[fnSplit](
    @sInputList VARCHAR(8000) -- List of delimited items
  , @sDelimiter VARCHAR(8000) = ',' -- delimiter that separates items
) RETURNS @List TABLE (item VARCHAR(8000))

BEGIN
DECLARE @sItem VARCHAR(8000)
WHILE CHARINDEX(@sDelimiter,@sInputList,0) <> 0
 BEGIN
 SELECT
  @sItem=RTRIM(LTRIM(SUBSTRING(@sInputList,1,CHARINDEX(@sDelimiter,@sInputList,0)-1))),
  @sInputList=RTRIM(LTRIM(SUBSTRING(@sInputList,CHARINDEX(@sDelimiter,@sInputList,0)+LEN(@sDelimiter),LEN(@sInputList))))

 IF LEN(@sItem) > 0
  INSERT INTO @List SELECT @sItem
 END

IF LEN(@sInputList) > 0
 INSERT INTO @List SELECT @sInputList -- Put the last item in
RETURN
END

2
이 방법으로 테이블 함수에 조인 할 수도 있습니다.
Michael Haren

Oracle에서도 이와 비슷한 솔루션을 사용합니다. 다른 솔루션 중 일부와 마찬가지로 다시 구문 분석 할 필요가 없습니다.
레이 리펠

9
이것은 데이터베이스 외부의 코드에서 다른 작업이 필요한 순수한 데이터베이스 접근 방식입니다.
David Basarab

이것을 테이블 스캔에 사용합니까, 아니면 인덱스 등을 이용할 수 있습니까?
Pure.Krome

더는 기본적으로 반환되는 테이블에 대해 조인하는 (적어도 이후 2005 년)에 SQL 테이블 함수에 적용 CROSS을 사용하는 것입니다
아돌프 마늘

66

나는 팟 캐스트 오늘 (에 이것에 대해 제프 / 조엘의 이야기들을 에피소드 34 , 2008-12-16 (MP3 31 MB), 1 시간 03 분 38 초 - 1 시간 06 분 45 초)을, 그리고 내가 스택 오버플로를 회상 생각 사용 된 SQL에 LINQ를 ,하지만 어쩌면 그것은 도랑에 빠지게되었다. LINQ to SQL에서도 마찬가지입니다.

var inValues = new [] { "ruby","rails","scruffy","rubyonrails" };

var results = from tag in Tags
              where inValues.Contains(tag.Name)
              select tag;

그게 다야. 그렇습니다. LINQ는 이미 거꾸로 보입니다 Contains. 직장에서 프로젝트에 대해 비슷한 쿼리를 수행해야 할 때 자연스럽게 로컬 배열과 SQL Server 테이블 사이의 조인을 수행하여 잘못된 방법으로 시도했습니다. 어떻게 든 번역. 그것은 아니었지만 설명 적이었고 Contains 을 사용하도록 지시 하는 오류 메시지를 제공했습니다 .

어쨌든 권장되는 LINQPad 에서이 쿼리를 실행하고이 쿼리를 실행하면 SQL LINQ 공급자가 생성 한 실제 SQL을 볼 수 있습니다. IN절에 매개 변수화되는 각 값을 보여줍니다 .


50

.NET에서 전화하는 경우 Dapper dot net을 사용할 수 있습니다 .

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = dataContext.Query<Tags>(@"
select * from Tags 
where Name in @names
order by Count desc", new {names});

여기서 Dapper는 생각을하기 때문에 필요하지 않습니다. 물론 LINQ to SQL 에서도 비슷한 것이 가능합니다 .

string[] names = new string[] {"ruby","rails","scruffy","rubyonrails"};
var tags = from tag in dataContext.Tags
           where names.Contains(tag.Name)
           orderby tag.Count descending
           select tag;

11
실제 질문 ( dapper
Sam Saffron


이름이 긴 경우에 해당
cs0815

29

이것은 아마도 그것을하는 반의 불쾌한 방법 일 것입니다. 한 번 사용했는데 오히려 효과적이었습니다.

목표에 따라 유용 할 수 있습니다.

  1. 하나의 열로 임시 테이블 을 작성하십시오 .
  2. INSERT 해당 열에 대한 각 조회 값.
  3. 를 사용하는 대신 IN표준 JOIN규칙을 사용할 수 있습니다 . (유연성 +)

이것은 당신이 할 수있는 일에 약간의 유연성을 제공하지만, 색인을 생성하고 쿼리 할 큰 테이블이 있고 매개 변수가있는 목록을 두 번 이상 사용하려는 상황에 더 적합합니다. 두 번 실행하고 모든 위생을 수동으로 수행해야하는 수고를 덜어줍니다.

나는 그것이 얼마나 빠른지 정확히 프로파일 링 하지는 않았지만 내 상황에서는 필요했습니다.


이것은 전혀 불쾌하지 않습니다! 더욱이 IMHO는 매우 깨끗한 방법입니다. 실행 계획을 살펴보면 IN 절과 같다는 것을 알 수 있습니다. 임시 테이블 대신 SESSIONID와 함께 매개 변수를 저장하는 인덱스가있는 고정 테이블을 작성할 수도 있습니다.
SQL Police

27

에서 SQL Server 2016+당신은 사용할 수있는 STRING_SPLIT기능 :

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT * 
FROM Tags
WHERE Name IN (SELECT [value] FROM STRING_SPLIT(@names, ','))
ORDER BY [Count] DESC;

또는:

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails';

SELECT t.*
FROM Tags t
JOIN STRING_SPLIT(@names,',')
  ON t.Name = [value]
ORDER BY [Count] DESC;

LiveDemo

허용 응답 코스 작업의 뜻과 길을 가야하는 것 중 하나이지만, 안티 패턴이다.

E. 값 목록으로 행 찾기

이는 응용 프로그램 계층이나 Transact-SQL에서 동적 SQL 문자열을 만들거나 LIKE 연산자를 사용하여 일반적인 안티 패턴을 대체합니다.

SELECT ProductId, Name, Tags
FROM Product
WHERE ',1,2,3,' LIKE '%,' + CAST(ProductId AS VARCHAR(20)) + ',%';

부록 :

STRING_SPLIT테이블 함수 행 추정 을 개선하려면 분할 된 값을 임시 테이블 / 테이블 변수로 구체화하는 것이 좋습니다.

DECLARE @names NVARCHAR(MAX) = 'ruby,rails,scruffy,rubyonrails,sql';

CREATE TABLE #t(val NVARCHAR(120));
INSERT INTO #t(val) SELECT s.[value] FROM STRING_SPLIT(@names, ',') s;

SELECT *
FROM Tags tg
JOIN #t t
  ON t.val = tg.TagName
ORDER BY [Count] DESC;

SEDE-라이브 데모

관련 : 저장 프로 시저에 값 목록을 전달하는 방법


원래 질문에는 요구 사항이 SQL Server 2008있습니다. 이 질문은 종종 중복으로 사용되므로이 답변을 참조로 추가했습니다.


1
나는 이것을 perf 테스트하지 않았지만 이것이 가장 깨끗한 2016 + 솔루션이라고 생각합니다. 나는 여전히 int 배열을 전달할 수 있기를 원하지만 그때까지는 ...
Daniel

24

우리는 당신이 조인 할 수있는 테이블 변수를 만드는 함수를 가지고 있습니다 :

ALTER FUNCTION [dbo].[Fn_sqllist_to_table](@list  AS VARCHAR(8000),
                                           @delim AS VARCHAR(10))
RETURNS @listTable TABLE(
  Position INT,
  Value    VARCHAR(8000))
AS
  BEGIN
      DECLARE @myPos INT

      SET @myPos = 1

      WHILE Charindex(@delim, @list) > 0
        BEGIN
            INSERT INTO @listTable
                        (Position,Value)
            VALUES     (@myPos,LEFT(@list, Charindex(@delim, @list) - 1))

            SET @myPos = @myPos + 1

            IF Charindex(@delim, @list) = Len(@list)
              INSERT INTO @listTable
                          (Position,Value)
              VALUES     (@myPos,'')

            SET @list = RIGHT(@list, Len(@list) - Charindex(@delim, @list))
        END

      IF Len(@list) > 0
        INSERT INTO @listTable
                    (Position,Value)
        VALUES     (@myPos,@list)

      RETURN
  END 

그래서:

@Name varchar(8000) = null // parameter for search values    

select * from Tags 
where Name in (SELECT value From fn_sqllist_to_table(@Name,',')))
order by Count desc

20

이것은 거칠지 만 적어도 하나 이상이 보장되면 다음을 수행 할 수 있습니다.

SELECT ...
       ...
 WHERE tag IN( @tag1, ISNULL( @tag2, @tag1 ), ISNULL( @tag3, @tag1 ), etc. )

IN ( 'tag1', 'tag2', 'tag1', 'tag1', 'tag1')은 SQL Server에서 쉽게 최적화됩니다. 또한 직접 인덱스를 검색 할 수 있습니다


1
옵티마이 저는 효율적인 쿼리를 작성하는 데 사용되는 많은 수의 매개 변수를 필요로하기 때문에 널 (null) 체크가있는 선택적 매개 변수는 성능을 손상시킵니다. 5 개의 매개 변수에 대한 쿼리는 500 개의 매개 변수에 대한 쿼리 계획과 다른 쿼리 계획이 필요할 수 있습니다.
Erik Hart

18

내 의견으로는이 문제를 해결하는 가장 좋은 출처는이 사이트에 게시 된 것입니다.

Syscomments. 디나 카르 네티

CREATE FUNCTION dbo.fnParseArray (@Array VARCHAR(1000),@separator CHAR(1))
RETURNS @T Table (col1 varchar(50))
AS 
BEGIN
 --DECLARE @T Table (col1 varchar(50))  
 -- @Array is the array we wish to parse
 -- @Separator is the separator charactor such as a comma
 DECLARE @separator_position INT -- This is used to locate each separator character
 DECLARE @array_value VARCHAR(1000) -- this holds each array value as it is returned
 -- For my loop to work I need an extra separator at the end. I always look to the
 -- left of the separator character for each array value

 SET @array = @array + @separator

 -- Loop through the string searching for separtor characters
 WHILE PATINDEX('%' + @separator + '%', @array) <> 0 
 BEGIN
    -- patindex matches the a pattern against a string
    SELECT @separator_position = PATINDEX('%' + @separator + '%',@array)
    SELECT @array_value = LEFT(@array, @separator_position - 1)
    -- This is where you process the values passed.
    INSERT into @T VALUES (@array_value)    
    -- Replace this select statement with your processing
    -- @array_value holds the value of this element of the array
    -- This replaces what we just processed with and empty string
    SELECT @array = STUFF(@array, 1, @separator_position, '')
 END
 RETURN 
END

사용하다:

SELECT * FROM dbo.fnParseArray('a,b,c,d,e,f', ',')

크레딧 : Dinakar Nethi


테이블에 대한 초기 CSV 구문 분석 (한 번에 적은 수의 요소)을 제외하고 큰 대답, 깨끗하고 모듈 식이며 초고속 실행. patindex () 대신 더 간단하고 빠른 charindex ()를 사용할 수 있습니까? Charindex () 또한 인수 'start_location'을 허용하여 각 반복 자마다 입력 문자열을 자르지 않을 수 있습니까? 원래 질문에 대답하기 위해 함수 결과와 결합 할 수 있습니다.
crokusek

18

나는 (그 이후 테이블 형식의 매개 변수를 전달 할 SQL 서버 2008 ), 그리고 작업을 수행 where exists하거나 내부 조인. 을 사용하여 XML을 사용한 sp_xml_preparedocument다음 해당 임시 테이블을 색인화 할 수도 있습니다 .


Ph.E의 답변에는 임시 테이블 작성 (csv에서) 예제가 있습니다.
crokusek

12

IMHO의 올바른 방법은 목록을 문자열로 저장하는 것입니다 (DBMS가 지원하는 길이로 제한됨). 유일한 트릭은 (처리를 단순화하기 위해) 문자열의 시작과 끝에 구분 기호 (예 : 쉼표)가 있다는 것입니다. 아이디어는 "즉시 정규화"하여 목록을 값당 하나의 행을 포함하는 하나의 열로 변환하는 것입니다. 이것은 당신이 설정할 수 있습니다

에서 (ct1, ct2, ct3 ... ctn)

에서 (선택 ...)

또는 목록에서 중복 값으로 인한 문제를 피하기 위해 "고유"를 추가하는 경우 정규 조인을 사용하십시오.

불행히도, 문자열을 슬라이스하는 기술은 제품마다 상당히 다릅니다. 다음은 SQL Server 버전입니다.

 with qry(n, names) as
       (select len(list.names) - len(replace(list.names, ',', '')) - 1 as n,
               substring(list.names, 2, len(list.names)) as names
        from (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' names) as list
        union all
        select (n - 1) as n,
               substring(names, 1 + charindex(',', names), len(names)) as names
        from qry
        where n > 1)
 select n, substring(names, 1, charindex(',', names) - 1) dwarf
 from qry;

오라클 버전 :

 select n, substr(name, 1, instr(name, ',') - 1) dwarf
 from (select n,
             substr(val, 1 + instr(val, ',', 1, n)) name
      from (select rownum as n,
                   list.val
            from  (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val
                   from dual) list
            connect by level < length(list.val) -
                               length(replace(list.val, ',', ''))));

그리고 MySQL 버전 :

select pivot.n,
      substring_index(substring_index(list.val, ',', 1 + pivot.n), ',', -1) from (select 1 as n
     union all
     select 2 as n
     union all
     select 3 as n
     union all
     select 4 as n
     union all
     select 5 as n
     union all
     select 6 as n
     union all
     select 7 as n
     union all
     select 8 as n
     union all
     select 9 as n
     union all
     select 10 as n) pivot,    (select ',Doc,Grumpy,Happy,Sneezy,Bashful,Sleepy,Dopey,' val) as list where pivot.n <  length(list.val) -
                   length(replace(list.val, ',', ''));

물론 "피벗"은 목록에서 찾을 수있는 최대 항목 수만큼 행을 반환해야합니다.


11

당신이 가지고있는 경우에 SQL 서버 2008 이상 내가 사용하는 거라고 매개 변수 값을 갖는 테이블 .

운이 좋지 않다면 SQL Server 2005 에 갇히게되면 다음 과 같은 CLR 기능을 추가 할 수 있습니다.

[SqlFunction(
    DataAccessKind.None,
    IsDeterministic = true,
    SystemDataAccess = SystemDataAccessKind.None,
    IsPrecise = true,
    FillRowMethodName = "SplitFillRow",
    TableDefinintion = "s NVARCHAR(MAX)"]
public static IEnumerable Split(SqlChars seperator, SqlString s)
{
    if (s.IsNull)
        return new string[0];

    return s.ToString().Split(seperator.Buffer);
}

public static void SplitFillRow(object row, out SqlString s)
{
    s = new SqlString(row.ToString());
}

이런 식으로 사용할 수있는

declare @desiredTags nvarchar(MAX);
set @desiredTags = 'ruby,rails,scruffy,rubyonrails';

select * from Tags
where Name in [dbo].[Split] (',', @desiredTags)
order by Count desc

10

정적 쿼리가 갈 길이 아닌 경우라고 생각합니다. in 절에 대한 목록을 동적으로 작성하고 작은 따옴표를 이스케이프 처리하고 SQL을 동적으로 작성하십시오. 이 경우 목록이 작기 때문에 어떤 방법으로도 큰 차이가 보이지 않지만 가장 효율적인 방법은 실제로 게시물에 기록 된 그대로 SQL을 보내는 것입니다. 가장 좋은 코드를 만드는 작업을 수행하거나 SQL을 동적으로 작성하는 것은 나쁜 습관이라고 생각하는 것이 아니라 가장 효율적인 방법으로 작성하는 것이 좋은 습관이라고 생각합니다.

매개 변수가 커지는 많은 경우에 분할 함수가 쿼리 자체보다 실행하는 데 시간이 오래 걸립니다. SQL 2008의 테이블 값 매개 변수가있는 저장 프로 시저는 내가 고려해야 할 유일한 다른 옵션이지만, 아마도 귀하의 경우 속도가 느릴 것입니다. TVP는 기본적으로 목록이 큰 경우 SQL이 목록에 대한 임시 테이블을 작성하기 때문에 TVP의 기본 키를 검색하는 경우 큰 목록에 대해서만 더 빠를 것입니다. 테스트하지 않으면 확실하지 않습니다.

또한 기본값이 null이고 매개 변수가 500이고 매개 변수가 WHERE Column1 IN (@ Param1, @ Param2, @ Param3, ..., @ Param500) 인 저장 프로 시저를 보았습니다. 이로 인해 SQL이 임시 테이블을 작성하고 정렬 / 구별을 수행 한 다음 인덱스 검색 대신 테이블 스캔을 수행했습니다. 그것은 작은 규모로 눈에 띄는 차이를 만들지 않지만 본질적으로 해당 쿼리를 매개 변수화하여 수행하는 작업입니다. NOT IN으로 변경되면 의도 한대로 작동하지 않는 것처럼 IN 목록에 NULL을 사용하지 않는 것이 좋습니다. 동적으로 매개 변수 목록을 작성할 수 있지만 얻을 수있는 확실한 것은 개체가 작은 따옴표를 이스케이프한다는 것입니다. 개체가 매개 변수를 찾기 위해 쿼리를 구문 분석해야하기 때문에이 방법은 응용 프로그램 쪽에서도 약간 느려집니다.

저장 프로 시저 또는 매개 변수화 된 쿼리에 실행 계획을 재사용하면 성능이 향상 될 수 있지만 실행 된 첫 번째 쿼리에 의해 결정된 하나의 실행 계획에 고정됩니다. 많은 경우에 후속 쿼리에 적합하지 않을 수 있습니다. 귀하의 경우, 실행 계획의 재사용은 아마 플러스 일 것이지만, 예제가 실제로 간단한 쿼리이기 때문에 전혀 차이가 없을 수 있습니다.

클리프 노트 :

귀하의 경우, 목록에 고정 된 수의 항목으로 매개 변수화 (사용하지 않으면 null), 매개 변수가 있거나없는 쿼리를 동적으로 작성하거나 테이블 값 매개 변수가있는 저장 프로 시저를 사용하면 큰 차이가 없습니다. . 그러나 일반적인 권장 사항은 다음과 같습니다.

매개 변수가 적은 사례 / 간단한 쿼리 :

테스트 결과 성능이 더 우수한 경우 매개 변수가있는 동적 SQL

단순히 매개 변수를 변경하거나 쿼리가 복잡한 경우 재사용 가능한 실행 계획이있는 쿼리 (여러 번 호출 됨) :

동적 매개 변수가있는 SQL

큰 목록이있는 쿼리 :

테이블 값 매개 변수가있는 저장 프로 시저 목록이 대량으로 변경 될 수있는 경우 저장 프로 시저에서 WITH RECOMPILE을 사용하거나 매개 변수없이 동적 SQL을 사용하여 각 쿼리에 대해 새 실행 계획을 생성하십시오.


여기서 "저장된 절차"란 무엇입니까? 예를 들어 주시겠습니까?
struhtanov

9

여기서 XML을 사용할 수 있습니다.

    declare @x xml
    set @x='<items>
    <item myvalue="29790" />
    <item myvalue="31250" />
    </items>
    ';
    With CTE AS (
         SELECT 
            x.item.value('@myvalue[1]', 'decimal') AS myvalue
        FROM @x.nodes('//items/item') AS x(item) )

    select * from YourTable where tableColumnName in (select myvalue from cte)

1
CTE그리고 @x매우 신중하게 수행하는 경우와 같이, 부속 선택에 인라인 / 제거 할 수있다 이 문서 .
robert4

9

기본적으로 테이블 값 함수 (문자열에서 테이블을 반환하는 함수)를 IN 조건으로 전달하여 이것에 접근합니다.

다음은 UDF 코드입니다 (어딘가에서 스택 오버플로에서 가져 왔습니다. 지금 소스를 찾을 수 없습니다)

CREATE FUNCTION [dbo].[Split] (@sep char(1), @s varchar(8000))
RETURNS table
AS
RETURN (
    WITH Pieces(pn, start, stop) AS (
      SELECT 1, 1, CHARINDEX(@sep, @s)
      UNION ALL
      SELECT pn + 1, stop + 1, CHARINDEX(@sep, @s, stop + 1)
      FROM Pieces
      WHERE stop > 0
    )
    SELECT 
      SUBSTRING(@s, start, CASE WHEN stop > 0 THEN stop-start ELSE 512 END) AS s
    FROM Pieces
  )

일단 이것을 얻으면 코드는 다음과 같이 간단합니다.

select * from Tags 
where Name in (select s from dbo.split(';','ruby;rails;scruffy;rubyonrails'))
order by Count desc

엄청나게 긴 문자열이 없으면 테이블 인덱스와 잘 작동합니다.

필요한 경우 임시 테이블에 삽입하고 색인을 생성 한 다음 조인을 실행할 수 있습니다.


8

또 다른 가능한 해결책은 가변 개수의 인수를 저장 프로 시저에 전달하는 대신 이름이 포함 된 단일 문자열을 전달하지만 '<>'로 둘러 쌉으로써 고유하게 만듭니다. 그런 다음 PATINDEX를 사용하여 이름을 찾으십시오.

SELECT * 
FROM Tags 
WHERE PATINDEX('%<' + Name + '>%','<jo>,<john>,<scruffy>,<rubyonrails>') > 0

8

다음 저장 프로 시저를 사용하십시오. 여기 에서 찾을 수있는 커스텀 스플릿 기능을 사용합니다 .

 create stored procedure GetSearchMachingTagNames 
    @PipeDelimitedTagNames varchar(max), 
    @delimiter char(1) 
    as  
    begin
         select * from Tags 
         where Name in (select data from [dbo].[Split](@PipeDelimitedTagNames,@delimiter) 
    end

8

IN 절 안에 쉼표 (,)로 구분 된 문자열이 저장되어 있으면 charindex 함수를 사용하여 값을 얻을 수 있습니다. .NET을 사용하는 경우 SqlParameters를 사용하여 매핑 할 수 있습니다.

DDL 스크립트 :

CREATE TABLE Tags
    ([ID] int, [Name] varchar(20))
;

INSERT INTO Tags
    ([ID], [Name])
VALUES
    (1, 'ruby'),
    (2, 'rails'),
    (3, 'scruffy'),
    (4, 'rubyonrails')
;

T-SQL :

DECLARE @Param nvarchar(max)

SET @Param = 'ruby,rails,scruffy,rubyonrails'

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

.NET 코드에서 위의 문을 사용하고 SqlParameter를 사용하여 매개 변수를 매핑 할 수 있습니다.

피들러 데모

편집 : 다음 스크립트를 사용하여 SelectedTags라는 테이블을 만듭니다.

DDL 스크립트 :

Create table SelectedTags
(Name nvarchar(20));

INSERT INTO SelectedTags values ('ruby'),('rails')

T-SQL :

DECLARE @list nvarchar(max)
SELECT @list=coalesce(@list+',','')+st.Name FROM SelectedTags st

SELECT * FROM Tags
WHERE CharIndex(Name,@Param)>0

하드 코딩 된 가능한 값 목록이없는 경우이 작업의 예를 보여줄 수 있습니까?
John Saunders

@ JohnSaunders, 하드 코딩 된 목록을 사용하지 않고 스크립트를 편집했습니다. 확인 부탁합니다.
Gowdhaman008

3
이 옵션의 한 가지 제한 사항입니다. 문자열이 발견되면 CharIndex는 1을 반환합니다. IN은 정확한 용어 일치를 반환합니다. "Stack"에 대한 CharIndex는 "StackOverflow"에 대해 1을 반환하고 IN은 그렇지 않습니다. 위의 PatIndex를 사용 하여이 답변에 약간의 주가 있습니다.이 제한을 극복하는 '<'% name % '>'로 이름을 묶습니다. 이 문제에 대한 창의적인 해결책.
Richard Vivian

7

이와 같은 가변 수의 인수의 경우 내가 알고있는 유일한 방법은 SQL을 명시 적으로 생성하거나 임시 테이블을 원하는 항목으로 채우고 임시 테이블에 대해 조인하는 작업을 수행하는 것입니다.


7

ColdFusion 에서는 다음을 수행합니다.

<cfset myvalues = "ruby|rails|scruffy|rubyonrails">
    <cfquery name="q">
        select * from sometable where values in <cfqueryparam value="#myvalues#" list="true">
    </cfquery>

7

다음은 쿼리 문자열에 사용할 로컬 테이블을 다시 만드는 기술입니다. 이렇게하면 모든 파싱 문제가 해결됩니다.

문자열은 모든 언어로 만들 수 있습니다. 이 예제에서는 SQL이 원래의 문제 였기 때문에 SQL을 사용했습니다. 나중에 실행할 문자열로 테이블 데이터를 즉시 전달할 수있는 깔끔한 방법이 필요했습니다.

사용자 정의 유형을 사용하는 것은 선택 사항입니다. 유형 작성은 한 번만 작성되며 미리 수행 할 수 있습니다. 그렇지 않으면 문자열의 선언에 전체 테이블 유형을 추가하십시오.

일반적인 패턴은 확장하기 쉽고 더 복잡한 테이블을 전달하는 데 사용할 수 있습니다.

-- Create a user defined type for the list.
CREATE TYPE [dbo].[StringList] AS TABLE(
    [StringValue] [nvarchar](max) NOT NULL
)

-- Create a sample list using the list table type.
DECLARE @list [dbo].[StringList]; 
INSERT INTO @list VALUES ('one'), ('two'), ('three'), ('four')

-- Build a string in which we recreate the list so we can pass it to exec
-- This can be done in any language since we're just building a string.
DECLARE @str nvarchar(max);
SET @str = 'DECLARE @list [dbo].[StringList]; INSERT INTO @list VALUES '

-- Add all the values we want to the string. This would be a loop in C++.
SELECT @str = @str + '(''' + StringValue + '''),' FROM @list

-- Remove the trailing comma so the query is valid sql.
SET @str = substring(@str, 1, len(@str)-1)

-- Add a select to test the string.
SET @str = @str + '; SELECT * FROM @list;'

-- Execute the string and see we've pass the table correctly.
EXEC(@str)

7

SQL Server 2016+에서 또 다른 가능성은이 OPENJSON기능 을 사용하는 것입니다.

이 접근법은 OPENJSON 에서 블로그로 분류되어 있습니다. ID 목록으로 행을 선택하는 가장 좋은 방법 중 하나입니다 .

아래의 전체 예제

CREATE TABLE dbo.Tags
  (
     Name  VARCHAR(50),
     Count INT
  )

INSERT INTO dbo.Tags
VALUES      ('VB',982), ('ruby',1306), ('rails',1478), ('scruffy',1), ('C#',1784)

GO

CREATE PROC dbo.SomeProc
@Tags VARCHAR(MAX)
AS
SELECT T.*
FROM   dbo.Tags T
WHERE  T.Name IN (SELECT J.Value COLLATE Latin1_General_CI_AS
                  FROM   OPENJSON(CONCAT('[', @Tags, ']')) J)
ORDER  BY T.Count DESC

GO

EXEC dbo.SomeProc @Tags = '"ruby","rails","scruffy","rubyonrails"'

DROP TABLE dbo.Tags 

7

다른 대안이 있습니다. 저장 프로 시저에 쉼표로 구분 된 목록을 문자열 매개 변수로 전달하면됩니다.

CREATE PROCEDURE [dbo].[sp_myproc]
    @UnitList varchar(MAX) = '1,2,3'
AS
select column from table
where ph.UnitID in (select * from CsvToInt(@UnitList))

그리고 기능 :

CREATE Function [dbo].[CsvToInt] ( @Array varchar(MAX))
returns @IntTable table
(IntValue int)
AS
begin
    declare @separator char(1)
    set @separator = ','
    declare @separator_position int
    declare @array_value varchar(MAX)

    set @array = @array + ','

    while patindex('%,%' , @array) <> 0
    begin

        select @separator_position = patindex('%,%' , @array)
        select @array_value = left(@array, @separator_position - 1)

        Insert @IntTable
        Values (Cast(@array_value as int))
        select @array = stuff(@array, 1, @separator_position, '')
    end
    return
end

6

IN이 SELECT * FROM Test where Data IN (SELECT Value FROM TABLE)과 같은 select 문을 허용하기 때문에 UDF가 필요없는 답변이 있습니다.

문자열을 테이블로 변환하는 방법 만 있으면됩니다.

재귀 CTE 또는 숫자 테이블 (또는 Master..spt_value)을 사용하여 쿼리를 수행 할 수 있습니다.

다음은 CTE 버전입니다.

DECLARE @InputString varchar(8000) = 'ruby,rails,scruffy,rubyonrails'

SELECT @InputString = @InputString + ','

;WITH RecursiveCSV(x,y) 
AS 
(
    SELECT 
        x = SUBSTRING(@InputString,0,CHARINDEX(',',@InputString,0)),
        y = SUBSTRING(@InputString,CHARINDEX(',',@InputString,0)+1,LEN(@InputString))
    UNION ALL
    SELECT 
        x = SUBSTRING(y,0,CHARINDEX(',',y,0)),
        y = SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y))
    FROM 
        RecursiveCSV 
    WHERE
        SUBSTRING(y,CHARINDEX(',',y,0)+1,LEN(y)) <> '' OR 
        SUBSTRING(y,0,CHARINDEX(',',y,0)) <> ''
)
SELECT
    * 
FROM 
    Tags
WHERE 
    Name IN (select x FROM RecursiveCSV)
OPTION (MAXRECURSION 32767);

6

가장 투표가 많은 답변의 간결한 버전 사용합니다 .

List<SqlParameter> parameters = tags.Select((s, i) => new SqlParameter("@tag" + i.ToString(), SqlDbType.NVarChar(50)) { Value = s}).ToList();

var whereCondition = string.Format("tags in ({0})", String.Join(",",parameters.Select(s => s.ParameterName)));

태그 매개 변수를 두 번 반복합니다. 그러나 그것은 대부분의 시간에 중요하지 않습니다 (병목 현상이되지 않습니다. 그렇다면 루프를 풉니 다).

실제로 성능에 관심이 있고 루프를 두 번 반복하지 않으려는 경우 덜 아름다운 버전이 있습니다.

var parameters = new List<SqlParameter>();
var paramNames = new List<string>();
for (var i = 0; i < tags.Length; i++)  
{
    var paramName = "@tag" + i;

    //Include size and set value explicitly (not AddWithValue)
    //Because SQL Server may use an implicit conversion if it doesn't know
    //the actual size.
    var p = new SqlParameter(paramName, SqlDbType.NVarChar(50) { Value = tags[i]; } 
    paramNames.Add(paramName);
    parameters.Add(p);
}

var inClause = string.Join(",", paramNames);

5

이 문제에 대한 또 다른 대답이 있습니다.

(6/4/13에 게시 된 새 버전).

    private static DataSet GetDataSet(SqlConnectionStringBuilder scsb, string strSql, params object[] pars)
    {
        var ds = new DataSet();
        using (var sqlConn = new SqlConnection(scsb.ConnectionString))
        {
            var sqlParameters = new List<SqlParameter>();
            var replacementStrings = new Dictionary<string, string>();
            if (pars != null)
            {
                for (int i = 0; i < pars.Length; i++)
                {
                    if (pars[i] is IEnumerable<object>)
                    {
                        List<object> enumerable = (pars[i] as IEnumerable<object>).ToList();
                        replacementStrings.Add("@" + i, String.Join(",", enumerable.Select((value, pos) => String.Format("@_{0}_{1}", i, pos))));
                        sqlParameters.AddRange(enumerable.Select((value, pos) => new SqlParameter(String.Format("@_{0}_{1}", i, pos), value ?? DBNull.Value)).ToArray());
                    }
                    else
                    {
                        sqlParameters.Add(new SqlParameter(String.Format("@{0}", i), pars[i] ?? DBNull.Value));
                    }
                }
            }
            strSql = replacementStrings.Aggregate(strSql, (current, replacementString) => current.Replace(replacementString.Key, replacementString.Value));
            using (var sqlCommand = new SqlCommand(strSql, sqlConn))
            {
                if (pars != null)
                {
                    sqlCommand.Parameters.AddRange(sqlParameters.ToArray());
                }
                else
                {
                    //Fail-safe, just in case a user intends to pass a single null parameter
                    sqlCommand.Parameters.Add(new SqlParameter("@0", DBNull.Value));
                }
                using (var sqlDataAdapter = new SqlDataAdapter(sqlCommand))
                {
                    sqlDataAdapter.Fill(ds);
                }
            }
        }
        return ds;
    }

건배.


4

유일한 승리의 움직임은 플레이하지 않는 것입니다.

무한한 변동성이 없습니다. 유한 한 가변성.

SQL에는 다음과 같은 절이 있습니다.

and ( {1}==0 or b.CompanyId in ({2},{3},{4},{5},{6}) )

C # 코드에서는 다음과 같이합니다.

  int origCount = idList.Count;
  if (origCount > 5) {
    throw new Exception("You may only specify up to five originators to filter on.");
  }
  while (idList.Count < 5) { idList.Add(-1); }  // -1 is an impossible value
  return ExecuteQuery<PublishDate>(getValuesInListSQL, 
               origCount,   
               idList[0], idList[1], idList[2], idList[3], idList[4]);

따라서 기본적으로 카운트가 0이면 필터가 없으며 모든 것이 통과합니다. 카운트가 0보다 크면 값은 목록에 있어야하지만 목록은 불가능한 값으로 5로 채워져 있습니다 (SQL이 여전히 의미가 있음)

때로는 절름발이 솔루션이 실제로 작동하는 유일한 솔루션입니다.

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