【问题标题】:Oracle SQL: Further sort PARTITION BY groups based on first row in each partitionOracle SQL:根据每个分区中的第一行进一步对 PARTITION BY 组进行排序
【发布时间】:2021-10-14 12:17:36
【问题描述】:

我目前有一个非常大的查询,并且正在尝试以特定方式进一步对数据进行排序。查询以如下方式返回数据,项目按 TimeToComplete 降序排序:

|   id     |    ItemKey     |TimeToComplete|
| -------- | -------------- |--------------|
| ABC      | KEY-211-01     |    580       |
| DEF      | KEY-311-01     |    456       |
| GHI      | KEY-111-01     |    150       |
| JKL      | KEY-411-01     |     87       |
| XYZ      | KEY-311-01     |     23       |
| KNE      | KEY-211-01     |     17       |

我要做的是对这些数据进行排序,以便将 ItemKeys 分组在一起,但除此之外仍保留基于组中第一项的 TimeToComplete 排序。像这样的例子:

|   id     |    ItemKey     |TimeToComplete|
| -------- | -------------- |--------------|
| ABC      | KEY-211-01     |    580       |
| KNE      | KEY-211-01     |     17       |
| DEF      | KEY-311-01     |    456       |
| XYZ      | KEY-311-01     |     23       |
| GHI      | KEY-111-01     |    150       |
| JKL      | KEY-411-01     |     87       |

我确实有一个部分工作的例子,但它有一些问题:

WITH GroupedRecords AS (
  SELECT 
          OriginalQuery.*,
          ROW_NUMBER() OVER (partition by ItemKey order by TimeToComplete DESC) as RN
  FROM (originally giant query here) OriginalQuery  
),
Sequence AS (
  SELECT 
         ItemKey,
         TimeToComplete,
         ROW_NUMBER() OVER (order by TimeToComplete DESC) as SequenceOrder
  FROM GroupedRecords
  WHERE RN = 1
)
select T.*, s.SequenceOrder
from GroupedRecords T
INNER JOIN Sequence S ON T.ItemKey = S.ItemKey
ORDER BY S.SequenceOrder ASC, T.TimeToComplete DESC

这样做的问题是:

  1. GroupedRecords 和序列之间的内部连接将我的一堆列名(但不是全部)更改为 Oracle 随机生成的名称 (QCSJ_0000006)
  2. join 也使得查询方式太慢(OriginalQuery 已经不是很优化,但是它的执行时间增加了一倍)

问题: 有没有更有效的方法可以在不使用序列/连接部分的情况下实现这种排序?

