Посоветуйте, как оптимизировать решение (LOOP для всех записей и проверка на ошибки) - PullRequest
0 голосов
/ 01 октября 2011

Я использовал следующее (проверьте наличие ошибок в цикле и, если они существуют, я вставлю их в таблицу):

FOR rec IN (SELECT MAX(t.s_id) as s_id,t.sdate,t.stype,t.snumber,t.code,
            SUM(t.amount) as amount, t... (other fields)
            FROM stable t WHERE t.sdate=p_date AND t.stype=p_type 
                          AND t.snumber=p_num 
            GROUP BY t.sdate,t.snumber,t.stype, t... (other fields)) LOOP  
    v_reason := null;

    BEGIN
      SELECT d.source_id INTO i_source_id FROM mapping m, source d 
      WHERE TO_NUMBER(m.stage)=rec.snumber AND 
            m.month=EXTRACT(MONTH FROM rec.sdate) AND 
            m.year=EXTRACT(YEAR FROM rec.sdate) AND m.desc=d.source_desc AND
            m.month=d.month AND m.year=d.year AND m.name='SOURCE';
    EXCEPTION
      WHEN OTHERS
        THEN 
           e_id := 1;
           v_reason := 'source_id';
    END;

    IF (v_reason IS NULL) THEN
        BEGIN     
          SELECT p.product_id INTO i_product_id FROM mapping m, product p
          WHERE m.stage=rec.code AND 
                m.month=EXTRACT(MONTH FROM rec.sdate) AND 
                m.year=EXTRACT(YEAR FROM rec.sdate) AND 
                m.desc=p.product_name AND m.month=p.month AND 
                m.year=p.year AND m.name='PRODUCT';               
        EXCEPTION
          WHEN OTHERS
            THEN 
               e_id := 2;
               v_reason := 'product_id';
        END;
      END IF;

    --- and 5 more checks from other tables ---
    ---....---

    IF (v_reason IS NULL) THEN
       INSERT INTO tbl_destination(sdate,source_id,product_id,amount, ... and others) 
       VALUES(rec.sdate,i_source_id,i_product_id,NVL(abs(rec.amount),0), ...);  
    ELSE
       INSERT INTO tbl_errors(rec_id,e_id,desc) VALUES(rec.s_id,e_id,v_reason);
    END IF; 
    COMMIT;                         
END LOOP;    

Это слишком медленно для большого количества записей (около 20000). Пожалуйста, помогите мне.

Ответы [ 2 ]

4 голосов
/ 01 октября 2011

Переход назад и вперед между SQL и PLSQL дает огромные накладные расходы. В вашем случае вы выполняете запрос, а затем выполняете новые запросы для каждой записи, найденной в основном запросе. Это замедляет процесс из-за всех этих переключений контекста между SQL и PLSQL и из-за того, что отдельные запросы сложнее оптимизировать. Напишите один большой запрос. Оптимизатор может сделать всю свою магию, и у вас есть только один переключатель контекста.

Выполнить следующий запрос: каждая возвращаемая строка является ошибкой. Вам нужно только прочитать sourceCount и productCount, чтобы увидеть, какая из них является проблемой (или обе).

Чтобы вставить ошибки:

insert into tbl_errors (rec_id, e_id, desc) 
select
  s_id, 
  case 
    when sourceCount <> 1 then 1
    when productCount <> 1 then 2
    when ...
  end as e_id,
  case 
    when sourceCount <> 1 then 'source_id'
    when productCount <> 1 then 'product_id'
    when ...
  end as reason
from
(
    SELECT 
      MAX(t.s_id) as s_id,
      t.sdate,t.stype,t.snumber,t.code,
      SUM(t.amount) as amount, 

      (SELECT count(*) 
      FROM mapping m, source d 
      WHERE 
        TO_NUMBER(m.stage)=rec.snumber AND 
        m.month=EXTRACT(MONTH FROM rec.sdate) AND 
        m.year=EXTRACT(YEAR FROM rec.sdate) AND m.desc=d.source_desc AND
        m.month=d.month AND m.year=d.year AND m.name='SOURCE') as sourceCount,

      (SELECT count(*)
      FROM mapping m, product p
      WHERE 
        m.stage=rec.code AND 
        m.month=EXTRACT(MONTH FROM rec.sdate) AND 
        m.year=EXTRACT(YEAR FROM rec.sdate) AND 
        m.desc=p.product_name AND m.month=p.month AND 
        m.year=p.year AND m.name='PRODUCT') as productCount,

      /* other checks */        

    FROM 
      stable t 
    WHERE 
      t.sdate=p_date AND t.stype=p_type 
      AND t.snumber=p_num 
    GROUP BY 
      t.sdate, t.snumber, t.stype
) x
having 
  sourceCount <> 1 or productCount <> 1 or /* other checks */

