【问题标题】:Turn one column into multiple based on index ranges根据索引范围将一列变为多列
【发布时间】:2021-03-28 21:54:16
【问题描述】:

我在 SQL Server 中有下表:

| idx | value |
| --- | ----- |
| 1   | N     |
| 2   | C     |
| 3   | C     |
| 4   | P     |
| 5   | N     |
| 6   | N     |
| 7   | C     |
| 8   | N     |
| 9   | P     |

我想把它变成这样:

| idx 1-3 | idx 4-6 | idx 7-9 |
| ------- | ------- | ------- |
| N       | P       | C       |
| C       | N       | N       |
| C       | N       | P       |

我该怎么做?

【问题讨论】:

    标签: sql sql-server tsql pivot lateral-join


    【解决方案1】:

    如果您想将数据分成三列,数据按 id 排序 - 并假设 id 从 1 开始并且没有间隙 - 那么在您的特定数据上,您可以使用:

    select max(case when (idx - 1) / 3 = 0 then value end) as grp_1,
           max(case when (idx - 1) / 3 = 1 then value end) as grp_2,
           max(case when (idx - 1) / 3 = 2 then value end) as grp_3
    from t
    group by idx % 3
    order by min(idx);
    

    上面没有硬编码范围,但是“3”在不同的上下文中表示不同的东西——有时是列数,有时是结果集中的行数。

    但是,以下是概括的,因此它会根据需要添加额外的行:

    select max(case when (idx - 1) / num_rows = 0 then idx end) as grp_1,
           max(case when (idx - 1) / num_rows = 1 then idx end) as grp_2,
           max(case when (idx - 1) / num_rows = 2 then idx end) as grp_3
    from (select t.*, convert(int, ceiling(count(*) over () / 3.0)) as num_rows
          from t
         ) t
    group by idx % num_rows
    order by min(idx);
    

    Here 是一个 dbfiddle。

    【讨论】:

      【解决方案2】:

      您可以使用横向连接计算每行的类别,然后枚举每个类别中的行,最后使用条件聚合进行透视:

      select 
          max(case when cat = 'idx_1_3' then value end) as idx_1_3,
          max(case when cat = 'idx_4_6' then value end) as idx_4_6,
          max(case when cat = 'idx_7_9' then value end) as idx_7_9
      from (
          select t.*, row_number() over(partition by v.cat) as rn
          from mytable t
          cross apply (values (
              case 
                  when idx between 1 and 3 then 'idx_1_3'
                  when idx between 4 and 6 then 'idx_4_6'
                  when idx between 7 and 9 then 'idx_7_9'
              end
          )) v(cat)
      ) t
      group by rn
      

      【讨论】:

        【解决方案3】:

        union all 运算符和 row_number 函数的另一种解决方案

        select max(IDX_1_3) as IDX_1_3,  max(IDX_4_6) as IDX_4_6,  max(IDX_1_3) as IDX_1_3
        from (
        select 
        case when idx in (1, 2, 3) then value end as idx_1_3
        , null as idx_4_6
        , null as idx_7_9
        , row_number()over(order by idx) as rnb 
        from Your_table where idx in (1, 2, 3) 
        union all
        select null  as idx_1_3
        , case when idx in (4, 5, 6) then value end as idx_4_6
        , null  as idx_7_9
        , row_number()over(order by idx) as rnb
        from Your_table where idx in (4, 5, 6) 
        union all
        select null as idx_1_3
        , null as idx_4_6
        , case when idx in (7, 8, 9) then value end as idx_7_9
        , row_number()over(order by idx) as rnb 
        from Your_table where idx in (7, 8, 9)
        ) t
        group by rnb
        ;
        

        【讨论】:

          【解决方案4】:
          drop table if exists #t;
          
          create table #t (id int identity(1,1) primary key clustered, val varchar(20));
          
          insert into #t(val)
          select top (2002) concat(row_number() over(order by @@spid), ' - ', char(65 + abs(checksum(newid()))%26))
          from sys.all_objects
          order by row_number() over(order by @@spid);
          
          
          select p.r, 1+(p.r-1)/3 grp3id, p.[1] as [idx 1-3], p.[2] as [idx 4-6], p.[3] as [idx 7-9]
          from
          (
              select 
                  val, 
                  1+((1+(id-1)/3)-1)%3 as c3,
                  row_number() over(partition by 1+((1+(id-1)/3)-1)%3 order by id) as r
              from #t
          ) as src
          pivot
          (
          max(val) for c3 in ([1], [2], [3])
          ) as p
          order by p.r;
          

          【讨论】:

            【解决方案5】:

            您可以按如下方式使用该模组:

            select max(case when idx between 1 and 3 then value end) as idx_1_3,
                   max(case when idx between 4 and 6 then value end) as idx_4_6,
                   max(case when idx between 7 and 9 then value end) as idx_7_9
              from t
            group by (idx-1) % 3;
            

            如果您的 idx 不是连续数字,则使用以下代码代替 from t

            from (select value, row_number() over(order by idx) as idx
               from your_table t) t
            

            【讨论】:

              猜你喜欢
              • 2013-09-05
              • 2017-01-28
              • 1970-01-01
              • 2023-04-08
              • 1970-01-01
              • 1970-01-01
              • 2020-03-20
              • 1970-01-01
              • 2021-05-13
              相关资源
              最近更新 更多