ความแตกต่างของ Oracle ระหว่าง NVL และ Coalesce


208

มีความแตกต่างที่ชัดเจนระหว่าง NVL และ Coalesce ใน Oracle หรือไม่

ความแตกต่างที่ชัดเจนคือการรวมกันจะส่งคืนรายการที่ไม่ใช่โมฆะแรกในรายการพารามิเตอร์ในขณะที่ nvl รับเพียงสองพารามิเตอร์และคืนแรกถ้ามันไม่เป็นโมฆะมิฉะนั้นจะส่งคืนที่สอง

ดูเหมือนว่า NVL อาจเป็นเวอร์ชั่นรวมของ 'Base Case "

ฉันพลาดอะไรไปรึเปล่า?


เพิ่มเติมได้ที่นี่: jonathanlewis.wordpress.com/2018/02/13/coalesce-v-nvl
William Robertson

คำตอบ:


312

COALESCEเป็นฟังก์ชั่นที่ทันสมัยกว่าซึ่งเป็นส่วนหนึ่งของANSI-92มาตรฐาน

NVLเป็นOracleที่เฉพาะเจาะจงก็เป็นที่รู้จักใน80'ก่อนที่จะมีมาตรฐานใด

ในกรณีที่มีค่าสองค่าจะมีความหมายเหมือนกัน

อย่างไรก็ตามมีการใช้งานแตกต่างกัน

NVLมักจะประเมินทั้งสองข้อโต้แย้งในขณะที่COALESCEมักจะหยุดการประเมินเมื่อใดก็ตามที่พบไม่ใช่คนแรกNULL(มีข้อยกเว้นบางอย่างเช่นลำดับNEXTVAL):

SELECT  SUM(val)
FROM    (
        SELECT  NVL(1, LENGTH(RAWTOHEX(SYS_GUID()))) AS val
        FROM    dual
        CONNECT BY
                level <= 10000
        )

วิ่งนี้เกือบ0.5วินาทีเพราะมันสร้างSYS_GUID()'s แม้จะเป็นไม่ได้เป็น1NULL

SELECT  SUM(val)
FROM    (
        SELECT  COALESCE(1, LENGTH(RAWTOHEX(SYS_GUID()))) AS val
        FROM    dual
        CONNECT BY
                level <= 10000
        )

สิ่งนี้เข้าใจว่า1ไม่ใช่NULLและไม่ได้ประเมินอาร์กิวเมนต์ที่สอง

SYS_GUIDไม่ได้สร้างและแบบสอบถามเป็นทันที


11
พวกมันไม่ใช่คำพ้องความหมาย ... อย่างน้อยคุณก็จะพบความแตกต่างในความจริงที่ว่า NVL สร้างการส่งข้อมูลโดยนัยหากค่าที่กำหนดเป็นประเภทที่แตกต่างกัน ตัวอย่างเช่นฉันได้รับข้อผิดพลาดโดยใช้ COALESCE ผ่านค่า NULL สองค่า (ค่าหนึ่งถูกตั้งค่าไว้อย่างชัดเจนและอีกค่าที่นำมาจากคอลัมน์ในฐานข้อมูลประเภท NUMBER) ที่เพิ่งหายไปโดยการเปลี่ยนฟังก์ชันเป็น NVL
DanielM

170

NVL จะทำการแปลงประเภทข้อมูลของพารามิเตอร์แรกโดยปริยายดังนั้นสิ่งต่อไปนี้จะไม่เกิดข้อผิดพลาด

select nvl('a',sysdate) from dual;

COALESCE คาดว่าประเภทข้อมูลที่สอดคล้องกัน

select coalesce('a',sysdate) from dual;

จะโยน 'ข้อผิดพลาดประเภทข้อมูลที่ไม่สอดคล้องกัน'


22

NVL และ COALESCE ใช้เพื่อให้บรรลุหน้าที่การทำงานเดียวกันในการจัดเตรียมค่าเริ่มต้นในกรณีที่คอลัมน์ส่งคืนค่า NULL