Чтобы вставить записи, которые в порядке. Используйте тот же запрос для проверок, но добавьте дополнительные подзапросы, чтобы получить правильный идентификатор продукта и идентификатор источника.

insert into tbl_destination(sdate,source_id,product_id,amount, ...)
select
  sdate,
  source_id,
  product_id,
  amount,
  ...
from
(
    SELECT 
      MAX(t.s_id) as s_id,
      t.sdate,t.stype,t.snumber,t.code,
      SUM(t.amount) as amount, 

      (SELECT count(*) 
      FROM mapping m, source d 
      WHERE 
        TO_NUMBER(m.stage)=rec.snumber AND 
        m.month=EXTRACT(MONTH FROM rec.sdate) AND 
        m.year=EXTRACT(YEAR FROM rec.sdate) AND m.desc=d.source_desc AND
        m.month=d.month AND m.year=d.year AND m.name='SOURCE') as sourceCount,
      (SELECT min(source_id) 
      FROM mapping m, source d 
      WHERE 
        TO_NUMBER(m.stage)=rec.snumber AND 
        m.month=EXTRACT(MONTH FROM rec.sdate) AND 
        m.year=EXTRACT(YEAR FROM rec.sdate) AND m.desc=d.source_desc AND
        m.month=d.month AND m.year=d.year AND m.name='SOURCE') as source_id,

      (SELECT count(*)
      FROM mapping m, product p
      WHERE 
        m.stage=rec.code AND 
        m.month=EXTRACT(MONTH FROM rec.sdate) AND 
        m.year=EXTRACT(YEAR FROM rec.sdate) AND 
        m.desc=p.product_name AND m.month=p.month AND 
        m.year=p.year AND m.name='PRODUCT') as productCount,
      (SELECT min(product_id)
      FROM mapping m, product p
      WHERE 
        m.stage=rec.code AND 
        m.month=EXTRACT(MONTH FROM rec.sdate) AND 
        m.year=EXTRACT(YEAR FROM rec.sdate) AND 
        m.desc=p.product_name AND m.month=p.month AND 
        m.year=p.year AND m.name='PRODUCT') as product_id,

      /* other checks */        

    FROM 
      stable t 
    WHERE 
      t.sdate=p_date AND t.stype=p_type 
      AND t.snumber=p_num 
    GROUP BY 
      t.sdate, t.snumber, t.stype
) x
having 
  sourceCount = 1 and productCount = 1 and /* other checks */
2 голосов
/ 01 октября 2011

Обычно наиболее эффективный способ - преобразовать plsql в операции, основанные на множествах, и избавиться от LOOP. Я бы начал с запроса управления и встраивания его в каждый из запросов (в цикле).Затем включите их во вставки.стараясь включить любую логику в операторы IF в предложение WHERE.

Например: поскольку вы вставляете ошибку, когда не найдено записей, вы можете изменить первый блок SELECT INTO .... EXCEPTION впрямая вставка, при которой он не может найти никаких строк в таблицах сопоставления

    INSERT INTO tbl_errors
    SELECT s_id, 1 as e_id , 'source_id' as reason 
    FROM
    (
        SELECT MAX(t.s_id) as s_id,t.sdate,t.stype,t.snumber,t.code,
                   SUM(t.amount) as amount, t... (other fields)
        FROM stable t 
        WHERE t.sdate=p_date AND t.stype=p_type AND t.snumber=p_num 
        GROUP BY t.sdate,t.snumber,t.stype, t... (other fields)
    ) drv
    LEFT JOIN mapping m ON TO_NUMBER(m.stage) = drv.s_id   --etc 
    LEFT JOIN source d ON m.desc=d.source_desc AND m.month=d.month --etc
    WHERE m.stage IS NULL

, в итоге вы получите несколько вставок, теперь должна быть возможность оптимизировать furthur и объединить все выборки в один оператори выполните операцию как одну вставку.

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

, то есть:

INSERT INTO tbl_destination
SELECT * from drv
WHERE NOT EXISTS(SELECT * from tbl_errors WHERE s_id=drv.s_id)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...