【问题讨论】:

    标签: sql oracle aggregate-functions partition-by


    【解决方案1】:

    幸运的是,您只需在order by 子句中添加一个解析max()。您无需执行其他任何操作

    假设“当前查询”是您现有的查询,尚未以任何方式排序(没有order by 子句)。在最后添加以下内容:

    ... existing query ...
    order  by max(timetocomplete) over (partition by itemkey) desc,
              itemkey,
              timetocomplete desc
    ;
    

    请注意,您确实不需要需要将解析函数添加到select 子句。 SQL标准说你做; Oracle 语法说你没有。 Oracle 正在幕后为我们处理额外的小步骤。

    这会计算完成每个键的最长时间。它首先按该最大值排序。在 tie 的情况下(两个或多个不同的 key 具有相同的 ma​​x 完成时间),它进一步先按 key 排序,然后在每个 key 内,按时间排序完成(降序)。

    【讨论】:

    • +1 " 它首先按键进一步排序,然后在每个键内按时间完成(降序)。"谢谢!这完全涵盖了场景
    【解决方案2】:

    只需将以下窗口函数添加到您的选择列表中,然后将结果排序如下:

    SELECT id, ItemKey, TimeToComplete
         , MAX(TimeToComplete) OVER (PARTITION BY ItemKey) AS max_time
      FROM data
     ORDER BY max_time DESC, ItemKey, TimeToComplete DESC
    ;
    

    结果:

    +------+------------+----------------+----------+
    | id   | ItemKey    | TimeToComplete | max_time |
    +------+------------+----------------+----------+
    | ABC  | KEY-211-01 |            580 |      580 |
    | KNE  | KEY-211-01 |             17 |      580 |
    | DEF  | KEY-311-01 |            456 |      456 |
    | XYZ  | KEY-311-01 |             23 |      456 |
    | GHI  | KEY-111-01 |            150 |      150 |
    | JKL  | KEY-411-01 |             87 |       87 |
    +------+------------+----------------+----------+
    

    结果,当两个ItemKeys 具有相同的max_time 时,有数据要测试:

    +------+------------+----------------+----------+
    | id   | ItemKey    | TimeToComplete | max_time |
    +------+------------+----------------+----------+
    | ABC  | KEY-211-01 |            580 |      580 |
    | KNE  | KEY-211-01 |             17 |      580 |
    | ABD  | KEY-211-02 |            580 |      580 |
    | ABE  | KEY-211-02 |            200 |      580 |
    | DEF  | KEY-311-01 |            456 |      456 |
    | XYZ  | KEY-311-01 |             23 |      456 |
    | GHI  | KEY-111-01 |            150 |      150 |
    | JKL  | KEY-411-01 |             87 |       87 |
    +------+------------+----------------+----------+
    

    您可以在没有额外列的情况下生成相同的结果,方法是使用 CTE 术语添加新列,按外部查询表达式中的顺序排序,但不要在外部查询表达式选择列表中选择该列。

    如下:

    WITH cte1 AS (
            SELECT id, ItemKey, TimeToComplete
                 , MAX(TimeToComplete) OVER (PARTITION BY ItemKey) AS max_time
              FROM data
         )
    SELECT id, ItemKey, TimeToComplete
      FROM cte1
     ORDER BY max_time DESC, ItemKey, TimeToComplete DESC
    ;
    

    结果(使用额外数据更新):

    +------+------------+----------------+
    | id   | ItemKey    | TimeToComplete |
    +------+------------+----------------+
    | ABC  | KEY-211-01 |            580 |
    | KNE  | KEY-211-01 |             17 |
    | ABD  | KEY-211-02 |            580 |
    | ABE  | KEY-211-02 |            200 |
    | DEF  | KEY-311-01 |            456 |
    | XYZ  | KEY-311-01 |             23 |
    | GHI  | KEY-111-01 |            150 |
    | JKL  | KEY-411-01 |             87 |
    +------+------------+----------------+
    

    Working test case - Updated to handle case raised by @mathguy

    【讨论】:

    • 这还不够 - 它不能保证在常量 ItemKey 内,行进一步按 TimeToComplete 降序排序。 (这可以很容易地添加到您的答案中。)
    • @mathguy 是的。确实如此:ORDER BY max_time DESC, TimeToComplete DESC ... max_time 对于所有匹配的ItemKey 值都是相同的值,然后对原始TimeToComplete DESC 进行二次排序。请参阅工作测试用例。
    • 对不起,我看错了你的答案。它 not 所做的是按照 OP 的要求将具有相同 ItemKey 的行保持在一起。只有当两个 ItemKey(两个不同的)具有相同的 max_TimeToComplete 时,它​​才会发挥作用。同样,这很容易解决(只需添加到order by)。
    • 不,它没有 - 在我已经提到的情况下。假设您有两个不同的密钥,KEY-AAA 和 KEY-BBB。它们的值分别为 300、200、100 和 300、150。第一个排序表达式不区分键(两者都有 max = 300)。第二个表达式按值排序:300、300、200、150、100 - 现在键是混合的。
    • @mathguy 是的。我同意。当max_time 在结果中与不同的ItemKey 值不同时,这是可以的。这需要解决。为此,我将向测试用例添加数据。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-17
    • 2020-11-19
    • 2018-11-18
    • 1970-01-01
    • 2021-11-11
    • 1970-01-01
    相关资源
    最近更新 更多