MySQL tidak memiliki fungsi untuk menghitung jumlah bidang non-NULL pada satu baris, sejauh yang saya tahu.
Jadi satu-satunya cara yang bisa saya pikirkan adalah menggunakan kondisi eksplisit:
SELECT * FROM mytable
ORDER BY (IF( column1 IS NULL, 0, 1)
+IF( column2 IS NULL, 0, 1)
...
+IF( column45 IS NULL, 0, 1)) DESC;
... itu jelek seperti dosa, tetapi harus melakukan trik.
Anda juga dapat merancang PEMICU untuk menambah kolom tambahan "fields_filled". Pemicu dikenakan biaya pada UPDATE
, 45 JIKA menyakiti Anda di SELECT
; Anda harus membuat model yang lebih nyaman.
Perhatikan bahwa mengindeks semua bidang untuk mempercepat SELECT
akan dikenakan biaya saat memperbarui (dan 45 indeks berbeda mungkin berharga sama seperti pemindaian tabel pada pilihan, belum lagi bidang yang diindeks adalah VARCHAR
). Jalankan beberapa pengujian, tetapi saya yakin bahwa solusi 45-IF kemungkinan besar akan menjadi yang terbaik secara keseluruhan.
PERBARUI :Jika Anda dapat mengerjakan ulang struktur tabel Anda untuk menormalkannya, Anda dapat meletakkan bidang dalam my_values
meja. Maka Anda akan memiliki "tabel tajuk" (mungkin hanya dengan ID unik) dan "tabel data". Kolom kosong tidak akan ada sama sekali, lalu Anda dapat mengurutkan berdasarkan jumlah kolom yang terisi dengan menggunakan RIGHT JOIN
, menghitung bidang yang diisi dengan COUNT()
. Ini juga akan sangat mempercepat UPDATE
operasi, dan akan memungkinkan Anda menggunakan indeks secara efisien.
CONTOH (dari penyiapan tabel hingga dua penyiapan tabel yang dinormalisasi) :
Katakanlah kita memiliki satu set Customer
catatan. Kami akan memiliki subset singkat dari data "wajib" seperti ID, nama pengguna, kata sandi, email, dll.; maka kita akan memiliki subset data "opsional" yang mungkin jauh lebih besar seperti nama panggilan, avatar, tanggal lahir, dan sebagainya. Sebagai langkah pertama mari kita asumsikan bahwa semua data ini adalah varchar
(ini, pada pandangan pertama, terlihat seperti batasan jika dibandingkan dengan solusi tabel tunggal di mana setiap kolom mungkin memiliki tipe datanya sendiri).
Jadi kita punya tabel seperti,
ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
Kemudian kita memiliki tabel data-opsional. Di sini John Doe telah mengisi semua bidang, Joe Q. Rata-rata hanya dua, dan Kilroy tidak ada (bahkan jika dia adalah di sini).
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
Untuk mereproduksi output "tabel tunggal" di MySQL, kita harus membuat VIEW
yang cukup rumit dengan banyak LEFT JOIN
s. Tampilan ini tetap akan sangat cepat jika kita memiliki indeks berdasarkan (userid, var)
(bahkan lebih baik jika kita menggunakan konstanta numerik atau SET daripada varchar untuk tipe data 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)
;
Setiap bidang dalam model logis kami, misalnya, "nama", akan dimuat dalam sebuah tuple ( id, 'name', value ) dalam tabel data opsional.
Dan akan menghasilkan baris dengan bentuk <FIELDNAME>s.val AS <FIELDNAME>
di bagian (1) kueri di atas, mengacu pada baris formulir LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>')
pada ayat (2). Jadi kita dapat membuat kueri secara dinamis dengan menggabungkan baris teks pertama dari kueri di atas dengan Bagian 1 dinamis, teks 'FROM users ' dan Bagian 2 yang dibuat secara dinamis.
Setelah kita melakukan ini, SELECT pada tampilan sama persis dengan sebelumnya -- tetapi sekarang mereka mengambil data dari dua tabel yang dinormalisasi melalui GABUNG.
EXPLAIN SELECT * FROM usertable;
akan memberi tahu kami bahwa menambahkan kolom ke penyiapan ini tidak memperlambat operasi yang berarti, yaitu, solusi ini berskala cukup baik.
INSERT harus dimodifikasi (kami hanya memasukkan data wajib, dan hanya di tabel pertama) dan juga UPDATE:kami MEMPERBARUI tabel data wajib, atau satu baris tabel data opsional. Tetapi jika baris target tidak ada, maka harus DIMASUKKAN.
Jadi kita harus mengganti
UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;
dengan 'upsert', dalam hal ini
INSERT INTO userdata VALUES
( 1, 'name', 'John Doe' ),
( 1, 'born', 'New York' )
ON DUPLICATE KEY UPDATE val = VALUES(val);
(Kami membutuhkan UNIQUE INDEX on userdata(id, var)
untuk ON DUPLICATE KEY
untuk bekerja).
Bergantung pada ukuran baris dan masalah disk, perubahan ini mungkin menghasilkan peningkatan kinerja yang cukup besar.
Perhatikan bahwa jika modifikasi ini tidak dilakukan, kueri yang ada tidak akan menghasilkan kesalahan - mereka akan gagal secara diam-diam .
Di sini misalnya kita memodifikasi nama dua pengguna; satu memang memiliki nama dalam catatan, yang lain memiliki NULL. Yang pertama dimodifikasi, yang kedua tidak.
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)
Untuk mengetahui peringkat setiap baris, bagi pengguna yang memiliki peringkat, kita cukup mengambil jumlah baris data pengguna per id:
SELECT id, COUNT(*) AS rank FROM userdata GROUP BY id
Sekarang untuk mengekstrak baris dalam urutan "status terisi", kita lakukan:
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
memastikan bahwa individu tanpa pangkat juga diambil, dan pemesanan tambahan dengan id
memastikan bahwa orang dengan peringkat yang sama selalu keluar dalam urutan yang sama.