1

我有一张桌子,有 45 列,但其中只有几列尚未完成。该表会不断更新和添加等。在我的自动完成功能中,我想选择按完成最多的字段排序的这些记录(希望您理解)?

一种解决方案是创建另一个字段(“排名”字段)并创建一个 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;

...它像罪恶一样丑陋,但应该可以解决问题。

您还可以设计一个触发器来增加一个额外的列“fields_filled”。触发器会让你付出代价UPDATE,45个IF会伤害你SELECT;您必须对更方便的模型进行建模。

请注意,索引所有字段以加快速度SELECT将在更新时花费您(并且 45 个不同的索引可能花费与 select 上的表扫描一样多,并不是说索引字段是 a VARCHAR)。运行一些测试,但我相信 45-IF 解决方案可能是整体上最好的。

更新如果您可以修改表结构以对其进行某种程度的规范化,则可以将字段放在my_values表中。然后你会有一个“头表”(可能只有一个唯一的 ID)和一个“数据表”。空字段根本不存在,然后您可以使用 a 按填充字段的数量排序,用RIGHT JOIN计算填充字段COUNT()。这也将大大加快UPDATE操作,并允许您有效地使用索引。

示例(从表设置到两个规范化表设置)

假设我们有一组Customer记录。我们将有一小部分“强制性”数据,例如 ID、用户名、密码、电子邮件等;那么我们可能会有更大的“可选”数据子集,例如昵称、头像、出生日期等。作为第一步,让我们假设所有这些数据都是varchar(乍一看,与每列可能有自己的数据类型的单表解决方案相比,这看起来像是一个限制)。

所以我们有一张桌子,

ID   username    ....
1    jdoe        etc.
2    jqaverage   etc.
3    jkilroy     etc.

然后我们有可选数据表。在这里,John Doe 填写了所有字段,Joe Q.平均只有两个,而 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 JOINs 的非常复杂的表。如果我们有一个基于的索引,这个视图仍然会非常快(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)
;

我们逻辑模型中的每个字段,例如“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) 部分中的表格行。因此,我们可以通过将上述查询的第一个文本行与动态 Section 1、文本“FROM users”和动态构建的 Section 2 连接来动态构建查询。

一旦我们这样做了,视图上的 SELECT 就与以前完全相同了——但现在它们通过 JOIN 从两个规范化表中获取数据。

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);

(我们需要一个UNIQUE INDEX on userdata(id, var)forON 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 的 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 回答