MySQL查询顺序由“最完成的字段”

时间:2012-08-24 09:54:51

标签: php mysql

我有一个有45列的表,但只有少数几个尚未完成。这个表不断更新和添加等。在我的自动完成功能中,我想选择最完整字段排序的这些记录(希望你理解)?

其中一个解决方案是创建另一个字段(“rank”字段)并创建一个php函数,用于选择*记录并为每条记录提供排名。

...但我想知道是否有一种更简单的方法可以做到这一点只有一个ORDER BY?

1 个答案:

答案 0 :(得分: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将在更新时花费您的成本(45个不同的索引的成本可能与select上的表扫描一样多,而不是说索引字段是{{1} })。进行一些测试,但我相信45-IF解决方案可能是最好的。

<强>更新如果,您可以重新设计表结构以对其进行标准化,您可以将字段放在VARCHAR表中。然后你会有一个“标题表”(可能只有一个唯一的ID)和一个“数据表”。根本不存在空字段,然后您可以使用my_values对填充字段进行排序,并使用RIGHT JOIN计算填充字段。这也将大大加快COUNT()操作,并允许您有效地使用索引。

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

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

所以我们有一个类似的表,

varchar

然后我们有了可选数据表。在这里John Doe填补了所有领域,Joe Q.平均只有两个,而Kilroy没有(即使他 在这里)。

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

为了在MySQL中重现“单个表”输出,我们必须创建一个非常复杂的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 ,其中包含许多VIEW个。如果我们有一个基于LEFT JOIN的索引,那么这个视图将非常快(如果我们使用数字常量或SET而不是varchar用于(userid, var)的数据类型,那么该视图会更好:

var

我们逻辑模型中的每个字段,例如“name”,都将包含在可选数据表中的元组(id,'name',value)中。

它将在上述查询的第(1)部分中生成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) ; 形式的一行,引用第(2)节中<FIELDNAME>s.val AS <FIELDNAME>形式的一行。因此,我们可以通过将上述查询的第一个文本行与动态第1部分,文本“FROM users”和动态构建的第2部分连接在一起来动态构造查询。

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

LEFT JOIN userdata AS <FIELDNAME>s ON ( users.id = <FIELDNAME>s.id AND <FIELDNAME>s.var = '<FIELDNAME>')

将告诉我们,在此设置中添加列不会减慢明显的操作速度,即此解决方案可以很好地扩展。

INSERT必须被修改(我们只插入必需数据,仅在第一个表中插入)和UPDATE:我们要么更新强制数据表,要么修改可选数据表的单行。但是如果目标行不在那里,那么它必须被INSERTed。

所以我们必须更换

EXPLAIN SELECT * FROM usertable;

带有'upsert',在本例中为

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)才能正常工作。

根据行大小和磁盘问题,此更改可能会产生明显的性能提升。

请注意,如果未执行此修改,现有查询将不会产生错误 - 它们将无声地失败

这里我们例如修改两个用户的名字;一个在记录中有一个名字,另一个有NULL。第一个是修改的,第二个不是。

ON DUPLICATE KEY

要知道每行的排名,对于那些具有排名的用户,我们只需检索每个id的userdata行数:

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)

现在以“填充状态”顺序提取行,我们执行:

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的额外排序可确保排名相同的人总是以相同的顺序出现。