Natural Sort in MySQL

后端 未结 21 1100
南旧
南旧 2020-11-22 02:25

Is there an elegant way to have performant, natural sorting in a MySQL database?

For example if I have this data set:

  • Final Fantasy
  • Final Fant
相关标签:
21条回答
  • 2020-11-22 02:50

    I think this is why a lot of things are sorted by release date.

    A solution could be to create another column in your table for the "SortKey". This could be a sanitized version of the title which conforms to a pattern you create for easy sorting or a counter.

    0 讨论(0)
  • 2020-11-22 02:50

    I've written this function for MSSQL 2000 a while ago:

    /**
     * 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
    
    0 讨论(0)
  • 2020-11-22 02:51

    Just found this:

    SELECT names FROM your_table ORDER BY games + 0 ASC
    

    Does a natural sort when the numbers are at the front, might work for middle as well.

    0 讨论(0)
  • 2020-11-22 02:51

    If you're using PHP you can do the the natural sort in 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]; 
    }
    

    I hope MySQL will implement natural sorting in a future version, but the feature request (#1588) is open since 2003, So I wouldn't hold my breath.

    0 讨论(0)
  • 2020-11-22 02:51

    I know this topic is ancient but I think I've found a way to do this:

    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
    

    Scrap that, it sorted the following set incorrectly (It's useless 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

    0 讨论(0)
  • 2020-11-22 02:53

    Here is a quick solution:

    SELECT alphanumeric, 
           integer
    FROM sorting_test
    ORDER BY LENGTH(alphanumeric), alphanumeric
    
    0 讨论(0)
提交回复
热议问题