SQL 艺术:分组去重排序关联

事情交代

在日常唠嗑前先简单说一下本文想要记录的一次 MySQL 技术回顾:两张表,对其中一张表进行分组去重后,对结果集进行排序,然后关联另一张表得到最终的结果集

当时推翻重来了几次,从中也学到不少 SQL 知识,因此有必要写成文章系统的给自己一个交代。

OK,接下来先谈些感悟。不喜的请略过,谢谢

日常唠嗑

自从来到这家公司之后,一直在维护后台系统,其中包括一些旧版页面的整改、增加新功能等等。熟悉一个已有的业务逻辑较复杂的系统,我个人是没有什么经验的。大二进入老师的实验室后一直做的是外包项目,每一个外包项目都是全新的,都是自己去从头搭建,所以其中涉及到的代码我都非常熟悉。

因此刚来的时候,拿到这个项目的源代码,开始去了解熟悉,发现其中用到的语言版本、框架版本等都普遍偏低,非常不适应,心里也不免有不少抱怨(当我看完李笑来先生的《把时间当做朋友》后,深刻明白抱怨一点积极意义都没有,反而会打乱自己。现在遇事也会成熟不少,更多的是去积极的看待事物。改变自己挺难的,但改变自己很重要)。最初一段时间,很不情愿的去看这个项目的先辈们写下的对于现在来说不太优秀的代码,然后对于组长指派的迭代任务我也是依样画葫芦的在原有系统上增加修改。

觉得很多时候都在和老系统的代码作斗争,修 bug,加补丁,等等。

没有思考,当然也不会有沉淀,更别提进步。 我带着一种不屑、不愿的态度接手项目,在经过一段消极的工作之旅后,我通过阅读书籍带给我的思考对自己审视了一番。

其实对于工程师而言,一个很好的练习,就是试着重构一个有很多问题的老系统。老系统在很多时候,因为设计初期很多需求不一样,资源和约束也不一样,随着时间推移,才慢慢显得不足。而当你了解一个系统要做的东西,以及所有的缺陷和坑,试着去思考:如果是你来重新设计这个系统,你会怎么做?一定会有哪些选择?一定会避免哪些选择?
这样的练习,在脑海里一遍遍地过,即使你没有时间和精力去真的重构,也会对你能力的提高有很大的帮助。而有机会的时候,从一些小的地方着手,试图一点点地在力所能及的范围内改进系统。这比鄙视旧系统,然后不断写出更烂的代码、把系统变得更糟糕要好很多。

这段话来自微信公众号:嘀嗒嘀嗒,文章题为《为什么有的程序员觉得自己是个打杂的?

当时看到这标题的时候总感觉是写给我的,不过现在明白醒悟也不晚,幸运如我,不是吗 :)

唠嗑结束了,接下来是这次的正文。

需求场景

事情是这样的,上周五运营人员提了一个需求(仅为一个例子):希望在用户列表页面能对登录时间进行过滤排序,比如想要看到3月1日至3月2日登录过的用户,且根据这段时间内用户的登录时间进行降序排列。结合数据库的情况,这个需求要用到两张表(一个用户表:user,一个用户登录记录表:user_login_record)。

user

user_id nick
1 amy
2 bob

user_login_record

id user_id login_time
1 1 2017-03-01 00:00:00
2 1 2017-03-01 20:00:00
3 2 2017-03-01 05:00:00
4 2 2017-03-01 09:00:00
5 2 2017-03-01 16:00:00

心路历程

如果某个用户在过滤的时间段内登录过两次及以上,那么一定会遇到需要分组去重的问题。

版本1.0

执着于使用 DISTINCT 来完成去重的操作,但这样只能对某一列进行去重,且无法列出其他列,也就没法在一个 SQL 中再进行排序操作。

SELECT
    DISTINCT user_id
FROM
    user_login_record
WHERE
    login_time BETWEEN '2017-03-01' AND '2017-03-02'
GROUP BY
    user_id
版本2.0

经人提醒,可以使用 聚合函数 来达到去重的效果。

SELECT
    user_id, MAX(login_time) as max_login_time
FROM
    user_login_record
WHERE
    login_time BETWEEN '2017-03-01' AND '2017-03-02'
GROUP BY
    user_id
ORDER BY
    max_login_time DESC

这里拿到了已去重排好序的满足过滤条件的用户 ID 以及那个时间段内最近一次登录时间,接下来就是将 user 表中用户信息获取到即可

user_id max_login_time
1 2017-03-01 20:00:00
2 2017-03-01 16:00:00

可以通过后端脚本将 user_id 拼接起来作为查询条件

SELECT
    user_id,
    nick
FROM
    user
WHERE
    user_id IN (1, 2)
ORDER BY
    FIELD(user_id, 1, 2)
user_id nick
1 amy
2 bob

由于两个结果集的顺序是一样的,因此登录时间也是一一对应的,所以对其中一个结果集循环,对应另一个结果集中索引对应的值合并。

MySQL 知识点:FIELD

  • 作用:自定义排序

  • 格式

FIELD(value, str1, str2...)
  • 场景

由于 MySQL 并不会按 IN 中传入的参数顺序输出结果集,因此需要用到 FIELD 来自定义排序,只需传入的参数与 IN 中传入的参数完全相同即可

SQL 语句有多重要~

在减少数据库连接与减少数据库执行负担两者间进行权衡。运用得好可以提升性能,减少代码。总之,多思考。

晚安,各位,床上那位催着睡觉了。。。


首发于个人博客 StephenCode

同步在:

知乎专栏:黑白之间

简书专题:黑白之间

SegmentFault 专栏:黑白之间

微信公众号:黒白之间

wechat.jpg

推荐阅读更多精彩内容