1. 程式人生 > >【資料庫】關於日期的處理(以greenplum和postgresql和hive為例)

【資料庫】關於日期的處理(以greenplum和postgresql和hive為例)

那個啥,本人菜鳥一隻,如果有什麼說錯的地方還請大家批評指出!!

好,開始說正事,日期處理和判斷是十分常見的,本文就自己使用的資料庫,和hive資料倉庫來說說,我使用到的一些日期的處理和判斷,當然技術能力有限,我也很菜,所以如果有說錯或者遺漏的還請大家多多包涵,望能批評指出,也讓我的水平提高提高!

一、日期型別的選擇與使用

hive:

那我先說hive的,hive很簡單,直接用String(hive有這個資料型別)存日期,一般格式是這種——"20180917",然後使用的時候,直接:

--第一種:(推薦),加不加引號沒什麼區別,但是最好加下,因為欄位型別是string
select 欄位1,欄位2 
from 資料庫.表 
where date_id <= '20180901' and date_id <='20180917'

--第二種:(用between日期要最好加引號,曾經遇到過不加引號報錯,然後時間別寫反了,寫反可能就沒有日期),其他就沒什麼要注意的了,hive的比較隨意
select 欄位1,欄位2 
from 資料庫.表 
where date_id between '20180901' and '20180917'

pg和gp資料庫:

資料庫存日期,我用過三種類型來存時間的值,int,varchar,timestamp,下面我會來說明這三種資料型別的使用區別。

1、int型別

int型別很簡單(我最推薦這種),只需要:

select 欄位1,欄位2 
from 資料庫.表 
where date_id <= 20180901 and date_id <= 20180917

但是也有缺陷,因為這樣存的話,理論上我們只會存到具體的某一天,對於日更的資料無所謂,但是有些資料我們可能要記錄一些準確的時間,例如,年月日時分秒都要記錄,那麼int好像就不太符合我們的需求,因此就需要用另外兩種資料型別。

2、varchar型別

varchar型別,理論可以存任何東西啊,各種字串只要長度夠,怎麼存都可以,理論上來說就可以存如下的型別:

2018-08-03 15:44:56.523791,但是我們要關注的是如何取資料!

--使用between或者>(大於號)和<(小於號)來限定where條件都可以,日期必須用引號引起,否則會報錯
--ERROR:  operator does not exist: character varying >= integer
SELECT 欄位1,欄位2 
FROM 資料庫.表名 
WHERE create_time BETWEEN '20180901' and '20180902'
--
WHERE create_time > '20180911' and create_time < '20180913' 

--如果想要檢視比如某一天有多少資料量,就需要group by 日期,因此可能就需要group by日期
--完整日期欄位1格式:2018-08-03 15:44:56.523791
--sql結果如下圖
SELECT substr(create_time, 1, 10),count(1) 
FROM 資料庫.表名 
GROUP BY substr(create_time, 1, 10) 
ORDER BY substr(create_time, 1, 10) desc

3、timestamp

其實timestamp才是資料庫中正規的日期型別呀,他的範圍判斷就很簡單了

但是這裡一定要注意:字串上的日期一定要是存在的日期,比如,'20180932'這種日期是不存在的,因此,如果timestamp來where這個日期的話,是會直接報錯的(varchar型別不會報錯),報錯請看程式碼塊

--時間必須用引號引起來否則會報錯,然後也是大於小於號可以,between也可以
--> ERROR:  operator does not exist: timestamp without time zone >= integer
SELECT * FROM 資料庫.表名
WHERE crawl_time > '20180912' and crawl_time < '20180913'
--WHERE crawl_time between '20180912' and '20180913'


--報錯:
> ERROR:  date/time field value out of range: "20180932"
  LINE 1: ...nt WHERE crawl_time > '20180912' and crawl_time < '20180932'...
                                                               ^
  HINT:  Perhaps you need a different "datestyle" setting.

型別,大體上就介紹到這裡,如果有遺漏歡迎提醒補充!

二、日期型別的轉換

關鍵是很多時候,一些sql並不是手寫的,舉個例子,我現在傳入一個日期,並且需要獲取一個7天前的日期,問題來了,怎麼轉換?

先提一句,在gp中轉換,直接通過兩個冒號就可以轉換,或者也可以嘗試cast('20180801' as timestamp) ,通過函式來做轉換

1、欄位是int型別儲存的

int轉ts是最複雜的,我來解釋下,首先int想直接轉ts是不行的

SELECT 20180901::TIMESTAMP會直接報錯

> ERROR:  cannot cast type integer to timestamp without time zone

 LINE 1: SELECT 20180901::TIMESTAMP

--第一步,我們需要先通過to_date這個函式,將int轉換成日期型別,如下:
select to_date('20180901','yyyymmdd')

--第二步,要獲取7天前的日期
SELECT to_date('20180901','yyyymmdd') - interval '7 day'
--獲取一個月前的日期也行
SELECT to_date('201809','yyyymm') - interval '1 month'

--第三步,再把ts轉成int,但是ts又不能直接轉int,所以要先通過to_char這個函式轉成字串型別
select to_char((to_date('20180901','yyyymmdd')+ interval '2 day'),'yyyymmdd')

