Различия Oracle между NVL и Coalesce

208

Есть ли неочевидные различия между NVL и Coalesce в Oracle?

Очевидные различия заключаются в том, что coalesce вернет первый ненулевой элемент в своем списке параметров, тогда как nvl принимает только два параметра и возвращает первый, если он не нулевой, в противном случае он возвращает второй.

Кажется, что NVL может быть просто «базовым» вариантом объединения.

Я что-то упускаю?

Том Хаббард
источник
Больше здесь: jonathanlewis.wordpress.com/2018/02/13/coalesce-v-nvl
Уильям Робертсон,

Ответы:

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, хотя 1и не является NULL.

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

Это понимает, что 1не является NULLи не оценивает второй аргумент.

SYS_GUIDне генерируются и запрос мгновенный.

Quassnoi
источник
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

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). В противном случае я согласен и ценю ваш ответ. :-)
выплеск
17

Существует также разница в обработке плана.

Oracle может формировать оптимизированный план с конкатенацией фильтров ветвлений, когда поиск содержит сравнение 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)

COALESCE:

---------------------------------------------------------------------------------------
| 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 .

Vadzim
источник
6

Еще одно доказательство того, что coalesce () не останавливает вычисление с первым ненулевым значением:

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

Запустите это, затем проверьте my_sequence.currval;

Херб Свифт
источник
5

На самом деле я не могу согласиться с каждым утверждением.

«COALESCE ожидает, что все аргументы будут одного типа».

Это неправильно, см. Ниже. Аргументы могут быть разных типов данных, что также задокументировано : если все вхождения expr являются числовым типом данных или любым нечисловым типом данных, который может быть неявно преобразован в числовой тип данных, то Oracle Database определяет аргумент с наивысшим числовым приоритетом, неявно преобразует оставшиеся аргументы в этот тип данных и возвращает этот тип данных. , На самом деле это даже противоречит общему выражению «COALESCE останавливается при первом появлении ненулевого значения», в противном случае контрольный пример № 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 противоречит "COALESCE останавливает оценку при первом ненулевом значении" . Я не согласен. Тест 4 показывает, что компилятор проверяет согласованность типов данных с COALESCE. Остановка на первом ненулевом значении - это проблема времени выполнения, а не проблема времени компиляции. Во время компиляции компилятор не знает, что третье значение (скажем) будет ненулевым; он настаивает на том, чтобы четвертый аргумент также имел правильный тип данных, даже если это четвертое значение фактически никогда не будет оценено.
mathguy
3

Хотя это очевидно, и даже упомянуто в том виде, в каком он был поставлен Томом, который задал этот вопрос. Но давайте снова смиримся.

NVL может иметь только 2 аргумента. Коалесция может иметь более 2.

select nvl('','',1) from dual;// Результат:: ORA-00909неверное количество аргументов
select coalesce('','','1') from dual; // Вывод: возвращает 1

Ниль
источник
3

NVL: заменить ноль на значение.

COALESCE: вернуть первое ненулевое выражение из списка выражений.

Таблица: 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) :)
Флорин Гита
что быстрее, с точки зрения производительности, что следует использовать? учитывая тысячи записей для загрузки?
rickyProgrammer