MySQL의 자연 정렬


81

MySQL 데이터베이스에서 성능적이고 자연스러운 정렬을 수행하는 우아한 방법이 있습니까?

예를 들어이 데이터 세트가있는 경우 :

  • 파이널 판타지
  • 파이널 판타지 4
  • 파이널 판타지 10
  • 파이널 판타지 12
  • 파이널 판타지 12 : 프로 마시 아의 사슬
  • 파이널 판타지 어드벤처
  • 파이널 판타지 오리진
  • 파이널 판타지 전술

게임 이름을 구성 요소로 분할하는 것보다 다른 모든 우아한 솔루션

  • 제목 : "파이널 판타지"
  • 번호 : "12"
  • 부제 : "Promathia의 사슬"

올바른 순서로 나오는지 확인하려면? (2 이전이 아니라 4 이후 10).

그렇게하는 것은 a **에게 고통입니다. 왜냐하면 때때로 게임 제목을 파싱하는 메커니즘을 깨는 또 다른 게임이 있기 때문입니다 (예 : "Warhammer 40,000", "James Bond 007").


28
Promathia의 사슬은 11과 관련이 있습니다.
Flame


답변:


20

이것이 출시일별로 많은 것이 분류되는 이유라고 생각합니다.

해결책은 "SortKey"에 대한 테이블에 다른 열을 만드는 것입니다. 이것은 쉬운 정렬이나 카운터를 위해 만든 패턴을 따르는 제목의 정리 된 버전 일 수 있습니다.



2
이것은 확실히 올바른 접근 방식이지만 그 자체로는 답이 아닙니다!
Doin

90

다음은 빠른 해결책입니다.

SELECT alphanumeric, 
       integer
FROM sorting_test
ORDER BY LENGTH(alphanumeric), alphanumeric

49
모든 것이 "파이널 판타지"라면 좋지만 FF 스위트보다 "구피"를 앞세운다.
fortboise 2011

4
이 솔루션은 항상 작동하지 않습니다. 때때로 깨집니다. 차라리 이것을 사용해야합니다 : stackoverflow.com/a/12257917/384864
Borut Tomazin 2013 년

6
kludge에 kludge 쌓기 : SELECT alphanumeric, integer FROM sorting_test ORDER BY SOUNDEX(alphanumeric), LENGTH(alphanumeric), alphanumeric. 이것이 전혀 작동한다면 SOUNDEX가 편리하게 숫자를 버려서 예가 apple1앞에 오는 것을 보장 하기 때문 z1입니다.
offby1 2014 년

훌륭한 솔루션, 감사, 내가 스위치를해야했다하지만 alphanmuric, length(alphanumeric)피할 "바보"로 "파이널 판타지"전
Asped

1
@ offby1 제안 SOUNDEX()은 영어 단어에서만 올바르게 작동하도록 설계된 텍스트가 100 % 영어로 작성된 경우에만 작동합니다.
Raymond Nijland

56

방금 발견 :

SELECT names FROM your_table ORDER BY games + 0 ASC

숫자가 앞에있을 때 자연스러운 정렬을 수행하고 중간에서도 작동 할 수 있습니다.


2
나는 그것을 시도하지 않았지만 심각하게 의심합니다. 앞의 숫자와 함께 작동하는 이유 games는 숫자 컨텍스트에서와 같이 사용되어 비교 전에 숫자로 변환되기 때문입니다. 중간에 있으면 항상 0으로 변환되고 정렬은 의사 랜덤이됩니다.
manixrock

1
이것은 자연스러운 종류가 아닙니다.
차라리이

@fedir 이것은 나에게도 잘 작동했습니다. 왜 이것이 작동하는지 정확히 알지 못합니다. 설명 markletp의 가능성은?
BizNuge 2014

이것에 대한 빠른 조사가 있었고 나는 그것을 얻었습니다. 나는 MySQL이 문자열에 수학 연산자를 사용하는 것만으로 이런 종류의 캐스팅을 할 수 있다는 것을 깨닫지 못했습니다! 멋진 점은 "cast"할 문자열 앞에 정수가없는 경우 zer0을 반환한다는 것입니다. 감사합니다! ---> SELECT ADDRESS, (ADDRESS * 1) as _cast FROM premises WHERE POSTCODE like 'NE1 %'ORDER BY ADDRESS * 1 ASC, ADDRESS LIMIT 100000;
BizNuge 2014

1
"Final Fantasy 100"또는 "Final Fantasy 2"와 같이 숫자가 중간에있을 때는 실제로 작동하지 않습니다. "파이널 판타지 100"이 먼저 공개됩니다. 그러나 정수가 처음 "100 Final Fantasy"일 때 작동합니다
dwenaus

52

@plalx가 게시 한 것과 동일한 기능이지만 MySQL로 다시 작성되었습니다.

