MySQL nie ma funkcji do zliczania liczby pól innych niż NULL w wierszu, o ile wiem.
Więc jedynym sposobem, jaki przychodzi mi do głowy, jest użycie wyraźnego warunku:
SELECT * FROM mytable
ORDER BY (IF( column1 IS NULL, 0, 1)
+IF( column2 IS NULL, 0, 1)
...
+IF( column45 IS NULL, 0, 1)) DESC;
...jest brzydkie jak grzech, ale powinno wystarczyć.
Możesz również wymyślić TRIGGER, aby zwiększyć dodatkową kolumnę „fields_filled”. Wyzwalacz kosztuje Cię w dniu UPDATE
, 45 IF zaszkodziło Ci przy SELECT
; będziesz musiał modelować, co jest wygodniejsze.
Pamiętaj, że indeksowanie wszystkich pól przyspiesza SELECT
będzie cię kosztować podczas aktualizacji (a 45 różnych indeksów prawdopodobnie kosztuje tyle, co skanowanie tabeli przy wyborze, nie mówiąc, że indeksowane pole to VARCHAR
). Przeprowadź kilka testów, ale uważam, że rozwiązanie 45-IF będzie prawdopodobnie najlepsze ogólnie.
AKTUALIZUJ :Jeśli możesz przerobić strukturę tabeli, aby nieco ją znormalizować, możesz umieścić pola w moje_wartości
stół. Wtedy będziesz miał „tablicę nagłówków” (może z tylko unikalnym identyfikatorem) i „tablicę danych”. Puste pola w ogóle by nie istniały, a następnie możesz sortować według liczby wypełnionych pól, używając RIGHT JOIN
, licząc wypełnione pola za pomocą COUNT()
. To również znacznie przyspieszyłoby AKTUALIZACJĘ
operacji i pozwoli na efektywne wykorzystanie indeksów.
PRZYKŁAD (od konfiguracji stołu do konfiguracji dwóch znormalizowanych stołów) :
Powiedzmy, że mamy zestaw Klienta
dokumentacja. Będziemy mieli krótki podzbiór „obowiązkowych” danych, takich jak identyfikator, nazwa użytkownika, hasło, adres e-mail itp.; wtedy będziemy mieli być może znacznie większy podzbiór „opcjonalnych” danych, takich jak pseudonim, awatar, data urodzenia i tak dalej. Jako pierwszy krok załóżmy, że wszystkie te dane są varchar
(na pierwszy rzut oka wygląda to na ograniczenie w porównaniu z rozwiązaniem z pojedynczą tabelą, w którym każda kolumna może mieć swój własny typ danych).
Mamy więc taki stół,
ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
Następnie mamy tabelę danych opcjonalnych. Tutaj John Doe wypełnił wszystkie pola, Joe Q. Średnio tylko dwa, a Kilroy żadnego (nawet jeśli był tutaj).
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
Aby odtworzyć wynik "pojedynczej tabeli" w MySQL, musimy stworzyć dość złożony VIEW
z dużą ilością LEFT JOIN
s. Ten widok będzie jednak bardzo szybki, jeśli mamy indeks oparty na (userid, var)
(nawet lepiej, jeśli użyjemy stałej numerycznej lub SET zamiast varchar dla typu danych zmienna
:
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)
;
Każde pole w naszym modelu logicznym, np. "name", będzie zawarte w krotce ( id, 'name', value ) w opcjonalnej tabeli danych.
I zwróci wiersz w postaci LEFT JOIN userdata AS
w sekcji (2). Możemy więc skonstruować zapytanie dynamicznie, łącząc pierwszą linię tekstu powyższego zapytania z dynamiczną sekcją 1, tekstem „FROM users” i dynamicznie utworzoną sekcją 2.
Gdy to zrobimy, SELECT w widoku są dokładnie takie same jak wcześniej — ale teraz pobierają dane z dwóch znormalizowanych tabel za pomocą JOIN.
EXPLAIN SELECT * FROM usertable;
powie nam, że dodanie kolumn do tej konfiguracji nie spowalnia znacząco operacji, tj. to rozwiązanie skaluje się dość dobrze.
WSTAWKI będą musiały zostać zmodyfikowane (wstawiamy tylko dane obowiązkowe i tylko w pierwszej tabeli) oraz UAKTUALNIEMY:albo AKTUALIZujemy obowiązkową tabelę danych, albo pojedynczy wiersz opcjonalnej tabeli danych. Ale jeśli nie ma tam docelowego wiersza, należy go WSTAWIĆ.
Więc musimy wymienić
UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;
z 'upsert', w tym przypadku
INSERT INTO userdata VALUES
( 1, 'name', 'John Doe' ),
( 1, 'born', 'New York' )
ON DUPLICATE KEY UPDATE val = VALUES(val);
(Potrzebujemy UNIKALNEGO INDEKSU na dane użytkownika(id, var)
dla W ZDUPLIKOWANYM KLUCZE
do pracy).
W zależności od rozmiaru wiersza i problemów z dyskiem ta zmiana może przynieść znaczny wzrost wydajności.
Pamiętaj, że jeśli ta modyfikacja nie zostanie wykonana, istniejące zapytania nie spowodują błędów — po cichu zakończą się niepowodzeniem .
Tutaj na przykład modyfikujemy nazwy dwóch użytkowników; jeden ma zapisane imię, drugi ma NULL. Pierwsza jest modyfikowana, druga nie.
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)
Aby poznać pozycję każdego wiersza, dla tych użytkowników, którzy mają pozycję, po prostu pobieramy liczbę wierszy danych użytkownika na identyfikator:
SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id
Teraz, aby wyodrębnić wiersze w kolejności „wypełnione”, robimy:
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;
LEWY DOŁĄCZ
zapewnia, że pobierane są również osobniki bez rangą, a dodatkowe uporządkowanie według id
zapewnia, że ludzie o identycznej randze zawsze wychodzą w tej samej kolejności.