J'ai toujours compris que l' CASE
énoncé fonctionnait selon un principe de «court-circuit» en ce sens que l'évaluation des étapes suivantes n'a pas lieu si une étape antérieure est évaluée comme vraie. (Cette réponse L'instruction CASE de SQL Server évalue-t-elle toutes les conditions ou quitte-t-elle la première condition VRAIE? Est liée mais ne semble pas couvrir cette situation et concerne SQL Server).
Dans l'exemple suivant, je souhaite calculer le MAX(amount)
entre une plage de mois qui diffère en fonction du nombre de mois entre le début et les dates payées.
(Ceci est évidemment un exemple construit mais la logique a un raisonnement commercial valide dans le code réel où je vois le problème).
S'il y a moins de 5 mois entre les dates de début et de paiement, l' expression 1 sera utilisée, sinon l' expression 2 sera utilisée.
Il en résulte l'erreur «ORA-01428: l'argument« -1 »est hors limites» car 1 enregistrement a une condition de données non valide qui se traduit par une valeur négative pour le début de la clause BETWEEN de la commande ORDER BY.
Requête 1
SELECT ref_no,
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 5 THEN
-- Expression 1
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN MONTHS_BETWEEN(paid_date, start_date) PRECEDING
AND CURRENT ROW)
ELSE
-- Expression 2
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)
END
END
FROM payment
Je suis donc allé pour cette deuxième requête pour éliminer d'abord où que cela puisse se produire:
SELECT ref_no,
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 0 THEN 0
ELSE
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 5 THEN
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN MONTHS_BETWEEN(paid_date, start_date) PRECEDING
AND CURRENT ROW)
ELSE
MAX(amount)
OVER (PARTITION BY ref_no ORDER BY paid_date ASC
ROWS BETWEEN 5 PRECEDING AND CURRENT ROW)
END
END
FROM payment
Malheureusement, il existe un comportement inattendu qui signifie que les valeurs que l' expression 1 DEVRAIT utiliser seraient validées, même si l'instruction ne sera pas exécutée car la condition négative est désormais piégée par l'extérieur CASE
.
Je peux contourner le problème en utilisant ABS
sur MONTHS_BETWEEN
dans Expression 1 , mais je pense que cela ne devrait pas être nécessaire.
Ce comportement est-il conforme aux attentes? Si oui «pourquoi» car cela me semble illogique et ressemble plus à un bug?
Cela va créer une table et tester les données. La requête consiste simplement à vérifier que le chemin d'accès correct CASE
est pris.
CREATE TABLE payment
(ref_no NUMBER,
start_date DATE,
paid_date DATE,
amount NUMBER)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('01-01-2016','DD-MM-YYYY'),3000)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('12-12-2015','DD-MM-YYYY'),5000)
INSERT INTO payment
VALUES (1001,TO_DATE('10-03-2016','DD-MM-YYYY'),TO_DATE('10-02-2016','DD-MM-YYYY'),2000)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('03-03-2016','DD-MM-YYYY'),6000)
INSERT INTO payment
VALUES (1001,TO_DATE('01-11-2015','DD-MM-YYYY'),TO_DATE('28-11-2015','DD-MM-YYYY'),10000)
SELECT ref_no,
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 0 THEN '<0'
ELSE
CASE WHEN MONTHS_BETWEEN(paid_date, start_date) < 5 THEN
'<5'
-- MAX(amount)
-- OVER (PARTITION BY ref_no ORDER BY paid_date ASC ROWS
-- BETWEEN MONTHS_BETWEEN(paid_date, start_date) PRECEDING
-- AND CURRENT ROW)
ELSE
'>=5'
-- MAX(amount)
-- OVER (PARTITION BY ref_no ORDER BY paid_date ASC ROWS
-- BETWEEN 5 PRECEDING AND CURRENT ROW)
END
END
FROM payment
MAX(amount) OVER (PARTITION BY ref_no ORDER BY paid_date ASC ROWS BETWEEN GREATEST(0, LEAST(5, MONTHS_BETWEEN(paid_date, start_date))) PRECEDING AND CURRENT ROW)
Réponses:
Il était donc difficile pour moi de déterminer quelle était votre véritable question dans le post, mais je suppose que lorsque vous exécutez:
Vous obtenez toujours ORA-01428: l'argument «-1» est hors de portée ?
Je ne pense pas que ce soit un bug. Je pense que c'est une question d'ordre de fonctionnement. Oracle doit effectuer l'analyse sur toutes les lignes renvoyées par l'ensemble de résultats. Ensuite, il peut aller au fond de la transformation de la sortie.
Deux autres façons de contourner ce problème seraient d'exclure la ligne avec une clause where:
Ou vous pouvez intégrer un cas dans votre analyse comme:
Explication
J'aimerais pouvoir trouver de la documentation pour sauvegarder l'ordre de fonctionnement, mais je n'ai rien trouvé ... pour l'instant.
L'
CASE
évaluation du court-circuit a lieu après l'évaluation de la fonction analytique. L'ordre des opérations pour la requête en question serait:Donc, puisque le
max over()
se produit avant le cas, la requête échoue.Les fonctions analytiques d'Oracle seraient considérées comme une source de lignes . Si vous exécutez un plan d'explication sur votre requête, vous devriez voir un "tri de fenêtre" qui est l'analyse, générant des lignes, qui sont alimentées par la source de ligne précédente, la table de paiement. Une instruction case est une expression qui est évaluée pour chaque ligne de la source de ligne. Il est donc logique (du moins pour moi) que le cas se produise après l'analyse.
la source
SQL définit ce qu'il faut faire, pas comment le faire. Bien qu'Oracle court-circuite normalement l'évaluation des cas, il s'agit d'une optimisation et sera donc évitée si l'optimiseur estime qu'un chemin d'exécution différent fournit des performances supérieures. Une telle différence d'optimisation serait attendue lorsque des analyses sont impliquées.
La différence d'optimisation n'est pas limitée au cas. Votre erreur peut être reproduite en utilisant la fusion, ce qui normalement court-circuiterait également.
Il ne semble pas y avoir de documentation expliquant explicitement que l’évaluation à court terme peut être ignorée par l’optimiseur. La chose la plus proche (mais pas assez proche) que je peux trouver est la suivante :
Cette question montre que l'évaluation des courts-circuits est ignorée même sans analyse (bien qu'il y ait un regroupement).
Tom Kyte mentionne que le court-circuit peut être ignoré dans sa réponse à une question sur l' ordre d'évaluation des prédicats .
Vous devez ouvrir un SR avec Oracle. Je soupçonne qu'ils l'accepteront comme bogue de documentation et amélioreront la documentation dans la prochaine version pour inclure une mise en garde concernant l'optimiseur.
la source
Il semble que ce soit un fenêtrage qui pousse Oracle à commencer à évaluer toutes les expressions dans CASE. Voir
Les deux premières requêtes s'exécutent correctement.
la source