Sybase怎麼將多行數據分組合併成一行多列

背景

題目比較抽象,具體解釋一下。
有這麼一張表,裏面是多位客戶在不同時間的不同狀態。例如:

客戶 時間 狀態
小老鼠 20200428 高興
小八戒 20200429 開心
小笨喵 20200501 悲傷
小老鼠 20200502 難受
小老鼠 20200503 相思
小八戒 20200504 懷舊
小笨喵 20200505 頭大

這裏多行數據比較混亂,想將多行數據按照標識分組,再改爲一行多列:

客戶 狀態
小老鼠 20200428:高興 20200502 難受 20200503:相思
小八戒 20200429:開心 20200504:懷舊
小笨喵 20200501:悲傷 20200505:頭大

但是生產上往往不太需要這樣的彙總,更多時候是希望彙總最近一次狀態和上一次狀態,如圖:

客戶 最新狀態 上次狀態
小老鼠 相思 難受
小八戒 懷舊 開心
小笨喵 頭大 悲傷

那麼具體要怎麼實現呢?

心路歷程

項目上線以後突然倍感壓力,這幾天休息的很差。然而項目上線並不算完,後續還需要對千萬數據提數取數。
由於當初設計的時候是面向頁面設計的,爲客戶crud方便考慮。但是後續領導告訴我還需要從數據庫中提數生成報表。這一下給我弄得手忙腳亂。
一個java開發工作,上需要改前端頁面,下需要搞數據提取,對於工作不足一年的我,真的有些難以接受。
抱怨許久,感慨萬分。

解決方案

本來以爲就是group_concat的事,沒想到,sybase竟然不支持。只得手寫存儲過程。

步驟

  1. 建表造數
    首先爲了演示這個效果,我們根據以下語句建造數據來模擬這個過程。效果如圖所示:
    建表
    代碼如下:
CREATE TABLE dbo.FRIEND_LOG
	(
	ID_          NUMERIC (19) NOT NULL,
	FRIEND_NAME_ VARCHAR (32) NOT NULL,
	DATE_        VARCHAR (8) NULL,
	STATE_       VARCHAR (4) NULL,
	CONSTRAINT PK_FRIEND_LOG PRIMARY KEY (ID_)
	)
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (1, '小老鼠', '20200428', '高興')
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (2, '小八戒', '20200429', '開心')
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (3, '小笨喵', '20200501', '悲傷')
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (4, '小老鼠', '20200502', '難受')
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (5, '小老鼠', '20200503', '相思')
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (6, '小八戒', '20200504', '懷舊')
GO

INSERT INTO dbo.FRIEND_LOG (ID_, FRIEND_NAME_, DATE_, STATE_)
VALUES (7, '小笨喵', '20200505', '頭大')
GO
  1. 將數據傳到臨時表
    爲了不破壞上表的結構以及數據的完整性,我們需要將FRIEND_LOG表中需要的內容傳至臨時表中。在創建臨時表前要先判斷是否已經存在相同名字的臨時表,若存在刪除即可。然後臨時表的結構分別爲角色名、狀態、所有狀態、狀態次數。
    首先,角色名、狀態是FRIEND_LOG表中字段。
    ALL_STATE_字段的存在是因爲我們需要把多行狀態遷移到一行中,故我們需要一個字段來存儲這些狀態。
    而TIMES字段的設計目的則是爲了記錄各個角色的狀態數量,具體功能下面會詳解。
    當執行完這些語句後,效果如圖:
    臨時表
    代碼如下:
IF OBJECT_ID('#TEMP1') IS NOT NULL
      drop table #TEMP1
 
GO


SELECT  
FRIEND_NAME_,
STATE_,
space(40) AS ALL_STATE_,
0 as TIMES 
INTO #TEMP1 FROM FRIEND_LOG ORDER BY FRIEND_NAME_ , DATE_  DESC 

  1. 使用計數法插入
    不要被標題嚇跑。所謂計數法,也不過是十以內數字加減法。
    具體什麼原理?
    其實很簡單,無非是一條一條遍歷數據,如果是第一次遇到這個角色,就直接將狀態寫入ALL_STATE_,TIMES記爲1,count也記爲1。如果是第n次,則一直將狀態追加至ALL_STATE_,count也依次累加,而TIMES則爲count+1。
    我們用效果圖來解釋一下:在這裏插入圖片描述
    可以看到,第一次遍歷小八戒,ALL_STATE_寫入了“懷舊”狀態,而TIMES爲1,此時count也爲1。然後我們第二次記錄小八戒,此時狀態追加了“開心”,TIMES爲2,count也爲2。後面皆以此類推即可。
    代碼如下:
declare @state varchar(400)
declare @id VARCHAR(32)
declare @count int
set @state=''
set @count=0  
update #TEMP1 
set ALL_STATE_=(case when @id =FRIEND_NAME_ then @state||STATE_ else STATE_ end) 
	,@state=(case when @id =FRIEND_NAME_ then @state||STATE_ else STATE_ end)
    ,TIMES=(case when @id =FRIEND_NAME_ then @count+1 else 1 end)
    ,@count=(case when @id =FRIEND_NAME_ then @count+1 else 1 end)
    ,@id =FRIEND_NAME_
  1. 分列查看
    最後一步了,我們對臨時表的ALL_STATE_列進行分列查看皆可。通過substring函數,將其分到其他列。另外“(select FRIEND_NAME_, (case when max(TIMES) > 2 then 2 else max(TIMES) end)”語句是用取狀態次數爲2的,即最近兩次狀態,效果如圖:有限制效果圖
    此時代碼如下:
select t.FRIEND_NAME_,substring(t.ALL_STATE_,1,2) state1,substring(t.ALL_STATE_,3,2) state2,,TIMES
    from #TEMP1 t inner join (select FRIEND_NAME_, (case when max(TIMES) > 2 then 2 else max(TIMES) end) as tl from #TEMP1 group by FRIEND_NAME_) c  
        on t.FRIEND_NAME_=c.FRIEND_NAME_ and t.TIMES=c.tl

另外,我們可以修改代碼來取消限制,但是一定要確保正確分列,效果如圖:
無限制代碼如下:

select t.FRIEND_NAME_,substring(t.ALL_STATE_,1,2) state1,substring(t.ALL_STATE_,3,2) state2,substring(t.ALL_STATE_,5,2) state3,TIMES
    from #TEMP1 t inner join (select FRIEND_NAME_,  max(TIMES) as tl from #TEMP1 group by FRIEND_NAME_) c  
        on t.FRIEND_NAME_=c.FRIEND_NAME_ and t.TIMES=c.tl

附錄

將上述源碼粘貼至同一sql文件即可測試運行,另外,如果土豪,也可以直接下載附件~
sql文件

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章