oracle-databaseplsqlwith-statement

Refactoring a slow plsql cursor with a WITH update statement


I have a slow plsql cursor where I'm updating a column in a table so I wanted to replace this cursor with a single sql statement (albeit somewhat complex ). This is what I've tried so far

with gexc_cursor as (      
            select * 
            from ( select tmp.*, rownum rn from (
            select  id, oib_dob as oib, opis_grupe, smjer  from GRU_EXC order by ID ) tmp )
            where  rn between 1 and 5 ), 
            
        gd_cursor as (
          
        select gc.id, gd.id_grupe from GRU_DOK gd, gexc_cursor gc where 
        gc.smjer = 'ON' AND gd.opis_grupe = gc.opis_grupe
        and gd.id_vlasnika  in ( select id_vlasnika from VLAS where oib_vlasnika = gc.oib )
        union
        select gc.id, gd.id_grupe from GRU_DOK gd, gexc_cursor gc where 
        gc.smjer = 'OFF' AND gd.opis_grupe = gc.opis_grupe
        and gd.id_vlasnika not in ( select id_vlasnika from VLAS where oib_vlasnika = gc.oib )
        )
        update GRU_EXC set id_grupe =  gd_cursor.id_grupe 
        where id = gd_cursor.id;
  
--ORA-00928: missing SELECT keyword

The next sql block executes but doesn't work as expected (it always updates all rows and not just the matching rows )

update GRU_EXC ge set id_grupe =   (
with gexc_cursor as (      
            select * 
            from ( select tmp.*, rownum rn from (
            select  id, oib_dob as oib, opis_grupe, smjer  from GRU_EXC order by ID ) tmp )
            where  rn between 1 and 5 )
            
        select gd.id_grupe from GRU_DOK gd, gexc_cursor gc
        where gc.smjer = 'ON' and gc.id = ge.id and gd.opis_grupe = gc.opis_grupe
        and gd.id_vlasnika  in ( select id_vlasnika from VLAS where oib_vlasnika = gc.oib )
        UNION
        select gd.id_grupe from GRU_DOK gd, gexc_cursor gc
        where gc.smjer = 'OFF' and  gc.id = ge.id and gd.opis_grupe = gc.opis_grupe
        and gd.id_vlasnika  not in ( select id_vlasnika from VLAS where oib_vlasnika = gc.oib )           
  )  

Solution

  • Without pagination, I think your update could be done like so (minimally tested, and it seems to behave like your original update would):

    MERGE INTO (SELECT *
                FROM   gru_exc
                WHERE  smjer IN ('ON', 'OFF')) tgt
      USING gru_dok src
      ON (tgt.opis_grupe = src.opis_grupe)
    WHEN MATCHED THEN
      UPDATE SET tgt.id_grupe = src.id_grupe
      WHERE (tgt.smjer = 'ON' AND src.id_vlasnika IN (SELECT id_vlasnika FROM vlas WHERE (vlas.oib_vlasnika = tgt.oib_dob))
             OR (tgt.smjer = 'OFF' AND src.id_vlasnika NOT IN (SELECT id_vlasnika FROM vlas WHERE vlas.oib_vlasnika = tgt.oib_dob))
      AND   (tgt.id_grupe != src.id_grupe OR tgt.id_grupe IS NULL);
    

    Updating a table in chunks is, IMO, something that needs a good reason (e.g. it's a one-off update and the rollback tablespace isn't sized appropriately), otherwise I'd be pushing back on that requirement. Especially if this is something that's going to be run frequently.