Проблемы с курсорами PL / SQL - PullRequest
1 голос
/ 26 апреля 2020

Это таблицы:

SQL> DESC pais;

Name                                      Null?    Type                        
----------------------------------------- -------- ----------------------------
COD_PAIS                                  NOT NULL NUMBER(4)                   
NOMBRE                                             VARCHAR2(30)                
CAPITAL                                            VARCHAR2(20)                
EXTENSION                                          NUMBER(10)                  
MONEDA                                             VARCHAR2(20)                
NUM_HAB                                            NUMBER(10)                  
PIB                                                NUMBER(20,2)                
CONTINENTE                                         VARCHAR2(20)                
CASCOS                                             CHAR(1)                     
SQL> DESC pertenece_a;

Name                                      Null?    Type                        
----------------------------------------- -------- ----------------------------
COD_ORGANIZACION                          NOT NULL NUMBER(10)                  
COD_PAIS                                  NOT NULL NUMBER(4)                   
SQL> DESC organizacion;

Name                                      Null?    Type                        
----------------------------------------- -------- ----------------------------
COD_ORGANIZACION                          NOT NULL NUMBER(10)                  
NOMBRE                                             VARCHAR2(80)                
SIGLAS                                             VARCHAR2(6)                 

Мне нужно обновить значение cascos до S , если siglas равно ONU . Если нет, его необходимо обновить до N . Это мой код SQL Разработчик не сообщает об ошибках, однако изменение курсора С1 не обновляется. Я должен использовать курсоры.

SET SERVEROUTPUT ON;
DECLARE
    CURSOR C1 IS SELECT cascos FROM pais P, organizacion O, pertenece_a PE WHERE P.cod_pais=PE.cod_pais AND O.cod_organizacion=PE.cod_organizacion AND O.siglas='ONU' FOR UPDATE;
    CURSOR C2 IS SELECT cascos FROM pais WHERE cascos IS NULL FOR UPDATE;
    registro1 C1%ROWTYPE;
    registro2 C2%ROWTYPE;
BEGIN
    IF NOT C1%ISOPEN THEN
            OPEN C1;
    END IF;
    IF NOT C2%ISOPEN THEN
            OPEN C2;
    END IF;
    LOOP
        FETCH C1 INTO registro1;
        EXIT WHEN C1%NOTFOUND;
        UPDATE pais SET cascos='S' WHERE CURRENT OF C1;
    END LOOP;
    LOOP
        FETCH C2 INTO registro2;
        EXIT WHEN C2%NOTFOUND;
        UPDATE pais SET cascos='N' WHERE CURRENT OF C2;
    END LOOP;
    CLOSE C1;
    CLOSE C2;
END;
/
SELECT * FROM pais WHERE cascos='S';

Вывод SQL для разработчиков просто говорит:

PL/SQL procedure successfully completed.

no rows selected

В чем может быть ошибка? Может ли быть проблема в том, что ряд pais может быть связан с более чем одним рядом (siglas) organizacion? Например:

NOMBRE                         SIGLAS
------------------------------ ------
Venezuela                      ONU   
Venezuela                      OEA   
Venezuela                      MS    
Venezuela                      OPEP  
Estados Unidos                 ONU   
Estados Unidos                 OTAN  
Estados Unidos                 OEA   
Estados Unidos                 APEC  
Estados Unidos                 OCDE  
Estados Unidos                 OSCE  
Estados Unidos                 TLCAN 

Спасибо.

Ответы [ 3 ]

1 голос
/ 26 апреля 2020

Кажется, вы хотите обновить все pais строки. Те, у кого совпадает ONU, получают cascos = 'S', остальные получают cascos = 'N'. Для этого вы можете использовать простой оператор обновления, который даже быстрее, чем PL / SQL.

Например:

update pais
set cascos = 
  case when cod_pais in (
                          select pe.cod_pais 
                          from pertenece_a pe
                          join organizacion o using (cod_organizacion)
                          where o.siglas = 'ONU'
                        )
    then 'S'
    else 'N'
  end;
0 голосов
/ 26 апреля 2020

Если все значения cascos равны нулю для начала, то обновления во втором l oop будут перезаписывать изменения, сделанные в первом l oop. Быстрое решение состоит в том, чтобы открыть c2 после завершения первого l oop, а не перед ним.

Однако вы можете упростить код следующим образом:

begin
    for r in (
        select p.cod_pais
        from   pais p
               join pertenece_a pe on pe.cod_pais = p.cod_pais
               join organizacion o on o.cod_organizacion = pe.cod_organizacion
        where  o.siglas = 'ONU'
        and    nvl(p.cascos,'?') <> 'S'
        for    update
    )
    loop
        update pais set cascos = 'S'
        where  cod_pais = r.cod_pais;
    end loop;

    update pais set cascos = 'N' where cascos is null;
end;

A дальнейший рефакторинг будет состоять в том, чтобы переписать все как один update или merge, и не иметь никакой явной обработки курсора или циклов. Это также было бы более эффективно, потому что один оператор SQL лучше, чем один в строке, и вы могли бы избежать двух проходов через pais, хотя, возможно, если это очень маленькая таблица, это не имеет большого значения.

Кстати, вам не нужно проверять IF NOT C1%ISOPEN, потому что вы только что объявили c1 и не открыли его, поэтому он не может быть открыт. Также хорошей практикой является использование явного синтаксиса соединения в стиле ANSI, поскольку сложнее пропустить условия соединения.

Кроме того, вы должны использовать стандартный тип строки varchar2 для всех символьных столбцов и никогда char. Распространенной ошибкой считается, что char имеет какое-то особое преимущество для коротких значений, но это не так, а использование нестандартных типов приводит только к ошибкам.

0 голосов
/ 26 апреля 2020

Вы можете попробовать это, используя два оператора UPDATE.

UPDATE pais P SET cascos='S' WHERE EXISTS ( SELECT 1 FROM pais P, organizacion O, pertenece_a PE WHERE P.cod_pais=PE.cod_pais AND O.cod_organizacion=PE.cod_organizacion AND O.siglas='ONU');

UPDATE pais P SET cascos='N' WHERE nvl(cascos,'S')<>'S'; `

...