私の知る限り、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の異なるインデックスはおそらくselectでのテーブルスキャンと同じくらいのコストがかかります )。いくつかのテストを実行しますが、45-IFソリューションが全体的に最良である可能性が高いと思います。
更新 :もし テーブル構造を作り直して、ある程度正規化することができます。フィールドをmy_values
に配置することもできます。 テーブル。次に、「ヘッダーテーブル」(おそらく一意のIDのみを持つ)と「データテーブル」があります。空のフィールドはまったく存在しないため、RIGHT JOIN
を使用して、入力されたフィールドの数で並べ替えることができます。 、COUNT()
で入力されたフィールドをカウントします 。これにより、UPDATE
も大幅に高速化されます オペレーション、およびインデックスを効率的に使用できるようになります。
例(テーブルの設定から2つの正規化されたテーブルの設定まで) :
Customer
のセットがあるとしましょう 記録。 ID、ユーザー名、パスワード、電子メールなどの「必須」データの短いサブセットがあります。そうすれば、ニックネーム、アバター、生年月日などの「オプション」データのサブセットがはるかに大きくなります。最初のステップとして、これらすべてのデータがvarchar
であると想定します。 (これは、各列が独自のデータ型を持つ可能性がある単一テーブルソリューションと比較すると、一見制限のように見えます)。
つまり、
のようなテーブルがあります。ID username ....
1 jdoe etc.
2 jqaverage etc.
3 jkilroy etc.
次に、optional-dataテーブルがあります。ここで、John Doeはすべてのフィールドに入力し、Joe Q.平均は2つだけで、Kilroyは何も入力していません(たとえ彼がだったとしても ここに)。
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
s。それでも、(userid, var)
に基づくインデックスがある場合、このビューは非常に高速になります。 (var
のデータ型にvarcharの代わりに数値定数またはSETを使用するとさらに良いです :
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)
;
論理モデルの各フィールド(「name」など)は、オプションのデータテーブルのタプル(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、テキスト「FROMユーザー」、および動的に作成されたセクション2と連結することで、クエリを動的に作成できます。
これを行うと、ビューのSELECTは以前とまったく同じになりますが、現在はJOINを介して2つの正規化されたテーブルからデータをフェッチします。
EXPLAIN SELECT * FROM usertable;
この設定に列を追加しても、操作がそれほど遅くなることはありません。つまり、このソリューションは適度に拡張性があります。
INSERTは変更する必要があり(必須データのみを挿入し、最初のテーブルにのみ挿入します)、UPDATEも同様に変更する必要があります。必須データテーブルまたはオプションのデータテーブルの単一行を更新します。ただし、ターゲット行がない場合は、挿入する必要があります。
したがって、交換する必要があります
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);
(userdata(id、var)にUNIQUE INDEX on userdata(id, var)
ON 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
による追加の順序付けが行われます。 同じランクの人が常に同じ順序で出てくるようにします。