How to improve performance of a function with cursors in PostgreSQL?

孤者浪人 提交于 2019-12-05 18:35:29
Erwin Brandstetter

In a first step, I radically simplified your procedural code:

CREATE OR REPLACE FUNCTION ccdb_dummy.o_payments1(a integer)
  RETURNS void AS
$func$
DECLARE
   t   record;
   t1  record;
BEGIN

FOR t IN
   SELECT *
         ,nextval('ccdb_stg.payments_seq') AS payment_no
         ,c.cin
   FROM   ccdb_stg.o_payments_stg   p
   LEFT   JOIN ccdb_dummy.consumers c USING (consumer_num)
   WHERE  p.section_code = $1
LOOP

   INSERT INTO ccdb_dummy.payments(payment_id,receipt_id,source_system_flag,cin, ... ,pm_amount,ref_transaction_id,creation_dt,created_by)
   VALUES(t.payment_no,t.receipt_id,t.origin_flag,t.cin, ... ,t.pm_amount,null,now(),'system');

   FOR t1 IN
      SELECT *
      FROM   ccdb_stg.o_payment_head_dtls_stg h
      WHERE  h.mbc_receipt_id = t.receipt_id
   LOOP
      INSERT INTO ccdb_dummy.payment_head_dtls(payment_id,mbc_receipt_id,charge_head_code,amount,tariff_id,creation_dt,created_by)
      VALUES (t.payment_no,t1.mbc_receipt_id,t1.charge_head_code,t1.amount,t1.tariff_id,now(),'system');
   END LOOP;
END LOOP;

END
$func$  LANGUAGE plpgsql;
  • Use the implicit cursor of a FOR LOOP instead of unwieldy explicit cursors coupled with redundant counts and loops. Much simpler and faster. Read the chapter "Looping Through Query Results" in the manual.

  • LEFT JOIN to ccdb_dummy.consumers in the first SELECT instead of running a separate select for every row.

  • Also include nextval('ccdb_stg.payments_seq') AS payment_no in the first SELECT. cheaper than lots of separate queries.

  • Minor detail: assignment operator in plpgsql is :=, not =. Details here.

But that's far from perfect, still. Consider a completely new approach with set-based operations instead of individual inserts in loops. Much cleaner and faster, yet. That's how modern RDBMS operate best.

One SQL statement with a data-modifying CTE

Wrapped into an SQL function to be a drop-in replacement.
Data-modifying CTEs require Postgres 9.1 or later.

CREATE OR REPLACE FUNCTION ccdb_dummy.o_payments2(integer)
  RETURNS void AS
$func$

WITH ins1 AS (
   INSERT INTO ccdb_dummy.payments(
          payment_id,                        cin,  receipt_id,   ...  ,   pm_amount, ref_transaction_id,creation_dt,created_by)   
   SELECT nextval('ccdb_stg.payments_seq'),c.cin,p.receipt_id,   ...  , p.pm_amount, null,              now(),      'system'
   FROM   ccdb_stg.o_payments_stg   p
   LEFT   JOIN ccdb_dummy.consumers c USING (consumer_num)
   WHERE  p.section_code = $1
   RETURNING payment_id, receipt_id
   )
INSERT INTO ccdb_dummy.payment_head_dtls(
         payment_id,  mbc_receipt_id,  charge_head_code,  amount,  tariff_id,creation_dt,created_by)
SELECT i.payment_id,h.mbc_receipt_id,h.charge_head_code,h.amount,h.tariff_id,now(),      'system'
FROM   ins1 i
JOIN   ccdb_stg.o_payment_head_dtls_stg h ON h.mbc_receipt_id = i.receipt_id;

$func$  LANGUAGE sql;

Should do the same as the above plpgsql function exactly (barring errors in translation). Just much simpler and faster.

Find more examples for INSERTs using data-modifying CTEs here on SO.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!