有条件地加入 - COALESCE 与 OR - Oracle SQL

Posted

技术标签:

【中文标题】有条件地加入 - COALESCE 与 OR - Oracle SQL【英文标题】:Conditionally joining - COALESCE vs OR - Oracle SQL 【发布时间】:2019-07-18 14:43:58 【问题描述】:

我正在使用如下所示的数据集(由于它太小,所以没有创建小提琴)

我有一张表tblReqs,其基本结构如下:

| Onum | Pnum | ReqNum |
|:----:|:----:|:------:|
| NULL | P427 | RN1148 |
| NULL | P324 | RN1725 |
| NULL | P229 | RN1242 |
| O396 | NULL | RN1457 |
| O380 | NULL | RN1205 |
| O258 | NULL | RN1482 |

然后我有一个直数表,称为tblnums,如下所示:

| nums |
|------|
| O258 |
| O370 |
| O490 |
| O314 |
| O379 |
| P341 |
| P230 |
| P280 |
| P324 |
| P395 |

我需要有条件地加入 tblnumstblReqsOnumtblReqs 中的Pnum 将等于tblnums 中的nums 字段。

目标是如下所示的数据集:

| nums | ReqNum |
|:----:|:------:|
| O258 | RN1482 |
| O370 |        |
| O490 |        |
| O314 |        |
| O379 |        |
| P341 |        |
| P230 |        |
| P280 |        |
| P324 | RN1725 |
| P395 |        |

我知道我可以像这样在连接中使用OR 运算符:

   SELECT
    tblnums.nums,
    tblReqs.ReqNum

FROM
    tblnums
        LEFT JOIN tblReqs ON tblnums.nums = tblReqs.Onum OR tblnums.nums = tblReqs.Pnum

但我最近了解到COALESCE

SELECT
    tblnums.nums,
    tblReqs.ReqNum

FROM
    tblnums
        LEFT JOIN tblReqs ON tblnums.nums = COALESCE(tblReqs.Onum, tblReqs.Pnum)

是否对其中一个有偏好?我知道我可以在我的数据集上同时尝试这两种方法,但测量时间并不是一个好的指标,因为它受到许多其他限制(数据库维护、网络带宽、cpu / ram 功率等)的影响。此外,由于我的 IT 团队已将其锁定,因此我无法通过 SQL Developer 提取许多指标。

ORCOALESCE 这两种方法中的哪一种更适合条件连接?从运行时复杂性等角度来看发生了什么?

【问题讨论】:

您可以将执行计划作为起点。但请注意,仅当您的第一个表不允许 Onum 和 Pnum 在同一行中为非空时,它们才等效。 OR 在这种情况下会同时匹配; COALESCE 只会匹配 Onum,这当然不会影响您的示例数据,但您的真实数据可能在两列中都有值。 我确定两栏都不能填;它只能是两者之一,这就是为什么我不确定ORCOALESCE 之间是否存在性能差异,而不使用运行时间作为可靠指标 【参考方案1】:

使用 coalesce(或 NVL)有一个好处,您可以利用 function-based indexes 来提高查询性能。

设置:

FSITJA@db01 2019-07-18 09:21:33> create table tblreqs (onum, pnum, reqnum primary key) as
  2  with t (onum, pnum, reqnum) as (
  3  select NULL, 'P427', 'RN1148' from dual union all
  4  select NULL, 'P324', 'RN1725' from dual union all
  5  select NULL, 'P229', 'RN1242' from dual union all
  6  select 'O396', NULL, 'RN1457' from dual union all
  7  select 'O380', NULL, 'RN1205' from dual union all
  8  select 'O258', NULL, 'RN1482' from dual
  9  ) select * from t;

Table created.

FSITJA@db01 2019-07-18 09:21:33> create table tblnums (nums primary key) as
  2  with t (nums)as (
  3  select 'O258' from dual union all
  4  select 'O370' from dual union all
  5  select 'O490' from dual union all
  6  select 'O314' from dual union all
  7  select 'O379' from dual union all
  8  select 'P341' from dual union all
  9  select 'P230' from dual union all
 10  select 'P280' from dual union all
 11  select 'P324' from dual union all
 12  select 'P395' from dual
 13  ) select * from t;

Table created.

请注意,由于条件连接(TBLREQS 上的 TABLE ACCESS STORAGE FULL),oracle 如何无法利用索引:

FSITJA@db01 2019-07-18 09:21:33> explain plan for
  2  SELECT n.nums,
  3         r.ReqNum
  4    FROM tblnums n
  5    LEFT JOIN tblReqs r ON n.nums = r.Onum OR n.nums = r.pnum;

Explained.

FSITJA@db01 2019-07-18 09:21:33> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 1571794044

