문제

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

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

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

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

  • 제목:"파이널 판타지"
  • 숫자: "12"
  • 부제:"프로마시아의 사슬"

올바른 순서로 나오도록 하려면?(2 이전이 아닌 4 이후 10).

그렇게 하는 것은 정말 고통스러운 일입니다. 때때로 게임 제목을 구문 분석하는 메커니즘을 깨뜨리는 또 다른 게임이 있기 때문입니다(예:"워해머 40,000", "제임스 본드 007")

도움이 되었습니까?

해결책

이것이 바로 많은 것들이 출시일로 정렬되는 이유라고 생각합니다.

해결책은 "Sortkey"의 테이블에 다른 열을 만드는 것일 수 있습니다. 이것은 쉽게 정렬하거나 카운터를 만들기 위해 만든 패턴을 준수하는 제목의 위생 버전 일 수 있습니다.

다른 팁

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

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

방금 찾았습니다.

SELECT names FROM your_table ORDER BY games + 0 ASC

숫자가 정면에있을 때 자연스러운 종류를 사용하면 중간에도 효과가있을 수 있습니다.

@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, ".")

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"을 포함한 데이터세트로 깔끔하게 확장될 가능성도 없습니다. .

이 기능을 작성했습니다 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

그래서 당신이 만족스러운 답변을 발견했다는 것을 알고 있지만, 나는이 문제로 인해 잠시 동안 어려움을 겪고 있었고, 우리는 이전에 SQL에서 합리적으로 잘 할 수 없다고 판단했으며 JSON에서 JavaScript를 사용해야 할 것이라고 결정했습니다. 정렬.

SQL을 사용하여 해결 한 방법은 다음과 같습니다. 바라건대 이것은 다른 사람들에게 도움이되기를 바랍니다.

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

Scene 1
Scene 1A
Scene 1B
Scene 2A
Scene 3
...
Scene 101
Scene XXA1
Scene XXA2

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

먼저 데이터에서 변경되지 않은 부분,이 경우 "장면"을 교체 한 다음 LPAD를 사용하여 일을 일관화했습니다. 이것은 알파 문자가 번호가 매겨진 것뿐만 아니라 올바르게 정렬하는 데 꽤 잘 허용되는 것 같습니다.

나의 ORDER BY 절은 다음과 같습니다.

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

분명히 이것은 균일하지 않은 원래의 문제에 도움이되지 않지만 이것이 다른 많은 관련 문제에 효과가있을 것이라고 생각합니다.

  1. 테이블에 정렬 키 (순위)를 추가하십시오. ORDER BY rank

  2. "릴리스 날짜"열을 사용하십시오. ORDER BY release_date

  3. SQL에서 데이터를 추출 할 때 객체를 정렬 할 때 (예 : 세트로 추출하고, 트리 세트로 만들고, 데이터 모델을 비슷하게 구현하고 여기에서 자연스런 정렬 알고리즘을 제정하십시오) 컬렉션이없는 언어) 모델을 만들고 컬렉션에 삽입 할 때 SQL의 행을 하나씩 읽을 때)

Richard Toth의 최상의 응답에 대해 https://stackoverflow.com/a/12257917/4052357

2byte (또는 그 이상) 문자와 숫자를 포함하는 UTF8 인코딩 문자열을 조심하십시오.

12 南新宿

MySQL 사용 LENGTH() 안에 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
;;

추신 : 나는 이것을 원본에 댓글로 추가했지만 명성이 충분하지 않습니다 (아직).

주문하려면 :
0
1
2
10
23
101
205
1000

AAC

Casdsadsa
CSS

이 쿼리를 사용하십시오.

SELECT 
    column_name 
FROM 
    table_name 
ORDER BY
    column_name REGEXP '^\d*[^\da-z&\.\' \-\"\!\@\#\$\%\^\*\(\)\;\:\\,\?\/\~\`\|\_\-]' DESC, 
    column_name + 0, 
    column_name;

휠을 재창조하고 싶지 않거나 작동하지 않는 코드가 많은 두통이 있으면 사용하십시오. Drupal Natural 정렬 ... 단지 지퍼 (mysql 또는 postgre)가 나오는 SQL을 실행하십시오. 쿼리를 만들 때 간단히 주문하십시오.

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

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

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

모든 숫자 문자열이 고정 길이에 제로 패딩 된 "Sort Key"의 필드를 추가 한 다음 해당 필드에 정렬하십시오.

숫자가 길어질 수있는 경우 다른 방법은 각 자릿수 문자열에 숫자 (고정 된 넓이, 제로 패딩)의 수를 전제하는 것입니다. 예를 들어, 연속으로 99 자리를 넘지 않으면 "Super Blast 10 Ultra"의 경우 정렬 키는 "Super Blast 0210 Ultra"입니다.

"정렬 열"을 역동적 인 방식으로 만들 수도 있습니다.

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

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

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

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
...
*/

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 년부터 열려 있으므로 숨을 참지 않을 것입니다.

@plaix/richard toth/luke hoggett의 최상의 응답의 단순화 된 비우스 버전은 필드의 첫 번째 정수에만 적용됩니다.

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

또한 있습니다 Natsort. 그것은 a의 일부입니다 드 루팔 플러그인, 그러나 그것은 독립형 독립형으로 작동합니다.

나는이 주제가 고대라는 것을 알고 있지만 이것을 할 방법을 찾았다 고 생각합니다.

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

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

Final Fantasy 1 Final Fantasy 2 Final Fantasy 5 Final Fantasy 7 Final Fantasy 7 : Advent Children Final Fantasy 12 Final Fantasy 112 FF1 FF2

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top