Nécessité de "lier" UPDATE
avec ORDER BY
. J'essaie d'utiliser des curseurs, mais l'erreur est la suivante:
cursor "cursupd" doesn't specify a line, SQL state: 24000
Code:
BEGIN;
DECLARE cursUpd CURSOR FOR SELECT * FROM "table" WHERE "field" = 5760 AND "sequence" >= 0 AND "sequence" < 9 ORDER BY "sequence" DESC;
UPDATE "table" SET "sequence" = "sequence" + 2 WHERE CURRENT OF cursUpd;
CLOSE cursUpd;
COMMIT;
Comment le faire correctement?
Sans curseur, quand j'aime ça:
UPDATE "CableLinePoint" AS "t"
SET "sequence" = t."sequence" + 2
from (
select max("sequence") "sequence", "id"
from "CableLinePoint"
where
"CableLine" = 5760
group by "id"
ORDER BY "sequence" DESC
) "s"
where "t"."id" = "s"."id" and "t"."sequence" = "s"."sequence"
Je reçois l'erreur unique. Donc, besoin de mettre à jour à partir de la fin plutôt que du début.
Table:
id|CableLine|sequence
10| 2 | 1
11| 2 | 2
12| 2 | 3
13| 2 | 4
14| 2 | 5
Besoin de mettre à jour (augmenter) le champ "séquence". "sequence" a le type "index", donc impossible:
UPDATE "table" SET "sequence" = "sequence" + 1 WHERE "CableLine" = 2
Lorsque "séquence" dans la ligne avec id = 10
est incrémentée de 1
, je reçois une erreur indiquant qu'une autre ligne avec "sequence" = 2
existe déjà.
UPDATE
avec ORDER BY
En ce qui concerne la question soulevée par le titre: Il n’existe pas de ORDER BY
dans une commande SQL UPDATE
. Postgres met à jour les lignes dans un ordre arbitraire. Mais vous avez des options (limitées) pour décider si les contraintes sont vérifiées après chaque ligne, après chaque instruction ou à la fin de la transaction. Vous pouvez éviter les violations de clé en double pour les états intermédiaire avec une contrainte DEFERRABLE
.
Je cite ce que nous avons élaboré sous cette question:
contrainte définie DEFERRABLE INITIALY IMMEDIATE est toujours DIFFEREE?
Les contraintes NOT DEFERRED
sont vérifiées après chaque ligne.
Les contraintes DEFERRABLE
définies sur IMMEDIATE
(INITIALLY IMMEDIATE
ou via SET CONSTRAINTS
) sont vérifiées après chaque instruction.
Il y a cependant des limites. Les contraintes de clé étrangère nécessitent non-différable contraintes sur la ou les colonnes cibles.
Les colonnes référencées doivent être les colonnes d'un unique non reportable ou contrainte de clé primaire dans la table référencée.
Mis à jour après la mise à jour de la question.
En supposant que "sequence"
n’est jamais négatif en fonctionnement normal, vous pouvez éviter des erreurs uniques telles que:
UPDATE tbl SET "sequence" = ("sequence" + 1) * -1
WHERE "CableLine" = 2;
UPDATE tbl SET "sequence" = "sequence" * -1
WHERE "CableLine" = 2
AND "sequence" < 0;
Avec une contrainte non différable (par défaut), vous devez exécuter deux transactions distinctes pour que cela fonctionne. Exécutez les commandes en succession rapide pour éviter les problèmes de simultanéité. La solution n'est évidemment pas adaptée à une charge simultanée importante.
De côté:
On peut sauter la clé Word AS
pour les alias de table, mais il est déconseillé de faire la même chose pour les alias de colonnes.
Je conseillerais de ne pas utiliser les mots-clés SQL comme identificateurs, même si cela est autorisé.
Sur une plus grande échelle ou pour des bases de données avec une charge simultanée importante, il est plus sage d'utiliser une colonne serial
pour le classement relatif des lignes. Vous pouvez générer des nombres à partir de 1 et aucun espace avec la fonction de fenêtre row_number()
dans une vue ou une requête. Considérez cette réponse connexe:
Est-il possible d’utiliser une séquence PG sur une étiquette par disque?
UPDATE
avec ORDER BY
:
UPDATE thetable
SET columntoupdate=yourvalue
FROM (SELECT rowid, 'thevalue' AS yourvalue
FROM thetable
ORDER BY rowid
) AS t1
WHERE thetable.rowid=t1.rowid;
L'ordre UPDATE
est toujours aléatoire (je suppose), mais les valeurs fournies à la commande UPDATE
correspondent à la condition thetable.rowid=t1.rowid
. Donc, ce que je fais, c'est de sélectionner d'abord la table 'mise à jour' dans la mémoire, elle s'appelle t1
dans le code ci-dessus, puis de faire en sorte que ma table physique ressemble à t1
. Et l'ordre de mise à jour n'a plus d'importance.
Pour ce qui est du vrai UPDATE
commandé, je ne pense pas que cela puisse être utile à personne.
Cela a fonctionné pour moi:
[update update here] OPTION (MAXDOP 1) - empêche la taille de ligne de provoquer l’utilisation d’un spool très vif, qui mutile l’ordre dans lequel les enregistrements sont mis à jour.
J'utilise un index int en cluster dans un ordre séquentiel (en générant un si nécessaire) et je n'avais rencontré aucun problème récemment, et même dans ce cas uniquement sur de petits ensembles de lignes pour lesquels l'optimiseur de plan de requête avait décidé d'utiliser un spool différé.
Théoriquement, je pourrais utiliser la nouvelle option pour interdire l'utilisation du spool, mais je trouve maxdop plus simple.
Je suis dans une situation unique car les calculs sont isolés (utilisateur unique). Une situation différente peut nécessiter une alternative à l'utilisation de maxdop limit pour éviter les conflits.
Lazy Way , (aka pas le plus rapide ou le meilleur moyen)
CREATE OR REPLACE FUNCTION row_number(table_name text, update_column text, start_value integer, offset_value integer, order_by_column text, order_by_descending boolean)
RETURNS void AS
$BODY$
DECLARE
total_value integer;
my_id text;
command text;
BEGIN
total_value = start_value;
command = 'SELECT ' || order_by_column || ' FROM ' || table_name || ' ORDER BY ' || order_by_column;
if (order_by_descending) THEN
command = command || ' desc';
END IF;
FOR my_id in EXECUTE command LOOP
command = 'UPDATE ' || table_name || ' SET ' || update_column || ' = ' || total_value || ' WHERE ' || order_by_column || ' = ' || my_id|| ';';
EXECUTE command;
total_value = total_value + offset_value;
END LOOP;
END;
$BODY$
LANGUAGE 'plpgsql' VOLATILE
COST 100;
Exemple
SELECT row_number ('regispro_spatial_2010.ags_states_spatial', 'order_id', 10,1, 'ogc_fid', true)