주문 및 문자와 숫자의 혼합 문자열 비교


9

일반적으로 '자연스럽게'정렬해야하는 숫자와 문자가 혼합 된 문자열 값에 대해보고해야합니다. "P7B18"또는 "P12B3"과 같은 것. @ 문자열은 대부분 문자의 순서이며 숫자가 교대로 나타납니다. 그러나 이러한 세그먼트의 수와 길이는 다를 수 있습니다.

우리는 이것들의 숫자 부분을 숫자 순서로 정렬하고 싶습니다. 분명히 문자열 값을으로 직접 처리하면 ORDER BY"P1"이 "P7"보다 빠르기 때문에 "P12B3"이 "P7B18"보다 앞에 올 것입니다. "P12".

또한 범위 비교, 예를 들어 @bin < 'P13S6'또는 일부 를 수행하고 싶습니다 . 부동 소수점이나 음수를 처리 할 필요가 없습니다. 이것들은 우리가 다루는 음이 아닌 정수입니다. 문자열 길이와 세그먼트 수는 고정 된 상한없이 임의적 일 수 있습니다.

우리의 경우 문자열 케이싱은 중요하지 않지만 데이터 정렬 인식 방식 으로이 작업을 수행 할 수있는 방법이 있다면 다른 사람들이 유용 할 수 있습니다. 이 모든 것의 가장 추악한 부분은 WHERE절 에서 순서와 범위 필터링을 모두 수행하고 싶습니다 .

C # 에서이 작업을 수행하는 경우 꽤 간단한 작업입니다. 알파를 숫자와 분리하고 IComparable을 구현하기 위해 파싱을 수행하면 기본적으로 완료됩니다. 물론 SQL Server는 적어도 내가 아는 한 유사한 기능을 제공하지 않는 것으로 보입니다.

이 작업을 수행하는 좋은 요령을 아는 사람이 있습니까? IComparable을 구현하고 예상대로 동작하는 사용자 지정 CLR 형식을 만드는 약간의 공개 기능이 있습니까? 또한 Stupid XML Tricks (목록 연결 참조)에 반대하지 않으며 서버에서 CLR 정규식 일치 / 추출 / 교체 래퍼 기능을 사용할 수 있습니다.

편집 : 좀 더 자세한 예로써, 데이터가 이와 같이 작동하기를 원합니다.

SELECT bin FROM bins ORDER BY bin

bin
--------------------
M7R16L
P8RF6JJ
P16B5
PR7S19
PR7S19L
S2F3
S12F0

즉, 문자열을 모든 문자 또는 숫자의 토큰으로 나누고 가장 왼쪽의 토큰이 가장 중요한 정렬 용어 인 알파벳 또는 숫자로 각각 정렬합니다. 앞에서 언급했듯이 IComparable을 구현하면 .NET의 케이크 조각이지만 SQL Server에서 어떻게 그런 일을 할 수 있는지 (또는 경우) 알 수 없습니다. 확실히 10 년 정도 일한 적이있는 것은 아닙니다.


문자열을 정수로 바꾸어 일종의 인덱스 계산 열 로이 작업을 수행 할 수 있습니다. 그래서 P7B12될 수있는 P 07 B 12(ASCII를 통해) 한 후, 80 07 65 12그래서80076512
Philᵀᴹ

각 숫자 구성 요소를 큰 길이 (즉, 0)로 채우는 계산 열을 만드는 것이 좋습니다. 형식은 매우 임의적이므로 꽤 큰 인라인 표현식이 필요하지만 가능합니다. 당신은 당신이 원하는만큼 그 열의 / 위치별로 색인 / 순서를 지정할 수 있습니다.
Nick.McDermaid

내 답변 맨 위에 추가 한 링크를 참조하십시오 :)
Solomon Rutzky

1
@ srutzky 니스, 나는 투표했다.
db2

Hey db2 : Microsoft가 Connect에서 UserVoice로 이동하고 투표 수를 정확하게 유지하지 못하기 때문에 (댓글을 달았지만 잘 보지 못함) 다시 투표해야합니다. "자연 정렬"지원 데이터 정렬 옵션으로 / DIGITSASNUMBERS . 감사!
Solomon Rutzky

답변:


8