DROP FUNCTION IF EXISTS `udf_FirstNumberPos`;
DELIMITER ;;
CREATE FUNCTION `udf_FirstNumberPos` (`instring` varchar(4000)) 
RETURNS int
LANGUAGE SQL
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
    DECLARE position int;
    DECLARE tmp_position int;
    SET position = 5000;
    SET tmp_position = LOCATE('0', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF; 
    SET tmp_position = LOCATE('1', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('2', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('3', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('4', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('5', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('6', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('7', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('8', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;
    SET tmp_position = LOCATE('9', instring); IF (tmp_position > 0 AND tmp_position < position) THEN SET position = tmp_position; END IF;

    IF (position = 5000) THEN RETURN 0; END IF;
    RETURN position;
END
;;

DROP FUNCTION IF EXISTS `udf_NaturalSortFormat`;
DELIMITER ;;
CREATE FUNCTION `udf_NaturalSortFormat` (`instring` varchar(4000), `numberLength` int, `sameOrderChars` char(50)) 
RETURNS varchar(4000)
LANGUAGE SQL
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
    DECLARE sortString varchar(4000);
    DECLARE numStartIndex int;
    DECLARE numEndIndex int;
    DECLARE padLength int;
    DECLARE totalPadLength int;
    DECLARE i int;
    DECLARE sameOrderCharsLen int;

    SET totalPadLength = 0;
    SET instring = TRIM(instring);
    SET sortString = instring;
    SET numStartIndex = udf_FirstNumberPos(instring);
    SET numEndIndex = 0;
    SET i = 1;
    SET sameOrderCharsLen = CHAR_LENGTH(sameOrderChars);

    WHILE (i <= sameOrderCharsLen) DO
        SET sortString = REPLACE(sortString, SUBSTRING(sameOrderChars, i, 1), ' ');
        SET i = i + 1;
    END WHILE;

    WHILE (numStartIndex <> 0) DO
        SET numStartIndex = numStartIndex + numEndIndex;
        SET numEndIndex = numStartIndex;

        WHILE (udf_FirstNumberPos(SUBSTRING(instring, numEndIndex, 1)) = 1) DO
            SET numEndIndex = numEndIndex + 1;
        END WHILE;

        SET numEndIndex = numEndIndex - 1;

        SET padLength = numberLength - (numEndIndex + 1 - numStartIndex);

        IF padLength < 0 THEN
            SET padLength = 0;
        END IF;

        SET sortString = INSERT(sortString, numStartIndex + totalPadLength, 0, REPEAT('0', padLength));

        SET totalPadLength = totalPadLength + padLength;
        SET numStartIndex = udf_FirstNumberPos(RIGHT(instring, CHAR_LENGTH(instring) - numEndIndex));
    END WHILE;

    RETURN sortString;
END
;;

용법:

SELECT name FROM products ORDER BY udf_NaturalSortFormat(name, 10, ".")

5
이것이 실제로 작동하는 유일한 솔루션입니다. 나는 또한 drupals 코드를 테스트했지만 때때로 실패합니다. 감사합니다!
Borut Tomazin

정말 큰 테이블에서 천만 이상 사용하는 사람이 있습니까?
Mark Steudel

3
@MarkSteudel 우리는 여러 테이블에서 자연 정렬을 위해 이것과 비슷한 함수를 사용합니다 (정확한 것은 아니지만). 그러나 쿼리에서 직접 호출하지 않고 대신 nat_name열 값을 설정하는 데 사용합니다 . 행이 업데이트 될 때마다 함수를 실행하기 위해 트리거를 사용합니다. 이 접근 방식은 추가 열을 사용하는 대신 실제 성능 비용없이 자연스러운 정렬을 제공합니다.
Jacob

이것은 문자 앞의 숫자를 정렬하고 작동하며, 이것과 유사한 것을 사용하여 hook_views_query_alter를 사용하여 Drupal에서 구현 될 수 있습니다if ($query->orderby[0]["field"] === "node_field_data.title") { $orderBySql = " udf_NaturalSortFormat(node_field_data.title, 10, '.') "; $query->orderby = []; $query->addOrderBy(NULL, $orderBySql, $query->orderby[0]["direction"], 'title_natural'); array_unshift($query->orderby, end($query->orderby)); }
realgt

16

얼마 전에 MSSQL 2000 용으로이 함수를 작성했습니다 .

/**
 * Returns a string formatted for natural sorting. This function is very useful when having to sort alpha-numeric strings.
 *
 * @author Alexandre Potvin Latreille (plalx)
 * @param {nvarchar(4000)} string The formatted string.
 * @param {int} numberLength The length each number should have (including padding). This should be the length of the longest number. Defaults to 10.
 * @param {char(50)} sameOrderChars A list of characters that should have the same order. Ex: '.-/'. Defaults to empty string.
 *
 * @return {nvarchar(4000)} A string for natural sorting.
 * Example of use: 
 * 
 *      SELECT Name FROM TableA ORDER BY Name
 *  TableA (unordered)              TableA (ordered)
 *  ------------                    ------------
 *  ID  Name                    ID  Name
 *  1.  A1.                 1.  A1-1.       
 *  2.  A1-1.                   2.  A1.
 *  3.  R1      -->         3.  R1
 *  4.  R11                 4.  R11
 *  5.  R2                  5.  R2
 *
 *  
 *  As we can see, humans would expect A1., A1-1., R1, R2, R11 but that's not how SQL is sorting it.
 *  We can use this function to fix this.
 *
 *      SELECT Name FROM TableA ORDER BY dbo.udf_NaturalSortFormat(Name, default, '.-')
 *  TableA (unordered)              TableA (ordered)
 *  ------------                    ------------
 *  ID  Name                    ID  Name
 *  1.  A1.                 1.  A1.     
 *  2.  A1-1.                   2.  A1-1.
 *  3.  R1      -->         3.  R1
 *  4.  R11                 4.  R2
 *  5.  R2                  5.  R11
 */
CREATE FUNCTION dbo.udf_NaturalSortFormat(
    @string nvarchar(4000),
    @numberLength int = 10,
    @sameOrderChars char(50) = ''
)
RETURNS varchar(4000)
AS
BEGIN
    DECLARE @sortString varchar(4000),
        @numStartIndex int,
        @numEndIndex int,
        @padLength int,
        @totalPadLength int,
        @i int,
        @sameOrderCharsLen int;

    SELECT 
        @totalPadLength = 0,
        @string = RTRIM(LTRIM(@string)),
        @sortString = @string,
        @numStartIndex = PATINDEX('%[0-9]%', @string),
        @numEndIndex = 0,
        @i = 1,
        @sameOrderCharsLen = LEN(@sameOrderChars);

    -- Replace all char that has to have the same order by a space.
    WHILE (@i <= @sameOrderCharsLen)
    BEGIN
        SET @sortString = REPLACE(@sortString, SUBSTRING(@sameOrderChars, @i, 1), ' ');
        SET @i = @i + 1;
    END

    -- Pad numbers with zeros.
    WHILE (@numStartIndex <> 0)
    BEGIN
        SET @numStartIndex = @numStartIndex + @numEndIndex;
        SET @numEndIndex = @numStartIndex;

        WHILE(PATINDEX('[0-9]', SUBSTRING(@string, @numEndIndex, 1)) = 1)
        BEGIN
            SET @numEndIndex = @numEndIndex + 1;
        END

        SET @numEndIndex = @numEndIndex - 1;

        SET @padLength = @numberLength - (@numEndIndex + 1 - @numStartIndex);

        IF @padLength < 0
        BEGIN
            SET @padLength = 0;
        END

        SET @sortString = STUFF(
            @sortString,
            @numStartIndex + @totalPadLength,
            0,
            REPLICATE('0', @padLength)
        );

        SET @totalPadLength = @totalPadLength + @padLength;
        SET @numStartIndex = PATINDEX('%[0-9]%', RIGHT(@string, LEN(@string) - @numEndIndex));
    END

    RETURN @sortString;
END

GO

@MarkSteudel 당신은 그것을 시도하고 직접 테스트해야 할 것입니다. 더 나쁜 경우에는 항상 형식화 된 값을 캐시 할 수 있습니다. 필드를 인덱싱 할 수도 있으므로 큰 테이블에 대해 그렇게 할 수 있습니다.
plalx

15

MySQL은 이러한 종류의 "자연 정렬"을 허용하지 않으므로 위에서 설명한대로 (별도의 ID 필드 등) 데이터 설정을 분할하거나 실패하는 것이 가장 좋은 방법 인 것 같습니다. 즉, 제목이 아닌 요소, db의 색인화 된 요소 (날짜, db에 삽입 된 ID 등)를 기반으로 정렬을 수행합니다.

db가 정렬을 수행하도록하는 것은 대용량 데이터 세트를 선택한 프로그래밍 언어로 읽어서 정렬하는 것보다 거의 항상 빠릅니다. 따라서 여기에서 db 스키마를 제어 할 수있는 경우 추가를 살펴보십시오. 위에서 설명한대로 필드를 쉽게 분류하면 장기적으로 많은 번거 로움과 유지 관리를 줄일 수 있습니다.

"자연스러운 정렬"을 추가하라는 요청은 때때로 MySQL 버그토론 포럼 에서 발생하며 많은 솔루션이 데이터의 특정 부분을 제거 ORDER BY하고 쿼리 의 일부로 캐스팅하는 것입니다.

SELECT * FROM table ORDER BY CAST(mid(name, 6, LENGTH(c) -5) AS unsigned) 

이러한 종류의 솔루션은 위의 Final Fantasy 예제에서 작동하도록 만들 수 있지만 특별히 유연하지 않으며 "Warhammer 40,000"및 "James Bond 007"과 같은 데이터 세트로 깔끔하게 확장 할 가능성이 낮습니다. .


9

따라서 만족스러운 답을 찾았다는 것을 알고 있지만 잠시 동안이 문제로 어려움을 겪고 있었으며 이전에는 SQL에서 합리적으로 잘 수행 될 수 없다고 판단했고 JSON에서 자바 스크립트를 사용해야했습니다. 정렬.

다음은 SQL을 사용하여 해결 한 방법입니다. 다른 사람들에게 도움이되기를 바랍니다.

다음과 같은 데이터가 있습니다.

장면 1
장면 1A
장면 1B
장면 2A
장면 3
...
장면 101
장면 XXA1
장면 XXA2

나는 그것이 또한 효과가있을 것이라고 생각하지만 실제로 물건을 "캐스트"하지 않았다.

먼저 데이터에서 변경되지 않은 부분 (이 경우 "Scene")을 교체 한 다음 LPAD를 사용하여 정렬했습니다. 이것은 알파 문자열과 번호가 매겨진 문자열을 적절하게 정렬 할 수 있도록하는 것 같습니다.

ORDER BY조항은 다음과 같습니다.

ORDER BY LPAD(REPLACE(`table`.`column`,'Scene ',''),10,'0')

분명히 이것은 그렇게 획일적이지 않은 원래의 문제에는 도움이되지 않습니다. 그러나 나는 이것이 아마도 다른 많은 관련 문제에 대해 효과가있을 것이라고 생각합니다.


LPAD()힌트는 매우 도움이되었다. 정렬 할 단어와 숫자가 있는데 LPAD자연스럽게 숫자를 정렬 할 수 있습니다. 그리고 CONCAT나는 숫자가 아닌 것을 무시합니다. 내 쿼리는 다음과 같습니다 (별칭은 정렬 할 열입니다) : IF(CONCAT("",alias*1)=alias, LPAD(alias,5,"0"), alias) ASC;👍
Kai Noack

6
  1. 테이블에 정렬 키 (순위)를 추가합니다. ORDER BY rank

  2. "출시일"열을 활용하십시오. ORDER BY release_date

  3. SQL에서 데이터를 추출 할 때 개체가 정렬을 수행하도록합니다. 예를 들어 집합으로 추출하는 경우 TreeSet으로 만들고 데이터 모델이 Comparable을 구현하고 여기에서 자연 정렬 알고리즘을 제정합니다 (사용중인 경우 삽입 정렬로 충분합니다. 컬렉션이없는 언어) 모델을 생성하고 컬렉션에 삽입 할 때 SQL에서 하나씩 행을 읽으므로


5

Richard Toth의 최고의 답변에 관하여 https://stackoverflow.com/a/12257917/4052357

2 바이트 (또는 그 이상) 문자와 숫자를 포함하는 UTF8 인코딩 문자열을주의하십시오.

12 南新宿

MySQL의 LENGTH()in udf_NaturalSortFormat함수를 사용 하면 문자열의 바이트 길이가 반환되고 잘못된 대신 CHAR_LENGTH()올바른 문자 길이를 반환하는 대신 사용 됩니다.

제 경우에는 LENGTH()쿼리가 완료되지 않고 MySQL에 대해 100 % CPU 사용량이 발생했습니다.

DROP FUNCTION IF EXISTS `udf_NaturalSortFormat`;
DELIMITER ;;
CREATE FUNCTION `udf_NaturalSortFormat` (`instring` varchar(4000), `numberLength` int, `sameOrderChars` char(50)) 
RETURNS varchar(4000)
LANGUAGE SQL
DETERMINISTIC
NO SQL
SQL SECURITY INVOKER
BEGIN
    DECLARE sortString varchar(4000);
    DECLARE numStartIndex int;
    DECLARE numEndIndex int;
    DECLARE padLength int;
    DECLARE totalPadLength int;
    DECLARE i int;
    DECLARE sameOrderCharsLen int;

    SET totalPadLength = 0;
    SET instring = TRIM(instring);
    SET sortString = instring;
    SET numStartIndex = udf_FirstNumberPos(instring);
    SET numEndIndex = 0;
    SET i = 1;
    SET sameOrderCharsLen = CHAR_LENGTH(sameOrderChars);

    WHILE (i <= sameOrderCharsLen) DO
        SET sortString = REPLACE(sortString, SUBSTRING(sameOrderChars, i, 1), ' ');
        SET i = i + 1;
    END WHILE;

    WHILE (numStartIndex <> 0) DO
        SET numStartIndex = numStartIndex + numEndIndex;
        SET numEndIndex = numStartIndex;

        WHILE (udf_FirstNumberPos(SUBSTRING(instring, numEndIndex, 1)) = 1) DO
            SET numEndIndex = numEndIndex + 1;
        END WHILE;

        SET numEndIndex = numEndIndex - 1;

        SET padLength = numberLength - (numEndIndex + 1 - numStartIndex);

        IF padLength < 0 THEN
            SET padLength = 0;
        END IF;

        SET sortString = INSERT(sortString, numStartIndex + totalPadLength, 0, REPEAT('0', padLength));

        SET totalPadLength = totalPadLength + padLength;
        SET numStartIndex = udf_FirstNumberPos(RIGHT(instring, CHAR_LENGTH(instring) - numEndIndex));
    END WHILE;

    RETURN sortString;
END
;;

추신 나는 이것을 원본에 대한 주석으로 추가했지만 충분한 평판이 없습니다 (아직)


4

고정 길이로 0으로 채워진 모든 숫자 문자열이있는 "정렬 키"필드를 추가 한 다음 대신 해당 필드에서 정렬합니다.

긴 자릿수 문자열이있을 수있는 경우 다른 방법은 각 자릿수 문자열 앞에 자릿수 (고정 너비, 0으로 채워짐)를 추가하는 것입니다. 예를 들어 연속 된 숫자가 99 자리를 넘지 않는 경우 "Super Blast 10 Ultra"의 경우 정렬 키는 "Super Blast 0210 Ultra"가됩니다.


4

: 주문에
0
1
2
10
23
101
205
1,000 AAC ㄴ casdsadsa의 CSS





이 쿼리를 사용하십시오.

고르다 
    column_name 
에서 
    table_name 
주문
    column_name REGEXP '^ \ d * [^ \ da-z & \. \'\-\ "\! \ @ \ # \ $ \ % \ ^ \ * \ (\) \; \ : \\, \? \ / \ ~ \`\ | \ _ \-] 'DESC, 
    column_name + 0, 
    column_name;

당신은 같은 값을 추가하면 아래 불행하게도이 휴식 a1, a2, a11, 등 ...
random_user_name

4

작동하지 않는 많은 코드로 인해 바퀴를 재발 명하고 싶지 않거나 두통이있는 경우 Drupal Natural Sort를 사용 하십시오. 압축 된 SQL (MySQL 또는 Postgre)을 실행하기 만하면됩니다. 쿼리를 작성할 때 다음을 사용하여 간단히 주문하십시오.

... ORDER BY natsort_canon(column_name, 'natural')

감사합니다. 저는 모든 종류의 솔루션을 시도해 왔지만 (하하 제가 거기서 무엇을했는지 보셨나요?) 제가 가진 모든 데이터에 대해 제대로 작동하지 않았습니다. drupal 기능은 매력처럼 작동했습니다. 게시 해 주셔서 감사합니다.
Ben Hitchcock

이것은 작동하지만 끝에 숫자를 정렬합니다 (AZ 다음에 0-9)
realgt

4

또 다른 옵션은 mysql에서 데이터를 가져온 후 메모리에서 정렬하는 것입니다. 성능 관점에서 볼 때 최선의 선택은 아니지만 방대한 목록을 정렬하지 않으면 괜찮습니다.

Jeff의 게시물을 살펴보면 작업중인 언어에 대한 많은 알고리즘을 찾을 수 있습니다. 인간을위한 정렬 : 자연 정렬 순서


2

동적 방식으로 "sort column"을 만들 수도 있습니다.

SELECT name, (name = '-') boolDash, (name = '0') boolZero, (name+0 > 0) boolNum 
FROM table 
ORDER BY boolDash DESC, boolZero DESC, boolNum DESC, (name+0), name

이렇게하면 정렬 할 그룹을 만들 수 있습니다.

내 질문에서 나는 모든 것 앞에 '-', 숫자, 그리고 텍스트를 원했다. 다음과 같은 결과가 나올 수 있습니다.

-
0    
1
2
3
4
5
10
13
19
99
102
Chair
Dog
Table
Windows

이렇게하면 데이터를 추가 할 때 정렬 열을 올바른 순서로 유지할 필요가 없습니다. 필요한 항목에 따라 정렬 순서를 변경할 수도 있습니다.


이게 얼마나 성능이 좋을지 모르겠습니다. 불편 함없이 항상 사용하고 있습니다. 내 데이터베이스는 크지 않습니다.
antoine 2013-10-17

1

PHP를 사용하는 경우 php에서 자연스러운 정렬을 수행 할 수 있습니다.

$keys = array();
$values = array();
foreach ($results as $index => $row) {
   $key = $row['name'].'__'.$index; // Add the index to create an unique key.
   $keys[] = $key;
   $values[$key] = $row; 
}
natsort($keys);
$sortedValues = array(); 
foreach($keys as $index) {
  $sortedValues[] = $values[$index]; 
}

MySQL이 향후 버전에서 자연스러운 정렬을 구현하기를 바랍니다. 그러나 기능 요청 (# 1588) 은 2003 년부터 열려 있으므로 숨을 참지 않을 것입니다.


이론적으로는 가능하지만 먼저 모든 데이터베이스 레코드를 웹 서버로 읽어야합니다.
BlaM 2011 년

또는 고려 사항 : usort($mydata, function ($item1, $item2) { return strnatcmp($item1['key'], $item2['key']); });(연관 배열이 있고 키별로 정렬합니다.) 참조 : stackoverflow.com/q/12426825/1066234
Kai Noack

1

필드의 첫 번째 정수에 대해서만 작동하는 @ plaix / Richard Toth / Luke Hoggett의 최상의 응답의 단순화 된 non-udf 버전은 다음과 같습니다.

SELECT name,
LEAST(
    IFNULL(NULLIF(LOCATE('0', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('1', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('2', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('3', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('4', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('5', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('6', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('7', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('8', name), 0), ~0),
    IFNULL(NULLIF(LOCATE('9', name), 0), ~0)
) AS first_int
FROM table
ORDER BY IF(first_int = ~0, name, CONCAT(
    SUBSTR(name, 1, first_int - 1),
    LPAD(CAST(SUBSTR(name, first_int) AS UNSIGNED), LENGTH(~0), '0'),
    SUBSTR(name, first_int + LENGTH(CAST(SUBSTR(name, first_int) AS UNSIGNED)))
)) ASC

1

몇 가지 솔루션을 시도했지만 실제로는 매우 간단합니다.

SELECT test_column FROM test_table ORDER BY LENGTH(test_column) DESC, test_column DESC

/* 
Result 
--------
value_1
value_2
value_3
value_4
value_5
value_6
value_7
value_8
value_9
value_10
value_11
value_12
value_13
value_14
value_15
...
*/

1
형식으로 숫자를 정렬하는 데 매우 효과적입니다 23-4244. 감사합니다 :)
Pyton

1
숫자 앞의 문자열이 모두 동일하기 때문에이 테스트 데이터에서만 작동합니다. z_99거기에 값 을 집어 넣으면 맨 위에 z놓이지 만 v.
Samuel Neff

@SamuelNeff는 SQL : ORDER BY LENGTH (test_column) DESC, test_column DESC 를 참조하십시오. 그렇기 때문에 열의 길이를 기준으로 먼저 정렬됩니다. 이것은 "test_column DESC"로만 정렬 할 수없는 테이블의 접두사 그룹을 정렬하는 데 잘 작동합니다.
Tarik

1

여기에서 볼 수있는 다른 많은 답변 (및 중복 질문에서)은 기본적으로 매우 구체적으로 형식이 지정된 데이터, 예를 들어 완전히 숫자이거나 고정 길이 알파벳 접두사가있는 문자열 에서만 작동 합니다. 이것은 일반적인 경우에는 작동하지 않습니다.

그것은 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);  ### 0 means process all numbers - resulting sort key might be quite long for certain inputs

그러나 큰 테이블을 효율적으로 정렬하려면 정렬 키를 다른 열에 미리 저장하는 것이 좋습니다 (인덱스가있을 수 있음).

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
/****
  Converts numbers in the input string s into a format such that sorting results in a nat-sort.
  Numbers of up to 359 digits (before the decimal point, if one is present) are supported.  Sort results are undefined if the input string contains numbers longer than this.
  For n>0, only the first n numbers in the input string will be converted for nat-sort (so strings that differ only after the first n numbers will not nat-sort amongst themselves).
  Total sort-ordering is preserved, i.e. if s1!=s2, then NatSortKey(s1,n)!=NatSortKey(s2,n), for any given n.
  Numbers may contain ',' as a thousands separator, and '.' as a decimal point.  To reverse these (as appropriate for some European locales), the code would require modification.
  Numbers preceded by '+' sort with numbers not preceded with either a '+' or '-' sign.
  Negative numbers (preceded with '-') sort before positive numbers, but are sorted in order of ascending absolute value (so -7 sorts BEFORE -1001).
  Numbers with leading zeros sort after the same number with no (or fewer) leading zeros.
  Decimal-part-only numbers (like .75) are recognised, provided the decimal point is not immediately preceded by either another '.', or by a letter-type character.
  Numbers with thousand separators sort after the same number without them.
  Thousand separators are only recognised in numbers with no leading zeros that don't immediately follow a ',', and when they format the number correctly.
  (When not recognised as a thousand separator, a ',' will instead be treated as separating two distinct numbers).
  Version-number-like sequences consisting of 3 or more numbers separated by '.' are treated as distinct entities, and each component number will be nat-sorted.
  The entire entity will sort after any number beginning with the first component (so e.g. 10.2.1 sorts after both 10 and 10.995, but before 11)
  Note that The first number component in an entity like this is also permitted to contain thousand separators.

  To achieve this, numbers within the input string are prefixed and suffixed according to the following format:
  - The number is prefixed by a 2-digit base-36 number representing its length, excluding leading zeros.  If there is a decimal point, this length only includes the integer part of the number.
  - A 3-character suffix is appended after the number (after the decimals if present).
    - The first character is a space, or a '+' sign if the number was preceded by '+'.  Any preceding '+' sign is also removed from the front of the number.
    - This is followed by a 2-digit base-36 number that encodes the number of leading zeros and whether the number was expressed in comma-separated form (e.g. 1,000,000.25 vs 1000000.25)
    - The value of this 2-digit number is: (number of leading zeros)*2 + (1 if comma-separated, 0 otherwise)
  - For version number sequences, each component number has the prefix in front of it, and the separating dots are removed.
    Then there is a single suffix that consists of a ' ' or '+' character, followed by a pair base-36 digits for each number component in the sequence.

  e.g. here is how some simple sample strings get converted:
  'Foo055' --> 'Foo0255 02'
  'Absolute zero is around -273 centigrade' --> 'Absolute zero is around -03273 00 centigrade'
  'The $1,000,000 prize' --> 'The $071000000 01 prize'
  '+99.74 degrees' --> '0299.74+00 degrees'
  'I have 0 apples' --> 'I have 00 02 apples'
  '.5 is the same value as 0000.5000' --> '00.5 00 is the same value as 00.5000 08'
  'MariaDB v10.3.0018' --> 'MariaDB v02100130218 000004'

  The restriction to numbers of up to 359 digits comes from the fact that the first character of the base-36 prefix MUST be a decimal digit, and so the highest permitted prefix value is '9Z' or 359 decimal.
  The code could be modified to handle longer numbers by increasing the size of (both) the prefix and suffix.
  A higher base could also be used (by replacing CONV() with a custom function), provided that the collation you are using sorts the "digits" of the base in the correct order, starting with 0123456789.
  However, while the maximum number length may be increased this way, note that the technique this function uses is NOT applicable where strings may contain numbers of unlimited length.

  The function definition does not specify the charset or collation to be used for string-type parameters or variables:  The default database charset & collation at the time the function is defined will be used.
  This is to make the function code more portable.  However, there are some important restrictions:

  - Collation is important here only when comparing (or storing) the output value from this function, but it MUST order the characters " +0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" in that order for the natural sort to work.
    This is true for most collations, but not all of them, e.g. in Lithuanian 'Y' comes before 'J' (according to Wikipedia).
    To adapt the function to work with such collations, replace CONV() in the function code with a custom function that emits "digits" above 9 that are characters ordered according to the collation in use.

  - For efficiency, the function code uses LENGTH() rather than CHAR_LENGTH() to measure the length of strings that consist only of digits 0-9, '.', and ',' characters.
    This works for any single-byte charset, as well as any charset that maps standard ASCII characters to single bytes (such as utf8 or utf8mb4).
    If using a charset that maps these characters to multiple bytes (such as, e.g. utf16 or utf32), you MUST replace all instances of LENGTH() in the function definition with CHAR_LENGTH()

  Length of the output:

  Each number converted adds 5 characters (2 prefix + 3 suffix) to the length of the string. n is the maximum count of numbers to convert;
  This parameter is provided as a means to limit the maximum output length (to input length + 5*n).
  If you do not require the total-ordering property, you could edit the code to use suffixes of 1 character (space or plus) only; this would reduce the maximum output length for any given n.
  Since a string of length L has at most ((L+1) DIV 2) individual numbers in it (every 2nd character a digit), for n<=0 the maximum output length is (inputlength + 5*((inputlength+1) DIV 2))
  So for the current input length of 100, the maximum output length is 350.
  If changing the input length, the output length must be modified according to the above formula.  The DECLARE statements for x,y,r, and suf must also be modified, as the code comments indicate.
****/
  DECLARE x,y varchar(100);            # need to be same length as input s
  DECLARE r varchar(350) DEFAULT '';   # return value:  needs to be same length as return type
  DECLARE suf varchar(101);   # suffix for a number or version string. Must be (((inputlength+1) DIV 2)*2 + 1) chars to support version strings (e.g. '1.2.33.5'), though it's usually just 3 chars. (Max version string e.g. 1.2. ... .5 has ((length of input + 1) DIV 2) numeric components)
  DECLARE i,j,k int UNSIGNED;
  IF n<=0 THEN SET n := -1; END IF;   # n<=0 means "process all numbers"
  LOOP
    SET i := REGEXP_INSTR(s,'\\d');   # find position of next digit
    IF i=0 OR n=0 THEN RETURN CONCAT(r,s); END IF;   # no more numbers to process -> we're done
    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;   # Allow decimal number (but not version string) to begin with a '.', provided preceding char is neither another '.', nor a member of the unicode character classes: "Alphabetic", "Letter", "Block=Letterlike Symbols" "Number", "Mark", "Join_Control"
      IF i>1 AND SUBSTRING(s,i-1,1)='+' THEN SET suf := '+', j := i-1; ELSE SET j := i; END IF;   # move any preceding '+' into the suffix, so equal numbers with and without preceding "+" signs sort together
      SET r := CONCAT(r,SUBSTRING(s,1,j-1)); SET s = SUBSTRING(s,i);   # add everything before the number to r and strip it from the start of s; preceding '+' is dropped (not included in either r or s)
    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++)*+'));   # capture the number + following decimals (including multiple consecutive '.<digits>' sequences)
    SET s := SUBSTRING(s,LENGTH(x)+1);   # NOTE: LENGTH() can be safely used instead of CHAR_LENGTH() here & below PROVIDED we're using a charset that represents digits, ',' and '.' characters using single bytes (e.g. latin1, utf8)
    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;   # move any following decimals into y
    SET i := LENGTH(x);
    SET x := REPLACE(x,',','');
    SET j := LENGTH(x);
    SET x := TRIM(LEADING '0' FROM x);   # strip leading zeros
    SET k := LENGTH(x);
    SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294) + IF(i=j,0,1),10,36),2,'0'));   # (j-k)*2 + IF(i=j,0,1) = (count of leading zeros)*2 + (1 if there are thousands-separators, 0 otherwise)  Note the first term is bounded to <= base-36 'ZY' as it must fit within 2 characters
    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);   # k = count of digits in number, bounded to be <= '9Z' base-36
    ELSE   # encode a version number (like 3.12.707, etc)
      SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);   # k = count of digits in number, bounded to be <= '9Z' base-36
      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);   # strip leading zeros
        SET k := LENGTH(x);
        SET r := CONCAT(r,LPAD(CONV(LEAST(k,359),10,36),2,'0'),x);   # k = count of digits in number, bounded to be <= '9Z' base-36
        SET suf := CONCAT(suf,LPAD(CONV(LEAST((j-k)*2,1294),10,36),2,'0'));   # (j-k)*2 = (count of leading zeros)*2, bounded to fit within 2 base-36 digits
        SET n := n-1;
      END WHILE;
      SET r := CONCAT(r,y,suf);
    END IF;
  END LOOP;
END
$$
delimiter ;

저는 MySQL의 초보자이며 이것을 시도했습니다. 이 오류가 발생했습니다. "# 1305-FUNCTION mydatabase.REGEXP_INSTR이 존재하지 않습니다." 어떤 생각?
John T

다른 초보자를 위해. MySQL 8.0을 설치하지 않았습니다. REGEXP_INSTR (및 기타 REGEXP 항목)에 필요합니다.
John T

NatSortKey의 심각한 버그를 수정했습니다. 잘못된 정규식 문자가있었습니다. 이 기능을 직접 사용한 경우 코드를 업데이트하십시오!
Doin


0

제목에 버전이 숫자로만있는 경우 다음은 간단한 것입니다.

ORDER BY CAST(REGEXP_REPLACE(title, "[a-zA-Z]+", "") AS INT)';

그렇지 않으면 패턴을 사용하는 경우 간단한 SQL을 사용할 수 있습니다 (이 패턴은 버전 이전에 # 사용).

create table titles(title);

insert into titles (title) values 
('Final Fantasy'),
('Final Fantasy #03'),
('Final Fantasy #11'),
('Final Fantasy #10'),
('Final Fantasy #2'),
('Bond 007 ##2'),
('Final Fantasy #01'),
('Bond 007'),
('Final Fantasy #11}');

select REGEXP_REPLACE(title, "#([0-9]+)", "\\1") as title from titles
ORDER BY REGEXP_REPLACE(title, "#[0-9]+", ""),
CAST(REGEXP_REPLACE(title, ".*#([0-9]+).*", "\\1") AS INT);     
+-------------------+
| title             |
+-------------------+
| Bond 007          |
| Bond 007 #2       |
| Final Fantasy     |
| Final Fantasy 01  |
| Final Fantasy 2   |
| Final Fantasy 03  |
| Final Fantasy 10  |
| Final Fantasy 11  |
| Final Fantasy 11} |
+-------------------+
8 rows in set, 2 warnings (0.001 sec)

필요한 경우 다른 패턴을 사용할 수 있습니다. 예를 들어 영화 "I 'm # 1"및 "I 'm # 1 part 2"가있는 경우 "Final Fantasy {11}"와 같이 버전을 래핑 할 수 있습니다.


-4

이 주제가 오래된 주제라는 것을 알고 있지만이를 수행하는 방법을 찾은 것 같습니다.

SELECT * FROM `table` ORDER BY 
CONCAT(
  GREATEST(
    LOCATE('1', name),
    LOCATE('2', name),
    LOCATE('3', name),
    LOCATE('4', name),
    LOCATE('5', name),
    LOCATE('6', name),
    LOCATE('7', name),
    LOCATE('8', name),
    LOCATE('9', name)
   ),
   name
) ASC

스크랩, 다음 세트를 잘못 정렬했습니다 (쓸모 없습니다 ㅋㅋ).

파이널 판타지 1 파이널 판타지 2 파이널 판타지 5 파이널 판타지 7 파이널 판타지 7 : 강림절 어린이 파이널 판타지 12 파이널 판타지 112 FF1 FF2


3
이 답변을 제거하지 않는 이유는 무엇입니까? 당신은 이것에 대한 배지거야
m47730
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.