ความแตกต่างคือ:

  1. NVL ยอมรับข้อโต้แย้งเพียง 2 ข้อเท่านั้นในขณะที่ COALESCE สามารถรับได้หลายอาร์กิวเมนต์
  2. NVL จะประเมินทั้งการขัดแย้งและการหยุด COALESCE ที่การเกิดขึ้นครั้งแรกของค่าที่ไม่ใช่ค่าว่าง
  3. NVL ทำการแปลงประเภทข้อมูลโดยนัยตามอาร์กิวเมนต์แรกที่กำหนด COALESCE คาดว่าอาร์กิวเมนต์ทั้งหมดจะเป็นประเภทข้อมูลเดียวกัน
  4. COALESCE ให้ปัญหาในการสืบค้นที่ใช้คำสั่ง UNION ตัวอย่างด้านล่าง
  5. COALESCE เป็นมาตรฐาน ANSI โดยที่ NVL นั้นเฉพาะกับ Oracle

ตัวอย่างสำหรับกรณีที่สาม กรณีอื่นนั้นง่าย

select nvl('abc',10) from dual; จะทำงานเป็น NVL จะทำการแปลงโดยปริยายของตัวเลข 10 ถึงสตริง

select coalesce('abc',10) from dual; จะล้มเหลวด้วยข้อผิดพลาด - ประเภทข้อมูลที่ไม่สอดคล้องกัน: CHAR ที่คาดไว้มี NUMBER

ตัวอย่างสำหรับ UNION use-case

SELECT COALESCE(a, sysdate) 
from (select null as a from dual 
      union 
      select null as a from dual
      );

ล้มเหลวด้วย ORA-00932: inconsistent datatypes: expected CHAR got DATE

SELECT NVL(a, sysdate) 
from (select null as a from dual 
      union 
      select null as a from dual
      ) ;

ประสบความสำเร็จ

ข้อมูลเพิ่มเติม: http://www.plsqlinformation.com/2016/04/difference-between-nvl-and-coalesce-in-oracle.html


ฉันไม่คิดว่าจะมีปัญหาเฉพาะกับ "ยูเนี่ยน" มากเกินไปดูเหมือนว่า Oracle ต้องการที่จะพิมพ์โพลล์โมฆะในตัวคุณแบบสอบถามย่อยเพื่อถ่านโดยค่าเริ่มต้นแล้วคุณมีปัญหาเดียวกันอยู่ในรายการของคุณ 3 (ข้อมูลผสม ชนิด) หากคุณเปลี่ยนเป็น TO_DATE (NULL) คุณอาจไม่ได้รับข้อผิดพลาด (ฉันไม่สามารถทำซ้ำข้อผิดพลาดในเวอร์ชันของ Oracle ที่ฉันใช้อยู่) มิฉะนั้นฉันก็เห็นด้วยและขอบคุณคำตอบของคุณ :-)
splashout

17

นอกจากนี้ยังมีความแตกต่างในการจัดการแผน

ออราเคิลสามารถจัดทำแผนการปรับให้เหมาะสมพร้อมการต่อฟิลเตอร์สาขาเมื่อการค้นหามีการเปรียบเทียบnvlผลลัพธ์กับคอลัมน์ที่จัดทำดัชนี

create table tt(a, b) as
select level, mod(level,10)
from dual
connect by level<=1e4;

alter table tt add constraint ix_tt_a primary key(a);
create index ix_tt_b on tt(b);

explain plan for
select * from tt
where a=nvl(:1,a)
  and b=:2;

explain plan for
select * from tt
where a=coalesce(:1,a)
  and b=:2;

NVL:

-----------------------------------------------------------------------------------------
| Id  | Operation                     | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
-----------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT              |         |     2 |    52 |     2   (0)| 00:00:01 |
|   1 |  CONCATENATION                |         |       |       |            |          |
|*  2 |   FILTER                      |         |       |       |            |          |
|*  3 |    TABLE ACCESS BY INDEX ROWID| TT      |     1 |    26 |     1   (0)| 00:00:01 |
|*  4 |     INDEX RANGE SCAN          | IX_TT_B |     7 |       |     1   (0)| 00:00:01 |
|*  5 |   FILTER                      |         |       |       |            |          |
|*  6 |    TABLE ACCESS BY INDEX ROWID| TT      |     1 |    26 |     1   (0)| 00:00:01 |
|*  7 |     INDEX UNIQUE SCAN         | IX_TT_A |     1 |       |     1   (0)| 00:00:01 |
-----------------------------------------------------------------------------------------

Predicate Information (identified by operation id):
---------------------------------------------------
   2 - filter(:1 IS NULL)
   3 - filter("A" IS NOT NULL)
   4 - access("B"=TO_NUMBER(:2))
   5 - filter(:1 IS NOT NULL)
   6 - filter("B"=TO_NUMBER(:2))
   7 - access("A"=:1)

