web-dev-qa-db-fra.com

UNION avec clause WHERE

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 UNIONing 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?

41
MNIK

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 :)

20
Ronnis

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.

9
Gary Myers

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.

9
btilly

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.

7
EvilTeach

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.

2
rayman86

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.

1
Randy
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

0
Anbarasi Selvaraj