Viele andere Antworten, die ich hier (und in den doppelten Fragen) sehe, funktionieren grundsätzlich nur für sehr spezifisch formatierte Daten, z. B. eine Zeichenfolge, die vollständig eine Zahl ist oder für die es ein alphabetisches Präfix fester Länge gibt. Dies wird im allgemeinen Fall nicht funktionieren.
Es ist wahr , dass es nicht wirklich eine Möglichkeit , eine 100% allgemeine nat-Art in MySQL zu implementieren, weil es zu tun , was Sie wirklich brauchen , ist ein modifizierter Vergleichsfunktion , die ob / wann es Begegnungen zwischen lexicographic Sortierung der Strings und numerische Sortierung Schalter eine Zahl. Ein solcher Code könnte jeden Algorithmus implementieren, den Sie zum Erkennen und Vergleichen der numerischen Teile innerhalb von zwei Zeichenfolgen wünschen könnten. Leider ist die Vergleichsfunktion in MySQL intern im Code und kann vom Benutzer nicht geändert werden.
Dies hinterlässt einen Hack, bei dem Sie versuchen, einen Sortierschlüssel für Ihre Zeichenfolge zu erstellen, in dem die numerischen Teile neu formatiert werden, sodass die lexikografische Standardsortierung sie tatsächlich nach Ihren Wünschen sortiert .
Für einfache Ganzzahlen bis zu einer maximalen Anzahl von Ziffern besteht die offensichtliche Lösung darin, sie einfach mit Nullen links aufzufüllen, damit sie alle eine feste Breite haben. Dies ist der Ansatz des Drupal-Plugins und der Lösungen von @plalx / @RichardToth. (@Christian hat eine andere und viel komplexere Lösung, bietet aber keine Vorteile, die ich sehen kann).
Wie @tye hervorhebt, können Sie dies verbessern, indem Sie jeder Zahl eine feste Ziffernlänge voranstellen, anstatt sie einfach links aufzufüllen. Es gibt jedoch noch viel mehr Verbesserungen, selbst angesichts der Einschränkungen eines im Wesentlichen umständlichen Hacks. Es scheint jedoch keine vorgefertigten Lösungen zu geben!
Was ist zum Beispiel mit:
- Plus- und Minuszeichen? +10 vs 10 vs -10
- Dezimalstellen? 8,2, 8,5, 1,006, 0,75
- Führende Nullen? 020, 030, 00000922
- Tausend Separatoren? "1.001 Dalmations" gegen "1001 Dalmations"
- Versionsnummern? MariaDB v10.3.18 vs MariaDB v10.3.3
- Sehr lange Zahlen? 103.768.276.592.092.364.859.236.487.687.870.234.598.55
Als Erweiterung der Methode von @ tye habe ich eine ziemlich kompakte gespeicherte NatSortKey () -Funktion erstellt, die eine beliebige Zeichenfolge in einen Nat-Sort-Schlüssel konvertiert, alle oben genannten Fälle behandelt, einigermaßen effizient ist und eine vollständige Sortierung beibehält. Reihenfolge (keine zwei verschiedenen Zeichenfolgen haben Sortierschlüssel, die gleich sind). Ein zweiter Parameter kann verwendet werden, um die Anzahl der in jeder Zeichenfolge verarbeiteten Zahlen zu begrenzen (z. B. auf die ersten 10 Zahlen), mit denen sichergestellt werden kann, dass die Ausgabe in eine bestimmte Länge passt.
HINWEIS: Sortierschlüsselzeichenfolgen, die mit einem bestimmten Wert dieses zweiten Parameters generiert wurden, sollten nur nach anderen Zeichenfolgen sortiert werden, die mit demselben Wert für den Parameter generiert wurden , da sie sonst möglicherweise nicht richtig sortiert werden.
Sie können es direkt bei der Bestellung verwenden, z
SELECT myString FROM myTable ORDER BY NatSortKey(myString,0);
Für eine effiziente Sortierung großer Tabellen ist es jedoch besser, den Sortierschlüssel in einer anderen Spalte vorab zu speichern (möglicherweise mit einem Index darauf):
INSERT INTO myTable (myString,myStringNSK) VALUES (@theStringValue,NatSortKey(@theStringValue,10)), ...
...
SELECT myString FROM myTable ORDER BY myStringNSK;
[Idealerweise würden Sie dies automatisch erreichen, indem Sie die Schlüsselspalte als berechnete gespeicherte Spalte erstellen und dabei Folgendes verwenden:
CREATE TABLE myTable (
...
myString varchar(100),
myStringNSK varchar(150) AS (NatSortKey(myString,10)) STORED,
...
KEY (myStringNSK),
...);
Aber jetzt weder MySQL noch MariaDB erlauben Funktionen in berechneten Spalten gespeichert , so leider kann man noch nicht das tun .]
Meine Funktion betrifft nur das Sortieren von Zahlen . Wenn Sie andere Sortierungsnormalisierungsaufgaben ausführen möchten, z. B. das Entfernen aller Satzzeichen oder das Trimmen von Leerzeichen an jedem Ende oder das Ersetzen von Sequenzen mit mehreren Leerzeichen durch einzelne Leerzeichen, können Sie die Funktion entweder erweitern oder vorher oder nachher NatSortKey()
ausführen auf Ihre Daten angewendet. (Ich würde empfehlen, REGEXP_REPLACE()
für diesen Zweck zu verwenden).
Es ist auch etwas anglozentrisch, da ich davon ausgehe, dass '.' für einen Dezimalpunkt und ',' für das Tausendertrennzeichen, aber es sollte leicht genug sein, Änderungen vorzunehmen, wenn Sie das Gegenteil wünschen oder wenn Sie möchten, dass dies als Parameter umschaltbar ist.
Es könnte auf andere Weise einer weiteren Verbesserung zugänglich sein; Beispielsweise werden derzeit negative Zahlen nach dem absoluten Wert sortiert, sodass -1 vor -2 steht und nicht umgekehrt. Es gibt auch keine Möglichkeit, eine DESC-Sortierreihenfolge für Zahlen anzugeben, während die lexikografische ASC-Sortierung für Text beibehalten wird. Beide Probleme können mit etwas mehr Arbeit behoben werden. Ich werde den Code aktualisieren, wenn ich Zeit habe.
Es gibt viele andere Details zu beachten - einschließlich einiger kritischer Abhängigkeiten von der von Ihnen verwendeten Chaset und Sortierung -, aber ich habe sie alle in einen Kommentarblock innerhalb des SQL-Codes eingefügt. Bitte lesen Sie dies sorgfältig durch, bevor Sie die Funktion selbst nutzen!
Also, hier ist der Code. Wenn Sie einen Fehler finden oder eine Verbesserung haben, die ich nicht erwähnt habe, lassen Sie es mich bitte in den Kommentaren wissen!
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 ;