Je fais un UNION
de deux requêtes sur une base de données Oracle. Les deux ont une clause WHERE
. Y a-t-il une différence dans les performances si je fais la WHERE
après UNION
ing les requêtes par rapport à l'exécution de la clause UNION
après WHERE
?
Par exemple:
SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colA, colB FROM tableB WHERE colA > 1
par rapport à:
SELECT *
FROM (SELECT colA, colB FROM tableA
UNION
SELECT colA, colB FROM tableB)
WHERE colA > 1
Je crois que dans le deuxième cas, il effectue une analyse complète des tables sur les deux tables affectant les performances. Est-ce exact?
D'après mon expérience, Oracle est très bon pour pousser simple prédicats autour. Le test suivant a été effectué sur Oracle 11.2. Je suis assez certain qu'il produit également le même plan d'exécution sur toutes les versions de 10g.
(S'il vous plaît les gens, n'hésitez pas à laisser un commentaire si vous exécutez une version antérieure et avez essayé ce qui suit)
create table table1(a number, b number);
create table table2(a number, b number);
explain plan for
select *
from (select a,b from table1
union
select a,b from table2
)
where a > 1;
select *
from table(dbms_xplan.display(format=>'basic +predicate'));
PLAN_TABLE_OUTPUT
---------------------------------------
| Id | Operation | Name |
---------------------------------------
| 0 | SELECT STATEMENT | |
| 1 | VIEW | |
| 2 | SORT UNIQUE | |
| 3 | UNION-ALL | |
|* 4 | TABLE ACCESS FULL| TABLE1 |
|* 5 | TABLE ACCESS FULL| TABLE2 |
---------------------------------------
Predicate Information (identified by operation id):
---------------------------------------------------
4 - filter("A">1)
5 - filter("A">1)
Comme vous pouvez le voir aux étapes (4,5), le prédicat est poussé vers le bas et appliqué avant le tri (union).
Je n'ai pas pu obtenir l'optimiseur pour pousser une sous-requête entière telle que
where a = (select max(a) from empty_table)
ou une jointure. Avec des contraintes PK/FK appropriées en place, cela pourrait être possible, mais il y a clairement des limites :)
Juste une mise en garde
Si tu as essayé
SELECT colA, colB FROM tableA WHERE colA > 1
UNION
SELECT colX, colA FROM tableB WHERE colA > 1
par rapport à:
SELECT *
FROM (SELECT colA, colB FROM tableA
UNION
SELECT colX, colA FROM tableB)
WHERE colA > 1
Ensuite, dans la deuxième requête, le colA de la clause where aura en fait le colX de tableB, ce qui en fait une requête très différente. Si les colonnes sont aliasées de cette manière, cela peut prêter à confusion.
REMARQUE: alors que mon conseil était vrai il y a de nombreuses années, l'optimiseur d'Oracle s'est amélioré de sorte que l'emplacement du où n'a définitivement plus d'importance ici. Cependant, préférer UNION ALL
Vs UNION
sera toujours true, et SQL portable devrait éviter de dépendre des optimisations qui peuvent ne pas être présentes dans toutes les bases de données.
Réponse courte, vous voulez le WHERE
avant le UNION
et vous voulez utiliser UNION ALL
Si possible. Si vous utilisez UNION ALL
, Vérifiez la sortie EXPLAIN, Oracle peut être suffisamment intelligent pour optimiser la condition WHERE
si elle est laissée après.
La raison est la suivante. La définition d'un UNION
dit que s'il y a des doublons dans les deux ensembles de données, ils doivent être supprimés. Par conséquent, il y a un GROUP BY
Implicite dans cette opération, qui a tendance à être lent. Pire encore, l'optimiseur d'Oracle (au moins il y a 3 ans, et je ne pense pas qu'il ait changé) n'essaie pas de pousser les conditions à travers un GROUP BY
(Implicite ou explicite). Par conséquent, Oracle doit construire des ensembles de données plus volumineux que nécessaire, les regrouper, puis ne filtre que. Préfiltrer autant que possible est donc officiellement une bonne idée. (C'est d'ailleurs pourquoi il est important de mettre des conditions dans le WHERE
chaque fois que possible au lieu de les laisser dans une clause HAVING
.)
De plus, si vous savez qu'il n'y aura pas de doublons entre les deux ensembles de données, utilisez UNION ALL
. C'est comme UNION
en ce qu'il concatène les jeux de données, mais il n'essaye pas de dédupliquer les données. Cela permet d'économiser une opération de regroupement coûteuse. D'après mon expérience, il est assez courant de pouvoir profiter de cette opération.
Puisque UNION ALL
Ne contient pas de GROUP BY
Implicite, il est possible que l'optimiseur d'Oracle sache comment pousser les conditions à travers lui. Je n'ai pas Oracle assis pour tester, vous devrez donc le tester vous-même.
Vous devez regarder les plans d'explication, mais à moins qu'il n'y ait un INDEX ou une PARTITION sur COL_A, vous regardez un FULL TABLE SCAN sur les deux tables.
Dans cet esprit, votre premier exemple est de jeter certaines des données comme il le fait le FULL TABLE SCAN. Ce résultat est trié par l'UNION, puis les données en double sont supprimées. Cela vous donne votre jeu de résultats.
Dans le deuxième exemple, vous extrayez le contenu complet des deux tables. Ce résultat sera probablement plus important. L'UNION trie donc plus de données, puis supprime les éléments en double. Ensuite, le filtre est appliqué pour vous donner le jeu de résultats que vous recherchez.
En règle générale, plus vous filtrez les données tôt, plus l'ensemble de données est petit et plus vous obtiendrez rapidement vos résultats. Comme toujours, votre kilométrage peut varier.
Je voudrais m'assurer que vous avez un index sur ColA, puis les exécuter tous les deux et les chronométrer. Cela vous donnerait la meilleure réponse.
je pense que cela dépendra de beaucoup de choses - exécutez EXPLAIN PLAN
sur chacun pour voir ce que votre optimiseur sélectionne. Sinon - comme @rayman le suggère - exécutez-les tous les deux et chronométrez-les.
SELECT * FROM (SELECT colA, colB FROM tableA UNION SELECT colA, colB FROM tableB) as tableC WHERE tableC.colA > 1
Si nous utilisons une union qui contient le même nom de champ dans 2 tables, alors nous devons donner un nom à la sous-requête en tant que tableC (dans la requête ci-dessus). Enfin, la condition WHERE
doit être WHERE tableC.colA > 1