SQL Server 分区窗口中多个属性的 Min() 和 Max()

Posted

技术标签:

【中文标题】SQL Server 分区窗口中多个属性的 Min() 和 Max()【英文标题】:Min() and Max() of multiple attributes in a partition window on SQL Server 【发布时间】:2020-09-28 15:22:28 【问题描述】:

我在 SQL Server 中有一个时间表,其中包含公共交通工具的 [SERV_ID](服务 ID)、[STATION](车站)、[ARR](到达时间)、[DEP](出发时间)。每个服务都可以每天都在[SERV_DAY]

目标是总结Serviceday、Service-line、First-station、Last-station以及相应的时间戳。 --> 每天每服务一行。

对于[SERV_ID] N170,这将是:

SERV_DAY                SERV_ID     FIRST_STATION   MIN_DEP                 LAST_STATION        MAX_ARR
2019-08-14 00:00:00     N170        Downtown        2019-08-14 06:06:00     CentralStation      2019-08-14 07:11:00

我尝试通过([SERV_DAY], [SERV_ID]) 进行分区,然后为每个分区获取MAX([ARR])MIN([DEP])。这工作了很长时间,但现在我想为每个最小值和最大值获取对应的 Station。

SELECT 
       [SERV_DAY],[SERV_ID],
       MAX([ARR]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MAX_ARR,
       MIN([DEP]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MIN_DEP 
FROM #demo

稍后我需要在最后一站添加延迟,该延迟在数据集的扩展版本中可用,为 [ARR_EFFECTIVE][DEP_EFFECTIVE]。希望一旦我知道如何总结如上所述的每日线路,我就能够添加这些属性。

这个话题很接近,但我不明白如何适应“差距和孤岛问题” Min() and Max() based on partition in sql server

我在 dbfiddle 中设置了一个演示数据集 https://dbfiddle.uk/?rdbms=sqlserver_2016&fiddle=52e53d43a49ddb8f67454e576bfa7d74

谁能帮我完成查询?

【问题讨论】:

什么版本的 SQL Server? 我使用 Microsoft SQL Server 2017 【参考方案1】:
SELECT 
       [SERV_DAY]
       ,[SERV_ID],
       FIRST_VALUE(STATION) over (Partition by [SERV_DAY],[SERV_ID] Order by ARR DESC) Station1
       , FIRST_VALUE(STATION) over (Partition by [SERV_DAY],[SERV_ID] Order by DEP ASC) Station2
FROM #demo

【讨论】:

注意那个技巧。因为当获取多于一列的first_value 时,这些值可能来自不同的行。因此,一列与其他列不匹配。如果不止一行提供与订单相同的排名(此处为ARRDEP),则可能会发生这种情况。这也是我使用 row_number 而不是 dense_rank 进行过滤的原因。 @casenonsensitive 我按 Datetime 日期类型(Arr 和 Dep)排序,并且根据场景,我们不能有相同的到达时间(ARR)或出发时间(Dep)一天,所以这里不是这种情况。【参考方案2】:

如果您有大量数据,我想我会使用临时表而不是 CTE,但这里有一个关于它应该如何工作的快速想法:

WITH CTE AS
(
    SELECT * 
    , ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY ARR  ) RN
    , ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY DEP  ) RN2
    from #demo
)
SELECT t1.[SERV_DAY],t1.[SERV_ID],t1.[STATION] FIRST_STATION, t1.[DEP] MIN_DEP, t2.STATION LAST_STATION
FROM CTE t1
INNER JOIN CTE t2 on t1.SERV_DAY = t2.SERV_DAY and t1.SERV_ID = t2.SERV_ID and t2.RN2 = 1
WHERE t1.RN = 1

【讨论】:

谢谢!这似乎真的很完美,因为这个查询很容易扩展到有效到达、离开,以便稍后计算延迟! 太棒了,如果它满足您的需求,请将其标记为您接受的答案。 对不起,我不能在这个糟糕的平台上标记答案;感谢您的反馈!声望低于 15 人的投票会被记录,但不会更改公开显示的帖子分数【参考方案3】:

您可以分两步完成: 首先添加一个按ARR 降序排序的row_number 和另一个按dep 排序的row_number。然后,您可以过滤 row_number = 1 的行以选择其他列。 下面是一个如何检索 max_arr 和 min_dep 的站的示例:

WITH T AS (
SELECT 
       [SERV_DAY], [SERV_ID],
       MAX([ARR]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MAX_ARR,
       MIN([DEP]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MIN_DEP,
       ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY [ARR] DESC) AS RN_ARR,
       ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY [DEP]) AS RN_DEP,
       *
  FROM #demo
)
SELECT MAX(CASE WHEN RN_ARR = 1 THEN [STATION] END) MAX_ARR_STATION,
       MAX(CASE WHEN RN_DEP = 1 THEN [STATION] END) MIN_DEP_STATION,
       *
  FROM T

【讨论】:

谢谢!不幸的是,@SELECT [SERV_DAY], [SERV_ID], MAX([ARR]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MAX_ARR, MIN([DEP]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MIN_DEP, ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY [ARR] DESC) AS RN_ARR, ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY [DEP]) AS RN_DEP, * FROM #demo 不适合在 temptable 中或与 [SERV_DAY], [SERV_ID] 一样作为重复列。有没有办法单独重命名一个版本? @Samamani 而不是*,只需写下您需要选择的所有列。这样你就可以确保没有列名被使用两次。【参考方案4】:

作为对@casenonsensitive 的回复,它可以使用他的代码并稍作修改!

WITH T AS (

SELECT 
       [SERV_DAY], [SERV_ID], [STATION],
       MAX([ARR]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MAX_ARR,
       MIN([DEP]) OVER(PARTITION BY [SERV_DAY],[SERV_ID]) AS MIN_DEP,
       ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY [ARR] ) AS RN_ARR,
       ROW_NUMBER() OVER(PARTITION BY [SERV_DAY],[SERV_ID] ORDER BY [DEP] ) AS RN_DEP
  FROM #demo
  )
  
 SELECT MAX(CASE WHEN RN_ARR = 1 THEN [STATION] END) MIN_DEP_STATION,
       MAX(CASE WHEN RN_DEP = 1 THEN [STATION] END) MAX_ARR_STATION, [SERV_DAY], [SERV_ID], MAX_ARR, MIN_DEP from T
       group by [SERV_DAY], [SERV_ID], MIN_DEP, MAX_ARR

【讨论】:

以上是关于SQL Server 分区窗口中多个属性的 Min() 和 Max()的主要内容,如果未能解决你的问题,请参考以下文章

SQL Server2008窗口计算

SQL Server 分区表

Sql Server系列:分区表操作

SQL Server 数据分区管理

SQL Server 数据分区管理

AWS Glue:SQL Server 多个分区数据库 ETL 到 Redshift