Mise à jour sur la base de sous-requête échoue
Question
Je suis en train de faire la mise à jour suivante dans Oracle 10gR2:
update
(select voyage_port_id, voyage_id, arrival_date, port_seq,
row_number() over (partition by voyage_id order by arrival_date) as new_seq
from voyage_port) t
set t.port_seq = t.new_seq
Voyage_port_id est la clé primaire, voyage_id est une clé étrangère. J'essaie d'attribuer un numéro de séquence en fonction des dates de chaque voyage.
Cependant, l'échec ci-dessus avec ORA-01732: opération de manipulation de données non juridique sur ce point de vue
Quel est le problème et comment puis-je éviter?
La solution
Puisque vous ne pouvez pas mettre à jour les sous-requêtes avec row_number
, vous devrez calculer le numéro de ligne dans la partie set
de la mise à jour. Au début, j'essayé ceci:
update voyage_port a
set a.port_seq = (
select
row_number() over (partition by voyage_id order by arrival_date)
from voyage_port b
where b.voyage_port_id = a.voyage_port_id
)
Mais cela ne fonctionne pas, parce que le sous-requête sélectionne une seule ligne, puis le row_number()
est toujours 1. En utilisant une autre sous-requête permet un résultat significatif:
update voyage_port a
set a.port_seq = (
select c.rn
from (
select
voyage_port_id
, row_number() over (partition by voyage_id
order by arrival_date) as rn
from voyage_port b
) c
where c.voyage_port_id = a.voyage_port_id
)
Il fonctionne, mais plus complexe que je pense à cette tâche.
Autres conseils
Vous pouvez mettre à jour des vues, mais il y a des restrictions et l'une est que la vue ne doit pas contenir des fonctions analytiques. Voir SQL Langage sur UPDATE et la recherche de la première apparition de « analytique ».
Cela fonctionne, à condition aucune visite au voyage plus d'un port le jour même (ou les dates comprennent un élément de temps qui les rend uniques):
update voyage_port vp
set vp.port_seq =
( select count(*)
from voyage_port vp2
where vp2.voyage_id = vp.voyage_id
and vp2.arrival_date <= vp.arrival_date
)
Je pense que cela traite le cas où une visite de voyage à plus de 1 port par jour et il n'y a pas de composante de temps (bien que la séquence des ports visités sur le même jour est alors arbitraire):
update voyage_port vp
set vp.port_seq =
( select count(*)
from voyage_port vp2
where vp2.voyage_id = vp.voyage_id
and (vp2.arrival_date <= vp.arrival_date)
or ( vp2.arrival_date = vp.arrival_date
and vp2.voyage_port_id <= vp.voyage_port_id
)
)
Ne pensez pas que vous pouvez mettre à jour une table dérivée, je réécris comme:
update voyage_port
set port_seq = t.new_seq
from
voyage_port p
inner join
(select voyage_port_id, voyage_id, arrival_date, port_seq,
row_number() over (partition by voyage_id order by arrival_date) as new_seq
from voyage_port) t
on p.voyage_port_id = t.voyage_port_id
Le premier jeton après la mise à jour doit être le nom de la table à mettre à jour, vos colonnes à jour. Je ne sais pas ce que vous essayez d'atteindre avec l'instruction select où il est, mais vous pouvez » mise à jour le jeu de résultats de la sélection légalement.
Une version de SQL, deviner ce que vous avez à l'esprit, pourrait ressembler à ...
update voyage_port t
set t.port_seq = (<select statement that generates new value of port_seq>)
NOTE: pour utiliser une instruction select pour définir une valeur comme celui-ci, vous devez vous assurer que 1 ligne sera retournée à partir de la sélection
EDIT: déclaration modifiée ci-dessus pour refléter ce que je tentais d'expliquer. La question a été répondu très bien par Andomar ci-dessus