私の知る限り、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
と、更新時にコストがかかることに注意してください (インデックス付きフィールドがVARCHAR
. いくつかのテストを実行しますが、45-IF ソリューションが全体的に最適である可能性が高いと考えています。
UPDATE :
テーブル構造を作り直して多少正規化できる場合は、フィールドをテーブルに入れることができmy_values
ます。次に、「ヘッダー テーブル」(おそらく一意の ID のみ) と「データ テーブル」を作成します。空のフィールドはまったく存在しないため、 を使用してRIGHT JOIN
、 で埋められたフィールドを数え、埋められたフィールドの数で並べ替えることができますCOUNT()
。これにより、操作も大幅に高速化されUPDATE
、インデックスを効率的に使用できるようになります。
例 (テーブルのセットアップから 2 つの正規化されたテーブルのセットアップまで) :
Customer
一連のレコードがあるとしましょう。ID、ユーザー名、パスワード、電子メールなどの「必須」データの短いサブセットがあります。次に、ニックネーム、アバター、生年月日などの「オプション」データのサブセットがおそらくはるかに大きくなります。varchar
最初のステップとして、これらすべてのデータが(各列が独自のデータ型を持つ可能性がある単一のテーブル ソリューションと比較すると、これは一見、制限のように見えます)と仮定します。
次のようなテーブルがあります。
ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
次に、オプションのデータ テーブルがあります。ここでは、John Doe がすべてのフィールドを埋めています。Joe Q. 平均は 2 つだけで、Kilroy は (彼がここにいたとしても) 1 つもありません。
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)
(のデータ型に varchar の代わりに数値定数または SET を使用する場合はさらに優れています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 ) に含まれます。
そして、セクション (2)<FIELDNAME>s.val AS <FIELDNAME>
のフォームの行を参照して、上記のクエリのセクション (1) のフォームの行を生成LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>')
します。したがって、上記のクエリの最初のテキスト行を動的なセクション 1、テキスト 'FROM users '、および動的に構築されたセクション 2 と連結することにより、クエリを動的に構築できます。
これを行うと、ビューの SELECT は以前とまったく同じになりますが、JOIN を介して 2 つの正規化されたテーブルからデータを取得します。
EXPLAIN SELECT * FROM usertable;
このセットアップに列を追加しても、操作がそれほど遅くならないことがわかります。つまり、このソリューションは適度に拡張されます。
INSERT を変更する必要があります (必須データのみを挿入し、最初のテーブルにのみ挿入します)。UPDATE も同様に行う必要があります。必須データ テーブルを更新するか、オプション データ テーブルの 1 行を更新します。ただし、ターゲット行がそこにない場合は、INSERT する必要があります。
だから私たちは交換する必要があります
UPDATE usertable SET name = 'John Doe', born = 'New York' WHERE id = 1;
この場合、「アップサート」を使用
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)
forON DUPLICATE KEY
が必要です)。
行のサイズとディスクの問題によっては、この変更によってパフォーマンスが大幅に向上する場合があります。
この変更が実行されない場合、既存のクエリでエラーが発生しないことに注意してください。エラーは表示されずに失敗します。
ここでは例として、2 人のユーザーの名前を変更します。1 つは記録に名前があり、もう 1 つは NULL です。1 つ目は変更されていますが、2 つ目は変更されていません。
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 ごとに userdata 行の数を取得するだけです。
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
、同じランクの人が常に同じ順序で出てくることが保証されます。