PHP.de Wissenssammlung

SQL - Spezielle Sortierungen

Manchmal möchte man eine spezielle Sortierung der Ausgabe, welche mit dem üblichen ORDER BY field1, field2, ... nicht erreicht werden kann. Nachfolgend ein paar Beispiele mit Lösungsansätzen dazu.

Sortierung nach unterschiedlichen Kriterien

Dazu verwenden wir folgende Ausgangstabelle:

SELECT name, date_birth FROM persons

+---------+------------+
| name    | date_birth |
+---------+------------+
| Stefan  | 1955-08-01 |
| Andrea  | 1966-11-01 |
| Klaus   | 1978-02-01 |
| Petra   | 2000-10-02 |
| Joachim | 2002-01-01 |
| Sandra  | 1980-12-01 |
| Dieter  | 1950-03-01 |
| Claudia | 2002-05-01 |
+---------+------------+
8 rows in set (0.00 sec)

Möchten wir uns nun beispielsweise die ältesten drei Personen ausgeben lassen, so reicht dafür ein LIMIT 3 mit der entsprechenden Sortierung aus.

SELECT name, date_birth FROM persons ORDER BY date_birth LIMIT 3

+--------+------------+
| name   | date_birth |
+--------+------------+
| Dieter | 1950-03-01 |
| Stefan | 1955-08-01 |
| Andrea | 1966-11-01 |
+--------+------------+
3 rows in set (0.00 sec)

Wollen wir nun wieder die ältersten drei Personen ausgeben, dieses mal jedoch, entgegen der obigen für LIMIT nötigen Sortierlogik, von jung nach alt (also umgekehrt) sortieren lasen, benötigen wir ein Sub-Select.

SELECT * FROM
  (SELECT name, date_birth FROM persons ORDER BY date_birth LIMIT 3) AS sub
ORDER BY sub.date_birth DESC

+--------+------------+
| name   | date_birth |
+--------+------------+
| Andrea | 1966-11-01 |
| Stefan | 1955-08-01 |
| Dieter | 1950-03-01 |
+--------+------------+
3 rows in set (0.00 sec)

Je nach Belieben kann die ORDER-Klausel verändert werden, z.B. die ältersen drei Personen, alphabetisch sortiert.

SELECT * FROM
  (SELECT name, date_birth FROM persons ORDER BY date_birth LIMIT 3) AS sub
ORDER BY sub.name

+--------+------------+
| name   | date_birth |
+--------+------------+
| Andrea | 1966-11-01 |
| Dieter | 1950-03-01 |
| Stefan | 1955-08-01 |
+--------+------------+
3 rows in set (0.00 sec)

Vorsortierung nach einer Bedingung

Ein anderer Fall ist eine Art Vorsortierung nach einer gewissen Bedingung.

Angenommen folgende Ausgangstabelle, in der das Geburtsdatum von Klaus und Sandra noch nicht bekannt, und daher mit NULL gesetzt ist.

SELECT name, date_birth FROM persons

+---------+------------+
| name    | date_birth |
+---------+------------+
| Stefan  | 1955-08-01 |
| Andrea  | 1966-11-01 |
| Klaus   | NULL       |
| Petra   | 2000-10-02 |
| Joachim | 2002-01-01 |
| Sandra  | NULL       |
| Dieter  | 1950-03-01 |
| Claudia | 2002-05-01 |
+---------+------------+
8 rows in set (0.00 sec)

Nun wollen wir uns zuerst alle Personen ausgeben lassen, die noch kein Geburtsdatum haben, und danach alle anderen, und das alphabetisch sortiert. Da dies mittels einem einfachen ORDER BY date_birth, name nicht wie gewünscht funktioniert, müssen wir in diesem Fall ein zusätzliches IS NULL in der ORDER-Klausel verwenden.

SELECT name, date_birth FROM persons ORDER BY date_birth IS NULL DESC, name

+---------+------------+
| name    | date_birth |
+---------+------------+
| Klaus   | NULL       |
| Sandra  | NULL       |
| Andrea  | 1966-11-01 |
| Claudia | 2002-05-01 |
| Dieter  | 1950-03-01 |
| Joachim | 2002-01-01 |
| Petra   | 2000-10-02 |
| Stefan  | 1955-08-01 |
+---------+------------+
8 rows in set (0.00 sec)

Blockweise Sortierung nach einem Wert je Block

Wir haben Kunden und zu jedem Kunden mehrere Aufträge. Es sollen die Aufträge “geblockt nach Kunden” so ausgeben werden, dass die Blöcke in sich nach Datum sortiert sind. Zusätzlich soll der gesamte Block mit dem aktuellsten/jüngsten Datum zuerst erscheinen.

Die Kunden

SELECT id, name FROM kunde

+----+----------+
| id | name     |
+----+----------+
|  1 | Aigner   |
|  2 | Gruber   |
|  3 | Sandmann |
+----+----------+
3 rows in set (0.00 sec)

Die Aufträge

SELECT id, kunde_id, produkt, deadline
FROM auftrag
ORDER BY deadline

