碰到朋友一個問題,基於Oracle環境,有點復雜,直接看代碼。
我的測試環境是sql server 2014
create table test101( [門店] int ,[繳費大類] int ,[支付方式] int ,[付款] int, [手續費] int ) insert into test101 values (1,0,1,10,2), (1,0,2,10,2), (1,0,3,10,2), (1,0,4,10,2), (1,1,1,10,2), (1,1,2,10,2), (1,1,3,10,2), (1,1,4,10,2), (1,2,1,10,2), (1,2,2,10,2), (1,2,3,10,2), (1,2,4,10,2), (1,3,1,10,2), (1,3,2,10,2), (1,3,3,10,2), (1,3,4,10,2)
數據如下:
然后是要求出掙得錢和繳多少稅。
消費大類:3為退款,0~2為入賬大類。
現在要計算每一種支付方式,掙了多少錢,繳了多少稅。
邏輯算法:
掙錢:當支付方式為1時:累加支付方式0~2的付款數,減去消費大類為3(退款)的付款數,即為支付方式1所掙的錢。
手續費:當支付方式為1時:累加支付方式0~2的手續費,減去消費大類為3(退款)的手續費,即為支付方式1所需要承擔的手續費。

掙錢:這里計算一下是10+10+10-10;
手續費:這里計算一下是2+2+2-2;
以此類推,算出每個門店下所有支付方式對應掙得錢和所需手續費。
方法:1(我朋友的方法,對此我是無比佩服),在實際業務中,肯定會有多條
[門店],[支付方式],[繳費大類]相同的,而付款和手續費不同的數據,所以實際業務中應當用sum代替max,這里只是測試就用Max了。
select [門店],[支付方式], max(case when [繳費大類]=0 then isnull([付款],0) else 0 end) + max(case when [繳費大類]=1 then isnull([付款],0) else 0 end) + max(case when [繳費大類]=2 then isnull([付款],0) else 0 end) - max(case when [繳費大類]=3 then isnull([付款],0) else 0 end) as [付款], max(case when [繳費大類]=0 then isnull([手續費],0) else 0 end) + max(case when [繳費大類]=1 then isnull([手續費],0) else 0 end) + max(case when [繳費大類]=2 then isnull([手續費],0) else 0 end) - max(case when [繳費大類]=3 then isnull([手續費],0) else 0 end) as [付款] from test101 t group by [門店],[支付方式] order by [門店]
看結果:(因為值都是一樣,所以每個支付方式都是一樣)
(1)原理剖析:通過門店與支付方式為分組,對支付方式進行行轉列,並帶着對應的付款數與手續費
select [門店],[支付方式], max(case when [繳費大類]=0 then [付款] end) as '0', max(case when [繳費大類]=1 then [付款] end) as '1', max(case when [繳費大類]=2 then [付款] end) as '2', max(case when [繳費大類]=3 then [付款] end) as '3', max(case when [繳費大類]=0 then [手續費] end) as '4', max(case when [繳費大類]=1 then [手續費] end) as '5', max(case when [繳費大類]=2 then [手續費] end) as '6', max(case when [繳費大類]=3 then [手續費] end) as '7' from test101 t group by [門店],[支付方式] order by [門店]
可是這不就是sum嘛,把其轉成負一就好
SUM(付款 * case when [繳費大類] = '3' then -1 else 1 end ) as 付款,
SUM(手續費 * case when [繳費大類] = '3' then -1 else 1 end ) as 手續費
FROM 表 group by 門店,支付方式
(2)由此可見,這里我們類似於下面這類形式的
max(case when [繳費大類]=0 then [付款] end)
其實是獲取,每個門店、每個消費方式對應繳費大類的值。
所以,我們可以直接通過下面這類值,來獲取當個【門店】下當個【支付方式】對應【繳費大類】下的【付款】與【手續費】
max(case when [繳費大類]=0 then isnull([付款],0) else 0 end) + max(case when [繳費大類]=1 then isnull([付款],0) else 0 end) + max(case when [繳費大類]=2 then isnull([付款],0) else 0 end) - max(case when [繳費大類]=3 then isnull([付款],0) else 0 end)
然后用此表達式就實現了我們的邏輯算法。
掙錢:當支付方式為1時:累加支付方式0~2的付款數,減去消費大類為3(退款)的付款數,即為支付方式1所掙的錢。
手續費:當支付方式為1時:累加支付方式0~2的手續費,減去消費大類為3(退款)的手續費,即為支付方式1所需要承擔的手續費。
當然,也可以去用我們傳統的方法:

