简体   繁体   English

SQL Server将多行合并为具有多列的一行

[英]SQL Server Combine multiple rows to one row with multiple columns

I have a database in which I have the following rows: 我有一个数据库,其中包含以下行:

  ID  |  Date start  |  Date end
----------------------------------
  a   |  01-01-1950  |  30-01-1951
  a   |  01-01-1948  |  31-12-1949
  a   |  31-01-1951  |  01-06-2000
  b   |  01-01-1980  |  01-08-2010
  c   |  01-01-1990  |  31-12-2017
  c   |  31-01-1985  |  31-12-1989

What I got 我得到了什么

  • Multiple rows per person 每人多排
  • One start and end date per row 每行一个开始和结束日期
  • In a not chronological order 按时间顺序


Select query which I want to return the following: 选择要返回以下内容的查询:

  ID  |  Date start 1 |  Date end 1  |  Date start 2 |  Date end 2  |  Date start 3 |  Date end 3
---------------------------------------------------------------------------------------------------
  a   |  01-01-1948   |  31-12-1949  |  01-01-1950   |  30-01-1951  |  31-01-1951   |  01-06-2000
  b   |  01-01-1980   |  01-08-2010
  c   |  31-01-1985   |  31-12-1989  |  01-01-1990   |  31-12-2017

What I want: 我想要的是:

  • One row per person 每人一行
  • Multiple start and end dates per row 每行有多个开始和结束日期
  • In a chronological order 按时间顺序


Most things I was able to find wanted it in the same column, or wouldn't want it sorted on chronological order, so unfortunately those situations didn't apply to me. 我能找到的大多数东西都希望在同一列中进行,或者不希望按时间顺序进行排序,因此不幸的是,这些情况不适用于我。

I really have now clue how to solve this. 我现在真的知道如何解决这个问题。

If you have only three dates, then pivot /conditional aggregation should be fine: 如果只有三个日期,则pivot /条件聚合应该可以:

select id,
       max(case when seqnum = 1 then dstart end) as start_1,
       max(case when seqnum = 1 then dend end) as end_1,
       max(case when seqnum = 2 then dstart end) as start_2,
       max(case when seqnum = 2 then dend end) as end_2,
       max(case when seqnum = 3 then dstart end) as start_3,
       max(case when seqnum = 3 then dend end) as end_3
from (select t.*,
             row_number() over (partition by id order by dstart) as seqnum
      from t
     ) t
group by id;

Note: You have to specify the number of columns in the output. 注意:您必须指定输出中的列数。 If you don't know how many there are, you can either: 如果您不知道有多少个,可以:

  • Generate a dynamic SQL statement to do the count in advance. 生成动态SQL语句以预先进行计数。
  • Manually count yourself and add the appropriate columns. 手动计算自己并添加适当的列。

Gordon's conditional aggregation would be my first choice. 戈登的条件聚合将是我的首选。 However, If you need to go DYNAMIC 但是,如果您需要动态

Declare @SQL varchar(max) = Stuff((Select Distinct ',' + QuoteName(concat('Date Start ',RN)) +',' + QuoteName(concat('Date End ',RN)) 
                                    From (Select Distinct RN=Row_Number() over (Partition By ID Order By [Date Start]) 
                                            From YourTable) A  
                                    Order by 1 For XML Path('')),1,1,'') 

Select  @SQL = '
Select [ID],' + @SQL + '
From (
        Select ID,B.*
         From (
                Select *,RN=Row_Number() over (Partition By ID Order By [Date Start]) From YourTable
              ) A
         Cross Apply (Values (concat(''Date Start '',A.RN),A.[Date Start])
                            ,(concat(''Date End '',A.RN),A.[Date End]) ) B (Col,Value)
     ) A
 Pivot (max(Value) For [Col] in (' + @SQL + ') ) p'
Exec(@SQL);

Returns 退货

在此处输入图片说明

Assuming you have a static number of date-ranges, you could achieve this using a window function as follows... 假设您有固定数量的日期范围,则可以使用如下窗口函数来实现此目的...

