Soweit ich weiß, hat MySQL keine Funktion, um die Anzahl der Nicht-NULL-Felder in einer Zeile zu zählen.
Die einzige Möglichkeit, die mir einfällt, ist die Verwendung einer expliziten Bedingung:
SELECT * FROM mytable
ORDER BY (IF( column1 IS NULL, 0, 1)
+IF( column2 IS NULL, 0, 1)
...
+IF( column45 IS NULL, 0, 1)) DESC;
...es ist hässlich wie die Sünde, sollte aber reichen.
Sie könnten auch einen TRIGGER entwickeln, um eine zusätzliche Spalte "fields_filled" zu erhöhen. Der Trigger kostet Sie am UPDATE
, die 45 IFs verletzen dich bei SELECT
; Sie müssen modellieren, was bequemer ist.
Beachten Sie, dass alle Felder indexiert werden, um SELECT
zu beschleunigen kostet Sie beim Aktualisieren (und 45 verschiedene Indizes kosten wahrscheinlich so viel wie ein Tabellenscan auf select, um nicht zu sagen, dass das indizierte Feld ein VARCHAR
ist ). Führen Sie einige Tests durch, aber ich glaube, dass die 45-IF-Lösung insgesamt wahrscheinlich die beste ist.
AKTUALISIEREN :Wenn Sie können Ihre Tabellenstruktur überarbeiten, um sie etwas zu normalisieren, Sie könnten die Felder in my_values
einfügen Tisch. Dann hätten Sie eine "Kopftabelle" (vielleicht mit nur einer eindeutigen ID) und eine "Datentabelle". Leere Felder würden überhaupt nicht existieren, und dann könnten Sie mit einem RIGHT JOIN
danach sortieren, wie viele gefüllte Felder vorhanden sind , wobei die gefüllten Felder mit COUNT()
gezählt werden . Dies würde auch UPDATE
erheblich beschleunigen Operationen und würde es Ihnen ermöglichen, Indizes effizient einzusetzen.
BEISPIEL (vom Tabellenaufbau zum Aufbau von zwei normalisierten Tabellen) :
Nehmen wir an, wir haben eine Reihe von Customer
Aufzeichnungen. Wir haben eine kurze Teilmenge von „obligatorischen“ Daten wie ID, Benutzername, Passwort, E-Mail usw.; dann haben wir eine vielleicht viel größere Teilmenge von "optionalen" Daten wie Spitzname, Avatar, Geburtsdatum und so weiter. Nehmen wir als ersten Schritt an, dass alle diese Daten varchar
sind (Dies sieht auf den ersten Blick wie eine Einschränkung im Vergleich zur Lösung mit einer einzelnen Tabelle aus, bei der jede Spalte ihren eigenen Datentyp haben kann).
Wir haben also eine Tabelle wie,
ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
Dann haben wir die optionale Datentabelle. Hier hat John Doe alle Felder ausgefüllt, Joe Q. Average nur zwei und Kilroy keines (auch wenn er war hier).
userid var val
1 name John
1 born Stratford-upon-Avon
1 when 11-07-1974
2 name Joe Quentin
2 when 09-04-1962
Um die "Single Table"-Ausgabe in MySQL zu reproduzieren, müssen wir eine ziemlich komplexe VIEW
erstellen mit viel LEFT JOIN
s. Diese Ansicht wird dennoch sehr schnell sein, wenn wir einen Index haben, der auf (userid, var)
basiert (Noch besser, wenn wir für den Datentyp von var
anstelle eines varchar eine numerische Konstante oder ein SET verwenden :
CREATE OR REPLACE VIEW usertable AS SELECT users.*,
names.val AS name // (1)
FROM users
LEFT JOIN userdata AS names ON ( users.id = names.id AND names.var = 'name') // (2)
;
Jedes Feld in unserem logischen Modell, z. B. "name", wird in einem Tupel ( id, 'name', value ) in der optionalen Datentabelle enthalten sein.
Und es ergibt eine Zeile der Form <FIELDNAME>s.val AS <FIELDNAME>
im Abschnitt (1) der obigen Abfrage, bezogen auf eine Zeile der Form LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>')
in Abschnitt (2). Daher können wir die Abfrage dynamisch erstellen, indem wir die erste Textzeile der obigen Abfrage mit einem dynamischen Abschnitt 1, dem Text „VON Benutzern“ und einem dynamisch erstellten Abschnitt 2 verketten.
Sobald wir dies getan haben, sind SELECTs in der Ansicht genau identisch mit vorher – aber jetzt holen sie Daten aus zwei normalisierten Tabellen über JOINs.
EXPLAIN SELECT * FROM usertable;
wird uns sagen, dass das Hinzufügen von Spalten zu diesem Setup den Betrieb nicht merklich verlangsamt, d. h. diese Lösung lässt sich recht gut skalieren.
INSERTs müssen geändert werden (wir fügen nur obligatorische Daten ein und nur in die erste Tabelle) und UPDATEs ebenfalls:Wir AKTUALISIEREN entweder die obligatorische Datentabelle oder eine einzelne Zeile der optionalen Datentabelle. Wenn die Zielzeile jedoch nicht vorhanden ist, muss sie eingefügt werden.
Also müssen wir ersetzen
UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;
mit einem 'Upsert', in diesem Fall
INSERT INTO userdata VALUES
( 1, 'name', 'John Doe' ),
( 1, 'born', 'New York' )
ON DUPLICATE KEY UPDATE val = VALUES(val);
(Wir brauchen einen UNIQUE INDEX on userdata(id, var)
für ON DUPLICATE KEY
zur Arbeit).
Je nach Zeilengröße und Festplattenproblemen kann diese Änderung zu einem spürbaren Leistungsgewinn führen.
Beachten Sie, dass die vorhandenen Abfragen keine Fehler liefern, wenn diese Änderung nicht durchgeführt wird – sie werden stillschweigend fehlschlagen .
Hier ändern wir zum Beispiel die Namen von zwei Benutzern; Einer hat einen Namen, der andere hat NULL. Der erste wird modifiziert, der zweite nicht.
mysql> SELECT * FROM usertable;
+------+-----------+-------------+------+------+
| id | username | name | born | age |
+------+-----------+-------------+------+------+
| 1 | jdoe | John Doe | NULL | NULL |
| 2 | jqaverage | NULL | NULL | NULL |
| 3 | jtkilroy | NULL | NULL | NULL |
+------+-----------+-------------+------+------+
3 rows in set (0.00 sec)
mysql> UPDATE usertable SET name = 'John Doe II' WHERE username = 'jdoe';
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
mysql> UPDATE usertable SET name = 'James T. Kilroy' WHERE username = 'jtkilroy';
Query OK, 0 rows affected (0.00 sec)
Rows matched: 0 Changed: 0 Warnings: 0
mysql> select * from usertable;
+------+-----------+-------------+------+------+
| id | username | name | born | age |
+------+-----------+-------------+------+------+
| 1 | jdoe | John Doe II | NULL | NULL |
| 2 | jqaverage | NULL | NULL | NULL |
| 3 | jtkilroy | NULL | NULL | NULL |
+------+-----------+-------------+------+------+
3 rows in set (0.00 sec)
Um den Rang jeder Zeile zu kennen, rufen wir für die Benutzer, die einen Rang haben, einfach die Anzahl der Benutzerdatenzeilen pro ID ab:
SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id
Um nun Zeilen in der Reihenfolge "gefüllter Status" zu extrahieren, gehen wir wie folgt vor:
SELECT usertable.* FROM usertable
LEFT JOIN ( SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id ) AS ranking
ON (usertable.id = ranking.id)
ORDER BY rank DESC, id;
Der LEFT JOIN
sorgt dafür, dass auch ranglose Personen gefunden werden, und die zusätzliche Sortierung nach id
sorgt dafür, dass Leute mit gleichem Rang immer in der gleichen Reihenfolge herauskommen.