(3)這里問題也來了,如果支付方式過多,1個門店會有很多行,這不利於我們查看,這里還需要再行轉列一下,把支付方式變成列,付款和手續費變成對應轉換后的列值
;with test1 as ( select [門店],[支付方式], max(case when [繳費大類]=0 then [付款] end) as '0', max(case when [繳費大類]=1 then [付款] end) as '1', max(case when [繳費大類]=2 then [付款] end) as '2', max(case when [繳費大類]=3 then [付款] end) as '3', max(case when [繳費大類]=0 then [手續費] end) as '4', max(case when [繳費大類]=1 then [手續費] end) as '5', max(case when [繳費大類]=2 then [手續費] end) as '6', max(case when [繳費大類]=3 then [手續費] end) as '7' from test101 t group by [門店],[支付方式] ) , test2 as ( select [門店],[支付方式],[0]+[1]+[2]-[3] as [付款],[4]+[5]+[6]-[7] as [手續費] from test1 ), test3 as ( select [門店],[1],[2],[3],[4] , '付款' as [tpye] from (select [門店],[支付方式],[付款] from test2) t pivot ( max([付款]) for [支付方式] in ([1] ,[2],[3],[4]) ) t1 union all select [門店],[1],[2],[3],[4],'手續費' as [tpye] from (select [門店],[支付方式],[手續費] from test2) t pivot ( max([手續費]) for [支付方式] in ([1],[2],[3],[4]) ) t1 ) select * from test3
test2數據:就是(2)中的圖
test3查詢完結果如圖:
很明顯,我這個方法效率很低,要多次查詢表,會造成太多額外的開銷。如果有多個需要這樣展示的選項,那開銷將大一點,除非數據量特別小!不過,cte會把數據緩存在內存中,邏輯讀還是比較快的,在數據量比較小的情況下不影響!如果數據量比較大,可以用全局臨時表(避免需要重復創建)+給其建立索引,來優化,這樣就會快一些了。
而,我的朋友是這么寫的
with test1 as (select [門店],[支付方式], max(case when [繳費大類]=0 then isnull([付款],0) else 0 end) + max(case when [繳費大類]=1 then isnull([付款],0) else 0 end) + max(case when [繳費大類]=2 then isnull([付款],0) else 0 end) - max(case when [繳費大類]=3 then isnull([付款],0) else 0 end) as [付款], max(case when [繳費大類]=0 then isnull([手續費],0) else 0 end) + max(case when [繳費大類]=1 then isnull([手續費],0) else 0 end) + max(case when [繳費大類]=2 then isnull([手續費],0) else 0 end) - max(case when [繳費大類]=3 then isnull([手續費],0) else 0 end) as [手續費] from test101 t group by [門店],[支付方式] ) select test1.[門店] , max(case when test1.[支付方式] = '1' then [付款] end ) as [付款_1], max(case when test1.[支付方式] = '1' then [手續費] end ) as [手續費_1], max(case when test1.[支付方式] = '2' then [付款] end ) as [付款_2], max(case when test1.[支付方式] = '2' then [手續費] end ) as [手續費_2], max(case when test1.[支付方式] = '3' then [付款] end ) as [付款_3], max(case when test1.[支付方式] = '3' then [手續費] end ) as [手續費_3] , max(case when test1.[支付方式] = '4' then [付款] end ) as [付款_4], max(case when test1.[支付方式] = '4' then [手續費] end ) as [手續費_4] from test1 group by test1.[門店]
這里的case when 都可以用decode來代替,顯得代碼少一點點。
結果:高下立判,他的方法高明的多,但這種情況,支付方式多了,或者顯示的數據多了(這里只有付款數和手續費),那列的字段數量也將是災難級的。看個人習慣喜歡看哪種方式展示數據。
或許是我還不太會用pivot,總覺得今天case when給了我很大震撼。。以前也不知道還能這么用,學習了,感謝小余同學的提問,互相學習共勉。
今天心里還在念叨這個事,於是又想了很多,去請教了一個人,終於用pivot unoivot解決了
;with t2 as ( SELECT 門店,支付方式, SUM(付款 * case when [繳費大類] = '3' then -1 else 1 end ) as 付款, SUM(手續費 * case when [繳費大類] = '3' then -1 else 1 end ) as 手續費 FROM test101 group by 門店,支付方式 ) ----***第二步將加工過的數據 行轉列 select * from
( ---****先將第一步匯總的數據源列轉行 select * from (SELECT [門店] ,[支付方式] ,[付款] ,[手續費] from t2 ) p UNPIVOT ( [Money] FOR PayClass IN (付款, 手續費) ) m
) p ----***列轉行結束 PIVOT ( SUM([Money]) FOR [支付方式] IN ( [1],[2],[3],[4] ) ) as pvt ---***行專列結束
結果如下:
多行轉列(2):~
另一種模式,我想這樣轉換
CREATE TABLE #T ( id UNIQUEIDENTIFIER, A INT, B INT ) INSERT INTO #T( id, A, B)
VALUES('EF28B498-A186-4B9B-AE38-3A2629F18377',1,2),
('EF28B498-A186-4B9B-AE38-3A2629F18377',3,4),
('EF28B498-A186-4B9B-AE38-3A2629F18377',5,6),
('D22B8E18-B69D-4763-9C1E-E28BBBE15369',7,8) SELECT * FROM #T SELECT id, SUM(CASE WHEN x=1 THEN A ELSE NULL END) AS A1, SUM(CASE WHEN x=1 THEN B ELSE NULL END) AS B1, SUM(CASE WHEN x=2 THEN A ELSE NULL END) AS A2, SUM(CASE WHEN x=2 THEN B ELSE NULL END) AS B2, SUM(CASE WHEN x=3 THEN A ELSE NULL END) AS A3, SUM(CASE WHEN x=3 THEN B ELSE NULL END) AS B3 FROM ( SELECT *,ROW_NUMBER() OVER(PARTITION BY id ORDER BY id) AS x FROM #T ) A GROUP BY id
這樣是固定的,其實是有問題的(比如我的X在一個大范圍內比如1~10000都有可能,不能1+1一步一步判斷吧),那么如果我要動態呢
DECLARE @M INT SELECT @M=MAX(A) FROM ( SELECT COUNT(*) A FROM #T GROUP BY id )A DECLARE @W VARCHAR(max)='' DECLARE @SQL VARCHAR(max)='' SELECT @W=@W+',SUM(CASE WHEN x='+CONVERT(VARCHAR(10),number)+' THEN A ELSE NULL END) AS A1'+',SUM(CASE WHEN x='+CONVERT(VARCHAR(10),number)+' THEN B ELSE NULL END) AS B1' FROM master..spt_values WHERE type='P' AND number>0 AND number<=@M SET @SQL='SELECT id'+@W+' FROM ( SELECT *,ROW_NUMBER() OVER(PARTITION BY id ORDER BY id) AS x FROM #T ) A GROUP BY id ' EXEC (@SQL)
感謝sql server技術群--東莞-小小 提供的代碼 以及其支持與幫助。
善用笛卡爾積
需求:

declare @temp table(a varchar(50), b varchar(16), c int); insert into @temp values ('2', '201705', 50), ('2', '201708', 200), ('23', '201708', 1), ('23', '201705', 2); select * from @temp as a left join @temp as b on(b.a=a.a) where a.b='201705' and b.b='201708'; select * from @temp pivot(sum(c) for b in([201705],[201708])) as x
結果:

相關參考文章:




