Différence entre la vue en ligne et la clause WITH?

9

Les vues en ligne vous permettent de sélectionner dans une sous-requête comme s'il s'agissait d'une table différente:

SELECT
    *
FROM /* Selecting from a query instead of table */
    (
        SELECT
            c1
        FROM
            t1
        WHERE
            c1 > 0
    ) a
WHERE
    a.c1 < 50;

J'ai vu que cela faisait référence à l'utilisation de différents termes: vues en ligne, clause WITH, CTE et tables dérivées. Pour moi, il semble que ce soit une syntaxe spécifique au fournisseur différente pour la même chose.

Est-ce une fausse hypothèse? Existe-t-il des différences techniques / de performances entre celles-ci?

Kshitiz Sharma
la source
5
Les noms "officiels" de SQL standard sont Derived Table (qu'Oracle nomme Inline View ) et Common Table Expression (= WITH...). Vous pouvez réécrire chaque table dérivée en CTE, mais peut-être pas l'inverse (par exemple, CTE récursif ou utiliser le CTE plusieurs fois)
dnoeth

Réponses:

8

Il existe des différences importantes entre les vues en ligne (tables dérivées) et la clause WITH (CTE) dans Oracle. Certains d'entre eux sont assez universels, c'est-à-dire qu'ils s'appliquent à d'autres SGBDR.

  1. WITH peut être utilisé pour créer des sous-requêtes récursives, vue en ligne -pas (pour autant que je sache, il en va de même pour tous les SGBDR qui prennent en charge CTE)
  2. La sous-requête de la WITHclause est plus susceptible d'être exécutée physiquement en premier; dans de nombreux cas, le choix entre WITHet la vue en ligne permet à l'optimiseur de choisir différents plans d'exécution (je suppose que c'est spécifique au fournisseur, peut-être même à la version).
  3. La sous-requête dans WITHpeut être matérialisée sous la forme d'une table temporaire (je ne sais pas si un autre fournisseur mais Oracle prend en charge cette fonctionnalité).
  4. La sous-requête dans WITHpeut être référencée plusieurs fois, dans d'autres sous-requêtes et dans la requête principale (vrai pour la plupart des SGBDR).
a1ex07
la source
MySQL (au moins les dernières versions de MariaDB) peut matérialiser des tables dérivées (et même ajouter des index).
ypercubeᵀᴹ
3
Je voudrais ajouter que, comme avantage secondaire, l'utilisation des CTE est généralement plus lisible pour les humains également.
Joishi Bodio
@JoishiBodio: Personnellement, je suis d'accord avec vous, mais la lisibilité est une question assez subjective. Je préfère éviter de le mentionner
a1ex07
De plus, un CTE peut référencer un CTE précédemment déclaré. Une table dérivée ne peut pas référencer une table dérivée précédemment déclarée au même niveau, sauf si elle LATERALest utilisée.
Lennart
8

D'autres réponses couvrent assez bien les différences de syntaxe, donc je n'entrerai pas dans les détails. Au lieu de cela, cette réponse couvrira simplement les performances dans Oracle.

L'optimiseur Oracle peut choisir de matérialiser les résultats d'un CTE dans une table temporaire interne. Il utilise une heuristique pour ce faire au lieu d'une optimisation basée sur les coûts. L'heuristique est quelque chose comme "Matérialiser le CTE s'il n'est pas une expression triviale et le CTE est référencé plus d'une fois dans la requête". Il existe certaines requêtes pour lesquelles la matérialisation améliorera les performances. Il existe certaines requêtes pour lesquelles la matérialisation dégradera considérablement les performances. L'exemple suivant est un peu artificiel mais il illustre bien le point:

Créez d'abord une table avec une clé primaire qui contient des entiers de 1 à 10000:

CREATE TABLE N_10000 (NUM_ID INTEGER NOT NULL, PRIMARY KEY (NUM_ID));

INSERT /*+APPEND */ INTO N_10000
SELECT LEVEL
FROM DUAL
CONNECT BY LEVEL <= 10000
ORDER BY LEVEL;

COMMIT;

Considérez la requête suivante qui utilise deux tables dérivées:

SELECT t1.NUM_ID
FROM 
(
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
) t1
LEFT OUTER JOIN 
(
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
) t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;

Nous pouvons examiner cette requête et déterminer rapidement qu'elle ne renverra aucune ligne. Oracle devrait également pouvoir utiliser l'index pour le déterminer. Sur ma machine, la requête se termine presque instantanément avec le plan suivant:

bon plan

Je n'aime pas me répéter, alors essayons la même requête avec un CTE:

WITH N_10000_CTE AS (
  SELECT n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
)
SELECT t1.NUM_ID
FROM N_10000_CTE t1
LEFT JOIN N_10000_CTE t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;

Voici le plan:

mauvais plan

C'est vraiment un mauvais plan. Au lieu d'utiliser l'index, Oracle matérialise 10000 X 10000 = 100000000 lignes dans une table temporaire pour finalement retourner 0 lignes. Le coût de ce plan est d'environ 6 M, ce qui est beaucoup plus élevé que l'autre requête. La requête a pris 68 secondes pour se terminer sur ma machine.

Notez que la requête peut avoir échoué s'il n'y a pas suffisamment de mémoire ou d'espace libre dans l'espace disque logique temporaire.

Je peux utiliser l' INLINEindice non documenté pour empêcher l'optimiseur de matérialiser le CTE:

WITH N_10000_CTE AS (
  SELECT /*+ INLINE */ n1.NUM_ID
  FROM N_10000 n1
  CROSS JOIN N_10000 n2
)
SELECT t1.NUM_ID
FROM N_10000_CTE t1
LEFT JOIN N_10000_CTE t2 ON t1.NUM_ID = t2.NUM_ID
WHERE t1.NUM_ID <= 0;

Cette requête est capable d'utiliser l'index et se termine presque instantanément. Le coût de la requête est le même qu'avant, 11. Donc pour la deuxième requête, l'heuristique utilisée par Oracle l'a amené à choisir une requête avec un coût estimé à 6 M au lieu d'une requête avec un coût estimé à 11.

Joe Obbish
la source
1

Pour SQL Server, WITH CTEspécifie l'ensemble de résultats nommé temporaire, mais n'est requis que pour le premier CTE. c'est à dire

WITH CTE AS (SELECT .... FROM), 
CTE2 AS (SELECT .... FROM)

SELECT CTE.Column, CTE2.Column
FROM CTE
INNER JOIN CTE2 on CTE.Column = CTE2.Column

Mais ce n'est pas une sous-requête ou une sous-requête corrélée. Il y a des choses que vous pouvez faire avec un CTE ce que vous ne pouvez pas faire avec une sous-requête dans SQL Server, comme mettre à jour les tables référencées dans un CTE. Voici un exemple de mise à jour d'une table avec un CTE.

Une sous-requête serait quelque chose comme

SELECT
   C1,
   (SELECT C2 FROM SomeTable) as C2
FROM Table

Ou une sous-requête corrélée est ce que vous avez fourni dans votre PO si vous deviez référencer / joindre / limiter vos résultats en fonction de a.c1.

Donc, ce n'est certainement pas la même chose, mais dans de nombreux cas, vous pouvez utiliser une ou plusieurs de ces méthodes pour obtenir le même résultat. Cela dépend simplement de ce que ce résultat final est.

scsimon
la source
1

La principale différence entre une withclause et une sous-requête dans Oracle est que vous pouvez référencer plusieurs fois une requête dans la clause. Vous pouvez ensuite faire quelques optimisations avec lui comme le transformer en une table temporaire en utilisant materializehint. Vous pouvez également effectuer des requêtes récursives avec lui en se référençant à l'intérieur d'une withclause. Vous ne pouvez pas faire cela avec une vue en ligne.

Plus d'informations peuvent être trouvées ici et ici .

Marko Vodopija
la source
En général, un indice de matérialisation n'est pas requis. Par défaut, l'optimiseur Oracle décide s'il est judicieux de matérialiser le CTE ou non - mais vous pouvez remplacer l'évaluation de l'optimiseur avec un indice MATERIALIZEresp. INLINEpour le contraire.
Wernfried Domscheit
@WernfriedDomscheit c'est vrai. Mais parfois, l'optimiseur ne choisit pas de matérialiser le CTE et dans ce cas, l'utilisation de l' materializeindice est une option valide. J'avais parfois besoin de le spécifier lors de l'optimisation de requêtes très complexes où je savais que la matérialisation du CTE bénéficierait au plan d'exécution.
Marko Vodopija
0

Vous devez être prudent avec les CTE dans SQL Server et pas seulement avec Oracle, il y a des cas où les requêtes sont bien plus mauvaises lorsque vous utilisez des CTE par rapport aux sous-requêtes, des applications croisées, etc.

Comme toujours, il est important de tester toute requête dans différentes conditions de charge pour déterminer celle qui fonctionne le mieux.

Semblable à @scsimon avec oracle, parfois MS SQL Server ne fait pas ce que vous attendez en ce qui concerne l'utilisation des index.

Si vous allez utiliser les mêmes données plus d'une fois, les CTE peuvent être plus utiles, si vous ne les utilisez qu'une seule fois, souvent une sous-requête est plus rapide dans les grands ensembles de données.

par exemple, sélectionnez * dans (ma sous-requête), joignez autre chose ...

Justin
la source