SQL no es C ++ ni JavaScript. Por lo tanto, el cálculo de las expresiones lógicas es diferente, y esto no es lo mismo:WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
En el proceso de optimización del plan de ejecución de la consulta, PostgreSQL puede "reorganizar" arbitrariamente las condiciones equivalentes , no calcular ninguna de ellas para registros individuales, asignarlas a la condición del índice aplicable ... En resumen, es más fácil asumir que no puede controlar en qué orden Se calcularán las condiciones de los pares (y si es que lo hay) .Por lo tanto, si aún desea controlar la prioridad, debe hacer que estas condiciones sean desiguales estructuralmente utilizando expresiones y operadores condicionales .Los datos y trabajar con ellos son la base de nuestro complejo VLSI , por lo que es muy importante para nosotros que las operaciones en ellos se realicen no solo correctamente, sino también de manera eficiente. Veamos ejemplos específicos en los que se pueden cometer errores de cálculo de expresiones y donde vale la pena mejorar su eficiencia.# 0: RTFM
Ejemplo de
partida de la documentación :Cuando el orden de cálculo es importante, se puede arreglar usando la construcción CASE
. Por ejemplo, tal forma de evitar dividir por cero en una oración WHERE
no es confiable:
SELECT ... WHERE x > 0 AND y/x > 1.5;
Opción segura:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
La construcción utilizada de esta manera CASE
protege la expresión de la optimización, por lo tanto, solo debe usarse si es necesario.
# 1: condición en el disparador
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
Parece que todo se ve bien, pero ... Nadie promete que lo adjunto SELECT
no se ejecutará si la primera condición es falsa. Corregir usando anidadoIF
:BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
Ahora veamos cuidadosamente: todo el cuerpo de la función de disparo resultó estar "envuelto" IF
. Y esto significa que nada nos impide eliminar esta condición del procedimiento usando la WHEN
condición :BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
Este enfoque permite el ahorro garantizado de los recursos del servidor en condiciones falsas.# 2: cadena OR / AND
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
De lo contrario, puede obtener que ambos EXISTS
serán "verdaderos", pero ambos se cumplirán .Pero si sabemos con certeza que uno de ellos es "verdadero" con mucha más frecuencia (o "falso" para la AND
cadena), ¿es posible de alguna manera "aumentar su prioridad" para que el segundo no se realice una vez más?Resulta que es posible: el enfoque del algoritmo está cerca del tema del artículo PostgreSQL Antipatterns: un registro raro llegará a la mitad de JOIN .Simplemente "pop en CASO" estas dos condiciones:SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END
En este caso, no determinamos el ELSE
valor -es decir, si ambas condiciones son falsas , CASE
regresará NULL
, lo que se interpreta como FALSE
en la WHERE
condición-.Este ejemplo se puede combinar de otra manera: para saborear y colorear:SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END
# 3: cómo [no] escribir condiciones
Pasamos dos días analizando los motivos del desencadenante "extraño" de este desencadenante. Veamos por qué.Fuente:IF( NEW."_" is null or NEW."_" = (select '""'::regclass::oid) or NEW."_" = (select to_regclass('""')::oid)
AND ( OLD."" <> NEW.""
OR OLD."" <> NEW.""
OR OLD."" <> NEW.""
OR OLD."" <> NEW.""
OR OLD."" <> NEW."" ) ) THEN ...
Problema # 1: la desigualdad no considera NULL
Imagina que todos los OLD
campos importan NULL
. ¿Lo que sucederá?SELECT NULL <> 1 OR NULL <> 2;
Y desde el punto de vista de resolver las condiciones es NULL
equivalente FALSE
, como se mencionó anteriormente.Solución : utilice el operador IS DISTINCT FROM
del operador ROW
, comparando registros completos a la vez:SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
Problema número 2: implementación diferente de la misma funcionalidad
Comparar:NEW."_" = (select '""'::regclass::oid)
NEW."_" = (select to_regclass('""')::oid)
¿Por qué hay anidamiento adicional SELECT
? ¿Qué hay de la función to_regclass
? Y de diferentes maneras, ¿por qué? ..Solución:NEW."_" = '""'::regclass::oid
NEW."_" = '""'::regclass::oid
Problema # 3: prioridad de operaciones bool
Formatear la fuente:{... IS NULL} OR
{... } OR
{... } AND
( {... } )
Vaya ... De hecho, resultó que en el caso de la verdad de cualquiera de las dos primeras condiciones, toda la condición se convierte TRUE
, sin tener en cuenta las desigualdades. Y esto no es en absoluto lo que queríamos.Reparar:(
{... IS NULL} OR
{... } OR
{... }
) AND
( {... } )
Problema 4 (pequeño): condición OR compleja para un campo
En realidad, los problemas en el número 3 surgieron precisamente porque había tres condiciones. Pero en lugar de ellos, puedes hacer uno, usando el mecanismo coalesce ... IN
:coalesce(NEW."_"::text, '') IN ('', '""', '""')
Así que NULL
"atrapamos", y complejos OR
con paréntesis no tienen que cercar.Total
Arreglamos lo que tenemos:IF (
coalesce(NEW."_"::text, '') IN ('', '""', '""') AND
(
OLD.""
, OLD.""
, OLD.""
, OLD.""
, OLD.""
) IS DISTINCT FROM (
NEW.""
, NEW.""
, NEW.""
, NEW.""
, NEW.""
)
) THEN ...
Y si tenemos en cuenta que esta función de disparo solo se puede usar en el UPDATE
disparador debido a la presencia de OLD/NEW
un nivel superior en la condición, entonces esta condición generalmente se puede poner en la WHEN
condición -como se mostró en el # 1 ...