Diferencias de Oracle entre NVL y Coalesce

208

¿Existen diferencias no obvias entre NVL y Coalesce en Oracle?

Las diferencias obvias son que la fusión devolverá el primer elemento no nulo en su lista de parámetros, mientras que nvl solo toma dos parámetros y devuelve el primero si no es nulo; de lo contrario, devuelve el segundo.

Parece que NVL puede ser solo una versión de 'Base Case' de fusión.

¿Me estoy perdiendo de algo?

Tom Hubbard
fuente

Respuestas:

312

COALESCEEs una función más moderna que forma parte del ANSI-92estándar.

NVLes Oracleespecífico, se introdujo en 80's antes de que hubiera estándares.

En el caso de dos valores, son sinónimos.

Sin embargo, se implementan de manera diferente.

NVLsiempre evalúa ambos argumentos, mientras que COALESCEgeneralmente detiene la evaluación cada vez que encuentra el primer no NULL(hay algunas excepciones, como la secuencia NEXTVAL):

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

Esto se ejecuta durante casi 0.5segundos, ya que genera SYS_GUID()'s', a pesar de 1no ser un NULL.

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

Esto comprende que 1no es un NULLy no evalúa el segundo argumento.

SYS_GUIDNo se generan y la consulta es instantánea.

Quassnoi
fuente
11
No son exactamente sinónimos ... Al menos puede encontrar una diferencia en el hecho de que NVL realiza una conversión de tipo de datos implícita si los valores dados son de diferentes tipos. Entonces, por ejemplo, recibí un error al usar COALESCE pasándole dos valores NULL (uno establecido explícitamente y el otro tomado de una columna en la base de datos, de tipo NUMBER), que simplemente desaparece al cambiar la función a NVL.
DanielM
170

NVL realizará una conversión implícita al tipo de datos del primer parámetro, por lo que lo siguiente no da error

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

COALESCE espera tipos de datos consistentes.

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

arrojará un 'error de tipo de datos inconsistente'

Gary Myers
fuente
22

NVL y COALESCE se utilizan para lograr la misma funcionalidad de proporcionar un valor predeterminado en caso de que la columna devuelva un NULL.

Las diferencias son:

  1. NVL acepta solo 2 argumentos, mientras que COALESCE puede tomar múltiples argumentos
  2. NVL evalúa los argumentos y COALESCE se detiene en la primera aparición de un valor no nulo.
  3. NVL realiza una conversión de tipo de datos implícita basada en el primer argumento que se le da. COALESCE espera que todos los argumentos sean del mismo tipo de datos.
  4. COALESCE da problemas en consultas que utilizan cláusulas UNION. Ejemplo a continuación
  5. COALESCE es el estándar ANSI, ya que NVL es específico de Oracle.

Ejemplos para el tercer caso. Otros casos son simples.

select nvl('abc',10) from dual; funcionaría ya que NVL hará una conversión implícita de 10 numérico a cadena.

select coalesce('abc',10) from dual; fallará con Error - tipos de datos inconsistentes: CHAR esperado obtuvo NUMBER

Ejemplo para el caso de uso de UNION

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

falla con 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
      ) ;

tiene éxito

Más información: http://www.plsqlinformation.com/2016/04/difference-between-nvl-and-coalesce-in-oracle.html

Brahmareddy K
fuente
No creo que haya un problema específico con la "unión", por lo que parece que Oracle quiere escribir cast nulo en su subconsulta a un carácter por defecto y luego tiene el mismo problema que aparece en su elemento 3 (datos mixtos tipos). Si lo cambia a TO_DATE (NULL), probablemente no obtendrá el error (no puedo reproducir el error en la versión de Oracle que estoy usando). De lo contrario, estoy de acuerdo y agradezco su respuesta. :-)
splashout
17

También hay diferencia en el manejo del plan.

Oracle puede formar un plan optimizado con concatenación de filtros de rama cuando la búsqueda contiene una comparación de nvlresultados con una columna indexada.

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)

juntarse:

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

Los créditos van a http://www.xt-r.com/2012/03/nvl-coalesce-concatenation.html .

Vadzim
fuente
6

Otra prueba de que coalesce () no detiene la evaluación con el primer valor no nulo:

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

Ejecute esto, luego verifique my_sequence.currval;

Herb Swift
fuente
5

En realidad no puedo aceptar cada declaración.

"COALESCE espera que todos los argumentos sean del mismo tipo de datos".

Esto está mal, ver abajo. Los argumentos pueden ser diferentes tipos de datos, eso también está documentado : si todas las apariciones de expr son tipos de datos numéricos o cualquier tipo de datos no numéricos que pueden convertirse implícitamente en un tipo de datos numéricos, Oracle Database determina el argumento con la mayor prioridad numérica, implícitamente convierte los argumentos restantes a ese tipo de datos y devuelve ese tipo de datos. . En realidad, esto está en contradicción con la expresión común "COALESCE se detiene en la primera aparición de un valor no nulo", de lo contrario, el caso de prueba No. 4 no debería generar un error.

También de acuerdo con el caso de prueba No. 5 COALESCErealiza una conversión implícita de argumentos.

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!
Wernfried Domscheit
fuente
1
Re: La prueba 4 contradice "COALESCE detiene la evaluación en el primer valor no nulo" . Estoy en desacuerdo. La prueba 4 muestra que el compilador verifica la coherencia del tipo de datos con COALESCE. Parar en el primer valor no nulo es un problema de tiempo de ejecución, no un problema de tiempo de compilación. En el momento de la compilación, el compilador no sabe que el tercer valor (por ejemplo) será no nulo; insiste en que el cuarto argumento sea del tipo de datos correcto también, incluso si ese cuarto valor nunca se evaluará realmente.
Mathguy
3

Aunque este es obvio, e incluso mencionado de una manera presentada por Tom, quien hizo esta pregunta. Pero vamos a aguantar de nuevo.

NVL solo puede tener 2 argumentos. La fusión puede tener más de 2.

select nvl('','',1) from dual;// Resultado:: ORA-00909número inválido de argumentos
select coalesce('','','1') from dual; // Salida: devuelve 1

Neel
fuente
3

NVL: Reemplace el nulo con valor.

COALESCE: Devuelve la primera expresión no nula de la lista de expresiones.

Tabla: PRICE_LIST

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

A continuación se muestra el ejemplo de

[1] Establecer el precio de venta al agregar un 10% de ganancia a todos los productos.
[2] Si no hay un precio de lista de compra, el precio de venta es el precio mínimo. Para liquidación de venta.
[3] Si tampoco hay un precio mínimo, establezca el precio de venta como precio predeterminado "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

Explicar con un ejemplo práctico de la vida real.

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

Puede ver que con NVL podemos lograr las reglas [1], [2]
Pero con COALSECE podemos lograr las tres reglas.

sandip
fuente
lo que se dice acerca NVL(Purchase_Price + (Purchase_Price * 0.10), nvl(Min_Price,50)) . O sobre: nvl(NVL(Purchase_Price + (Purchase_Price * 0.10), Min_Price) ,50) :)
Florin Ghita
¿cuál es más rápido, en cuanto al rendimiento, qué se debe usar? considerando miles de registros para cargar?
rickyProgrammer