SQL não é C ++, nem JavaScript. Portanto, o cálculo das expressões lógicas é diferente, e isso não é a mesma coisa:WHERE fncondX() AND fncondY()
= fncondX() && fncondY()
No processo de otimizar o plano de execução da consulta, o PostgreSQL pode "reorganizar" arbitrariamente as condições equivalentes , não calcular nenhuma delas para registros individuais, atribuí-las à condição do índice aplicável ... Em suma, é mais fácil supor que você não pode controlar em que ordem As condições dos pares serão calculadas (e, se for o caso) .Portanto, se você ainda deseja controlar a prioridade, precisa estruturalmente tornar essas condições desiguais usando expressões e operadores condicionais .Os dados e o trabalho com eles são a base do nosso complexo VLSI , por isso é muito importante para nós que as operações neles sejam executadas não apenas corretamente, mas também com eficiência. Vejamos exemplos específicos em que erros de computação de expressão podem ser cometidos e onde vale a pena melhorar sua eficiência.# 0: RTFM
Iniciando o exemplo da documentação :Quando a ordem de cálculo é importante, ela pode ser corrigida usando a construção CASE
. Por exemplo, essa maneira de evitar dividir por zero em uma frase WHERE
não é confiável:
SELECT ... WHERE x > 0 AND y/x > 1.5;
Opção segura:
SELECT ... WHERE CASE WHEN x > 0 THEN y/x > 1.5 ELSE false END;
A construção usada dessa maneira CASE
protege a expressão da otimização, portanto, ela só deve ser usada se necessário.
# 1: condição no gatilho
BEGIN
IF cond(NEW.fld) AND EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
Parece que tudo parece bom, mas ... Ninguém promete que o anexo SELECT
não será executado se a primeira condição for falsa. Corrija usando aninhadoIF
:BEGIN
IF cond(NEW.fld) THEN
IF EXISTS(SELECT ...) THEN
...
END IF;
END IF;
RETURN NEW;
END;
Agora, vamos olhar com cuidado - todo o corpo da função de gatilho acabou por estar "envolvido" IF
. E isso significa que nada nos impede de remover essa condição do procedimento usando a WHEN
condição- :BEGIN
IF EXISTS(SELECT ...) THEN
...
END IF;
RETURN NEW;
END;
...
CREATE TRIGGER ...
WHEN cond(NEW.fld);
Essa abordagem permite economia garantida de recursos do servidor em condições falsas.# 2: cadeia OR / AND
SELECT ... WHERE EXISTS(... A) OR EXISTS(... B)
Caso contrário, você pode entender que ambos EXISTS
serão "verdadeiros", mas ambos serão cumpridos .Mas se tivermos certeza de que um deles é “verdadeiro” com muito mais frequência (ou “falso” para a AND
cadeia), é possível de alguma forma “aumentar sua prioridade” para que o segundo não seja executado novamente?Acontece que é possível - a abordagem do algoritmo está próxima ao tópico do artigo Antipatterns do PostgreSQL: um registro raro chegará ao meio do JOIN .Vamos "pop sob CASE" nessas duas condições:SELECT ...
WHERE
CASE
WHEN EXISTS(... A) THEN TRUE
WHEN EXISTS(... B) THEN TRUE
END
Nesse caso, não determinamos o ELSE
valor-, ou seja, se ambas as condições forem falsas , ele CASE
retornará NULL
, o que é interpretado como FALSE
na WHERE
condição-.Este exemplo pode ser combinado de outra maneira - para provar e colorir:SELECT ...
WHERE
CASE
WHEN NOT EXISTS(... A) THEN EXISTS(... B)
ELSE TRUE
END
# 3: como [não] escrever condições
Passamos dois dias analisando os motivos do gatilho "estranho" desse gatilho - vamos ver o porquê.Fonte: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: a desigualdade não considera NULL
Imagine que todos os OLD
campos são importantes NULL
. O que vai acontecer?SELECT NULL <> 1 OR NULL <> 2;
E do ponto de vista de elaborar as condições é NULL
equivalente FALSE
, como mencionado acima.Solução : use o operador IS DISTINCT FROM
do operador ROW
, comparando registros inteiros de uma só vez:SELECT (NULL, NULL) IS DISTINCT FROM (1, 2);
Problema número 2: implementação diferente da mesma funcionalidade
Comparar:NEW."_" = (select '""'::regclass::oid)
NEW."_" = (select to_regclass('""')::oid)
Por que existem aninhamentos extras SELECT
? E a função to_regclass
? E de diferentes maneiras, por que ..?Fix:NEW."_" = '""'::regclass::oid
NEW."_" = '""'::regclass::oid
Problema nº 3: prioridade das operações booleanas
Formate a fonte:{... IS NULL} OR
{... } OR
{... } AND
( {... } )
Opa ... De fato, verificou-se que, no caso da verdade de qualquer uma das duas primeiras condições, toda a condição se transforma TRUE
, sem levar em conta as desigualdades. E isso não é o que queríamos.Consertar:(
{... IS NULL} OR
{... } OR
{... }
) AND
( {... } )
Problema 4 (pequeno): condição OR complexa para um campo
Na verdade, tivemos problemas no número 3 justamente porque havia três condições. Mas, em vez deles, você pode fazer um, usando o mecanismo coalesce ... IN
:coalesce(NEW."_"::text, '') IN ('', '""', '""')
Então, nós NULL
“pegamos”, e o complexo OR
com colchetes não precisa cercar.Total
Corrigimos o que conseguimos:IF (
coalesce(NEW."_"::text, '') IN ('', '""', '""') AND
(
OLD.""
, OLD.""
, OLD.""
, OLD.""
, OLD.""
) IS DISTINCT FROM (
NEW.""
, NEW.""
, NEW.""
, NEW.""
, NEW.""
)
) THEN ...
E se levarmos em conta que essa função de gatilho pode ser usada apenas no UPDATE
gatilho devido à presença de OLD/NEW
um nível superior na condição, essa condição geralmente pode ser colocada na WHEN
condição-, como foi mostrado no item 1 ...