Oracle:Max、Partition by 还是 rownum?

Posted

技术标签:

【中文标题】Oracle:Max、Partition by 还是 rownum?【英文标题】:Oracle: Max, Partition by, or even rownum? 【发布时间】:2017-05-16 09:19:53 【问题描述】:

我来自 SQL Server 背景,所以我在 Oracle 方面的技能最低。看起来Partition by 在性能方面比max 好。还是我使用rownum 来归档我的结果表?

我有下表 - TableW。

| P_TYPE      | TRX_DATE       | PROGRAM_NO | REF_NO    | SEQ_ID |  Select 
|-------------|----------------|------------|-----------|--------|
| 'Local'     | 2016/9/5 14:37 | C1         | null      | E1     |  Yes (latest in Sept 5)
| 'Local'     | 2016/9/5 14:36 | C1         | null      | E1     |
| 'Local'     | 2016/9/5 11:08 | C1         | null      | E1     |
|-------------|----------------|------------|-----------|--------|
| 'Local'     | 2016/9/2 15:16 | C1         | null      | E1     |  Yes (latest in Sept 2)
|-------------|----------------|------------|-----------|--------|
| 'Local'     | 2016/9/1 15:20 | C1         | null      | E1     |  Yes (latest in Sept 1)
| 'Local'     | 2016/9/1 14:33 | C1         | null      | E1     |
|-------------|----------------|------------|-----------|--------|
| '3rd Party' | 2016/9/4 18:00 | null       | D1        | E2     |  Yes
| '3rd Party' | 2016/9/4 17:55 | null       | D1        | E2     |

这是我想要的:

对于列 P_TYPE,如果它的值为“Local”,则使用列 PROGRAM_NO 和 SEQ_ID。否则,使用 REF_NO 和 SEQ_ID。 如果 P_TYPE 列中的值相同,请检查 TRX_DATE。如果 TRX_DATE 列指示相同的日期,则选择具有最新时间戳的日期。另一天?另一个具有最新时间戳的条目。

| P_TYPE      | TRX_DATE       | PROGRAM_NO | REF_NO    | SEQ_ID |
|-------------|----------------|------------|-----------|--------|
| 'Local'     | 2016/9/5 14:37 | C1         | null      | E1     |  
| 'Local'     | 2016/9/2 15:16 | C1         | null      | E1     |  
| 'Local'     | 2016/9/1 15:20 | C1         | null      | E1     |  
| '3rd Party' | 2016/9/4 18:00 | null       | D1        | E2     |  

我收到的一个脚本是在WHERE clause 中使用SELECT MAX

SELECT *
FROM TableW a
WHERE TRX_DATE = 
    CASE P_TYPE
        WHEN 'Local' THEN
            (SELECT MAX(TRX_DATE) FROM TableW
                WHERE PROGRAM_NO = a.PROGRAM_NO AND SEQ_ID = a.SEQ_ID)
        ELSE
            (SELECT MAX(TRX_DATE) FROM TableW
                WHERE REF_NO = a.REF_NO AND SEQ_ID = a.SEQ_ID)
    END
ORDER BY TRX_DATE desc, REF_NO ASC, SEQ_ID;

它完成了这项工作。然而,通过一些研究,partition by 似乎并没有那么昂贵。参考:Tune SQL statement with max subquery

我尝试将查询重写为:

SELECT *
FROM (
SELECT *,
    CASE P_TYPE 
        WHEN 'Local' THEN 
            MAX(TRX_DATE) OVER (PARTITION BY PROGRAM_NO, SEQ_ID)
        ELSE
            MAX(TRX_DATE) OVER (PARTITION BY REF_NO, SEQ_ID)
    END AS MAX_TRX_DATE
FROM TableW
WHERE P_TYPE = 'Local'
)
WHERE TRX_DATE = MAX_TRX_DATE

但是,我只得到这个:

| P_TYPE      | TRX_DATE       | PROGRAM_NO | REF_NO    | SEQ_ID |
|-------------|----------------|------------|-----------|--------|
| 'Local'     | 2016/9/5 14:37 | C1         | null      | E1     |  

请提供任何指南。如果可能,请用统计数据说明您的建议。谢谢。

编辑:看起来使用row_number和partition by会大大减少执行计划甚至时间?

| CASE             | OPERATION        | CARDINALITY | COST | LAST CR     | LAST ELAPSED  |
|                  |                  |             |      | BUFFER GETS | TIME          |
|------------------|------------------|-------------|------|-------------|---------------|
| 1 - max() in     | SELECT STATEMENT |             |  76  |             |               |
|     where clause | SORT (ORDER BY)  |      1      |  76  |     477     |      3602     |
|------------------|------------------|-------------|------|-------------|---------------|
| 2 - row_number   | SELECT STATEMENT |             |  18  |             |               |
|                  | SORT (ORDER BY)  |      8      |  18  |      53     |       607     |
|------------------|------------------|-------------|------|-------------|---------------|

【问题讨论】:

【参考方案1】:

对于Local 行,您需要在定义窗口分区时包括日期,因为PROGRAM_NO, REF_NO 的所有值对于这些行都是相同的:

select *
from (
  SELECT *,
         CASE P_TYPE
           when 'Local' then 
              row_number() over (partition by program_no, seq_id, trunc(trx_date) order by trx_date desc)
           else 
              row_number() over (partition by ref_no, seq_id order by trx_date desc)
         end as rn
  FROM TableW a
) t
where rn = 1;

在线示例:http://rextester.com/CZTY80559

(该示例使用 Postgres,但除了“忽略”时间戳的时间部分的不同方式之外,在 Oracle 中将是相同的)

【讨论】:

第二种情况是否还需要PARTITION BY子句中的TRUNC( trx_date ) @MT0:我不清楚,因为样本数据没有必要的情况 - 但可能是的。 来自 OP:是的,对于第 3 方,实际数据确实显示有相同 ref_no 和 seq_id 的行在同一日期。这意味着需要在两个 PARTITION BY 子句中同时包含 TRUNC( TRX_DATE )

以上是关于Oracle:Max、Partition by 还是 rownum?的主要内容,如果未能解决你的问题,请参考以下文章

等效于 OBIEE 中的 max() keep (partition by .. order by ..)

oracle分析函数over partition by 和group by的区别

ROW_NUMBER(), PARTITION_BY, TOP 2 MAX If MAX 第一个和最后一个位置

Sql Server Max() over partition by - 翻译成 MySql

Max a Sum of a partition by

如何使用“Partition By”或“Max”?对于 SQL 服务器