【问题标题】:Query trick - kind of unpivot查询技巧 - 一种反透视
【发布时间】:2014-02-21 14:05:51
【问题描述】:

我有下表

SnapShotDay OperationalUnitNumber   IsOpen      StatusDate
1-01-2014       001                   1         1-01-2014
2-01-2014       NULL                NULL           NULL
3-01-2014       001                   0         3-01-2014
4-01-2014       NULL                NULL           NULL
5-01-2014       001                   1         5-01-2014

我使用 SELECT 构造来获得它,但我现在需要做的是通过从之前的第一个非空行中获取值来填充“空”行。后者会给出:

SnapShotDay OperationalUnitNumber   IsOpen      StatusDate
1-01-2014       001                   1         1-01-2014
2-01-2014       001                   1         1-01-2014
3-01-2014       001                   0         3-01-2014
4-01-2014       001                   0         3-01-2014
5-01-2014       001                   1         5-01-2014

用实用的话来说:我有事件记录,可以为我提供一个特定日期的事件;事件是:IsOpen 或 IsClosed。根据日期将这些事件链接在一起会产生一种范围。我需要的是为这些范围生成每日记录(目标是事实表)。 我试图在普通的 SQL 查询(没有存储过程)中实现这一点。

你能想出一个窍门吗?

