我有一个有45列的表,但只有少数几个尚未完成。这个表不断更新和添加等。在我的自动完成功能中,我想选择最完整字段排序的这些记录(希望你理解)?
其中一个解决方案是创建另一个字段(“rank”字段)并创建一个php函数,用于选择*记录并为每条记录提供排名。
...但我想知道是否有一种更简单的方法可以做到这一点只有一个ORDER BY?
答案 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
的额外排序可确保排名相同的人总是以相同的顺序出现。