Преобразование простого обновления Oracle в CURSOR ДЛЯ ОБНОВЛЕНИЯ - PullRequest
1 голос
/ 17 января 2012

Я работаю в Oracle RDBMS.

Предположим, у меня есть следующий оператор UPDATE в контексте хранимой процедуры:

UPDATE memberPlan mp /* C$MP$MEMBERPLANID */
SET ( mp.lastContractId          ,
      mp.lastContractIdChanged   ,
      mp.lastGroupOrPolicyNumber ) = (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
            htu.contractId                        as lastContractId          ,
            CASE WHEN htu.contractId IS NULL THEN 
               NULL                               
            ELSE                                  
               varRunDate                         
            END                                   as lastContractIdChanged   ,
            htu.groupOrPolicyNumber               as lastGroupOrPolicyNumber
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
WHERE EXISTS (
    SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
    FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
    WHERE htu.memberPlanId = mp.memberPlanId);

Проблема в том, что таблица члена плана блокируется во время выполнения процедуры.

Я бы хотел блокировать, обновлять и освобождать только одну строку за раз и оставьте остальные строки в таблице незапертыми.

Итак, я разработал следующее решение:

DECLARE
   CURSOR memberPlan1_cur(parmRunDate IN DATE) IS
      SELECT 
         mp.lastContractId           as lastContractId             ,
         mp.lastContractIdChanged    as lastContractIdChanged      ,
         mp.lastGroupOrPolicyNumber  as lastGroupOrPolicyNumber    ,
         htu.lastContractId          as updLastContractId          ,
         htu.lastContractIdChanged   as updLastContractIdChanged   ,
         htu.lastGroupOrPolicyNumber as updLastGroupOrPolicyNumber 
      FROM 
         memberPlan mp
            INNER JOIN
              (SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
                  memberPlanId                      as memberPlanId            ,
                  contractId                        as lastContractId          ,
                  CASE WHEN contractId IS NULL THEN 
                     NULL                           
                  ELSE                              
                     parmRunDate
                  END                               as lastContractIdChanged   ,
                  groupOrPolicyNumber               as lastGroupOrPolicyNumber
               FROM htUpdateMemberPlan) htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
            ON mp.memberPlanId = htu.memberPlanId
      WHERE EXISTS (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur(varRunDate) LOOP
      UPDATE memberPlan mp /* C$MP$MEMBERPLANID */
      SET mp.lastContractId          = memberPlan1_row.updLastContractId          ,
          mp.lastContractIdChanged   = memberPlan1_row.updLastContractIdChanged   ,
          mp.lastGroupOrPolicyNumber = memberPlan1_row.updLastGroupOrPolicyNumber 
      WHERE CURRENT OF memberPlan1_cur;
   END LOOP;
   COMMIT;
END;

Я в основном возвращаю значения обновления вместе с исходные поля, которые я хочу обновить.

Решение, приведенное выше, может быть оптимизировано для кода ниже,
вариант SQL выше, где проверка существования
заменено на прямое внутреннее соединение:

DECLARE
   CURSOR memberPlan1_cur(parmRunDate IN DATE) IS
      SELECT /*+ FULL(mp) INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
         mp.lastContractId                     as lastContractId             ,
         mp.lastContractIdChanged              as lastContractIdChanged      ,
         mp.lastGroupOrPolicyNumber            as lastGroupOrPolicyNumber    ,
         htu.contractId                        as updLastContractId          ,
         CASE WHEN htu.contractId IS NULL THEN 
            NULL                           
         ELSE                              
            parmRunDate
         END                                   as updLastContractIdChanged   ,
         htu.groupOrPolicyNumber               as updLastGroupOrPolicyNumber 
      FROM 
         memberPlan mp
            INNER JOIN htUpdateMemberPlan htu  /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
            ON mp.memberPlanId = htu.memberPlanId
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur(varRunDate) LOOP
      UPDATE memberPlan mp /* C$MP$MEMBERPLANID */
      SET mp.lastContractId          = memberPlan1_row.updLastContractId          ,
          mp.lastContractIdChanged   = memberPlan1_row.updLastContractIdChanged   ,
          mp.lastGroupOrPolicyNumber = memberPlan1_row.updLastGroupOrPolicyNumber 
      WHERE CURRENT OF memberPlan1_cur;
   END LOOP;
   COMMIT;
END;

Я думал об этом по-другому:

DECLARE
   CURSOR memberPlan1_cur IS
      SELECT 
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber 
      FROM memberPlan mp
      WHERE EXISTS (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur LOOP
      UPDATE memberPlan mp
      SET (mp.lastContractId          ,
           mp.lastContractIdChanged   ,
           mp.lastGroupOrPolicyNumber ) =
              (SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
                  htu.contractId                        as lastContractId          ,
                  CASE WHEN htu.contractId IS NULL THEN 
                     NULL                           
                  ELSE                              
                     varRunDate
                  END                                   as lastContractIdChanged   ,
                  htu.groupOrPolicyNumber               as lastGroupOrPolicyNumber
               FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
               WHERE mp.memberPlanId = htu.memberPlanId)
      WHERE CURRENT OF memberPlan1_cur;
   END LOOP;
   COMMIT;
END;

Решение, которое я верю выше, создаст мне проблемы, потому что обновление не присваивает значения индивидуально, как в

SET column1=value1,column2=value2,column3=value3

, но с использованием

SET (column1,column2,column3) = (SELECT ...) syntax

Я думал о третьем способе достижения моей цели, используя второй
курсор и 3 переменные:

DECLARE
   varLastContractId          memberPlan.lastContractId%TYPE          ;
   varLastContractIdChanged   memberPlan.lastContractIdChanged%TYPE   ;
   varLastGroupOrPolicyNumber memberPlan.lastGroupOrPolicyNumber%TYPE ;

   CURSOR memberPlan1_cur IS
      SELECT 
         mp.memberPlanId            ,
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber 
      FROM memberPlan mp
      WHERE EXISTS (
         SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */ 1
         FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
         WHERE htu.memberPlanId = mp.memberPlanId)
      FOR UPDATE OF
         mp.lastContractId          ,
         mp.lastContractIdChanged   ,
         mp.lastGroupOrPolicyNumber ;

   CURSOR htUpdateMemberPlan1_cur(
             parmRunDate      IN DATE  ,
             parmMemberPlanId IN NUMBER) IS
      SELECT /*+ INDEX(htu,HTUPDATEMEMBERPLAN$MEMBERPLAN) */
         htu.contractId                        as lastContractId          ,
         CASE WHEN htu.contractId IS NULL THEN 
            NULL                           
         ELSE                              
            parmRunDate
         END                                   as lastContractIdChanged   ,
         htu.groupOrPolicyNumber               as lastGroupOrPolicyNumber
      FROM htUpdateMemberPlan htu /* HTUPDATEMEMBERPLAN$MEMBERPLAN */
      WHERE htu.memberPlanId = parmMemberPlanId;
BEGIN
   FOR memberPlan1_row IN memberPlan1_cur LOOP
      OPEN htUpdateMemberPlan1_cur(
         varRunDate                   ,
         memberPlan1_row.memberPlanId );
      FETCH htUpdateMemberPlan1_cur INTO 
         varLastContractId          ,
         varLastContractIdChanged   ,
         varLastGroupOrPolicyNumber ;

      UPDATE memberPlan mp
      SET mp.lastContractId          = varLastContractId          ,
          mp.lastContractIdChanged   = varLastContractIdChanged   ,
          mp.lastGroupOrPolicyNumber = varLastGroupOrPolicyNumber 
      WHERE CURRENT OF memberPlan1_cur;

      CLOSE htUpdateMemberPlan1_cur;
   END LOOP;
   COMMIT;
END;

Какое из трех представленных выше решений CURSOR является правильным способом для этого?

1 Ответ

0 голосов
/ 17 января 2012

Сразу за манжетой, я бы предположил, что первая версия курсора будет работать лучше, чем вторая версия курсора (при условии, что они в итоге получат одинаковые результаты).Обоснование: ожидается, что выполнение одного внутреннего соединения (первая версия курсора) с htUpdateMemberPlan будет работать лучше, чем множественные объединения (по одному для каждой итерации в цикле курсора) второй версии.Но это действительно только предположение.Я бы запустил трассировку sql и посмотрел, что происходит.

Также кажется, что первая версия получает статический снимок значений из htUpdateMemberPlan, который последовательно используется в цикле курсора.Вторая версия выполняет поиск htUpdateMemberPlan для каждой строки во время ее обработки.Таким образом, возможно, что значение, скажем, varRunDate может быть изменено другим сеансом между временем создания курсора и временем обработки определенной строки курсора во второй версии.Но не в первой версии.Это дало бы немного разные значения двум подходам.Что бы вы хотели?Опять же, просто махнув рукой здесь.Может быть ошибочным.

Также интересно, может ли оператор CASE быть заменен NVL2 (contractId, varRunDate, null).Опять же, может быть, не лучше :)

...