문자열의 숫자를 실제 숫자로 정렬하는 현명하고 효율적인 방법을 원하십니까? Microsoft Connect 제안에 대한 투표를 고려하십시오 . 데이터 정렬 옵션으로 "자연 정렬"/ DIGITSASNUMBERS 지원


이 작업을 수행하는 쉽고 내장 된 방법은 없지만 다음과 같은 가능성이 있습니다.

문자열을 고정 길이 세그먼트로 다시 포맷하여 문자열을 정규화하십시오.

  • 유형의 정렬 열을 작성하십시오 VARCHAR(50) COLLATE Latin1_General_100_BIN2. 최대 세그먼트 수와 잠재적 최대 길이에 따라 최대 길이 50을 조정해야 할 수도 있습니다.
  • 앱 계층에서 정규화를보다 효율적으로 수행 할 수 있지만 T-SQL UDF를 사용하여 데이터베이스에서이를 처리하면 스칼라 UDF를 AFTER [or FOR] INSERT, UPDATE트리거 에 배치하여 모든 레코드의 값을 올바르게 설정하도록 보장 할 수 있습니다. 물론 스칼라 UDF도 SQLCLR을 통해 처리 할 수 ​​있지만 실제로 어느 것이 더 효율적인지 판별하기 위해 테스트해야합니다. **
  • UDF (T-SQL 또는 SQLCLR에 관계없이)는 다음을 수행해야합니다.
    • 각 문자를 읽고 유형이 알파에서 숫자로 또는 숫자에서 알파로 전환 될 때 중지하여 알 수없는 세그먼트 수를 처리하십시오.
    • 각 세그먼트마다 고정 길이 문자열 세트를 모든 세그먼트의 가능한 최대 문자 / 숫자로 반환해야합니다 (또는 향후 성장을 설명하기 위해 최대 + 1 또는 2).
    • 알파 세그먼트는 왼쪽 정렬되고 오른쪽으로 채워 져야합니다.
    • 숫자 세그먼트는 오른쪽으로 정렬하고 왼쪽으로 0으로 채워야합니다.
    • 알파벳 문자가 대소 문자를 혼합하여 입력 할 수 있지만 순서는 대소 문자를 구분해야하는 경우 UPPER()모든 세그먼트의 최종 결과에 함수를 적용하십시오 (세그먼트 당이 아니라 한 번만 수행하면 됨). 이것은 정렬 열의 이진 데이터 정렬이 주어지면 적절한 정렬을 허용합니다.
  • AFTER INSERT, UPDATE정렬 열을 설정하기 위해 UDF를 호출하는 테이블 에서 트리거를 작성하십시오 . 성능을 향상 시키려면 UPDATE()함수를 사용 하여이 코드 열이 명령문 의 SET절에 있는지 여부를 판별 한 후 (거짓이면 UPDATE간단히 RETURN) 코드 열의 INSERTEDDELETED의사 테이블 을 조인 하여 코드 값이 변경된 행만 처리하십시오. . 변경이 있는지 COLLATE Latin1_General_100_BIN2판별 할 때 정확성을 보장하려면 해당 JOIN 조건 을 지정하십시오 .
  • 새 정렬 열에서 색인을 작성하십시오.

예:

P7B18   -> "P     000007B     000018"
P12B3   -> "P     000012B     000003"
P12B3C8 -> "P     000012B     000003C     000008"

이 방법에서는 다음을 통해 정렬 할 수 있습니다.

ORDER BY tbl.SortColumn

다음을 통해 범위 필터링을 수행 할 수 있습니다.

WHERE tbl.SortColumn BETWEEN dbo.MyUDF('P7B18') AND dbo.MyUDF('P12B3')

또는:

DECLARE @RangeStart VARCHAR(50),
        @RangeEnd VARCHAR(50);
SELECT @RangeStart = dbo.MyUDF('P7B18'),
       @RangeEnd = dbo.MyUDF('P12B3');

WHERE tbl.SortColumn BETWEEN @RangeStart AND @RangeEnd

ORDER BYWHERE필터 모두 SortColumn데이터 정렬 우선 순위로 인해 정의 된 이진 데이터 정렬을 사용해야합니다 .

평등 비교는 여전히 원래 값 열에서 수행됩니다.


