直接用一个例子来解释吧,我们要取账户表中取最新余额,也就是取user_acct中每个user_id的pt_d最大的那条记录的acct_bal
表结构和数据如下
mysql> select * from user_acct;
+---------+----------+----------+| user_id | acct_bal | pt_d |
+---------+----------+----------+
| A | 3000 | 20180101 |
| A | 900 | 20180102 |
| A | 2000 | 20180103 |
| B | 5000 | 20180102 |
| B | 10000 | 20180106 |
| B | 9000 | 20180107 |
+---------+----------+----------+
通常的做法是先写一个子查询取出每个user_id的最大pt_d,然后用user_acct表关联这个子查询得出结果,这种sql比较简单,下面是一个例子:
select a.*
from user_acct a
inner join (select user_id
,max(pt_d) as pt_d
from user_acct
group by user_id
) b
on a.user_id = b.user_id
and a.pt_d = b.pt_d
;
但是,如果不用子查询能不能做出来呢?答案是能,目前看来至少有以下两种
1.用group by中的having子句
直接上语句吧:
select a.user_id
,a.acct_bal
,a.pt_d
from user_acct a
inner join user_acct b
on a.user_id = b.user_id
group by a.pt_d
having a.pt_d = max(b.pt_d)
;
这种写法的优点是和子查询逻辑完全一致,比较通用,缺点是需要join一次,如果数据量大的话,开销比较大
2.通过拼接、比较、截取
还是直接上语句吧:
select a.user_id
,split(max(concat(pt_d,'\001',acct_bal)),'\001')[1] as acct_bal
,max(a.pt_d) as pt_d
from user_acct a
group by user_id
;
其中\001是不可见字符,且ascii码较小。
这种写法的优点是没有join,开销较小,缺点很明显,就是split函数在mysql和oracle中都不支持,需要自己创建,只有在hive中原生支持,另外还有两点需要注意:
1.分隔符\001在其他数据库也不一定支持
2.如果拼接的字段中有数字类型时,排序可能不对,需要用一些变通的办法如lpad填充使其排序正确。
总的来说方法1是值得推荐的,方法2的局限性很大,但是贵在提供了另外一种思考问题的方式。