【问题标题】:Get Last and Next Appointments获取最后和下一个约会
【发布时间】:2021-08-07 11:53:13
【问题描述】:

我在 SQL Server 中有下表,并希望获取每个客户的最后一次和下一次约会。

注意:如果第一次约会是在将来,最后一次约会应该是 N/A。同样,如果最后一次约会是过去,下一次约会将是 N/A。如果最后一次约会超过 30 天,则不应显示(如果没有未来的约会 - 被视为不活跃的客户)。

CustomerId (int) | Date (date) | Time (time)
1                | 20210801    | 11:00
1                | 20210802    | 13:00
1                | 20210805    | 10:00
1                | 20210811    | 16:00
1                | 20210821    | 17:00
2                | 20210801    | 11:00
2                | 20210802    | 11:00
2                | 20210803    | 11:00
2                | 20210804    | 11:00
3                | 20210831    | 11:00
4                | 20210526    | 10:00

在这种情况下,结果应该是(假设日期是今天 2021 年 8 月 7 日):

CustomerId (int) | LastAppointment (varchar) | NextAppointment (varchar)
1                | 05 Aug 2021 - 10:00       | 11 Aug 2021 - 16:00
2                | 04 Aug 2021 - 11:00       | N/A
3                | N/A                       | 31 Aug 2021 - 11:00

谁能帮帮我?一个例子将不胜感激。

【问题讨论】:

  • 根据问题指南,请展示您尝试过的内容以及遇到问题的地方。
  • 我尝试了多种方式,例如变量表,子查询,但卡住了,因此我在这里问。这是我在这里的第一个问题,所以不确定你是如何证明我之前尝试过的。
  • 为什么客户 4 没有包含在您的预期结果中?
  • 因为他上次约会超过 30 天
  • 您实际上应该在您的问题中包含您的一项尝试,即。实际代码。

标签: sql sql-server tsql sqldatetime


【解决方案1】:

您只需使用datetime 值,然后使用条件聚合来为每个客户选择所需的日期。首先使用 CTE 来尽可能简化日期转换,如下所示:

with ap as (
    select CustomerId, Convert(datetime,Left(Concat([date], ' ', [time]),15)) app
    from t
), groups as (
    select CustomerId, 
    Max(case when app <= GetDate() then app end) LastAppointment,
    Min(case when app > GetDate() then app end) NextAppointment
    from ap
    group by customerId
)
select CustomerID, 
    IsNull(Format(LastAppointment, 'dd MMM yyyy - hh:mm'), 'N/A') LastAppointment, 
    IsNull(Format(NextAppointment, 'dd MMM yyyy - hh:mm'), 'N/A') NextAppointment
from groups
where DateAdd(day,-30,GetDate()) < isnull(lastappointment,GetDate())

DB<>Fiddle

另请注意,此查询仅接触表一次并执行一次逻辑读取。

