假設存在下面二張表
t_media:
mediaid flags
11111 1<<17
22222 1<<18
33333 1<<17
44444 1<<2
t_media_locations:
mediaid locationid
11111 999999
22222 999999
33333 999999
現在有一個需求需要統計locationid=999999下面flags=1<<17和flag=1<<18的media數量,最初的丑陋實現如下:
SELECT a_tmp.locationid,a_tmp.attraction_count,r_tmp.restructant_count FROM ( SELECT COUNT(1)AS attraction_count, tmp.locationid FROM ( SELECT A . ID, b.locationid FROM t_media A, t_media_locations b WHERE A . ID = b.mediaid AND b.locationid IN(999999) AND A .flags & 134217728 > 0 GROUP BY A . ID, b.locationid )tmp GROUP BY tmp.locationid )AS a_tmp, ( SELECT COUNT(1)AS restructant_count, tmp.locationid FROM ( SELECT A . ID, b.locationid FROM t_media A, t_media_locations b WHERE A . ID = b.mediaid AND b.locationid IN(999999) AND A .flags & 268435456 > 0 GROUP BY A . ID, b.locationid )tmp GROUP BY tmp.locationid )AS r_tmp WHERE a_tmp.locationid = r_tmp.locationid
如此多的聯表,性能肯定很差,改進如下:
SELECT sum ( CASE WHEN (flags & 268435456 > 0) THEN 1 ELSE 0 END ),sum ( CASE WHEN (flags & 134217728 > 0) THEN 1 ELSE 0 END ),b.locationid FROM t_media a, t_media_locations b WHERE a.id= b.mediaid AND b.locationid IN(99999) and a.flags is not null group by b.locationid
小結:
1.如果不需要locationid,group by的操作也可以省略,注意,聚合函數本身針對多記錄操作並不一定是group by之后才使用,group by分組記錄也是多記錄。所以聚合函數使用的字段不需要出現在group by里。但是如果是直接select字段,postgres就需要group by該字段,有的db是不需要的比如sql lite.
2.postgres對於oracle中的decode函數的替代:CASE WHEN .....
3.flag這樣的type類型值在db中直接存儲為int,postgres的位與運算符&可以直接計算。
4.count(expr)如果expr是一個boolean值,count會被誤用。
錯誤的使用方式:
SELECT --錯誤的統計行數的方式 count((flags & 134217728 > 0) ) FROM t_media a, t_media_locations b WHERE a.id= b.mediaid AND b.locationid IN(999999)
正確的方式就是case when使用之后sum:
SELECT sum( CASE WHEN (flags & 134217728 > 0) THEN 1 ELSE 0 END ) FROM t_media a, t_media_locations b WHERE a.id= b.mediaid AND b.locationid IN(999999)