MySQL няма функция за преброяване на броя не-NULL полета в ред, доколкото знам.
Така че единственият начин, за който се сещам, е да използвам изрично условие:
SELECT * FROM mytable
ORDER BY (IF( column1 IS NULL, 0, 1)
+IF( column2 IS NULL, 0, 1)
...
+IF( column45 IS NULL, 0, 1)) DESC;
...грозно е като грях, но трябва да свърши работа.
Можете също така да създадете TRIGGER за увеличаване на допълнителна колона "fields_filled". Тригерът ви струва на UPDATE
, 45-те IF-а ви нараняват при SELECT
; ще трябва да моделирате това, което е по-удобно.
Имайте предвид, че индексирането на всички полета за ускоряване на SELECT
ще ви струва при актуализиране (и 45 различни индекса вероятно струват колкото сканиране на таблица при избрано, да не казваме, че индексираното поле е VARCHAR
). Направете някои тестове, но вярвам, че решението 45-IF вероятно ще бъде най-доброто като цяло.
АКТУАЛИЗАЦИЯ :Ако можете да преработите структурата на таблицата си, за да я нормализирате донякъде, можете да поставите полетата в my_values
маса. Тогава ще имате "заглавна таблица" (може би само с уникален идентификатор) и "таблица с данни". Празните полета изобщо няма да съществуват и тогава можете да сортирате по колко попълнени полета има, като използвате RIGHT JOIN
, преброявайки попълнените полета с COUNT()
. Това също би ускорило значително UPDATE
операции и ще ви позволи да използвате ефективно индекси.
ПРИМЕР (от настройка на таблица до настройка на две нормализирани таблици) :
Да кажем, че имаме набор от Customer
записи. Ще имаме кратък поднабор от „задължителни“ данни като ID, потребителско име, парола, имейл и т.н.; тогава ще имаме може би много по-голямо подмножество от „незадължителни“ данни като псевдоним, аватар, дата на раждане и т.н. Като първа стъпка нека приемем, че всички тези данни са varchar
(това на пръв поглед изглежда като ограничение в сравнение с решението с една таблица, където всяка колона може да има свой собствен тип данни).
Така че имаме таблица като,
ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
След това имаме таблицата с незадължителни данни. Тук Джон Доу е попълнил всички полета, Джо Кю. Средно само две, а Килрой нито едно (дори ако беше тук).
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
За да възпроизведем изхода "единична таблица" в MySQL, трябва да създадем доста сложен VIEW
с много LEFT JOIN
с. Въпреки това този изглед ще бъде много бърз, ако имаме индекс, базиран на (userid, var)
(още по-добре, ако използваме числова константа или SET вместо varchar за типа данни на var
:
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)
;
Всяко поле в нашия логически модел, например "име", ще се съдържа в кортеж ( id, 'name', value ) в незадължителната таблица с данни.
И ще се получи ред във формата <FIELDNAME>s.val AS <FIELDNAME>
в раздел (1) на горната заявка, препращайки към ред във формата LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>')
в раздел (2). Така че можем да конструираме заявката динамично, като свържем първия текстов ред на горната заявка с динамична секция 1, текста „ОТ потребители“ и динамично изградена секция 2.
След като направим това, SELECT в изгледа са напълно идентични с предишните -- но сега те извличат данни от две нормализирани таблици чрез JOIN.
EXPLAIN SELECT * FROM usertable;
ще ни каже, че добавянето на колони към тази настройка не забавя значително операциите, т.е. това решение се мащабира сравнително добре.
Вмъкванията ще трябва да бъдат модифицирани (ние вмъкваме само задължителни данни и само в първата таблица), както и АКТУАЛИЗИРАНЕ:ние или АКТУАЛИЗИРАМЕ таблицата със задължителни данни, или един ред от незадължителната таблица с данни. Но ако целевият ред не е там, тогава той трябва да бъде ВМЪКЕН.
Така че трябва да заменим
UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;
с 'upsert', в този случай
INSERT INTO userdata VALUES
( 1, 'name', 'John Doe' ),
( 1, 'born', 'New York' )
ON DUPLICATE KEY UPDATE val = VALUES(val);
(Нуждаем се от UNIQUE INDEX on userdata(id, var)
за ON DUPLICATE KEY
на работа).
В зависимост от размера на реда и проблемите с диска, тази промяна може да доведе до значително увеличение на производителността.
Обърнете внимание, че ако тази модификация не бъде извършена, съществуващите заявки няма да дадат грешки - те тихо ще се провалят .
Тук например променяме имената на двама потребители; единият има записано име, другият има NULL. Първият е модифициран, вторият не.
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)
За да знаем ранга на всеки ред, за тези потребители, които имат ранг, ние просто извличаме броя на редовете с потребителски данни за id:
SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id
Сега, за да извлечем редове в реда на „попълнен статус“, ние правим:
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;
LEFT JOIN
гарантира, че лицата без ранг също ще бъдат извлечени и допълнителното подреждане по id
гарантира, че хората с еднакъв ранг винаги излизат в един и същ ред.