1. 程式人生 > >SQL語句不通過子查詢取某欄位最大的那一條記錄

SQL語句不通過子查詢取某欄位最大的那一條記錄

直接用一個例子來解釋吧,我們要取賬戶表中取最新餘額,也就是取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的侷限性很大,但是貴在提供了另外一種思考問題的方式。