;WITH cteData
    (
    ID,
    DateStart,
    DateEnd
    )
    AS
    (
    SELECT 'a', CONVERT(DATE, '01-01-1950'), CONVERT(DATE, '30-01-1951')
    UNION ALL SELECT 'a', '01-01-1948', '31-12-1949'
    UNION ALL SELECT 'a', '31-01-1951', '01-06-2000'
    UNION ALL SELECT 'b', '01-01-1980', '01-08-2010'
    UNION ALL SELECT 'c', '01-01-1990', '31-12-2017'
    UNION ALL SELECT 'c', '31-01-1985', '31-12-1989'
    ),
    cteDefineColumns
    AS
    (
    SELECT RangeColumnID = ROW_NUMBER() OVER (PARTITION BY ID ORDER BY DateStart),
        *
        FROM cteData
    )
    SELECT col1.ID,
        [Date start 1] = col1.DateStart,
        [Date end 1] = col1.DateEnd,
        [Date start 2] = col2.DateStart,
        [Date end 2] = col2.DateEnd,
        [Date start 3] = col3.DateStart,
        [Date end 3] = col3.DateEnd
        FROM cteDefineColumns AS col1
            LEFT OUTER JOIN cteDefineColumns AS col2
                ON col1.ID = col2.ID
                AND col2.RangeColumnID = 2
            LEFT OUTER JOIN cteDefineColumns AS col3
                ON col1.ID = col3.ID
                AND col3.RangeColumnID = 3
        WHERE col1.RangeColumnID = 1
        ORDER BY col1.ID,
            col1.DateStart,
            col1.DateEnd;

I'm not sure why do you neet it, because this structure is not too good. 我不确定为什么要取消它,因为这种结构不太好。

if object_Id('tempdb..#TmpRankedTable') is not null drop table #TmpRankedTable
select Id, strt_dt, end_dt, row_number() over(partition by Id order by strt_dt) OrbyCol
into #TmpRankedTable
from dbo.YourTable

if object_Id('tempdb..#TmpStarts') is not null drop table #TmpStarts
select *
into #TmpStarts
from (
    select Id, strt_dt, OrbyCol
    from #TmpRankedTable) t
pivot (min(strt_dt) for OrbyCol in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10])) p

if object_Id('tempdb..#TmpEnds') is not null drop table #TmpEnds
select *
into #TmpEnds
from (
    select Id, end_dt, OrbyCol
    from #TmpRankedTable) t
pivot (min(end_dt) for OrbyCol in ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10])) p

select 
    s.Id, 
    s.[1] [Start 1], 
    e.[1] [End 1], 
    s.[2] [Start 2], 
    e.[2] [End 2],
    s.[3] [Start 3],
    e.[3] [End 3], 
    s.[4] [Start 4],
    e.[4] [End 4],
    s.[5] [Start 5],
    e.[5] [End 5],
    s.[6] [Start 6], 
    e.[6] [End 6],
    s.[7] [Start 7], 
    e.[7] [End 7],
    s.[8] [Start 8], 
    e.[8] [End 8],
    s.[9] [Start 9], 
    e.[9] [End 9],
    s.[10] [Start 10], 
    e.[10] [End 10]
from #TmpStarts s
inner join #TmpEnds e on s.Id = e.Id

You can use pivot to transpose and query as below: 您可以按以下方式使用数据透视进行转置和查询:

;with cte as (
select *, RowN = row_number() over(partition by id order by datestart) from #temp ) 
, cte2 as (
    select id, [1] as [datestart1], [2] as [datestart2], [3] as datestart3 from 
    (select id, datestart, RowN from cte) sourcetable
    pivot (max(datestart) for RowN in ([1],[2],[3]) ) p
) 
, cte3 as (
    select id, [1] as [dateend1], [2] as [dateend2], [3] as dateend3 from 
    (select id, dateend, RowN from cte) sourcetable
    pivot (max(dateend) for RowN in ([1],[2],[3]) ) p
) select c2.id, c2.datestart1,c3.dateend1,c2.datestart2,c3.dateend2,c2.datestart3,c3.dateend3 
    from cte2 c2 left join cte3 c3 on c2.id = c3.id

If you have dynamic columns you can create dynamic query using stuff to create list of columns and run this query as dynamic query to get all list of columns 如果您有动态列,则可以使用东西创建动态查询以创建列列表,并将此查询作为动态查询运行以获取所有列列表

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM