Oracle Ref Cursor Vs Select into con manejo de excepciones
Pregunta
Tengo un par de escenarios:
-
Necesita leer el valor de una columna de tres tablas diferentes en un orden predefinido y solo 1 tabla tendrá los datos
-
Lea los datos de la tabla1 si los registros están presentes para los criterios dados o lea los datos de la Tabla2 para los criterios dados
En procedimientos almacenados de Oracle
La forma en que se manejan en este momento es obtener primero el recuento de una consulta determinada en una variable, y si el recuento > 0, luego ejecutamos la misma consulta para leer los datos reales que en:
select count(*) from table1 into v_count
if v_count > 0
then
select data into v_data from table1
end if;
Return v_data
Esto se está haciendo para evitar la excepción no_data_found, de lo contrario necesitaría tres bloques de manejador de excepciones para detectar la excepción no_data_found para cada acceso a la tabla.
Actualmente estoy reimplementando esto con los cursores para tener algo como esto:
cursor C1 is
select data from table1;
Open C1
Fetch C1 into v_data
if C1%FOUND
then
Close C1
Return v_data
End If
Quería averiguar cuál es mejor desde el punto de vista del rendimiento: el que tiene cursores o el que hace una selección en una variable y tiene tres bloques de excepción no_data_found. No quiero usar el proceso de consulta en dos etapas que tenemos actualmente.
Solución
No sé por qué estás tan interesado en evitar la excepción. Lo que está mal con:
begin
begin
select data into v_data from table1;
exception
when no_data_found then
begin
select data into v_data from table2;
exception
when no_data_found then
begin
select data into v_data from table3;
exception
when no_data_found then
v_data := null;
end;
end;
end;
return v_data;
end;
Creo que esto funcionará mejor que su otra solución porque hace el mínimo trabajo posible para lograr el resultado deseado.
Consulte ¿Qué tan malo es ignorar la excepción Oracle DUP_VAL_ON_INDEX? donde yo demostrar que el uso de excepciones funciona mejor que contar para ver si hay datos.
Otros consejos
select count(*) from table1 into v_count
if v_count > 0 then
select data into v_data from table1;
else
v_data := null;
end if;
return v_data;
NO es equivalente a
begin
select data into v_data from table1;
return v_data;
exception
when no_data_found then
return null;
end;
en un entorno multiusuario. En el primer caso, alguien podría actualizar la tabla entre los puntos donde verifica la existencia y cuando lee los datos.
En cuanto al rendimiento, no tengo idea de cuál es mejor, pero sé que la primera opción hace dos cambios de contexto al motor sql y la segunda solo hace un cambio de contexto.
La forma en que maneja el escenario 1 ahora no es buena. No solo está haciendo dos consultas cuando una será suficiente, sino que, como señaló Erik, abre la posibilidad de que los datos cambien entre las dos consultas (a menos que use una transacción de solo lectura o serializable).
Dado que usted dice que en este caso los datos estarán exactamente en una de las tres tablas, ¿qué tal esto?
SELECT data
INTO v_data FROM
(SELECT data FROM table1
UNION ALL
SELECT data FROM table2
UNION ALL
SELECT data FROM table3
)
Otro "truco" puede evitar escribir múltiples manejadores sin datos:
SELECT MIN(data) INTO v_data FROM table1;
IF v_data IS NOT NULL THEN
return v_data;
END IF;
SELECT MIN(data) INTO v_data FROM table2;
...etc...
pero realmente no veo ninguna razón que sea mejor que tener tres manejadores de excepciones.
Para su segundo escenario, creo que lo que quiere decir es que puede haber datos en ambas tablas y desea usar los datos de la tabla1 si está presente, de lo contrario, use los datos de la tabla 2. Una vez más, podría hacer esto en una sola consulta:
SELECT data
INTO v_data FROM
(SELECT data FROM
(SELECT 1 sort_key, data FROM table1
UNION ALL
SELECT 2 sort_key, data FROM table2
)
ORDER BY sort_key ASC
)
WHERE ROWNUM = 1
Una versión mejorada de la opción MIN de Dave Costa ...
SELECT COUNT(1), MIN(data) INTO v_rowcount, v_data FROM table2;
Ahora se puede verificar v_rowcount
para los valores 0, > 1 (mayor que 1) donde la consulta de selección normal arrojará NO_DATA_FOUND
o TOO_MANY_ROWS
excepción. Valor " 1 " indicará que existe exactamente una fila y servirá a nuestro propósito.
DECLARE
A VARCHAR(35);
B VARCHAR(35);
BEGIN
WITH t AS
(SELECT OM_MARCA, MAGAZIA FROM ifsapp.AKER_EFECTE_STOC WHERE (BARCODE = 1000000491009))
SELECT
(SELECT OM_MARCA FROM t) OM_MARCA,
(SELECT MAGAZIA FROM t) MAGAZIA
INTO A, B
FROM DUAL;
IF A IS NULL THEN
dbms_output.put_line('A este null');
END IF;
dbms_output.put_line(A);
dbms_output.put_line(B);
END;
/
Use la " para fila en el cursor " forma de un bucle y el bucle simplemente no se procesará si no hay datos:
declare cursor
t1Cur is
select ... from table1;
t2Cur is
select ... from table2;
t3Cur is
select ... from table3;
t1Flag boolean FALSE;
t2Flag boolean FALSE;
t3Flag boolean FALSE;
begin
for t1Row in t1Cur loop
... processing, set t1Flag = TRUE
end loop;
for t2Row in t2Cur loop
... processing, set t2Flag = TRUE
end loop;
for t3Row in t3Cur loop
... processing, set t3Flag = TRUE
end loop;
... conditional processing based on flags
end;