By Frank Kalis
In PHP existiert eine Funktion natsort, die ein Array von Argumenten derart sortiert, wie es auch ein Mensch machen würde. Also, angenommen man hat folgendes Array
$array1 = $array2 = array ("fk-1", "fk-2", "fk-10", "fk-12");
Dann liefert
$array1 = $array2 = array ("fk-1", "fk-2", "fk-10", "fk-12"); sort ($array1); echo "Standardsortierung"; print_r ($array1); natsort ($array2); echo "Sortieren mit natürlicher Reihenfolge"; print_r ($array2); Standardsortierung Array ( [0] => fk-1 [1] => fk-10 [2] => fk-12 [3] => fk-2 ) Sortieren mit natürlicher Reihenfolge Array ( [0] => fk-1 [1] => fk-2 [2] => fk-10 [3] => fk-12 )
In T-SQL existiert keine äquivalente Funktion zu natsort, so daß:
CREATE TABLE #crazy_sorting ( col1 VARCHAR(10) ) INSERT INTO #crazy_sorting SELECT 'fk-1' UNION ALL SELECT 'fk-2' UNION ALL SELECT 'fk-10' UNION ALL SELECT 'fk-12' SELECT * FROM #crazy_sorting ORDER BY col1 col1 ---------- fk-1 fk-10 fk-12 fk-2 (4 row(s) affected)
die Standardsortierung zurückliefert. Was muß man nun machen, um SQL Server dazu zu bewegen, die Daten in der natürlichen Reihenfolge zurückzuliefern?
Nun, unter der Prämisse, daß der Präfix statisch ist, funktioniert folgendes:
SELECT * FROM #crazy_sorting ORDER BY CAST(REPLACE(col1,'fk-','') AS INT) col1 ---------- fk-1 fk-2 fk-10 fk-12 (4 row(s) affected)
Oder
SELECT * FROM #crazy_sorting ORDER BY CAST(SUBSTRING(col1,4, LEN(col1)-3) AS INT)
Oder
SELECT * FROM #crazy_sorting ORDER BY CAST(RIGHT(col1, LEN(col1)-CHARINDEX('-', col1)) AS INT)
Oder irgendwelche Varianten mit CHARINDEX wie
SELECT * FROM #crazy_sorting ORDER BY CAST(SUBSTRING(col1,CHARINDEX('-',col1)+1,LEN(col1)-3) AS INT
Das
CAST(.. AS INT)
ist übrigens keinesfalls Kosmetik. Da alle obigen Varianten eine Spalte vom Type VARCHAR manipulieren mit Methoden, die ebenfalls eine Zeichenfolge zurückgeben, ist das Ergebnis stets eine (wenn auch kürzere) Zeichenfolge, die nicht "natürlich" sortiert wird. Erst die Umwandlung in einen nummerischen Wert, bringt das gewünschte Ergebnis. Beispiel:
SELECT * FROM #crazy_sorting ORDER BY RIGHT(col1, LEN(col1)-CHARINDEX('-', col1)) col1 ---------- fk-1 fk-10 fk-12 fk-2 (4 row(s) affected)
Für den Fall, daß der Präfix aber tatsächlich statisch ist, sollte man mit dem Designer der Tabelle ein ernstes Wort reden und dessen Logik mal etwas auf der Sprünge helfen. In diesem Fall kann man nämlich gänzlich auf die Speicherung des Präfix mit den Daten verzichten, und ihn entweder über eine Sicht oder in der Präsentationsschicht anzeigen lassen. Falls man jedoch nicht gänzlich darauf verzichten kann oder will, sollte man zumindest die Aufspaltung solcher Daten in zwei separate Spalten ins Auge fassen.
Ist der Präfix hingegen nicht statisch, steht man vor einem weiteren Problem
INSERT INTO #crazy_sorting SELECT 'fh-1' UNION ALL SELECT 'abcd-999'
Führt man nun obige Statements aus, erhält man
col1 ---------- fk-1 fh-1 fk-2 fk-10 fk-12 abcd-999 (6 row(s) affected)
Wohingegen die natürliche Sortierreihenfolge so aussehe:
Sortieren mit natürlicher Reihenfolge Array ( [4] => abcd-999 [5] => fh-1 [0] => fk-1 [1] => fk-2 [2] => fk-10 [3] => fk-12 )
Dies in T-SQL abzubilden, ist etwas aufwändiger und könnte folgendermassen aussehen:
SELECT * FROM #crazy_sorting ORDER BY LEFT(col1, PATINDEX('%[0-9]%', col1) - 1), CAST(SUBSTRING(col1, PATINDEX('%[0-9]%', col1), LEN(col1)-PATINDEX('%[0-9]%',col1)) AS INT) col1 ---------- abcd-999 fh-1 fk-1 fk-2 fk-10 fk-12 (6 row(s) affected)
Kurz gesagt macht das Statement oben nichts weiter als beim ORDER BY die Spalte in zwei Teile zu zerlegen und nach dem Teil, der nicht nummerisch ist, zu sortieren. Innerhalb dieses Teils wird dann nach dem nummerischen Teil sortiert.
Eine weitere Variante sieht so aus:
SELECT * FROM #crazy_sorting ORDER BY LEFT(col1, CHARINDEX('-', col1)-1), CAST(SUBSTRING(col1, CHARINDEX('-', col1)+1, LEN(col1)-CHARINDEX('-', col1)) AS INT)
Dieses Statement nimmt den linken und den rechten Teil vom '-' Trennzeichen und sortiert danach. In etwas kürzerer Schreibweise
SELECT *
FROM #crazy_sorting
ORDER BY LEFT(col1, CHARINDEX('-', col1)-1),
CAST(RIGHT(col1, LEN(col1)-CHARINDEX('-', col1)) AS INT)
Das Beispiel kann natürlich beliebig verkompliziert werden, was allenfalls dazu führt, daß der SQL Code exponentiell komplizierter wird. Einleuchten dürfte aber, daß solch ein Verfahren keine berauschende Performance bringen wird.