Frage

Gibt es eine elegante Art und Weise performant, natürliche Sortierung in einer MySQL-Datenbank zu haben?

Zum Beispiel, wenn ich diesen Datensatz:

  • Final Fantasy
  • Final Fantasy 4
  • Final Fantasy 10
  • Final Fantasy 12
  • Final Fantasy 12: Chains of Promathia
  • Final Fantasy Adventure
  • Final Fantasy Origins
  • Final Fantasy Tactics

Jede andere elegant Lösung als die Spiele Namen in ihre Bestandteile zu zerlegen

  • Titel : "Final Fantasy"
  • Anzahl : "12"
  • Untertitel : "Chains of Promathia"

, um sicherzustellen, dass sie in der richtigen Reihenfolge herauskommen? (10 nach 4 nicht vor 2).

zu tun, ist so ein Schmerz in den a **, weil ab und zu gibt es ein anderes Spiel ist, dass dieser Mechanismus bricht von den Spieltitel Parsen (zum Beispiel „Warhammer 40.000“, „James Bond 007“)

War es hilfreich?

Lösung

Ich denke, das ist, warum viele Dinge nach Erscheinungsdatum sortiert werden.

Eine Lösung könnte sein, eine andere Spalte in der Tabelle für die „SortKey“ zu erstellen. Dies könnte eine bereinigte Version des Titels sein, die ein Muster entsprechen Sie für einfache Sortierung oder einen Zähler erstellen.

Andere Tipps

Hier ist eine schnelle Lösung:

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

gefunden Nur soviel:

SELECT names FROM your_table ORDER BY games + 0 ASC

Hat eine natürliche Art, wenn die Zahlen an der Front sind, könnte genauso gut für mittlere arbeiten.

Die gleiche Funktion wie durch @plalx geschrieben, aber neu geschrieben 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
;;

Verbrauch:

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

MySQL nicht über diese Art von „natürlicher Sortierung“ ermöglichen, so dass es wie die beste Art und Weise sieht zu bekommen, was Sie nach Ihren Daten einrichten teilen ist, wie Sie oben (eigenes ID-Feld, etc.) beschrieben haben oder in Ermangelung eines solchen, führen Sie eine Art basiert auf einem nicht-Titelelement, indizierte Element in Ihrem db (Datum, eingefügt ID in der db, etc).

das dB tun, um die Sortierung für Sie fast immer gehen, schneller zu sein als große Datenmengen in Ihre Programmiersprache der Wahl zu lesen und dort sortiert wird, also wenn Sie keine Kontrolle haben bei der ganzen db Schema hier, dann Blick auf das Hinzufügen leicht sortierten Felder, wie oben beschrieben, es wird Ihnen auf lange Sicht eine Menge Ärger und Wartung sparen.

fordert eine „natürliche Art“ hinzufügen von Zeit zu Zeit auf dem MySQL-Bugs kommen und Diskussionsforen und viele Lösungen drehen sich um bestimmte Teile des Daten Strippen und sie für die ORDER BY Teil der Abfrage Gießen, zB

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

Diese Art von Lösung könnte fast oben auf Ihrem Final Fantasy Beispiel zu arbeiten, gemacht werden, aber nicht besonders flexibel und unwahrscheinlich sauber einschließlich zu einem Datensatz zu erweitern, sagen sie, „Warhammer 40,000“ und „James Bond 007“ I habe Angst.

Ich habe diese Funktion für geschrieben MSSQL 2000 vor einer Weile:

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

So, während ich weiß, dass Sie eine befriedigende Antwort gefunden haben, die ich für eine Weile mit diesem Problem zu kämpfen hat, und wir würden wir zuvor festgestellt, dass es nicht angemessen gut in SQL getan werden könnte, und wir gehen zu müssen Javascript verwenden auf einem JSON-Array.

Hier ist, wie ich es nur gelöst SQL. Hoffentlich ist das hilfreich für andere:

Ich hatte Daten wie zum Beispiel:

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

Ich habe eigentlich nicht „cast“ Dinge obwohl ich nehme an, dass auch gearbeitet hat.

I ersetzt zuerst die Teile, die in den Daten, in diesem Fall „Szene“ unveränderlich waren, und tat dann ein LPAD Dinge aufreihen. Das scheint ziemlich gut zu erlauben, für die Alpha-Strings wie die nummerierten richtig und zu sortieren.

Meine ORDER BY Klausel wie folgt aussieht:

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

