web-dev-qa-db-fra.com

MISE À JOUR AVEC ORDER BY

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?

MISE À JOUR 1

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.

MISE À JOUR 2

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à.

11
dedoki

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.

Workaround

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é.

Éviter le problème

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?

9

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.

12
alexkovelsky

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.

0
cmore

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)

0
user3605589