-----------------------------------------------------------------------------------------------
| Id  | Operation                   | Name            | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |                 |    10 |   130 |    31   (0)| 00:00:01 |
|   1 |  NESTED LOOPS OUTER         |                 |    10 |   130 |    31   (0)| 00:00:01 |
|   2 |   INDEX FULL SCAN           | SYS_C0047401    |    10 |    50 |     1   (0)| 00:00:01 |
|   3 |   VIEW                      | VW_LAT_EB747914 |     1 |     8 |     3   (0)| 00:00:01 |
|*  4 |    TABLE ACCESS STORAGE FULL| TBLREQS         |     1 |    13 |     3   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - filter("R"."ONUM" IS NOT NULL AND "N"."NUMS"="R"."ONUM" OR "R"."PNUM" IS NOT
              NULL AND "N"."NUMS"="R"."PNUM")

17 rows selected.

现在,如果我们使用 coalesce 函数创建基于函数的索引,oracle 检测到该索引存在并将使用它来提高连接性能,而无需进行全表扫描(INDEX RANGE SCAN 对新创建的 IDX_COALESCE_ONUM_PNUM):

FSITJA@db01 2019-07-18 09:21:33> create index idx_coalesce_onum_pnum on tblreqs (coalesce(Onum, Pnum));

Index created.

FSITJA@db01 2019-07-18 09:21:33> explain plan for
  2  SELECT n.nums,
  3         r.ReqNum
  4    FROM tblnums n
  5    LEFT JOIN tblReqs r ON n.nums = COALESCE(r.Onum, r.Pnum);

Explained.

FSITJA@db01 2019-07-18 09:21:33> select * from table(dbms_xplan.display);

PLAN_TABLE_OUTPUT
------------------------------------------------------------------------------------------------------------------------------------
Plan hash value: 605824869

---------------------------------------------------------------------------------------------------------------
| Id  | Operation                            | Name                   | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT                     |                        |    10 |   160 |     2   (0)| 00:00:01 |
|   1 |  NESTED LOOPS OUTER                  |                        |    10 |   160 |     2   (0)| 00:00:01 |
|   2 |   INDEX FULL SCAN                    | SYS_C0047401           |    10 |    50 |     1   (0)| 00:00:01 |
|   3 |   TABLE ACCESS BY INDEX ROWID BATCHED| TBLREQS                |     1 |    11 |     1   (0)| 00:00:01 |
|*  4 |    INDEX RANGE SCAN                  | IDX_COALESCE_ONUM_PNUM |     1 |       |     0   (0)| 00:00:01 |
---------------------------------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------

   4 - access("N"."NUMS"="R"."SYS_NC00004$"(+))

16 rows selected.

【讨论】:

这是个好主意,我什至没有考虑过(也不知道该怎么做)。表中定义的Cost 指标是否取决于运行查询的 CPU? (我问是因为我试图证明一个与另一个的合理性) 成本确实考虑了诸如 IO、CPU 和网络访问以及数据库对象统计等因素。请注意,比较 2 个不同的 SQL 语句之间的成本是不可靠的。成本是 Oracle 所做的估计,仅在比较 SQL 执行计划的上下文中才有意义,以尝试在可用于一个特定 SQL 语句的所有计划中找出访问数据的路径将更快且消耗更少的资源。通常,优化器在这方面做得很好,但有时却做得不好,因为它会根据关于表数据的不良统计数据做出错误的假设。 这是关于 DBMS_XPLAN 的一些文档:docs.oracle.com/database/121/ARPLS/d_xplan.htm 默认情况下,Oracle 使用名为 PLAN_TABLE 的表来存储解释计划估计值。我刚刚使用了上面的 DBMS_XPLAN,因为它是了解 Oracle 在运行 SQL 时所做的事情的最快方法。【参考方案2】:

一般来说,函数和or 会阻碍优化。两者的性能应该非常相似。

我认为最好的方法可能是两个连接:

SELECT n.nums,
       COALESCE(ro.ReqNum, rp.ReqNum) as reqNum
FROM tblnums n LEFT JOIN
     tblReqs ro
     ON n.nums = ro.Onum LEFT JOIN
     tblReqs rp
     ON n.nums = rp.Pnum AND
                 ro.Onum IS NULL;  -- no match the first time

【讨论】:

以上是关于有条件地加入 - COALESCE 与 OR - Oracle SQL的主要内容,如果未能解决你的问题,请参考以下文章

Python 操作Redis

python爬虫入门----- 阿里巴巴供应商爬虫

Python词典设置默认值小技巧

《python学习手册(第4版)》pdf

Django settings.py 的media路径设置

Python中的赋值,浅拷贝和深拷贝的区别