Natürlich bedeutet dies nicht mit dem ursprünglichen Problem zu helfen, die nicht so einheitlich war -. Aber ich glaube, dies ist wahrscheinlich für viele andere Probleme funktionieren würde, es so setzen da draußen

  1. Fügen Sie einen Sortierschlüssel (Rang) in der Tabelle. ORDER BY rank

  2. Nutzen Sie die "Release Date" Spalte. ORDER BY release_date

  3. Wenn Sie die Daten aus SQL extrahiert, stellen Sie Ihr Objekt kann die Sortierung, zB wenn in ein Set zu extrahieren, macht es zu einem TreeSet und Ihr Datenmodell vergleichbar machen implementieren und den natürlichen Sortieralgorithmus verordnen hier (Insertionsort genügt, wenn Sie eine Sprache ohne Sammlungen verwenden), wie Sie die Zeilen von SQL eins nach dem anderen zu lesen werde, wie Sie Ihr Modell erstellen und es in die Sammlung einfügen)

Im Hinblick auf die beste Antwort von Richard Toth https://stackoverflow.com/a/12257917/4052357

Achten Sie auf UTF8 codierten Zeichenketten, die 2-Byte (oder mehr) Zeichen und Zahlen enthalten z.

12 南新宿

Mit MySQL LENGTH() in udf_NaturalSortFormat Funktion wird die Byte-Länge der Zeichenfolge zurückgeben und falsch, sondern CHAR_LENGTH() verwenden, die die korrekte Zeichenlänge zurückkehren werden.

In meinem Fall mit LENGTH() Abfragen verursachten nie vollständig und führen zu 100% CPU-Auslastung für MySQL

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
;;

P. S. Ich würde dies als Kommentar zum Original hinzugefügt habe, aber ich habe nicht genug Ruf (noch)

Bestellung:
0
1 | 2
10
23
101
205
1000
ein
aac
b
casdsadsa
css

Verwenden Sie diese Abfrage:

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

Wenn Sie nicht das Rad neu erfinden wollen oder Kopfschmerzen haben mit viel Code, der nicht funktioniert, verwenden Sie einfach Drupal Natur Sortieren ... führen sie einfach die SQL, die mit Reißverschluss (MySQL oder Postgre) kommt, und das ist es. Wenn eine Abfrage machen, einfach bestellen mit:

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

Eine weitere Option ist die Sortierung im Speicher zu tun, nachdem die Daten aus MySQL zu ziehen. Es ist zwar nicht die beste Option aus anwendungstechnischer Sicht sein, wenn Sie nicht riesige Listen Sortierung sind, sollten Sie sich gut.

Wenn Sie einen Blick auf Jeffs Post nehmen, können Sie viele Algorithmen für das finden, was immer Sprache, die Sie vielleicht mit arbeiten. Sortierung für Menschen: Natürliche Sortierung

ein Feld hinzufügen für „Sortierschlüssel“, der alle Ziffernfolgen mit Nullen aufgefüllt, um eine feste Länge hat und dann Art auf diesem Gebiet statt.

Wenn Sie lange Ziffernfolgen haben könnte, ist eine andere Methode, um die Anzahl der Ziffern vorangestellt wird (mit fester Breite, mit Nullen aufgefüllt) in jede Ziffernfolge. Zum Beispiel werden, wenn Sie nicht mehr als 99 Ziffern in einer Reihe, dann für „Super-Explosion 10 Ultra“ Sortierschlüssel würde „Super-Explosion 0210 Ultra“ sein.

Sie können auch in einer dynamischen Art und Weise schaffen die „Sortierspalte“:

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

Auf diese Weise können Sie Gruppen erstellen zu sortieren.

In meiner Anfrage wollte ich die ‚-‘ vor allem dann die Zahlen, dann den Text. Die in so etwas wie führen könnte:

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

Auf diese Weise müssen Sie nicht die Sortierspalte in der richtigen Reihenfolge zu halten, wie Sie Daten hinzufügen. Sie können auch Ihre Sortierreihenfolge ändern, je nachdem, was Sie benötigen.

Ich habe mehrere Lösungen ausprobiert, aber das eigentlich ist es ganz einfach:

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

Wenn Sie PHP verwenden Sie können die die natürliche Art in PHP tun.

$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]; 
}

Ich hoffe, MySQL natürliche Sortierung in einer zukünftigen Version implementieren, aber der Feature-Request (# 1588) ist offen seit 2003, also ich würde nicht meinen Atem halten.

Ein vereinfachte Nicht-UDF-Version der besten Antwort von @ Plaix / Richard Toth / Luke Hoggett, die nur für die erste ganze Zahl im Bereich arbeitet, ist

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

Auch gibt es natsort . Es ist beabsichtigt, einen Teil einer Drupal Plugin zu sein, aber es funktioniert gut stand-alone.

Ich weiß, das Thema ist alt, aber ich glaube, ich habe einen Weg gefunden, dies zu tun:

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

Schrott, die sie sortiert die falsch eingestellt folgenden (Es ist sinnlos, 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

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top