PostgreSQL Antipatterns: cálculo de condiciones en SQL.

SQL no es ni C++ ni JavaScript. Por lo tanto, la evaluación de las expresiones lógicas se lleva a cabo de manera diferente, y esto no es lo mismo en absoluto:

WHERE fncondX() AND fncondY()

= fncondX() && fncondY()

Durante el proceso de optimización del plan de ejecución de una consulta, PostgreSQL puede reordenar libremente las condiciones equivalentes, no calcular algunas de ellas para registros individuales, asignarlas a la condición del índice aplicado… En resumen, es más fácil considerar que no puedes gestionar el orden en que se evaluarán (y si se evaluarán en absoluto) las condiciones equivalentes. Así que si realmente deseas gestionar la prioridad, es necesario estructuralmente hacer que estas condiciones sean desiguales

a través de expresiones condicionales. Los datos y su manejo son la base de nuestro complejo SBIS, por lo que es muy importante para nosotros que las operaciones sobre ellos se realicen no solo de manera correcta, sino también eficiente. Veamos ejemplos concretos donde pueden ocurrir errores en la evaluación de expresiones y dónde debemos mejorar su eficiencia. y operadores.

PostgreSQL Antipatterns: cálculo de condiciones en SQL.
Inicial Cuando el orden de evaluación es importante, se puede fijar utilizando la construcciónCASE.

#0: RTFM

Por ejemplo, esta forma de evitar la división por cero en la cláusula ejemplo de la documentación:

es poco confiable: SELECT ... WHERE x > 0 AND y/x > 1.5;Una opción segura: WHERE SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;

La construcción utilizada de esta manera

protege la expresión de la optimización, por lo que debe usarse solo cuando sea necesario.

BEGIN
  IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
    ...
  END IF;
  RETURN NEW;
END;

Parece que todo está bien, pero… Nadie garantiza que el bloque anidado SELECT ... WHERE x > 0 AND y/x > 1.5; no se ejecute cuando la primera condición sea falsa. Lo corregimos utilizando

#1: условие в триггере

IF anidados.

BEGIN IF cond(NEW.fld) THEN IF EXISTS(SELECT ...) THEN ... END IF; END IF; RETURN NEW; END; SELECCIONAR Ahora, observemos detenidamente: todo el cuerpo de la función del trigger está "envuelto" en . Lo que significa que nada impide que extraigamos esta condición de la procedura usando WHEN.:

BEGIN
  IF EXISTS(SELECT ...) THEN
    ...
  END IF;
  RETURN NEW;
END;
...
CREATE TRIGGER ...
  WHEN cond(NEW.fld);

Este enfoque permite garantizar el ahorro de recursos del servidor cuando la condición es falsa. WHEN.SELECT ... WHERE EXISTS(... A) OR EXISTS(... B) En un caso poco agradable, se puede dar que ambossean "verdaderos", pero:

ambos se ejecutarán.

Pero si sabemos que uno de ellos es "verdadero" con mucha más frecuencia (o "falso" para

#2: OR/AND-цепочка

-la cadena), ¿no se puede "elevar su prioridad" para que el segundo no se ejecute innecesariamente?

SELECT ... WHERE EXISTS(... A) OR EXISTS(... B) EXISTS En un caso desagradable, podría resultar que ambos sean "verdaderos", pero.

ambos se ejecuten. YPero si sabemos que uno de ellos tiende a ser "verdadero" con mucha más frecuencia (o "falso" en el caso de la cadena) — ¿hay alguna forma de "aumentar su prioridad" para que el segundo no se ejecute innecesariamente?

Resulta que sí — el enfoque algorítmico está cercano al tema del artículo PostgreSQL Antipatterns: una escritura rara llegará a la mitad del JOIN.

Simplemente 'encajemos en CASE' ambas condiciones:

SELECT ...
WHERE
  CASE
    WHEN EXISTS(... A) THEN TRUE
    WHEN EXISTS(... B) THEN TRUE
  END

En este caso no hemos definido ELSE-el valor, es decir, en caso de que ambas condiciones sean falsas SELECT ... WHERE x > 0 AND y/x > 1.5; devolverá NULL, que se interpreta como FALSO en WHERE-condiciones.

Este ejemplo se puede combinar de otra manera — al gusto:

SELECT ...
WHERE
  CASE
    WHEN NOT EXISTS(... A) THEN EXISTS(... B)
    ELSE TRUE
  END

#3: как [не] надо писать условия

Pasamos dos días analizando las razones del funcionamiento 'extraño' de este disparador — veamos por qué.

Código fuente:

IF( NEW."Documento_" is null or NEW."Documento_" = (select '"Conjunto"'::regclass::oid) or NEW."Documento_" = (select to_regclass('"DocumentoPorSalario"')::oid)
     AND (   OLD."DocumentoNuestraOrganizacion" <> NEW."DocumentoNuestraOrganizacion"
          OR OLD."Eliminado" <> NEW."Eliminado"
          OR OLD."Fecha" <> NEW."Fecha"
          OR OLD."Hora" <> NEW."Hora"
          OR OLD."Creador" <> NEW."Creador" ) ) THEN ...

Problema #1: la desigualdad no considera NULL

Imaginemos que todos OLD-campos tenían valor NULL. ¿Qué pasaría?

SELECT NULL <> 1 OR NULL <> 2;
-- NULL

Y desde el punto de vista de la validación de la condición NULL es equivalente FALSO, como se mencionó anteriormente.

Solución: utiliza el operador IS DISTINCT FROM desde ROW-operador, comparando inmediatamente registros enteros:

SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
-- TRUE

Problema #2: implementación diferente de la misma funcionalidad

Comparémos:

NEW."Documento_" = (select '"Conjunto"'::regclass::oid)
NEW."Documento_" = (select to_regclass('"DocumentoPorSalario"')::oid)

¿Por qué hay anidamientos innecesarios aquí? SELECCIONAR? А функция to_regclass? А по-разному-то почему?..

Corregir:

NEW."Documento_" = '"Conjunto"'::regclass::oid
NEW."Documento_" = '"DocumentoPorSalario"'::regclass::oid

Problema #3: prioridad de las operaciones booleanas

Formateemos el código fuente:

{... IS NULL} OR
{... Conjunto} OR
{... DocumentoPorSalario} AND
( {... desigualdades} )

Ups… De hecho, resultó que si cualquiera de las dos primeras condiciones es verdadera, toda la condición se transforma en VERDADERO, sin considerar desigualdades. Y eso no es en absoluto lo que queríamos.

Corregir:

(
  {... IS NULL} OR
  {... Conjunto} OR
  {... DocumentoPorSalario}
) AND
( {... desigualdades} )

Problema #4 (pequeño): una condición OR complicada para un campo

La verdad es que el problema en #3 surgió precisamente porque había tres condiciones. Pero en su lugar, se puede usar una sola, mediante el mecanismo coalesce ... IN:

coalesce(NEW."Documento_"::text, '') IN ('', '"Conjunto"', '"DocumentoPorSalario"')

Así que también NULL "capturaremos", y no tendremos que complicar con paréntesis. O no será necesario lidiar con paréntesis.

Total

Fijemos lo que hemos logrado:

IF (
  coalesce(NEW."Documento_"::text, '') IN ('', '"Conjunto"', '"DocumentoPorSalario"') AND
  (
    OLD."DocumentoNuestraOrganizacion"
  , OLD."Eliminado"
  , OLD."Fecha"
  , OLD."Hora"
  , OLD."Creador"
  ) IS DISTINCT FROM (
    NEW."DocumentoNuestraOrganizacion"
  , NEW."Eliminado"
  , NEW."Fecha"
  , NEW."Hora"
  , NEW."Creador"
  )
) THEN ...

Y si consideramos que esta función de disparador solo se puede aplicar en ACTUALIZAR-el disparador debido a la presencia de OLD/NEW en la condición de nivel superior, entonces esta condición se puede llevar a En un caso poco agradable, se puede dar que ambos-condición, como se mostró en #1...

Fuente: habr.com

Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS 🔥 Compra un hosting fiable para sitios web con protección contra DDoS, servidores VPS VDS | ProHoster