【讨论】:

    【解决方案2】:

    你需要条件聚合:

    SELECT CustomerId,
           COALESCE(
             MAX(CASE 
                   WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) < GETDATE() 
                     THEN FORMAT(CAST(Date AS DATETIME) + CAST(Time AS DATETIME), 'dd MMM yyyy - HH:mm')
                 END
             ), 'N/A'    
           ) LastAppointment,
           COALESCE(
             MIN(CASE 
                   WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) > GETDATE() 
                     THEN FORMAT(CAST(Date AS DATETIME) + CAST(Time AS DATETIME), 'dd MMM yyyy - HH:mm')
                 END
             ), 'N/A'    
           ) NextAppointment
    FROM tablename
    GROUP BY CustomerId
    HAVING COALESCE(DATEDIFF(
             d, 
             MAX(CASE 
                   WHEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME) < GETDATE() 
                     THEN CAST(Date AS DATETIME) + CAST(Time AS DATETIME)
                 END
             ),
             GETDATE()
           ), 0) < 30
    

    请参阅demo
    结果:

    CustomerId LastAppointment NextAppointment
    1 05 Aug 2021 - 10:00 11 Aug 2021 - 16:00
    2 04 Aug 2021 - 11:00 N/A
    3 N/A 31 Aug 2021 - 11:00

    【讨论】:

    • #4 不在 OP 的预期结果中。
    • @Stu 正确,我添加了一个 HAVING 子句来解决这个问题。
    【解决方案3】:

    注意:此解决方案有效,但在性能方面非常糟糕,请查看this answer 以获得更好的方法


    类似的东西

    SELECT DISTINCT customerid,
                    Isnull(CONVERT(VARCHAR,
                                     (SELECT TOP 1 Concat(date, ' ', TIME)
                                      FROM appointments B
                                      WHERE b.customerid = a.customerid
                                        AND ([date] < CONVERT(DATE, Getdate())
                                             OR ([date] = CONVERT(DATE, Getdate())
                                                 AND [time] <= CONVERT(TIME, Getdate())))
                                        ORDER  BY [date] DESC)), 'N/A') AS lastappointment,
                    Isnull(CONVERT(VARCHAR,
                                     (SELECT TOP 1 Concat(date, ' ', TIME)
                                      FROM appointments B
                                      WHERE b.customerid = a.customerid
                                        AND ([date] > CONVERT(DATE, Getdate())
                                             OR ([date] = CONVERT(DATE, Getdate())
                                                 AND [time] > CONVERT (TIME, Getdate())))
                                        ORDER  BY [date])), 'N/A') AS nextappointment
    FROM appointments A
    WHERE Datediff(DAY,
                     (SELECT TOP 1 date
                      FROM appointments B
                      WHERE b.customerid = a.customerid
                        AND [date] <= CONVERT(DATE, Getdate())
                        ORDER  BY [date] DESC), CONVERT(DATE, Getdate())) <= 30
      OR (((
              (SELECT TOP 1 date
               FROM appointments B
               WHERE b.customerid = a.customerid
                 AND [date] > CONVERT(DATE, Getdate())
                 ORDER  BY [date]) > CONVERT(DATE, Getdate())))
          OR ((
                 (SELECT TOP 1 date
                  FROM appointments B
                  WHERE b.customerid = a.customerid
                    AND [date] > CONVERT(DATE, Getdate())
                    ORDER  BY [date]) = CONVERT(DATE, Getdate()))
              AND (
                     (SELECT TOP 1 [time]
                      FROM appointments B
                      WHERE b.customerid = a.customerid
                        AND [date] > CONVERT(DATE, Getdate())
                        ORDER  BY [date]) > CONVERT(TIME, Getdate()))))
    

    我打电话给您的餐桌appointments,条件是选择过去 30 天内最后一次预约的客户或未来预约的客户。 我测试了列类型 Date 的日期和 Time(7) 的时间。

    【讨论】:

    • 谢谢,非常感谢。很接近。有没有办法在 WHERE 子句中更改这些内容以包括时间 - [Date]
    • @VladimirLenin 你说得对,我编辑了我的答案,试试这个
    • 请注意,如果性能是一个问题,这个查询会执行 94 次逻辑读取,而实际上只需要 1 次。
    • 我同意,@Stu 的解决方案更好,我会用参考更新我的答案
    【解决方案4】:

    由于优化目的,基表仅使用一次。使用 LAG() 函数和其他必要条件来选择实际的数据集。

    -- SQL SERVER
    
    SELECT p.CustomerId
         , CASE WHEN p.chk_condition = 1
                   THEN CONVERT(varchar(13), p.prev_Date, 113) + ' - ' + LEFT(p.prev_time, 5)
                WHEN p.chk_condition = 2
                   THEN CONVERT(varchar(13), p.Date, 113) + ' - ' + LEFT(p.time, 5)
                ELSE 'N/A'
           END "LastAppointment"
         , CASE WHEN p.chk_condition != 2
                   THEN CONVERT(varchar(13), p.Date, 113) + ' - ' + LEFT(p.time, 5)
                ELSE 'N/A'
           END "NextAppointment"
    FROM ( SELECT t.*
                , CASE WHEN  t.prev_Date < GETDATE() AND t.Date >= GETDATE()
                          THEN 1
                       WHEN t.prev_Date < GETDATE() AND t.Date <= GETDATE()
                          THEN 2
                       ELSE 0
                  END chk_condition
                , ROW_NUMBER() OVER (PARTITION BY CustomerId ORDER BY t.Date DESC, t.prev_Date DESC) row_num
           FROM (SELECT CustomerId, Date, Time
                      , LAG(Date) OVER (PARTITION BY CustomerId ORDER BY "Date", "Time") "prev_Date"
                      , LAG(Time) OVER (PARTITION BY CustomerId ORDER BY "Date", "Time") "prev_Time"
                 FROM appointment) t
           WHERE  CASE WHEN t.prev_Date < GETDATE() AND t.Date >= GETDATE() 
                          THEN 1
                       WHEN t.prev_Date IS NULL 
                          THEN CASE WHEN DATEDIFF(day, t.Date, GETDATE()) >= 30
                                       THEN 0
                                    ELSE 1
                               END
                       WHEN t.prev_Date < GETDATE() AND t.Date <= GETDATE()
                          THEN 1
                   END = 1 ) p
    WHERE p.row_num = 1
    ORDER BY p.CustomerId;
    

    请查看此网址https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=3813d09cf25ed14d249970654995b085

    【讨论】:

    • 嗨@VladimirLenin 我知道你选对了你想要的。我尝试尽可能优化地编写此查询,以便它可以处理大量数据。如果需要,请查看我的查询。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-12-11
    • 1970-01-01
    • 2016-12-12
    • 1970-01-01
    • 2014-01-02
    • 1970-01-01
    相关资源
    最近更新 更多