다른 생각들:

  • SQLCLR UDT를 사용하십시오. 위에서 설명한 접근 방식과 비교할 때 순 이득을 제공하는지 확실하지는 않지만 작동 할 수 있습니다.

    예, SQLCLR UDT는 사용자 정의 알고리즘으로 비교 연산자를 대체 할 수 있습니다. 이는 값이 이미 동일한 사용자 정의 유형 인 다른 값 또는 내재적으로 변환되어야하는 값과 비교되는 상황을 처리합니다. 이렇게 해야 A의 범위 필터 처리 WHERE조건.

    UDT를 계산 열이 아닌 일반 열 유형으로 정렬하는 것과 관련하여 UDT가 "바이트 순서"인 경우에만 가능합니다. "바이트 순서"는 UDT의 이진 표현 (UDT에서 정의 할 수 있음)이 자연스럽게 적절한 순서로 정렬됨을 의미합니다. 이진 표현이 채워진 고정 길이 세그먼트가있는 VARCHAR (50) 열에 대해 위에서 설명한 접근 방식과 유사하게 처리된다고 가정하면, 해당됩니다. 또는 이진 표현이 자연스럽게 올바른 방식으로 정렬되도록하는 것이 쉽지 않은 경우 올바르게 정렬 된 값을 출력하는 UDT의 메서드 또는 속성을 노출 한 다음 PERSISTED해당 열에 계산 열 을 만들 수 있습니다 방법 또는 속성. 이 방법은 결정적이고로 표시되어야합니다 IsDeterministic = true.

    이 방법의 장점은 다음과 같습니다.

    • "원래 값"필드가 필요하지 않습니다.
    • 데이터를 삽입하거나 값을 비교하기 위해 UDF를 호출 할 필요가 없습니다. ParseUDT 의 방법이 P7B18값을 가져 와서 변환 한다고 가정하면 간단히 자연스럽게 값을 삽입 할 수 있어야합니다 P7B18. 또한 UDT에 암시 적 변환 방법을 설정하면 WHERE 조건을 사용하여 간단히 P7B18도 사용할 수 있습니다.

    이 접근법의 결과는 다음과 같습니다.

    • 바이트 순서로 정렬 된 UDT를 열 데이터 유형으로 사용하는 경우 필드를 선택하면 이진 표현이 반환됩니다. 또는 PERSISTEDUDT의 속성 또는 메서드에서 계산 열을 사용하는 경우 속성 또는 메서드에서 반환 된 표현을 얻을 수 있습니다. 원래 P7B18값 을 원하면 해당 표현을 리턴하도록 코딩 된 UDT의 메소드 또는 특성을 호출해야합니다. ToString어쨌든 메서드 를 재정의해야하기 때문에이 방법을 제공하는 것이 좋습니다.
    • 이진 표현을 변경하는 것이 얼마나 쉽고 어려운지는 분명하지 않습니다 (적어도 지금은이 부분을 테스트하지 않았으므로 지금 당장 나에게 분명하지 않습니다). 저장된 정렬 가능한 표현을 변경하려면 필드를 삭제하고 다시 추가해야합니다. 또한 어느 방식 으로든 UDT가 포함 된 어셈블리를 삭제하면 실패하므로이 UDT 외에 어셈블리에 다른 것이 없는지 확인해야합니다. ALTER ASSEMBLY정의를 대체 할 수 있지만 이에 대한 제한이 있습니다.

      반면, VARCHAR()필드는 알고리즘에서 연결이 끊어진 데이터이므로 열을 업데이트하기 만하면됩니다. 그리고 수천만 행 (또는 그 이상)이 있다면, 그것은 일괄 처리 방식으로 수행 될 수 있습니다.

  • 실제로이 영숫자 정렬을 수행 할 수 있는 ICU 라이브러리를 구현하십시오 . 뛰어난 기능을 갖춘이 라이브러리는 C / C ++ 및 Java의 두 언어로만 제공됩니다. 즉, Visual C ++에서 작동하려면 약간의 조정이 필요하거나 IKVM을 사용하여 Java 코드를 MSIL로 변환 할 수있는 가능성이 있습니다 . 해당 사이트에 링크 된 관리 코드로 액세스 할 수있는 COM 인터페이스를 제공하는 하나 또는 두 개의 .NET 측 프로젝트가 있지만 한동안 업데이트되지 않았으며 시도하지는 않았습니다. 여기서 가장 좋은 점은 정렬 키를 생성한다는 목표로 앱 계층에서이를 처리하는 것입니다. 그러면 정렬 키가 새로운 정렬 열에 저장됩니다.

    이것이 가장 실용적인 방법은 아닙니다. 그러나 그러한 능력이 존재한다는 것은 여전히 ​​시원합니다. 다음 답변에서 이에 대한 자세한 예제를 제공했습니다.

    1,2,3,6,10,10A, 10B, 11 순서로 다음 문자열을 정렬하는 데이터 정렬이 있습니까?

    그러나 그 질문에서 다루는 패턴은 조금 더 간단합니다. 이 질문에서 다루고있는 패턴 유형도 작동 함을 보여주는 예를 보려면 다음 페이지로 이동하십시오.

    ICU 데이터 정렬 데모

    "설정"에서 "숫자"옵션을 "on"으로 설정하고 다른 모든 항목은 "default"로 설정해야합니다. 다음으로 "정렬"버튼 오른쪽에서 "diff strengths"옵션의 선택을 해제하고 "sort keys"옵션을 확인하십시오. 그런 다음 "입력"텍스트 영역의 항목 목록을 다음 목록으로 바꾸십시오.

    P12B22
    P7B18
    P12B3
    as456456hgjg6786867
    P7Bb19
    P7BA19
    P7BB19
    P007B18
    P7Bb20
    P7Bb19z23
    

    "정렬"버튼을 클릭하십시오. "출력"텍스트 영역에 다음이 표시되어야합니다.

    as456456hgjg6786867
        29 4D 0F 7A EA C8 37 35 3B 35 0F 84 17 A7 0F 93 90 , 0D , , 0D .
    P7B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P007B18
        47 0F 09 2B 0F 14 , 08 , FD F1 , DC C5 DC 05 .
    P7BA19
        47 0F 09 2B 29 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19
        47 0F 09 2B 2B 0F 15 , 09 , FD F2 , DC C5 DC 06 .
    P7BB19
        47 0F 09 2B 2B 0F 15 , 09 , FD FF 10 , DC C5 DC DC 05 .
    P7Bb19z23
        47 0F 09 2B 2B 0F 15 5B 0F 19 , 0B , FD F4 , DC C5 DC 08 .
    P7Bb20
        47 0F 09 2B 2B 0F 16 , 09 , FD F2 , DC C5 DC 06 .
    P12B3
        47 0F 0E 2B 0F 05 , 08 , FD F1 , DC C5 DC 05 .
    P12B22
        47 0F 0E 2B 0F 18 , 08 , FD F1 , DC C5 DC 05 .

    정렬 키는 쉼표로 구분 된 여러 필드의 구조입니다. 각 필드는 독립적으로 정렬되어야하므로 SQL Server에서이를 구현해야하는 경우 해결해야 할 또 다른 작은 문제가 있습니다.


