Natürliche Sortierreihenfolge erreichen

By Frank Kalis

Posted on Aug 20, 2010 von in SQL Server

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.

Dieser Eintrag wurde eingetragen von und ist abgelegt unter SQL Server. Tags: , ,

Noch kein Feedback


Formular wird geladen...