WITH
(Common Table
Expressions) #
WITH
fournit un moyen d'écrire des ordres
auxiliaires pour les utiliser dans des requĂȘtes plus importantes. Ces
requĂȘtes, qui sont souvent appelĂ©es Common Table Expressions ou
CTE, peuvent ĂȘtre vues comme des tables temporaires
qui n'existent que pour une requĂȘte. Chaque ordre auxiliaire dans une
clause WITH
peut ĂȘtre un SELECT
,
INSERT
, UPDATE
, ou
DELETE
; et la clause WITH
elle-mĂȘme est attachĂ©e Ă un ordre primaire qui peut lui aussi
ĂȘtre un SELECT
, INSERT
,
UPDATE
, DELETE
ou
MERGE
.
SELECT
dans WITH
#
L'intĂ©rĂȘt de SELECT
dans WITH
est de
diviser des requĂȘtes complexes en parties plus simples. Un exemple est:
WITH ventes_regionales AS ( SELECT region, SUM(montant) AS ventes_totales FROM commandes GROUP BY region ), meilleures_regions AS ( SELECT region FROM ventes_regionales WHERE ventes_totales > (SELECT SUM(ventes_totales)/10 FROM ventes_regionales) ) SELECT region, produit, SUM(quantite) AS unites_produit, SUM(montant) AS ventes_produit FROM commandes WHERE region IN (SELECT region FROM meilleures_regions) GROUP BY region, produit;
qui affiche les totaux de ventes par produit seulement dans les régions
ayant les meilleures ventes.
La clause WITH
définit deux ordres
auxiliaires appelés ventes_regionales
et meilleures_regions
, oĂč la sortie
de ventes_regionales
est utilisée dans
meilleures_regions
et la sortie de
meilleures_regions
est utilisée dans la
requĂȘte SELECT
primaire.
Cet exemple aurait pu ĂȘtre Ă©crit sans
WITH
, mais aurait alors nécessité deux niveaux de
sous-SELECT
imbriqués. Les choses sont un peu plus faciles à suivre de cette
façon.
Le modificateur optionnel RECURSIVE
fait passer
WITH
du statut de simple aide syntaxique Ă celui de
quelque chose qu'il serait impossible d'accomplir avec du SQL standard.
GrĂące Ă RECURSIVE
, une requĂȘte WITH
peut utiliser sa propre sortie. Un exemple trĂšs simple se trouve dans cette
requĂȘte, qui ajoute les nombres de 1 Ă 100 :
WITH RECURSIVE t(n) AS ( VALUES (1) UNION ALL SELECT n+1 FROM t WHERE n < 100 ) SELECT sum(n) FROM t;
La forme gĂ©nĂ©rale d'une requĂȘte WITH
est toujours un
terme non récursif, puis UNION
(ou
UNION ALL
), puis un terme récursif.
Seul le terme récursif peut contenir une référence à la sortie propre de la
requĂȘte. Une requĂȘte de ce genre est exĂ©cutĂ©e comme suit :
Ăvaluation de requĂȘte rĂ©cursive
Ăvaluer le terme non rĂ©cursif. Pour UNION
(mais pas
UNION ALL
), supprimer les enregistrements en double.
Inclure le reste dans le rĂ©sultat de la requĂȘte rĂ©cursive et le mettre
aussi dans une table temporaire de travail (working table.)
Tant que la table de travail n'est pas vide, répéter ces étapes :
Ăvaluer le terme rĂ©cursif, en substituant Ă la rĂ©fĂ©rence rĂ©cursive
le contenu courant de la table de travail.
Pour UNION
(mais pas UNION ALL
),
supprimer les doublons, ainsi que les enregistrements en doublon des
enregistrements déjà obtenus. Inclure les enregistrements restants dans
le rĂ©sultat de la requĂȘte rĂ©cursive, et les mettre aussi dans une table
temporaire intermédiaire (intermediate table).
Remplacer le contenu de la table de travail par celui de la table intermédiaire, puis supprimer la table intermédiaire.
Alors que RECURSIVE
autorise que les requĂȘtes soient
spĂ©cifiĂ©es rĂ©cursivement, en interne, ce type de requĂȘtes est Ă©valuĂ©
itérativement.
Dans l'exemple prĂ©cĂ©dent, la table de travail a un seul enregistrement Ă
chaque étape, et il prend les valeurs de 1 à 100 en étapes successives.
à la centiÚme étape, il n'y a plus de sortie en raison de la clause
WHERE
, ce qui met fin Ă la requĂȘte.
Les requĂȘtes rĂ©cursives sont utilisĂ©es gĂ©nĂ©ralement pour traiter des donnĂ©es hiĂ©rarchiques ou sous forme d'arbres. Cette requĂȘte est un exemple utile pour trouver toutes les sous-parties directes et indirectes d'un produit, si seule une table donne toutes les inclusions immĂ©diates :
WITH RECURSIVE parties_incluses(sous_partie, partie, quantite) AS ( SELECT sous_partie, partie, quantite FROM parties WHERE partie = 'notre_produit' UNION ALL SELECT p.sous_partie, p.partie, p.quantite * pr.quantite FROM parties_incluses pr, parties p WHERE p.partie = pr.sous_partie ) SELECT sous_partie, SUM(quantite) as quantite_totale FROM parties_incluses GROUP BY sous_partie
Lors du calcul d'un parcours d'arbre en utilisant une requĂȘte rĂ©cursive, vous pourriez vouloir trier les rĂ©sultats soit en depth-first soit en breadth-first. Ceci peut se faire en calculant une colonne de tri parmi les autres colonnes de donnĂ©es et en l'utilisant pour trier les rĂ©sultats Ă la fin. Notez que cela ne contrĂŽle pas rĂ©ellement dans qel ordre l'Ă©valuation de la requĂȘte visite les lignes ; ceci est toujours dĂ©pendant de l'implĂ©mentation SQL. Cette approche fournit simplement une façon agrĂ©able de trier les rĂ©sultats aprĂšs coup.
Pour créer un ordre depth-first, nous
calculons pour chaque rĂ©sultat un tableau de lignes que nous avons dĂ©jĂ
visitĂ©. Par exemple, considĂ©rez la requĂȘte suivante qui recherche dansune
table tree
en utilisant un champ
link
:
WITH RECURSIVE search_tree(id, link, data) AS ( SELECT t.id, t.link, t.data FROM tree t UNION ALL SELECT t.id, t.link, t.data FROM tree t, search_tree st WHERE t.id = st.link ) SELECT * FROM search_tree;
Pour ajouter l'information de tri depth-first, vous pouvez écrire ceci :
WITH RECURSIVE search_tree(id, link, data, path) AS ( SELECT t.id, t.link, t.data, ARRAY[t.id] FROM tree t UNION ALL SELECT t.id, t.link, t.data, path || t.id FROM tree t, search_tree st WHERE t.id = st.link ) SELECT * FROM search_tree ORDER BY path;
Dans le cas gĂ©nĂ©ral oĂč plus d'un champ a besoin d'ĂȘtre utilisĂ© pour
identifier une ligne, utilisez un tableau de lignes. Par exemple, si vous
avez besoin de tracer les champs f1
et
f2
:
WITH RECURSIVE search_tree(id, link, data, path) AS ( SELECT t.id, t.link, t.data, ARRAY[ROW(t.f1, t.f2)] FROM tree t UNION ALL SELECT t.id, t.link, t.data, path || ROW(t.f1, t.f2) FROM tree t, search_tree st WHERE t.id = st.link ) SELECT * FROM search_tree ORDER BY path;
Omettez la syntaxe ROW()
dans le cas commun oĂč
seulement un champ a besoin d'ĂȘtre tracĂ©. Ceci permet l'utilisation d'un
tableau simple plutĂŽt que d'un tableau de type composite, ce qui permet
de gagner en efficacité.
Pour créer un ordre breadth-first, vous pouvez utiliser un colonne qui trace la profondeur de la recherche, par exemple :
WITH RECURSIVE search_tree(id, link, data, depth) AS ( SELECT t.id, t.link, t.data, 0 FROM tree t UNION ALL SELECT t.id, t.link, t.data, depth + 1 FROM tree t, search_tree st WHERE t.id = st.link ) SELECT * FROM search_tree ORDER BY depth;
Pour obtenir un tri stable, ajoutez des colonnes de données comme colonnes secondaires de tri.
L'algorithme d'Ă©valuation de la requĂȘte rĂ©cursive produit sa sortie dans l'ordre de recherche breadth-first. NĂ©anmoins, ceci est un dĂ©tail d'implĂ©mentation et il n'est pas conseillĂ© de se baser deuss. L'ordre des lignes Ă l'intĂ©rieur de chaque niveau n'est pas dĂ©fini, donc un tri explicite pourrait ĂȘtre dĂ©sirĂ© dans tous les cas.
Une syntaxe native permet de calculer une colonne de tri depth-first ou breadth-first. Par exemple :
WITH RECURSIVE search_tree(id, link, data) AS ( SELECT t.id, t.link, t.data FROM tree t UNION ALL SELECT t.id, t.link, t.data FROM tree t, search_tree st WHERE t.id = st.link ) SEARCH DEPTH FIRST BY id SET ordercol SELECT * FROM search_tree ORDER BY ordercol; WITH RECURSIVE search_tree(id, link, data) AS ( SELECT t.id, t.link, t.data FROM tree t UNION ALL SELECT t.id, t.link, t.data FROM tree t, search_tree st WHERE t.id = st.link ) SEARCH BREADTH FIRST BY id SET ordercol SELECT * FROM search_tree ORDER BY ordercol;
Cette syntaxe est étendue en interne pour obtenir quelque chose de
similaire aux formes écrites manuellement. La clause
SEARCH
indique le type de recherche désiré, la liste
des colonnes Ă tracer pour le tri et un nom de colonnes qui contiendra
les donnĂ©es rĂ©sults pouvant ĂȘtre utilisĂ©e pour le tri. Cette colonne sera
ajoutée implicitement aux lignes en sortie de la CTE.
Quand on travaille avec des requĂȘtes rĂ©cursives, il est important d'ĂȘtre sĂ»r
que la partie rĂ©cursive de la requĂȘte finira par ne retourner aucun enregistrement,
au risque sinon de voir la requĂȘte boucler indĂ©finiment. Quelquefois, utiliser
UNION
Ă la place de UNION ALL
peut
rĂ©soudre le problĂšme en supprimant les enregistrements qui doublonnent ceux dĂ©jĂ
retournés. Toutefois, souvent, un cycle ne met pas en jeu des enregistrements de
sortie qui sont totalement des doublons : il peut s'avérer nécessaire de
vĂ©rifier juste un ou quelques champs, afin de s'assurer que le mĂȘme point a dĂ©jĂ
été atteint précédemment. La méthode standard pour gérer ces situations est de
calculer un tableau de valeurs déjà visitées. Par exemple, observez de nouveau
la requĂȘte
suivante, qui parcourt une table graphe
en utilisant
un champ lien
:
WITH RECURSIVE parcourt_graphe(id, lien, donnee, profondeur) AS ( SELECT g.id, g.lien, g.donnee, 0 FROM graphe g UNION ALL SELECT g.id, g.lien, g.donnee, sg.profondeur + 1 FROM graphe g, parcourt_graphe sg WHERE g.id = sg.lien ) SELECT * FROM parcourt_graphe;
Cette requĂȘte va boucler si la liaison lien
contient des boucles. Parce que nous avons besoin de la sortie
« profondeur », simplement remplacer UNION ALL
par UNION
ne résoudra pas le problÚme.
Ă la place, nous avons besoin d'identifier si nous avons atteint un enregistrement
que nous avons déjà traité pendant notre parcours des liens. Nous ajoutons
deux colonnes is_cycle
et path
Ă la requĂȘte :
WITH RECURSIVE search_graph(id, link, data, depth, is_cycle, path) AS ( SELECT g.id, g.link, g.data, 0, false, ARRAY[g.id] FROM graph g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1, g.id = ANY(path), path || g.id FROM graph g, search_graph sg WHERE g.id = sg.link AND NOT is_cycle ) SELECT * FROM search_graph;
En plus de prĂ©venir les boucles, cette valeur de tableau est souvent pratique en elle-mĂȘme pour reprĂ©senter le « chemin » pris pour atteindre chaque enregistrement.
De façon plus gĂ©nĂ©rale, quand plus d'un champ a besoin d'ĂȘtre vĂ©rifiĂ© pour
identifier une boucle, utilisez un tableau d'enregistrements. Par exemple,
si nous avions besoin de comparer les champs f1
et
f2
:
WITH RECURSIVE search_graph(id, link, data, depth, is_cycle, path) AS ( SELECT g.id, g.link, g.data, 0, false, ARRAY[ROW(g.f1, g.f2)] FROM graph g UNION ALL SELECT g.id, g.link, g.data, sg.depth + 1, ROW(g.f1, g.f2) = ANY(path), path || ROW(g.f1, g.f2) FROM graph g, search_graph sg WHERE g.id = sg.link AND NOT is_cycle ) SELECT * FROM search_graph;
Omettez la syntaxe ROW()
dans le cas courant oĂč un seul
champ a besoin d'ĂȘtre testĂ© pour dĂ©terminer une boucle. Ceci permet, par
l'utilisation d'un tableau simple plutĂŽt que d'un tableau de type composite,
de gagner en efficacité.
Il existe une syntaxe interne pour simplifier la dĂ©tection de cycles. La requĂȘte ci-dessus peut aussi ĂȘtre Ă©crite ainsi :
WITH RECURSIVE search_graph(id, link, data, depth) AS (
SELECT g.id, g.link, g.data, 1
FROM graph g
UNION ALL
SELECT g.id, g.link, g.data, sg.depth + 1
FROM graph g, search_graph sg
WHERE g.id = sg.link
) CYCLE id SET is_cycle USING path
SELECT * FROM search_graph;
et elle sera réécrite en interne sous le forme ci-dessus. La clause
CYCLE
indique tout d'abord la liste des colonnes Ă
tracer pour une détection de cycle, puis le nom de la colonne qui
indiquera si un cycle a été détecté, et enfin le nom d'une autre
colonne qui tracera le chemin. Les colonnes cycle et chemin seront
automatiquement ajoutées aux lignes en sortie de la CTE.
La colonne du chemindu cycle est calculĂ©e de la mĂȘme façon que
l'affiche la colonne de tri depth-first
dans la section prĂ©cĂ©dente. Une requĂȘte peut avoir Ă la fois une
clause SEARCH
et une clause
CYCLE
, mais une spécification de recherche
depth-first et une spécification de
recherche de cyle vont créer des calculs redondants, donc il est plus
efficace d'utiliser juste la clause CYCLE
et trier
par la colonne du chemin. Si un tri
breadth-first est voulu, alors indiquer
les deux, SEARCH
and CYCLE
, peut
ĂȘtre utile.
Si vous n'ĂȘtes pas certain qu'une requĂȘte puisse boucler, une astuce pratique
pour la tester est d'utiliser LIMIT
dans la requĂȘte parente.
Par exemple, cette requĂȘte bouclerait indĂ©finiment sans un
LIMIT
:
WITH RECURSIVE t(n) AS (
SELECT 1
UNION ALL
SELECT n+1 FROM t
)
SELECT n FROM t LIMIT 100;
Ceci fonctionne parce que l'implémentation de PostgreSQL
n'Ă©value que le nombre d'enregistrements de la requĂȘte WITH
rĂ©cupĂ©rĂ©s par la requĂȘte parente. L'utilisation de cette astuce en production
est déconseillée parce que d'autres systÚmes pourraient fonctionner différemment.
Par ailleurs, cela ne fonctionnera pas si vous demandez Ă la requĂȘte externe
de trier les rĂ©sultats de la requĂȘte rĂ©cursive, ou si vous les joignez Ă une
autre table, parce dans ces cas, la requĂȘte extĂ©rieure essaiera habituellement
de rĂ©cupĂ©rer toute la sortie de la requĂȘte WITH
de toute façon.
Une propriĂ©tĂ© intĂ©ressante des requĂȘtes WITH
est qu'elles
ne sont Ă©valuĂ©es qu'une seule fois par exĂ©cution de la requĂȘte parente ou
des requĂȘtes WITH
sĆurs.
Par conséquent, les calculs coûteux qui sont nécessaires à plusieurs endroits
peuvent ĂȘtre placĂ©s dans une requĂȘte WITH
pour éviter le
travail redondant. Un autre intĂ©rĂȘt peut ĂȘtre d'Ă©viter l'exĂ©cution multiple
d'une fonction ayant des effets de bord.
Néanmoins, le revers de la médaille est que l'optimiseur n'est pas en
mesure de faire descendre les restrictions de la requĂȘte parent dans
une requĂȘte WITH
à références multiples, car cela pourrait
affecter toutes les utilisations de la sortie de la requĂȘte WITH
alors que cela ne devrait en affecter qu'une seule.
La requĂȘte
WITH
sera généralement exécutée telle quelle, sans
suppression d'enregistrements, que la requĂȘte parente devra supprimer ensuite.
(Mais, comme mentionnĂ© prĂ©cĂ©demment, l'Ă©valuation pourrait s'arrĂȘter rapidement
si la (les) rĂ©fĂ©rence(s) Ă la requĂȘte ne demande(nt) qu'un nombre limitĂ©
d'enregistrements).
NĂ©anmoins, si une requĂȘte WITH
est non récursive et
qu'elle est libre de tout effet de bord (autrement dit un
SELECT
ne contenant aucune fonction volatile), alors
elle peut ĂȘtre intĂ©grĂ©e dans la requĂȘte parent, permettant ainsi une
optimisation de la jointure sur les deux niveaux de la requĂȘte. Par dĂ©faut,
ceci survient si la requĂȘte parente fait rĂ©fĂ©rence une seule fois Ă la
requĂȘte WITH
mais si elle y fait référence plusieurs
fois. Vous pouvez surcharger cette décision en indiquant
MATERIALIZED
pour forcer un calcul sĂ©parĂ© de la requĂȘte
WITH
ou en spécifiant NOT
MATERIALIZED
pour la forcer pour ĂȘtre intĂ©grĂ©e dans la requĂȘte
parente. Ce dernier choix risque de dupliquer des calculs sur la requĂȘte
WITH
, mais cela peut apporter un gain net si chaque
utilisation de la requĂȘte WITH
ne nécessite qu'une
petite partie de la sortie complĂšte de la requĂȘte WITH
.
Un exemple simple de ces rĂšgles est le suivant :
WITH w AS ( SELECT * FROM big_table ) SELECT * FROM w WHERE key = 123;
Cette requĂȘte WITH
va ĂȘtre intĂ©grĂ©e, produisant le mĂȘme
plan d'exécution que :
SELECT * FROM big_table WHERE key = 123;
EN particulier, s'il existe un index sur key
, il
sera probablement utilisé pour récupérer les lignes pour lesquelles
key = 123
. D'un autre cÎté, dans
WITH w AS ( SELECT * FROM big_table ) SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref WHERE w2.key = 123;
la requĂȘte WITH
sera matérialisée, produisant une copie
temporaire de big_table
qui est ensuite jointe avec
elle-mĂȘme -- sans intĂ©rĂȘt pour un index. Cette requĂȘte sera exĂ©cutĂ©e
bien plus efficacement s'il est écrite ainsi :
WITH w AS NOT MATERIALIZED ( SELECT * FROM big_table ) SELECT * FROM w AS w1 JOIN w AS w2 ON w1.key = w2.ref WHERE w2.key = 123;
pour que les restrictions de la requĂȘte parent puissent ĂȘtre appliquĂ©es
directement aux parcours de big_table
.
Voici un exemple oĂč NOT MATERIALIZED
pourrait ĂȘtre
indésirable :
WITH w AS ( SELECT key, very_expensive_function(val) as f FROM some_table ) SELECT * FROM w AS w1 JOIN w AS w2 ON w1.f = w2.f;
Ici, la matĂ©rialisation de la requĂȘte WITH
assure que
la very_expensive_function
est évaluée uniquement
une fois par ligne de table, et non pas deux fois.
Les exemples précédents ne montrent que des cas d'utilisation de WITH
avec SELECT
, mais on peut les attacher de la mĂȘme façon Ă un
INSERT
, UPDATE
, DELETE
ou MERGE
.
Dans chaque cas, le mécanisme fournit en fait des tables temporaires auxquelles on
peut faire référence dans la commande principale.
WITH
#
Vous pouvez utiliser des ordres de modification de données (INSERT
,
UPDATE
ou DELETE
, mais pas MERGE
)
dans WITH
. Cela
vous permet d'effectuer plusieurs opĂ©rations diffĂ©rentes dans la mĂȘme requĂȘte.
Par exemple:
WITH lignes_deplacees AS ( DELETE FROM produits WHERE "date" >= '2010-10-01' AND "date" < '2010-11-01' RETURNING * ) INSERT INTO log_produits SELECT * FROM lignes_deplacees;
Cette requĂȘte dĂ©place les enregistrements de produits
vers
log_produits
. Le DELETE
du WITH
supprime les enregistrements spécifiés de produits
, en retournant leurs
contenus par la clause RETURNING
; puis la requĂȘte primaire lit cette sortie et
l'insĂšre dans log_produits
.
Un point important à noter de l'exemple précédent est que la clause WITH
est attachée à l'INSERT
, pas au sous-SELECT
de l'
INSERT
. C'est nécessaire parce que les ordres de modification de données
ne sont autorisés que dans les clauses WITH
qui sont attachées à l'ordre de
plus haut niveau. Toutefois, les rÚgles de visibilité normales de WITH
s'appliquent, il est donc possible de faire référence à la sortie du WITH
dans le sous-SELECT
.
Les ordres de modification de données dans WITH
ont habituellement
des clauses RETURNING
(voir Section 6.4), comme dans l'exemple précédent.
C'est la sortie de la clause RETURNING
, pas la
table cible de l'ordre de modification de données, qui forme la table temporaire à laquelle
on pourra faire rĂ©fĂ©rence dans le reste de la requĂȘte. Si un ordre de
modification de données dans WITH
n'a pas de clause RETURNING
,
alors il ne produit pas de table temporaire et ne peut pas ĂȘtre utilisĂ© dans le reste de la requĂȘte.
Un ordre de ce type sera toutefois exécuté.
En voici un exemple (dĂ©nuĂ© d'intĂ©rĂȘt) :
WITH t AS ( DELETE FROM foo ) DELETE FROM bar;
Cet exemple supprimerait tous les éléments des tables foo
et
bar
. Le nombre d'enregistrements retourné au client n'inclurait
que les enregistrements supprimés de bar
.
Les autoréférences récursives dans les ordres de modification de données ne
sont pas autorisées. Dans certains cas, il est possible de contourner cette limitation
en faisant référence à la sortie d'un WITH
, par exemple:
WITH RECURSIVE pieces_incluses(sous_piece, piece) AS ( SELECT sous_piece, piece FROM pieces WHERE piece = 'notre_produit' UNION ALL SELECT p.sous_piece, p.piece FROM pieces_incluses pr, pieces p WHERE p.piece = pr.sous_piece ) DELETE FROM pieces WHERE piece IN (SELECT piece FROM pieces_incluses);
Cette requĂȘte supprimerait toutes les piĂšces directes et indirectes d'un produit.
Les ordres de modification de données dans WITH
sont exécutés exactement
une fois, et toujours jusqu'Ă la fin, indĂ©pendamment du fait que la requĂȘte primaire
lise tout (ou mĂȘme une partie) de leur sortie. Notez que c'est diffĂ©rent de la rĂšgle pour
SELECT
dans WITH
: comme précisé dans la section précédente,
l'exécution d'un SELECT
n'est poursuivie que tant que la requĂȘte primaire
consomme sa sortie.
Les sous-requĂȘtes du WITH
sont toutes exécutées simultanément et
simultanĂ©ment avec la requĂȘte principale. Par consĂ©quent, quand vous utilisez un ordre de
modification de données avec WITH
, l'ordre dans lequel les mises Ă jour
sont effectuĂ©es n'est pas prĂ©visible. Toutes les requĂȘtes sont exĂ©cutĂ©es dans le mĂȘme
instantané (voyez Chapitre 13), elles ne peuvent donc pas
voir les effets des autres sur les tables cibles. Ceci rend sans importance le problĂšme de
l'imprévisibilité de l'ordre des mises à jour, et signifie que RETURNING
est
la seule façon de communiquer les modifications entre les diffĂ©rentes sous-requĂȘtes
WITH
et la requĂȘte principale. En voici un exemple :
WITH t AS ( UPDATE produits SET prix = prix * 1.05 RETURNING * ) SELECT * FROM produits;
Le SELECT
externe retournerait les prix originaux avant
l'action de UPDATE
, alors qu'avec :
WITH t AS ( UPDATE produits SET prix = prix * 1.05 RETURNING * ) SELECT * FROM t;
le SELECT
externe retournerait les données mises à jour.
Essayer de mettre Ă jour le mĂȘme enregistrement deux fois dans le mĂȘme ordre n'est pas supportĂ©. Seule une des deux modifications a lieu, mais il n'est pas aisĂ© (et quelquefois impossible) de dĂ©terminer laquelle. Ceci s'applique aussi pour la suppression d'un enregistrement qui a dĂ©jĂ Ă©tĂ© mis Ă jour dans le mĂȘme ordre : seule la mise Ă jour est effectuĂ©e. Par consĂ©quent, vous devriez Ă©viter en rĂšgle gĂ©nĂ©rale de mettre Ă jour le mĂȘme enregistrement deux fois en un seul ordre. En particulier, Ă©vitez d'Ă©crire des sous-requĂȘtes qui modifieraient les mĂȘmes enregistrements que la requĂȘte principale ou une autre sous-requĂȘte. Les effets d'un ordre de ce type seraient imprĂ©visibles.
à l'heure actuelle, les tables utilisées comme cibles d'un ordre modifiant les données
dans un WITH
ne doivent avoir ni rĂšgle conditionnelle, ni rĂšgle
ALSO
, ni une rĂšgle INSTEAD
qui génÚre plusieurs ordres.