【问题标题】:Filter LEFT JOINed table with dates to display current event, else future, else past?过滤带有日期的 LEFT JOINed 表以显示当前事件、未来事件还是过去事件?
【发布时间】:2014-03-09 21:56:33
【问题描述】:

我有一个表格,列出了不同用户的假期信息(用户名、假期开始和假期结束日期)——下面列出了 4 个用户:

Username    VacationStart   DeploymentEnd
rsuarez     2014-03-10      2014-03-26
studd       2014-01-18      2014-01-29
studd       2014-02-11      2014-02-26
studd       2014-03-02      2014-03-04
ssteele     2014-03-11      2014-03-26
ssteele     2014-03-18      2014-03-28
atidball    2014-03-05      2014-03-20
atidball    2014-03-06      2014-03-26
atidball    2014-03-13      2014-03-20
atidball    2014-03-18      2014-03-31

对于一个新查询,我只想显示 4 行,每个用户只显示一组假期日期,当前/正在进行的假期、未来/下一个假期(如果当前不存在)或最近的(如果以上两个都是假的)。

最终结果应该如下(假设今天是 3/9/2014):

Username    VacationStart   DeploymentEnd
rsuarez     2014-03-10      2014-03-26
studd       2014-03-02      2014-03-04
ssteele     2014-03-11      2014-03-26
atidball    2014-03-05      2014-03-20

假期日期实际上来自另一个表 (data_vacations),我将它留给了 data_users。我正在尝试在左连接语句中执行案例选择。

这是我之前尝试过的,但我的逻辑在那里失败了,因为我最终混合了不同的假期结束日期和假期开始日期:

SELECT Username, VacationStart, VacationEnd
FROM data_users
LEFT JOIN  
(
    SELECT userGUID, 
    CASE WHEN MIN(CASE WHEN (VacationEnd < getdate()) THEN NULL ELSE VacationStart END) IS NULL THEN MAX(VacationStart) 
    ELSE MIN(VacationStart) END AS VacationStart,  


    CASE WHEN MIN(CASE WHEN (VacationEnd < getdate()) THEN NULL ELSE VacationEnd END) IS NULL THEN MAX(VacationEnd) 
    ELSE MIN(VacationEnd) END AS VacationEnd 


    FROM data_vacations
    GROUP BY userGUID
) b ON(data_empl_master.userGUID= b.userGUID) 

我做错了什么?我该如何解决?

另外.. 附注.. 我是否在 LEFT JOIN 中正确执行此过滤?由于 data_users 更大,具有不同的用户 ID...我想根据上面的示例加入可用的假期信息,同时仍显示所有唯一的用户 ID。

【问题讨论】:

    标签: sql sql-server-2008


    【解决方案1】:

    使用公用表表达式按类别(当前 = 1,未来 = 2,过去 = 3)和每个类别分别按开始日期/与 GETDATE() 的差异进行排名,您可以通过对结果进行排名来获得您想要的结果使用ROW_NUMBER();

    DECLARE @DATE DATETIME = GETDATE()
    
    ;WITH cte AS (
      SELECT *, 1 r, VacationStart s FROM data_users 
      WHERE @DATE BETWEEN VacationStart and DeploymentEnd
      UNION ALL
      SELECT *,2 r, VacationStart - @DATE s FROM data_users 
      WHERE VacationStart > @DATE
      UNION ALL
      SELECT *,3 r, @DATE - DeploymentEnd s FROM data_users 
      WHERE DeploymentEnd < @DATE
    ), cte2 AS (
      SELECT *, ROW_NUMBER() OVER (PARTITION BY username ORDER BY r,s) rn FROM cte
    )
    SELECT Username, VacationStart, DeploymentEnd FROM cte2 WHERE rn=1;
    

    An SQLfiddle to test with.

    将日期作为变量获取对于在整个查询中获得一致的GETDATE() 值是必要的,否则如果多次调用可能会不一致。

    【讨论】:

      【解决方案2】:
      select u.name,s.startdate,s.enddate 
      from users u
      left join 
      (
            select su.name,
                   max(su.start) as startdate,
                   max(su.end) as enddate from users su group by su.name
      )s on u.name= s.name
      group by u.name 
      

      【讨论】:

        【解决方案3】:

        由于您要问两个问题,我将回答一个关于获取假期日期的问题,并让您弄清楚加入。

        我认为您无法通过一个简单的查询获得所需的假期日期。首先,您需要确定给定的日期范围是过去、现在还是将来。然后,您需要按开始/结束日期对这些范围进行排序,以获得最近的或下一个即将到来的。您需要按降序对过去的假期进行排序,并按升序对即将到来的假期进行排序。有趣的是,用户 atidball 有两个假期正在进行中,我以与未来假期相同的方式对其进行排序。最后应用你的规则,我通过按状态排序来做到这一点。

        declare @currentDate date = '20140309'   
        ;
        with cte1 as
        (
          -- state: the lower number the higher priority
          select Username, VacationStart, DeploymentEnd,
            case 
              when VacationStart <= @currentDate and DeploymentEnd >= @currentDate
                then 0 -- in progress
              when VacationStart > @currentDate
                then 1 -- future
              when DeploymentEnd < @currentDate
                then 2 -- past
              else NULL
            end as state
          from data_vacations
        )
        , cte2 as
        (
          select *, 
            row_number() over(partition by username, state order by VacationStart, DeploymentEnd) as rn
          from cte1
          where state < 2 -- current or upcoming
        
          union all
        
          select *, 
            row_number() over(partition by username, state order by DeploymentEnd desc, VacationStart desc) as rn
          from cte1
          where state = 2 -- past
        )
        , cte3 as
        (
          -- apply the rules: find the record with highest priority
          select Username, min(state) as minstate
          from cte1
          group by Username
        )
        select cte2.Username, cte2.VacationStart, cte2.DeploymentEnd
        from cte2
        inner join cte3
          on cte2.Username = cte3.Username
          and cte2.state = cte3.minstate
          and cte2.rn = 1  -- most recent or next upcoming
        

        请参阅SQLFiddle

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2013-02-09
          • 1970-01-01
          • 1970-01-01
          • 2016-08-09
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-11-02
          相关资源
          最近更新 更多