여기에서 볼 수있는 다른 많은 답변 (및 중복 질문에서)은 기본적으로 매우 구체적으로 형식이 지정된 데이터, 예를 들어 완전히 숫자이거나 고정 길이 알파벳 접두사가있는 문자열 에서만 작동 합니다. 이것은 일반적인 경우에는 작동하지 않습니다.
그것은 100 % 일반 NAT-종류의 MySQL은, 그것을 할 수 있기 때문에 무엇을 구현할 수있는 방법 정말 거기 있다는 사실 정말 필요로하는 수정 된 비교 함수 문자열의 사전 식 정렬 및 숫자 정렬 경우 사이 스위치 / 때 만남 숫자. 이러한 코드는 두 문자열 내의 숫자 부분을 인식하고 비교하기 위해 원하는 알고리즘을 구현할 수 있습니다. 그러나 불행히도 MySQL의 비교 기능은 코드 내부에 있으며 사용자가 변경할 수 없습니다.
이것은 어떤 종류의 해킹을 남깁니다. 여기서 숫자 부분이 다시 형식화되는 문자열에 대한 정렬 키 를 만들어서 표준 사전 식 정렬이 실제로 원하는 방식으로 정렬되도록합니다 .
최대 자릿수까지의 일반 정수의 경우 명백한 해결책은 모두 고정 너비가되도록 0으로 왼쪽을 채우는 것입니다. 이것은 Drupal 플러그인과 @plalx / @RichardToth의 솔루션이 취하는 접근 방식입니다. (@Christian은 다르고 훨씬 더 복잡한 솔루션을 가지고 있지만 제가 볼 수있는 이점은 없습니다.)
@tye가 지적했듯이 단순히 왼쪽을 채우는 대신 각 숫자 앞에 고정 숫자 길이를 추가하여이를 개선 할 수 있습니다. 하지만 본질적으로 어색한 해킹이라는 한계를 감안하더라도 훨씬 더 개선 할 수있는 것이 있습니다. 그러나 거기에는 사전 구축 된 솔루션이없는 것 같습니다!
예를 들면 다음과 같습니다.
- 더하기 및 빼기 기호? +10 대 10 대 -10
- 소수? 8.2, 8.5, 1.006, .75
- 선행 0? 020, 030, 00000922
- 천 개의 구분 기호? "1,001 Dalmations"대 "1001 Dalmations"
- 버전 번호? MariaDB v10.3.18 대 MariaDB v10.3.3
- 아주 긴 숫자? 103,768,276,592,092,364,859,236,487,687,870,234,598.55
@tye의 메서드를 확장하여 임의의 문자열을 nat-sort 키로 변환하고 위의 모든 경우를 처리하고 합리적으로 효율적이며 전체 정렬을 유지하는 상당히 컴팩트 한 NatSortKey () 저장 함수를 만들었습니다. 순서 (두 개의 다른 문자열에 동일하게 비교되는 정렬 키가 없음). 두 번째 매개 변수는 각 문자열에서 처리되는 숫자의 수를 제한하는 데 사용할 수 있습니다 (예 : 처음 10 개의 숫자). 이는 출력이 주어진 길이에 맞도록하는 데 사용할 수 있습니다.
참고 :이 두 번째 매개 변수의 주어진 값으로 생성 된 정렬 키 문자열은 매개 변수에 대해 동일한 값으로 생성 된 다른 문자열에 대해서만 정렬되어야합니다. 그렇지 않으면 올바르게 정렬되지 않을 수 있습니다!
주문시 직접 사용할 수 있습니다.
SELECT myString FROM myTable ORDER BY NatSortKey(myString,0);
그러나 큰 테이블을 효율적으로 정렬하려면 정렬 키를 다른 열에 미리 저장하는 것이 좋습니다 (인덱스가있을 수 있음).
INSERT INTO myTable (myString,myStringNSK) VALUES (@theStringValue,NatSortKey(@theStringValue,10)), ...
...
SELECT myString FROM myTable ORDER BY myStringNSK;
[이상적으로는 다음과 같은 것을 사용하여 키 열을 계산 된 저장 열로 생성하여이 작업을 자동으로 수행합니다.
CREATE TABLE myTable (
...
myString varchar(100),
myStringNSK varchar(150) AS (NatSortKey(myString,10)) STORED,
...
KEY (myStringNSK),
...);
그러나 현재 MySQL이나 MariaDB는 계산 된 열에 저장된 함수를 허용 하지 않으므로 불행히도 아직이 작업을 수행 할 수 없습니다 .]
내 기능 은 숫자 정렬에만 영향을 미칩니다 . 당신은 모든 문장 부호를 제거하거나 양쪽 끝에 떨어져 공백을 트리밍, 또는 단일 공백으로 멀티 공백 시퀀스를 교체하는 등의 다른 정렬 정규화 일을 수행하려는 경우, 당신도 기능을 확장 할 수 있거나, 이전 또는 이후에 수행 될 수 NatSortKey()
IS 데이터에 적용됩니다. ( REGEXP_REPLACE()
이 목적으로 사용 하는 것이 좋습니다 ).
또한 내가 '.'라고 가정한다는 점에서 다소 앵글로 중심적입니다. 소수점은 소수점으로, ','는 천 단위로 구분하지만, 역방향을 원하거나 매개 변수로 전환 할 수 있도록하려는 경우 쉽게 수정할 수 있어야합니다.
다른 방법으로 더 개선 할 수 있습니다. 예를 들어 현재 절대 값으로 음수를 정렬하므로 -1이 -2 앞에옵니다. 텍스트에 대한 ASC 사전 정렬을 유지하면서 숫자에 대한 DESC 정렬 순서를 지정하는 방법도 없습니다. 이 두 가지 문제는 조금 더 작업하면 해결할 수 있습니다. 시간이되면 코드를 업데이트하겠습니다.
사용중인 chaset 및 데이터 정렬에 대한 몇 가지 중요한 종속성을 포함하여 알아야 할 다른 세부 사항이 많이 있지만 SQL 코드 내의 주석 블록에 모두 넣었습니다. 직접 기능을 사용하기 전에이 내용을주의 깊게 읽으십시오!
자, 여기에 코드가 있습니다. 버그를 발견하거나 언급하지 않은 개선 사항이 있으면 의견을 통해 알려주십시오!
delimiter $$
CREATE DEFINER=CURRENT_USER FUNCTION NatSortKey (s varchar(100), n int) RETURNS varchar(350) DETERMINISTIC
BEGIN
DECLARE x,y varchar(100);
DECLARE r varchar(350) DEFAULT '';
DECLARE suf varchar(101);
DECLARE i,j,k int UNSIGNED;
IF n<=0 THEN SET n := -1; END IF;
LOOP
SET i := REGEXP_INSTR(s,'\\d');
IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF;
SET n := n-1, suf := ' ';
IF i>1 THEN
IF SUBSTRING(s,i-1,1)='.' AND (i=2 OR SUBSTRING(s,i-2,1) RLIKE '[^.\\p{L}\\p{N}\\p{M}\\x{608}\\x{200C}\\x{200D}\\x{2100}-\\x{214F}\\x{24B6}-\\x{24E9}\\x{1F130}-\\x{1F149}\\x{1F150}-\\x{1F169}\\x{1F170}-\\x{1F189}]') AND (SUBSTRING(s,i) NOT RLIKE '^\\d++\\.\\d') THEN SET i:=i-1; END IF;
IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF;
SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i);
END IF;
SET x := REGEXP_SUBSTR(s,IF(SUBSTRING(s,1,1) IN ('0','.') OR (SUBSTRING(r,-1)=',' AND suf=' '),'^\\d*+(?:\\.\\d++)*','^(?:[1-9]\\d{0,2}(?:,\\d{3}(?!\\d))++|\\d++)(?:\\.\\d++)*+'));
SET s := SUBSTRING(s,LENGTH(x)+1);
SET i := INSTR(x,'.');
IF i=0 THEN SET y := ''; ELSE SET y := SUBSTRING(x,i); SET x := SUBSTRING(x,1,i-1); END IF;
SET i := LENGTH(x);
SET x := REPLACE(x,',','');
SET j := LENGTH(x);
SET x := TRIM(LEADING '0' FROM x);
SET k := LENGTH(x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0'));
SET i := LOCATE('.',y,2);
IF i=0 THEN
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x,y,suf);
ELSE
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);
WHILE LENGTH(y)>0 AND n!=0 DO
IF i=0 THEN SET x := SUBSTRING(y,2); SET y := ''; ELSE SET x := SUBSTRING(y,2,i-2); SET y := SUBSTRING(y,i); SET i := LOCATE('.',y,2); END IF;
SET j := LENGTH(x);
SET x := TRIM(LEADING '0' FROM x);
SET k := LENGTH(x);
SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);
SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0'));
SET n := n-1;
END WHILE;
SET r := CONCAT(r,y,suf);
END IF;
END LOOP;
END
$$
delimiter ;