【问题讨论】:

    标签: sql-server sql-server-2008 tsql sql-server-2008-r2 sql-server-2012


    【解决方案1】:
    Declare @t table(
        SnapShotDay date, 
        OperationalUnitNumber int,   
        IsOpen bit, 
        StatusDate date
    )
    
    insert into @t
    select '1-01-2014',       001 , 1 , '1-01-2014' union all
    select '2-01-2014',       NULL, NULL, NULL union all
    select '3-01-2014',       001 , 0 ,'3-01-2014' union all
    select '4-01-2014',       NULL,NULL,NULL union all
    select '5-01-2014',       001 ,1,'5-01-2014'
    ;
    with CTE as
    (
        select *,row_number()over( order by (select 0))rn from @t
    )
    select *,
    case when a.isopen is null then (
            select IsOpen from cte where rn=a.rn-1
        ) else a.isopen end 
    from cte a 
    

    好的,我知道了,然后再创建一个 cte1,

    ,cte1 as
    (
    select top 1 rn ,IsOpen from cte where IsOpen is not null order by rn desc
    )
    --select * from Statuses
    select *,
        case 
            when a.rn<=(select b.rn from cte1 b)  and a.IsOpen is null then 
                (
                    select 
                        a1.IsOpen 
                    from 
                        cte a1
    
                    where 
                        a1.rn=a.rn-1 
                ) 
             when a.rn>=(select b.rn from cte1 b) and  a.IsOpen is null then
             (select IsOpen from cte1)
            else 
            a.isopen 
        end 
    from 
        cte a
    

    【讨论】:

    • 我尝试了你的解决方案,但我有一个奇怪的问题.. 我似乎不明白,isopen 标志的选择在某个时候停止,并返回 NULL,如下所示:pastebin.com/vCRCysXu
    • @CoolStraw 好的,我明白了?如果最后一行为空会发生什么?
    • 该标志永远不能为 NULL,它是 0 或 1。操作单元密钥也是如此,因为它是正在执行的框架中的业务密钥 :-)。感谢添加将尝试一下!
    • @CoolStraw,那么就没有问题了。问题出在您的虚拟数据中,在最后一行中产生了空值。
    【解决方案2】:

    试试这个。在主查询中,我们正在查找具有非空值的上一个日期。然后用这个LastDate 加入这个表。

    WITH T1 AS 
    (
      SELECT *, (SELECT MAX(SnapShotDay) 
                        FROM T 
                        WHERE SnapShotDay<=TMain.SnapShotDay
                              AND OPERATIONALUNITNUMBER IS NOT NULL) 
                 as LastDate
      FROM T as TMain
     )
    
      SELECT T1.SnapShotDay,
             T.OperationalUnitNumber,
             T.IsOpen,
             T.StatusDate 
        FROM T1
        JOIN T ON T1.LastDate=T.SnapShotDay 
    

    SQLFiddle demo

    【讨论】:

    • 感谢您的技巧!除了它不起作用......问题是我有多个操作单元:-/因此我认为按它进行分区是强制性的......试图找到正确的方法:)
    【解决方案3】:
    SELECT 
        t1.SnapShotDay,
        CASE WHEN t1.OperationalUnitNumber IS NOT NUll 
            THEN t1.OperationalUnitNumber 
            ELSE  (SELECT TOP 1 t2.OperationalUnitNumber FROM  YourTable t2 WHERE t2.SnapShotDay < t1.SnapShotDay AND t2.OperationalUnitNumber IS NOT NULL ORDER BY SnapShotDay DESC)
        END AS OperationalUnitNumber,
        CASE WHEN t1.IsOpen IS NOT NUll 
            THEN t1.IsOpen 
            ELSE  (SELECT TOP 1 t2.IsOpen FROM  YourTable t2 WHERE t2.SnapShotDay < t1.SnapShotDay AND t2.IsOpen IS NOT NULL ORDER BY SnapShotDay DESC)
        END AS IsOpen,  
        CASE WHEN t1.StatusDate IS NOT NUll 
            THEN t1.StatusDate 
            ELSE  (SELECT TOP 1 t2.StatusDate FROM  YourTable t2 WHERE t2.SnapShotDay < t1.SnapShotDay AND t2.StatusDate IS NOT NULL ORDER BY SnapShotDay DESC)
        END AS StatusDate
    FROM YourTable t1 
    

    【讨论】:

      【解决方案4】:

      您要求的是“plain sql”,这是一个使用 SQL 和 cmets 进行测试的尝试,它给出了所需的答案。

      我已经在 windows xp 上使用“sqlite”和“mysql”测试了代码。它是纯 SQL,应该适用于任何地方。

      SQL 是关于“集合”并将它们组合起来并对结果进行排序。

      这个问题似乎是关于两个不同的集合:

      1) 有读数的“快照日”。

      2) 没有读数的“快照日”。

      我添加了额外的列,以便我们可以轻松查看值的来源。

      让我们先处理简单的集合:

      这是一组“提供”读数。

      SELECT dss.SnapShotDay theDay, 
             'supplied'  readingExists,       
             dss.OperationalUnitNumber,
             dss.IsOpen,       
             dss.StatusDate       
      FROM dailysnapshot dss
      WHERE dss.OperationalUnitNumber IS NOT NULL
      

      结果:

      theDay      readingExists   OperationalUnitNumber   IsOpen  StatusDate
      2014-01-01  supplied        001                     1       2014-01-01
      2014-01-03  supplied        001                     0       2014-01-03
      2014-01-05  supplied        001                     1       2014-01-05
      

      现在让我们处理一组“缺少读数的日子”。我们需要获取“读数与缺失读数最接近的最近一天”,并假设“当前”缺失日期之前的“最近一天”的值相同。

      这听起来很复杂,但事实并非如此。它问: foreach day without a reading - 给我最接近、更早的有读数的日期,我将使用这些读数。

      这里是查询:

      SELECT emptyDSS.SnapShotDay,  
             'missing'  readingExists,       
             maxPrevDSS.OperationalUnitNumber,
             maxPrevDSS.IsOpen,       
             maxPrevDSS.StatusDate       
      FROM dailysnapshot emptyDSS
        INNER JOIN dailysnapshot maxPrevDSS ON maxPrevDSS.SnapShotDay = 
                                                       (SELECT MAX(dss.SnapShotDay)
                                                        FROM   dailysnapshot dss
                                                        WHERE  dss.SnapShotDay < emptyDSS.SnapShotDay
                                                        AND    dss.OperationalUnitNumber IS NOT NULL)                                                  
      WHERE emptyDSS.OperationalUnitNumber IS NULL
      

      结果:

      SnapShotDay readingExists   OperationalUnitNumber   IsOpen  StatusDate
      2014-01-02  missing         001                     1       2014-01-01
      2014-01-04  missing         001                     0       2014-01-03
      

      这与效率无关!它是关于使用最容易理解的 SQL 代码获得正确的“结果集”。我假设数据库引擎会优化查询。如果需要,可以稍后“调整”查询。

      我们现在需要组合这两个查询,并以我们需要的方式对结果进行排序。

      组合 SQL 查询结果的标准方法是使用集合运算符(联合、交集、减号)。

      我们在结果集上使用“union”和“order by”。

      这给出了最终查询:

      SELECT dss.SnapShotDay theDay, 
             'supplied'  readingExists,       
             dss.OperationalUnitNumber,
             dss.IsOpen,       
             dss.StatusDate       
      FROM dailysnapshot dss
      WHERE `OperationalUnitNumber` IS NOT NULL
      UNION
      SELECT emptyDSS.SnapShotDay theDay,  
             'missing'  readingExists,       
             maxPrevDSS.OperationalUnitNumber,
             maxPrevDSS.IsOpen,       
             maxPrevDSS.StatusDate       
      FROM dailysnapshot emptyDSS
        INNER JOIN dailysnapshot maxPrevDSS ON  maxPrevDSS.SnapShotDay = 
                                                       (SELECT MAX(dss.SnapShotDay)
                                                        FROM   dailysnapshot dss
                                                        WHERE  dss.SnapShotDay < emptyDSS.SnapShotDay
                                                        AND    dss.OperationalUnitNumber IS NOT NULL)
      WHERE emptyDSS.OperationalUnitNumber IS NULL
      ORDER BY theDay ASC
      

      结果:

      theDay      readingExists   dss.OperationalUnitNumber  dss.IsOpen  dss.StatusDate
      2014-01-01  supplied        001                        1           2014-01-01
      2014-01-02  missing         001                        1           2014-01-01
      2014-01-03  supplied        001                        0           2014-01-03
      2014-01-04  missing         001                        0           2014-01-03
      2014-01-05  supplied        001                        1           2014-01-05
      

      我喜欢这样做。

      它应该适用于大多数 SQL 引擎。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-09-24
        • 1970-01-01
        • 2015-12-12
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-01-01
        相关资源
        最近更新 更多