PHP: Wie sortiert man ein Array nach einer Spalte?
Datensätze nach einer Spalte sortieren ist eine Zeile. Die ältere Variante mit dem Minuszeichen, die man in vielen Antworten noch findet, funktioniert allerdings bei genau dem Datentyp nicht, den man am häufigsten sortiert: Preisen.
Die Antwort
<?php
usort($server, fn($a, $b) => $a['preis'] <=> $b['preis']);
?>
aufsteigend: VPS 200/4.99 VPS 500/4.99 RS 2000/12.99 RS 4000/29.99
Absteigend drehst du die beiden Seiten um:
<?php
usort($server, fn($a, $b) => $b['preis'] <=> $a['preis']);
?>
Der <=> heißt Raumschiff-Operator und liefert -1, 0 oder 1. Genau das,
was usort() erwartet.
Warum nicht mit Minus
In älteren Antworten steht statt <=> oft das hier:
<?php
usort($server, fn($a, $b) => $a['preis'] - $b['preis']); // kaputt
?>
Als Begründung, warum das schlecht sei, liest man meist “Integer-Überlauf”. Der kommt in der Praxis aber selten vor. Der Fall, den man wirklich trifft, ist ein anderer:
$x['preis'] - $y['preis'] : -0.5
als int gecastet : 0 <- 0 heißt "gleich"!
mit <=> : -1
PHP castet den Rückgabewert des Vergleichs-Callbacks auf int. -0.5 wird
dabei zu 0, und 0 bedeutet für usort(): Die beiden Elemente sind
gleichwertig.
Das heißt: Jede Preisdifferenz unter einem Euro verschwindet. 4,99 und 5,49 gelten als gleich. Bei einer Preisliste ist damit ungefähr die Hälfte aller Vergleiche kaputt, und das Ergebnis sieht auf den ersten Blick trotzdem plausibel aus — es ist ja grob sortiert.
Bei ganzen Zahlen funktioniert die Minus-Variante. Aber es gibt keinen Grund,
sie zu verwenden, seit es <=> gibt.
Nach zwei Spalten sortieren
Wenn zwei Datensätze denselben Preis haben, willst du oft ein zweites Kriterium. Die naheliegende Lösung ist eine if-Kaskade. Es geht kürzer:
<?php
usort($server, fn($a, $b) =>
[$a['preis'], $a['name']] <=> [$b['preis'], $b['name']]);
?>
Preis, dann Name: VPS 200/4.99 VPS 500/4.99 RS 2000/12.99 RS 4000/29.99
<=> vergleicht Arrays elementweise und bricht beim ersten Unterschied ab.
Das entspricht exakt “erst nach Preis, bei Gleichstand nach Name” und liest
sich besser als drei verschachtelte if.
Sortierung ist seit PHP 8.0 stabil
Ein Detail, das man kennen sollte, weil es alten Code betrifft:
gleiche Preise: erster/5 zweiter/5 dritter/5
Elemente mit gleichem Sortierwert behalten ihre ursprüngliche Reihenfolge. Das ist seit PHP 8.0 garantiert. Davor war es das nicht — die Reihenfolge hing von der Implementierung ab und konnte sich zwischen PHP-Versionen ändern.
Praktisch heißt das: Wenn du nach Preis sortierst und die Liste vorher schon nach Name sortiert war, ist sie danach “nach Preis, bei Gleichstand nach Name” sortiert. Ohne dass du das zweite Kriterium hinschreiben musst.
Verlassen würde ich mich darauf trotzdem nur, wenn beide Sortierungen direkt beieinander stehen. Sonst schreibt jemand später eine Zeile dazwischen und wundert sich.
array_multisort() und array_column()
Die zweite verbreitete Variante:
<?php
array_multisort(array_column($server, 'preis'), SORT_ASC, $server);
?>
Funktioniert und ist kurz. Zwei Dinge stören mich daran: Das Array wird per
Referenz umgebaut, und bei mehreren Kriterien wird der Aufruf schnell
unleserlich. Ich bleibe bei usort().
array_column() für sich ist dagegen sehr nützlich:
array_column($server, 'preis') : [12.99,4.99,29.99,4.99]
array_column($server, 'preis', 'name') : {"RS 2000":12.99,"VPS 200":4.99,...}
array_column($server, null, 'name') : Schlüssel sind jetzt die Namen
Die letzte Variante — null als zweiter Parameter — schlüsselt ganze
Datensätze nach einer Spalte um. Das braucht man ständig, wenn man zwei
Ergebnismengen einander zuordnen will, und es ist deutlich schneller als eine
Schleife mit foreach.
Strings sortieren: zwei Überraschungen
Erstens die Sache mit den Zahlen im Text:
sort() : ["Item2","item10","item100","item9"]
SORT_NATURAL : ["Item2","item9","item10","item100"]
sort() vergleicht Zeichen für Zeichen, und 1 kommt vor 9. Deshalb landet
item100 vor item9. Mit SORT_NATURAL (oder natsort()) wird die Zahl als
Zahl gelesen. Bei Dateinamen, Versionsnummern und allem, was durchnummeriert
ist, willst du das.
Zweitens — und das ist der Punkt, den ich hier eigentlich machen will — die Umlaute:
sort() : ["Apfel","Baum","Zebra","Ähre"]
Collator de_DE : ["Ähre","Apfel","Baum","Zebra"]
Ähre landet hinter Zebra. Der Grund ist, dass sort() Bytes vergleicht.
In UTF-8 ist Ä die Bytefolge 0xC3 0x84, und 0xC3 ist größer als das
0x5A von Z. Alle Umlaute, alle Akzente, alles außerhalb von ASCII landet
also hinten.
Für eine Liste, die ein Mensch liest — Namen, Orte, Produkte — brauchst du
deshalb Collator aus der Erweiterung intl:
<?php
$col = new Collator('de_DE');
$col->sort($namen);
?>
Das sortiert nach den Regeln der jeweiligen Sprache, und die sind nicht überall
gleich: Im Deutschen wird Ä wie A einsortiert, im Schwedischen steht es
ganz am Ende des Alphabets.
Ob intl verfügbar ist, prüfst du mit class_exists('Collator'). Bei den
meisten Hostern ist die Erweiterung dabei, garantiert ist es nicht.
usort() wirft die Schlüssel weg
Zum Schluss noch ein Detail, das bei assoziativen Arrays beißt:
usort keys : [0,1,2] <- die Schlüssel sind weg
uasort keys : ["b","c","a"] <- erhalten
usort() vergibt die Schlüssel neu. Wenn deine Datensätze nach ID oder Name
verschlüsselt sind, brauchst du uasort(). Und wenn du nach den Schlüsseln
selbst sortieren willst, uksort().
Die Merkregel: a steht für “associative” (Schlüssel bleiben), k für “key”
(nach Schlüsseln sortieren).
Zusammenfassung
usort($daten, fn($a, $b) => $a['spalte'] <=> $b['spalte']).- Niemals mit
-: Der Rückgabewert wird aufintgecastet, und jede Differenz unter 1 wird damit zu “gleich”. - Zwei Kriterien:
[$a['x'], $a['y']] <=> [$b['x'], $b['y']]. - Seit PHP 8.0 ist die Sortierung stabil.
array_column($daten, null, 'spalte')zum Umschlüsseln.SORT_NATURALfür Zahlen im Text.Collatorfür Umlaute —sort()stelltÄhinterZ.uasort()stattusort(), wenn die Schlüssel erhalten bleiben sollen.
Hinweis zu Netcup (Werbung)
Der deutsche Hoster Netcup bietet unter anderem günstige und zugleich leistungsstarke Webhosting Pakete, KVM-basierte Root Server und dezidierte Server an. Mit unseren Gutscheincodes kannst du noch mehr Geld sparen (6€ bei deiner ersten Bestellung, 30% Rabatt auf alle KVM-basierten Root Server, ...).