รวมกัน:

---------------------------------------------------------------------------------------
| Id  | Operation                   | Name    | Rows  | Bytes | Cost (%CPU)| Time     |
---------------------------------------------------------------------------------------
|   0 | SELECT STATEMENT            |         |     1 |    26 |     1   (0)| 00:00:01 |
|*  1 |  TABLE ACCESS BY INDEX ROWID| TT      |     1 |    26 |     1   (0)| 00:00:01 |
|*  2 |   INDEX RANGE SCAN          | IX_TT_B |    40 |       |     1   (0)| 00:00:01 |
---------------------------------------------------------------------------------------

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

   1 - filter("A"=COALESCE(:1,"A"))
   2 - access("B"=TO_NUMBER(:2))

เครดิตไปhttp://www.xt-r.com/2012/03/nvl-coalesce-concatenation.html


6

หลักฐานอีกประการที่เชื่อมโยงกัน () ไม่หยุดการประเมินด้วยค่าที่ไม่ใช่ค่าว่างแรก:

SELECT COALESCE(1, my_sequence.nextval) AS answer FROM dual;

เรียกใช้สิ่งนี้จากนั้นตรวจสอบ my_sequence.currval;


5

ที่จริงฉันไม่สามารถเห็นด้วยกับแต่ละคำสั่ง

"COALESCE ต้องการให้อาร์กิวเมนต์ทั้งหมดเป็นประเภทข้อมูลเดียวกัน"

สิ่งนี้ผิดดูด้านล่าง อาร์กิวเมนต์อาจเป็นชนิดข้อมูลที่แตกต่างกันซึ่งมีการบันทึกไว้ด้วย : หากการเกิดขึ้นของ expr ทั้งหมดเป็นชนิดข้อมูลตัวเลขหรือชนิดข้อมูลที่ไม่ใช่ตัวเลขใด ๆ ที่สามารถแปลงเป็นชนิดข้อมูลตัวเลขโดยปริยาย Oracle Database จะกำหนดอาร์กิวเมนต์ด้วยลำดับความสำคัญสูงสุด แปลงอาร์กิวเมนต์ที่เหลือเป็นชนิดข้อมูลนั้นและส่งกลับชนิดข้อมูลนั้น. ที่จริงนี่คือแม้จะขัดแย้งกับนิพจน์ทั่วไป "COALESCE หยุดที่เกิดขึ้นครั้งแรกของค่าที่ไม่ใช่ค่า Null" มิฉะนั้นกรณีทดสอบหมายเลข 4 ไม่ควรเกิดข้อผิดพลาด

นอกจากนี้ตามกรณีทดสอบหมายเลข 5 COALESCEทำการแปลงข้อโต้แย้งโดยนัย

DECLARE
    int_val INTEGER := 1;
    string_val VARCHAR2(10) := 'foo';
BEGIN

    BEGIN
    DBMS_OUTPUT.PUT_LINE( '1. NVL(int_val,string_val) -> '|| NVL(int_val,string_val) );
    EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('1. NVL(int_val,string_val) -> '||SQLERRM ); 
    END;

    BEGIN
    DBMS_OUTPUT.PUT_LINE( '2. NVL(string_val, int_val) -> '|| NVL(string_val, int_val) );
    EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('2. NVL(string_val, int_val) -> '||SQLERRM ); 
    END;

    BEGIN
    DBMS_OUTPUT.PUT_LINE( '3. COALESCE(int_val,string_val) -> '|| COALESCE(int_val,string_val) );
    EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('3. COALESCE(int_val,string_val) -> '||SQLERRM ); 
    END;

    BEGIN
    DBMS_OUTPUT.PUT_LINE( '4. COALESCE(string_val, int_val) -> '|| COALESCE(string_val, int_val) );
    EXCEPTION WHEN OTHERS THEN DBMS_OUTPUT.PUT_LINE('4. COALESCE(string_val, int_val) -> '||SQLERRM ); 
    END;

    DBMS_OUTPUT.PUT_LINE( '5. COALESCE(SYSDATE,SYSTIMESTAMP) -> '|| COALESCE(SYSDATE,SYSTIMESTAMP) );

END;
Output:

