1

私は 45 列のテーブル ウィッチを持っていますが、まだ完成しているのはほんのわずかです。このテーブルは継続的に更新および追加されます。オートコンプリート機能で、最も完成度の高いフィールドで並べ替えられたこれらのレコードを選択したいのですが (理解していただければ幸いです)?

解決策の 1 つは、別のフィールド (「ランク」フィールド) を作成し、 * レコードを選択して各レコードのランクを与える php 関数を作成することです。

...しかし、単一のORDER BYだけでこれを行うより簡単な方法があるかどうか疑問に思っていましたか?

4

1 に答える 1

5

私の知る限り、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、同じランクの人が常に同じ順序で出てくることが保証されます。

于 2012-08-24T10:03:20.553 に答える