2
votes

Oracle: mise à jour conditionnelle basée sur le résultat SELECT

Je souhaite effectuer une mise à jour uniquement si l'instruction select renvoie une valeur particulière.

Exemple:

Table donnée:

ItemId | CheckIn
0        0
1        0
2        1
3        1
4        1

J'exploiterais une grande partie des ItemIds, par exemple ItemId 1,2,3.

Si TOUS les ItemIds sélectionnés ont la valeur CheckIn 0, alors je devrais mettre à jour CheckIn des ItemIds à 1. Sinon, si à au moins l'un des ItemIds sélectionnés est égal à 1, aucune mise à jour ne doit être effectuée.

Est-ce possible dans une seule instruction SELECT / UPDATE du tout? strong>

La vérification de la valeur CheckIn et la mise à jour qui suit doivent être thread-safe (Java 8).

Ce qui fonctionnerait, c'est un SELECT FOR UPDATE séparé avant l'instruction UPDATE réelle , ce que je veux éviter cependant.

En tant que tel, la seule autre option que je pense serait de placer le SELECT avec UPDATE dans une seule requête conditionnelle, et je ne suis pas sûr que cela soit possible, d'où ma question.

Je travaille avec Java 8 btw, donc une solution basée sur Java serait également acceptable si vous savez comment pour y parvenir.


4 commentaires

Pourquoi n'utilisez-vous pas l'instruction select comme sous-requête? Quelque chose comme ceci: update some_table set some_field = some_value where (select some_field = some_value from some_table where some_field in (some_list))


Je n'ai pas besoin d'une mise à jour conditionnelle dans laquelle je ne mets à jour que les lignes d'une certaine valeur, j'ai besoin d'une mise à jour conditionnelle dans laquelle je n'effectue une mise à jour que si la sous-requête renvoie un certain résultat. En tant que tel, je souhaite effectuer une mise à jour uniquement si AUCUNE des valeurs CheckIn des ItemIds sélectionnés n'a la valeur 1.


Dans ce cas, vous souhaiterez peut-être stocker le résultat de la requête select dans une variable. Et puis exécutez la requête update basée sur ce résultat.


Ce n'est pas thread-safe, comme décrit dans la question d'origine. Et je veux éviter de verrouiller les lignes, comme décrit


3 Réponses :


1
votes

Vous pouvez procéder de cette manière:

Requête MySQL:

UPDATE temp_table
SET CheckIn = 1
WHERE ItemId IN (1, 2, 3)
  AND 0 = (SELECT sum(derived_table.CheckIn)
       FROM (SELECT CheckIn FROM temp_table WHERE ItemId IN (1, 2, 3)) derived_table);

Requête Oracle:

UPDATE temp_table
SET CheckIn = 1
WHERE ItemId IN (1, 2, 3)
  AND (SELECT sum(derived_table.CheckIn) = 0
       FROM (SELECT CheckIn FROM temp_table WHERE ItemId IN (1, 2, 3)) AS derived_table);

J'ai créé une table dérivée et utilisé une fonction de groupe sum () pour gérer ce scénario. Je l'ai testé et il fonctionne comme décrit dans la question.


0 commentaires

0
votes

Essayez ceci:

UPDATE YOUR_TABLE TOUT
SET
    TOUT.CHECKIN = (
        SELECT
            CASE
                WHEN SUM(T.CHECKIN) = 0 THEN 1
                ELSE TOUT.CHECKIN
            END
        FROM
            YOUR_TABLE T
        WHERE
            T.ITEMID IN (
                1,
                2,
                3
            )
    )
WHERE
    TOUT.ITEMID IN (
        1,
        2,
        3
    );

Cheers !!


4 commentaires

Cette requête est pire que la version avec sum () dans la clause where car elle peut générer beaucoup plus d'annulation. Mettre à jour une valeur pour elle-même n'est pas la même chose que ne pas mettre à jour la valeur du tout.


Dans la clause WHERE, il n'y a que 3 éléments. Je ne pense pas que cela générera beaucoup de données d'annulation.


Bien, dans ce cas, ce ne sera pas le cas (à moins que la requête ne soit exécutée, disons 1 000 000 fois), mais ce n'est jamais une bonne pratique quel que soit le nombre d'éléments. De plus, s'il y a une nouvelle exigence pour supprimer le filtre par élément, votre requête doit être complètement refactorisée.


Bien sûr, vous avez raison, mais dans ce cas, je ne pense pas que nous devrions même penser à annuler



1
votes

Vous pouvez le faire dans une instruction de fusion, par exemple:

MERGE INTO your_table tgt
  USING (SELECT itemid,
                checkin,
                overall_checkin
         FROM   (SELECT itemid,
                        checkin,
                        MAX(CASE WHEN checkin = 0 THEN 0 ELSE 1) OVER () overall_checkin
                 FROM   your_table
                 WHERE  itemid IN (1, 2, 3))
         WHERE  overall_checkin = 0) src
   ON (tgt.itemid = src.itemid) -- assuming that itemid is a primary/unique column
WHEN MATCHED THEN
  UPDATE tgt.checkin = 1;

Et voici un db fiddle démontrant l'instruction de fusion.


1 commentaires

Pourquoi faire des hypothèses si vous pouvez fusionner sur rowid?