1. NVL(int_val,string_val) -> ORA-06502: PL/SQL: numeric or value error: character to number conversion error
2. NVL(string_val, int_val) -> foo
3. COALESCE(int_val,string_val) -> 1
4. COALESCE(string_val, int_val) -> ORA-06502: PL/SQL: numeric or value error: character to number conversion error
5. COALESCE(SYSDATE,SYSTIMESTAMP) -> 2016-11-30 09:55:55.000000 +1:0 --> This is a TIMESTAMP value, not a DATE value!

1
Re: ทดสอบ 4 ตรงกันข้าม "เชื่อมต่อกันหยุดการประเมินผลในครั้งแรกค่า null ไม่ใช่" ฉันไม่เห็นด้วย. การทดสอบ 4 แสดงว่าคอมไพเลอร์ตรวจสอบความสอดคล้องของชนิดข้อมูลด้วย COALESCE การหยุดที่ค่าที่ไม่ใช่ค่าแรกนั้นเป็นปัญหารันไทม์ไม่ใช่ปัญหาเวลาคอมไพล์ ณ เวลารวบรวมผู้รวบรวมไม่ทราบว่าค่าที่สาม (พูด) จะไม่เป็นโมฆะ มันยืนยันว่าอาร์กิวเมนต์ที่สี่เป็นชนิดข้อมูลที่ถูกต้องเช่นกันแม้ว่าค่าที่สี่จะไม่ถูกประเมินจริง
mathguy

3

แม้ว่าสิ่งนี้จะชัดเจนและถึงกับพูดถึงวิธีที่ทอมตั้งคำถามนี้ แต่ขอวางอีกครั้ง

NVL สามารถมีอาร์กิวเมนต์ได้ 2 ข้อเท่านั้น เชื่อมต่อกันอาจมีมากกว่า 2

select nvl('','',1) from dual;// ผลลัพธ์:: ORA-00909จำนวนอาร์กิวเมนต์ไม่ถูกต้อง
select coalesce('','','1') from dual; // ผลลัพธ์: ส่งคืน 1


3

NVL:แทนที่ค่าว่างด้วยค่า

เชื่อมต่อกัน:ส่งคืนนิพจน์ที่ไม่ใช่ค่า null แรกจากรายการนิพจน์

ตาราง: PRICE_LIST

+----------------+-----------+
| Purchase_Price | Min_Price |
+----------------+-----------+
| 10             | null      |
| 20             |           |
| 50             | 30        |
| 100            | 80        |
| null           | null      |
+----------------+-----------+   

ด้านล่างเป็นตัวอย่างของ

[1] กำหนดราคาขายพร้อมเพิ่มผลกำไร 10% ให้กับทุกผลิตภัณฑ์
[2] หากไม่มีรายการราคาซื้อราคาขายจะเป็นราคาขั้นต่ำ สำหรับการขายการกวาดล้าง
[3] หากไม่มีราคาขั้นต่ำให้ตั้งราคาขายเป็นราคาเริ่มต้น "50"

SELECT
     Purchase_Price,
     Min_Price,
     NVL(Purchase_Price + (Purchase_Price * 0.10), Min_Price)    AS NVL_Sales_Price,
COALESCE(Purchase_Price + (Purchase_Price * 0.10), Min_Price,50) AS Coalesce_Sales_Price
FROM 
Price_List

อธิบายด้วยตัวอย่างจริงในทางปฏิบัติในชีวิตจริง

+----------------+-----------+-----------------+----------------------+
| Purchase_Price | Min_Price | NVL_Sales_Price | Coalesce_Sales_Price |
+----------------+-----------+-----------------+----------------------+
| 10             | null      | 11              |                   11 |
| null           | 20        | 20              |                   20 |
| 50             | 30        | 55              |                   55 |
| 100            | 80        | 110             |                  110 |
| null           | null      | null            |                   50 |
+----------------+-----------+-----------------+----------------------+

คุณจะเห็นว่าด้วย NVL เราสามารถบรรลุกฎ [1], [2]
แต่ด้วย COALSECE เราสามารถบรรลุกฎทั้งสามได้


NVL(Purchase_Price + (Purchase_Price * 0.10), nvl(Min_Price,50)) สิ่งที่คุณพูดเกี่ยวกับ หรือประมาณ: nvl(NVL(Purchase_Price + (Purchase_Price * 0.10), Min_Price) ,50) :)
Florin Ghita

สิ่งใดเร็วกว่าควรใช้ประสิทธิภาพอย่างชาญฉลาด กำลังพิจารณาโหลดหลายพันระเบียนหรือไม่
rickyProgrammer
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.