--第四步,再通過::int,直接將字串轉換成int型別
select to_char((to_date('20180901','yyyymmdd')+ interval '2 day'),'yyyymmdd')::int

--ok,確實是很麻煩,所以完整的sql如下

SELECT * 
FROM 資料庫.表名 
WHERE date_id >= 20180901 and 
date_id <=  to_char((20180901:TIMESTAMP + interval '1 day'),'yyyymmdd')::int

2、欄位是varchar型別儲存的

--其實,只要掌握最麻煩的int,varchar原理其實是一樣的
--用的時候,注意下等於號是否要加,int型別不加等於號大於0912,小於0913,就會沒有資料,但是字串是有資料的(我就不解釋為什麼了,這個其實能想明白)
SELECT * 
FROM 資料庫.表名 
WHERE create_time > '20180912' and 
create_time < to_char(to_date('20180912', 'yyyymmdd') + interval '1 day','yyyymmdd')

3、欄位是timestamp型別儲存的

--這個就更簡單了!
--比如要獲取9月12號的資料,sql如下
SELECT * 
FROM 資料庫.表名 
WHERE crawl_time > '20180912' and 
crawl_time < to_date('20180912', 'yyyymmdd') + interval '1 day'

4、date型別轉換成int型別

--思路就是先將date型別轉換成字串型別,然後再轉換成int型別
SELECT cast(to_char(pt_days,'YYYYMMDD')as INTEGER) as date_id FROM 表 limit 1;

5、timestamp轉換成其他型別

--TO_TIMESTAMP傳入int型別的ts時間,可以返回timestamptz型別:2017-12-06 08:17:10+08
--例如:
select TO_TIMESTAMP(1512519430)

--EXTRACT這個函式是抽取ts中的具體的部分
--extract (field from source)
--century(世紀),decade(得到年份除10的值),millennium(得到第幾個千年,0-1000第一個,1001-2000第二個,2001-3000第三個),year(年),quarter(季度),month(月份),week(返回當前是幾年的第幾個周),dow(返回當前日期是周幾,週日:0,週一:1,週二:2,...),day(本月的第幾天),doy(本年的第幾天),hour(小時),min(得到時間中的分鐘),sec(返回時間中的秒)
--例如:
SELECT EXTRACT(min from cast('20181206' as TIMESTAMP))
SELECT EXTRACT(quarter from now())



三、舉幾個例子

ok,要說的概念差不多都說完了!接下來講幾個例子:

--注:gmt_modified 這個欄位是 default now()
--求5天前到當前時間的資料
select * 
from 庫名.表明 
WHERE gmt_modified >=  now() - interval '5 D'
 
--第二段是這樣的,表A和表C中對應天的資料計算結束之後,並且確定表B中沒有計算當天的資料之後,就返回要計算的日期,主要想說的是可以通過這種方式給定日期的範圍to_char((SELECT now() - interval '5 D'),'yyyyMMdd') ,date_id是int型別
 
select *from (
select rc.date_id::VARCHAR as date_id,'${date_type}' as date_type  from ( 
select  date_id,status_flag from 表A where date_id>='${date_id}' and status_flag='Completed' ORDER BY  date_id  desc 
limit 9999 ) rc 
where  not  EXISTS (select date_id from 表B b where rc.date_id=b.date_id and rc.status_flag=b.status_flag) 
and  EXISTS (select date_id from  表C b where rc.date_id=b.date_id) 
 ) tb
where tb.date_id <=(to_char((SELECT now() - interval '5 D'),'yyyyMMdd'))
ORDER BY date_id;
 
--第三段是求資料庫中的最新日期的前幾天
select count(1),gmt_modified,next_gmt_modified 
from ( 
select to_char(freshtime,'yyyyMMdd') as gmt_modified,(to_char(((select max(freshtime) from 表A)  - interval '3 D'),'yyyyMMdd')) as next_gmt_modified  from 表A   
where freshtime is not null 
) tb 
where gmt_modified >=  next_gmt_modified
GROUP BY gmt_modified,next_gmt_modified ORDER BY 2 desc;
 
--但是第三段有個缺點,缺點是gmt_modified這個欄位是更新資料的時間,一般來說不會做索引什麼的,如果要求這個欄位的最大值,很有可能查詢速度奇慢無比,因此就需要用到其他方式來解決這個問題
--例如,如果該表有自增的主鍵id,那麼我只要拿到id最大的那條資料的更新時間,就是最新的時間,而且實際執行,第三段sql在資料多的情況下,需要查詢半小時。。。但是如下sql只需要不到1秒鐘!!
select * from 表A where
freshtime >= 
(
select next_gmt_modified::TIMESTAMP from ( 
select to_char(freshtime,'yyyyMMdd'),to_char((freshtime  - interval '3 D'),'yyyyMMdd') as next_gmt_modified from 表A where 主鍵 in (select max(主鍵)from  表A )

嗯,大體上就這樣吧,如果大家對於時間的處理有什麼更好的方式,也可以給我留言!相互學習。

 

好了,本人菜雞一個,如果有說的不對的地方,還望各位大神指點迷津!!