zoukankan      html  css  js  c++  java
  • 深入行列转换----多行转多列,多行的计算

    碰到朋友一个问题,基于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嘛,把其转成负一就好
     SELECT  门店,支付方式,
          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

      结果:

      

    相关参考文章:

    动态行转列

    基本行列转换

  • 相关阅读:
    POJ
    CodeForces
    部分和问题
    NOIP200502校门外的树
    消灭虫子
    抓牛
    最长不下降子序列的长度
    HDNoip201501计算结果最小
    抽签
    Ants
  • 原文地址:https://www.cnblogs.com/gered/p/9271581.html
Copyright © 2011-2022 走看看