** 사용자 정의 함수의 사용과 관련하여 성능에 대한 우려가있는 경우 제안 된 접근 방식은이를 최소화합니다. 실제로 정규화 된 값을 저장하는 주된 이유는 각 쿼리의 각 행마다 UDF를 호출하지 않기위한 것입니다. 기본 접근법에서, UDF는의 값을 설정하기 위해 사용 SortColumn하고, 그 위에 만 수행 INSERT하고 UPDATE트리거로. 값 선택은 삽입 및 업데이트보다 훨씬 일반적이며 일부 값은 업데이트되지 않습니다. 절 에서 범위 필터에 대해 SELECT사용하는 각 쿼리 마다 UDF는 표준화 된 값을 가져 오기 위해 각 range_start 및 range_end 값당 한 번만 필요합니다. UDF는 행당이라고하지 않습니다.SortColumnWHERE

UDT와 관련하여 사용법은 실제로 스칼라 UDF와 동일합니다. 의미, 삽입 및 업데이트는 각 행마다 한 번씩 정규화 방법을 호출하여 값을 설정합니다. 그런 다음 정규화 방법은 범위 필터의 각 range_start 및 range_value 당 쿼리 당 한 번 호출되지만 행당은 호출되지 않습니다.

SQLCLR UDF에서 정규화를 완전히 처리하는 데 유리한 점은 데이터 액세스를 수행하지 않고 결정적이므로로 표시되어 있으면 IsDeterministic = true병렬 계획 ( INSERTUPDATE운영에 도움이 될 수 있음)에 참여할 수 있지만 T-SQL UDF는 병렬 계획을 사용하지 못하게합니다.

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