+----+----------+--------------+------------+
| id | kunde_id | produkt      | deadline   |
+----+----------+--------------+------------+
|  1 |        3 | Tisch eckig  | 2017-02-01 |
|  4 |        1 | Kasten       | 2017-03-15 |
|  7 |        2 | Gartenzaun   | 2017-04-01 |
|  5 |        1 | Tisch rund   | 2017-04-15 |
|  2 |        3 | Vitrine      | 2017-05-01 |
|  9 |        2 | Schreibtisch | 2017-09-01 |
|  6 |        1 | Essecke      | 2017-10-01 |
|  8 |        2 | Carport      | 2017-10-15 |
|  3 |        3 | Lampe        | 2018-02-01 |
+----+----------+--------------+------------+
9 rows in set (0.00 sec)

Ergibt “gejoint”

SELECT k.id, k.name, a.produkt, a.deadline
FROM kunde k
INNER JOIN auftrag a ON k.id = a.kunde_id
ORDER BY deadline

+----+----------+--------------+------------+
| id | name     | produkt      | deadline   |
+----+----------+--------------+------------+
|  3 | Sandmann | Tisch eckig  | 2017-02-01 |
|  1 | Aigner   | Kasten       | 2017-03-15 |
|  2 | Gruber   | Gartenzaun   | 2017-04-01 |
|  1 | Aigner   | Tisch rund   | 2017-04-15 |
|  3 | Sandmann | Vitrine      | 2017-05-01 |
|  2 | Gruber   | Schreibtisch | 2017-09-01 |
|  1 | Aigner   | Essecke      | 2017-10-01 |
|  2 | Gruber   | Carport      | 2017-10-15 |
|  3 | Sandmann | Lampe        | 2018-02-01 |
+----+----------+--------------+------------+
9 rows in set (0.00 sec)

Zuerst brauchen wir das jeweils jüngste Datum der Aufträge je Kunde.

SELECT k.id, k.name, MIN(a.deadline) AS min_date
FROM kunde k
INNER JOIN auftrag a ON k.id = a.kunde_id
GROUP BY k.id, k.name
ORDER BY min_date

+----+----------+------------+
| id | name     | min_date   |
+----+----------+------------+
|  3 | Sandmann | 2017-02-01 |
|  1 | Aigner   | 2017-03-15 |
|  2 | Gruber   | 2017-04-01 |
+----+----------+------------+
3 rows in set (0.00 sec)

Nun verwenden wir diese Daten als “innere” Tabelle und joinen uns den Rest wie benötigt dazu.

SELECT
    k.id, k.name,
    a.produkt, a.deadline,
    k.min_date
FROM
    (
        -- frühestes deadline-datum je kunde
        SELECT k.id, k.name, MIN(a.deadline) AS min_date
        FROM kunde k
        INNER JOIN auftrag a ON k.id = a.kunde_id
        GROUP BY k.id
        ORDER BY min_date
    ) k
INNER JOIN auftrag a ON k.id = a.kunde_id
ORDER BY k.min_date, a.deadline

+----+----------+--------------+------------+------------+
| id | name     | produkt      | deadline   | min_date   |
+----+----------+--------------+------------+------------+
|  3 | Sandmann | Tisch eckig  | 2017-02-01 | 2017-02-01 |
|  3 | Sandmann | Vitrine      | 2017-05-01 | 2017-02-01 |
|  3 | Sandmann | Lampe        | 2018-02-01 | 2017-02-01 |
|  1 | Aigner   | Kasten       | 2017-03-15 | 2017-03-15 |
|  1 | Aigner   | Tisch rund   | 2017-04-15 | 2017-03-15 |
|  1 | Aigner   | Essecke      | 2017-10-01 | 2017-03-15 |
|  2 | Gruber   | Gartenzaun   | 2017-04-01 | 2017-04-01 |
|  2 | Gruber   | Schreibtisch | 2017-09-01 | 2017-04-01 |
|  2 | Gruber   | Carport      | 2017-10-15 | 2017-04-01 |
+----+----------+--------------+------------+------------+
9 rows in set (0.00 sec)

Fertig!
Dies kann nun mit dem Gruppenbruch wie gewünscht ausgegeben werden.

Hinweis zur WHERE-Klausel

Aus Performancegründen sollten Bedingungen möglichst in der inneren Query notiert werden. Bspw. sollte ein Filter nach einer Kundengruppe so aussehen:

SELECT
    k.id, k.name,
    a.produkt, a.deadline,
    k.min_date
FROM
    (
        -- frühestes deadline-datum je kunde
        SELECT k.id, k.name, MIN(a.deadline) AS min_date
        FROM kunde k
        INNER JOIN auftrag a ON k.id = a.kunde_id
        WHERE k.gruppe = 'A' -- Hier ist der Filter anzugeben
        GROUP BY k.id
        ORDER BY min_date
    ) k
INNER JOIN auftrag a ON k.id = a.kunde_id
ORDER BY k.min_date, a.deadline

Dieser Beitrag wird zur Zeit diskutiert und wurde zuletzt von hausl verändert.

Beiträge die zur Diskussion gestellt werden, enthalten mitunter Informationen bei denen wir uns noch bezüglich der finalen Darstellung absprechen müssen. Gedulde dich etwas, wir stellen diesen Beitrag fertig, sobald die Diskussion beendet ist.

An diesem Beitrag waren bisher beteiligt: hausl