【问题标题】:SQL: How to 'split' rows in a table by the values of certain columns? [duplicate]SQL:如何通过某些列的值“拆分”表中的行? [复制]
【发布时间】:2020-01-05 06:57:32
【问题描述】:

我有一张这样的表格:

Name  | date_from  | date_to    | age
------+------------+------------+-----
Alice | 01.12.2004 | 03.04.2008 | 35
Bob   | 04.02.2013 | 04.11.2014 | 43

我想制作一个表格,将每一行分成一年的间隔 date_fromdate_to 列,保留 Name,并更新 age,如下所示:

Name  | date_from  | date_to    | age
------+------------+------------+-----
Alice | 01.12.2004 | 01.12.2005 | 35
Alice | 01.12.2005 | 01.12.2006 | 36
Alice | 01.12.2006 | 01.12.2007 | 37
Alice | 01.12.2007 | 01.12.2008 | 38
Alice | 01.12.2008 | 03.04.2008 | 39
Bob   | 04.02.2013 | 04.02.2014 | 43
Bob   | 04.02.2014 | 04.11.2014 | 44

这可以在 SQL 中实现吗?

【问题讨论】:

  • 你使用的是什么 sql(MS Sql、MySQL、Oracle、PostgreSQL)?
  • 有一个返回所有可能年份的帮助表(或递归 cte)。加入。
  • 好像少了一个爱丽丝 | 01.12.2008 | 03.04.2008 | 38(鲍勃也一样),或者鲍勃的第二行不应该在那里
  • @metal:你说得对,会更正
  • 一个与方言无关的答案可能会被证明是困难的,因为日期函数是高度特定于供应商的。

标签: sql sql-server database tsql select


【解决方案1】:

使用 SQL Server 的另一种可能的解决方案

-- data preparation
    create table test1
    (
        name varchar(20),
        date_from date,
        date_to date ,
        age int

    )



    insert into test values ('alice' , '01-2-2008' , '11-3-2014' , 35 )
    insert into test values ('bob' , '06-2-2005' , '7-10-2016' , 20)

    create table test2
    (
        name varchar(20),
        date_from date,
        date_to date ,
        age int

    )
    -- query
    declare @name varchar(20)
    declare @date_from date
    declare @date_to date
    declare @age int
    declare @date_step as date
    declare @sql_st as nvarchar(max)
    declare cur cursor for select  name, date_from , date_to , age from test
    open cur;
        fetch next from cur into @name , @date_from , @date_to , @age
        while @@FETCH_STATUS = 0
        begin
            set @date_step = dateadd(year,1,@date_from)
            while @date_to > @date_step
            begin
                set @sql_st = concat('insert into test2 values (''',@name , ''' , ''' , @date_from , ''' , ''',@date_step,''',',@age , ' )')
                print(@sql_st)
                exec sp_executesql @sql_st
                set @date_from = @date_step
                set @date_step = dateadd(year,1,@date_step)
                set @age = @age + 1
            end
            set @sql_st = concat('insert into test2 values (''',@name , ''' , ''' , @date_from , ''' , ''',@date_to,''',',@age , ' )')
            exec sp_executesql @sql_st
            --print(@sql_st)            
            fetch next from cur into @name , @date_from , @date_to , @age       
        end
    close cur;
    deallocate cur;

【讨论】:

    【解决方案2】:

    一种解决方案是生成一个数字列表并将其与原始表格连接起来,将开始日期添加到结束日期为止。

    以下查询可处理长达 5 年的跨度(要支持更多年,您需要使用更多 VALUESs 扩展子查询)

    SELECT
        name, 
        DATEADD(year, x.n, t.date_from) date_from,
        CASE 
            WHEN DATEADD(year, x.n + 1, t.date_from) > t.date_to 
            THEN date_to 
            ELSE DATEADD(year, x.n + 1, t.date_from) 
        END date_to,
        t.age + x.n age
    FROM 
        mytable t
        INNER JOIN (
            VALUES(0), (1), (2), (3), (4), (5)
        ) x(n) ON DATEADD(year, x.n, t.date_from) <= t.date_to
    ORDER BY name, age
    

    demo on DB Fiddle 与您的示例数据返回:

    name  | date_from           | date_to             | age
    :---- | :------------------ | :------------------ | --:
    Alice | 01/12/2004 00:00:00 | 01/12/2005 00:00:00 |  35
    Alice | 01/12/2005 00:00:00 | 01/12/2006 00:00:00 |  36
    Alice | 01/12/2006 00:00:00 | 01/12/2007 00:00:00 |  37
    Alice | 01/12/2007 00:00:00 | 03/04/2008 00:00:00 |  38
    Bob   | 04/02/2013 00:00:00 | 04/02/2014 00:00:00 |  43
    Bob   | 04/02/2014 00:00:00 | 04/11/2014 00:00:00 |  44
    

    【讨论】:

    • 最好使用现有的计数表,或者您可以使用...inner join (values(1),(2),(3),(4),(5),(6),(7)) x(n)... 而不是union all 查询。仍然是我的支持。
    • @ZoharPeled:是的 VALUES() 语法在这里要短得多,我相应地更新了我的答案。谢谢!
    【解决方案3】:

    这是您的查询。

    ;with cte as (
        select 1 as ctr, DATEDIFF(year, cast(date_from as datetime), cast(date_to as datetime)) as ct
            ,cast(date_from as date) as dt, cast(date_from as date) as dt2, date_to, cast(age as int) as age, [name] from test
        union all
        select ctr +  1, ct, dateadd(year, 1, dt), dt2, date_to, age + 1, [name]  from cte
        where ctr + 1 <= ct+1)
        select [name], dt as date_from, case when ctr - 1 != ct then dt else date_to end as date_to, age from cte order by dt2, age
    

    输出:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-05-17
      • 1970-01-01
      • 2020-10-23
      • 1970-01-01
      • 1970-01-01
      • 2014-07-02
      • 1970-01-01
      相关资源
      最近更新 更多