Skip to Main Content
  • Questions
  • Is Using ROWTYPE is better then fetching 75% columns values to different variables

Breadcrumb

Question and Answer

Chris Saxon

Thanks for the question, Tanumoy.

Asked: November 15, 2017 - 12:44 pm UTC

Last updated: November 27, 2017 - 2:55 pm UTC

Version: oracle 12C

Viewed 1000+ times

You Asked

Hi Tom,

I am using multiple variables to get columns values from a table(obviously I am using INTO clause and getting one row with filter criteria).

I can do this by using %ROWTYPE as well. This way it will fetch all of the columns.

Now, problem is which one is faster or take less memory? which one is the best practice?

By using %ROWTYPE all of the columns will be fetched, where few columns value I might not use in my PLSQL code block. Still fetching those values. But use of rowtype saves declaration of multiple variable, which atleast saves LOC(again which is not an issue as well).

Example,

Employee table does have 10 columns,
ID, Emp_name, emp_code, gender, email, dep_id, employer_id, organization_code, pay_cycle, address

Now, in my plsql code block, just say I need 7 of the columns. I don't need dep_id, employer_id, organization_code.

So, should I use rowtype instead of creating 7 different variables to store those 7 column values?

Can you please share your views on this.

and Chris said...

The obvious difference with fetching more columns is you'll consume more memory.

How much more? Well, unsurprisingly, it depends...

If the columns you exclude are (mostly) null then there will be minimal difference:

create table t as 
  select level id,
         lpad('x', 20, 'x') c1,
         sysdate c2,
         sysdate c3,
         level c4,
         level c5,
         level c6,
         cast(null as varchar2(500)) c7,
         cast(null as varchar2(500)) c8,
         cast(null as varchar2(500)) c9
  from   dual
  connect by level <= 10000;
  
exec dbms_session.FREE_UNUSED_USER_MEMORY ;

declare
  type r_tab is table of t%rowtype index by binary_integer;
  all_cols r_tab;
  
begin

  select *
  bulk collect into all_cols
  from   t;
  
  show_mem('All cols, nulls');
    
end;
/

All cols, nulls session pga memory: 11.511

exec dbms_session.FREE_UNUSED_USER_MEMORY ;

declare
  type r is record (
    id t.id%type, c1 t.c1%type, c2 t.c2%type, 
    c3 t.c3%type, c4 t.c4%type, c5 t.c5%type, 
    c6 t.c6%type
  ); 
  type r_t is table of r index by binary_integer;
  sub_cols r_t;
  
begin
  
  select id, c1, c2, c3, c4, c5, c6
  bulk collect into sub_cols
  from   t;
  
  show_mem('Col subset, nulls');
  
end;
/

Col subset, nulls session pga memory: 10.761


So selecting all the columns uses 11.5mb, vs 10.7mb when you exclude those that are null. Not much over 10,000 rows.

On the other hand, if you're excluding columns containing long strings the savings could be significant:

drop table t purge;
create table t as 
  select level id,
         lpad('x', 20, 'x') c1,
         sysdate c2,
         sysdate c3,
         level c4,
         level c5,
         level c6,
         lpad('x', 500, 'x') c7,
         lpad('y', 500, 'y') c8,
         lpad('z', 500, 'z') c9
  from   dual
  connect by level <= 10000;
  
exec dbms_session.FREE_UNUSED_USER_MEMORY ;

declare
  type r_tab is table of t%rowtype index by binary_integer;
  all_cols r_tab;
  
begin

  select *
  bulk collect into all_cols
  from   t;
  
  show_mem('All cols, long strings');
    
end;
/

All cols, long strings session pga memory: 27.948

exec dbms_session.FREE_UNUSED_USER_MEMORY ;

declare
  type r is record (
    id t.id%type, c1 t.c1%type, c2 t.c2%type, 
    c3 t.c3%type, c4 t.c4%type, c5 t.c5%type, 
    c6 t.c6%type
  ); 
  type r_t is table of r index by binary_integer;
  sub_cols r_t;
  
begin
  
  select id, c1, c2, c3, c4, c5, c6
  bulk collect into sub_cols
  from   t;
  
  show_mem('Col subset, long strings');
  
end;
/

Col subset, long strings session pga memory: 10.761


Selecting the subset uses the same amount of memory (10.7mb). But getting all the columns is now up to nearly 28mb, quite an increase. This could also have an impact on the performance of the SQL itself, particularly if it's "complex" and needs to sort data or write it to temp.

For example, in the SQL below I'm using the materialize hint to force a write to temp. Notice that the SQL getting all the columns uses far more reads, writes and buffers:

set serveroutput off
alter session set statistics_level = all;
with rws as (
  select /*+ materialize */* from t
)
  select count(*) from rws;
  
select * 
from   table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT                                                                                                                       
SQL_ID  cy1qpkps337c0, child number 1                                                                                                   
-------------------------------------                                                                                                   
with rws as (   select /*+ materialize */* from t )   select count(*)                                                                   
from rws                                                                                                                                
                                                                                                                                        
Plan hash value: 1481629063                                                                                                             
                                                                                                                                        
-------------------------------------------------------------------------------------------------------------------------------------   
| Id  | Operation                  | Name                       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |   
-------------------------------------------------------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT           |                            |      1 |        |      1 |00:00:00.17 |    7560 |   5000 |   2500 |   
|   1 |  TEMP TABLE TRANSFORMATION |                            |      1 |        |      1 |00:00:00.17 |    7560 |   5000 |   2500 |   
|   2 |   LOAD AS SELECT           |                            |      1 |        |      0 |00:00:00.11 |    5047 |   2500 |   2500 |   
|   3 |    TABLE ACCESS FULL       | T                          |      1 |   8412 |  10000 |00:00:00.03 |    2506 |   2500 |      0 |   
|   4 |   SORT AGGREGATE           |                            |      1 |      1 |      1 |00:00:00.05 |    2507 |   2500 |      0 |   
|   5 |    VIEW                    |                            |      1 |   8412 |  10000 |00:00:00.04 |    2507 |   2500 |      0 |   
|   6 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D708F_3B7555B |      1 |   8412 |  10000 |00:00:00.03 |    2507 |   2500 |      0 |   
-------------------------------------------------------------------------------------------------------------------------------------   
                                                                                                                                        
Note                                                                                                                                    
-----                                                                                                                                   
   - dynamic statistics used: dynamic sampling (level=2) 
  
with rws as (
  select /*+ materialize */id, c1, c2, c3, c4, c5, c6 from t
)
  select count(*) from rws;
  
select * 
from   table(dbms_xplan.display_cursor(null, null, 'ALLSTATS LAST'));

PLAN_TABLE_OUTPUT                                                                                                                       
SQL_ID  bzhjgyagqfq5a, child number 0                                                                                                   
-------------------------------------                                                                                                   
with rws as (   select /*+ materialize */id, c1, c2, c3, c4, c5, c6                                                                     
from t )   select count(*) from rws                                                                                                     
                                                                                                                                        
Plan hash value: 1481629063                                                                                                             
                                                                                                                                        
-------------------------------------------------------------------------------------------------------------------------------------   
| Id  | Operation                  | Name                       | Starts | E-Rows | A-Rows |   A-Time   | Buffers | Reads  | Writes |   
-------------------------------------------------------------------------------------------------------------------------------------   
|   0 | SELECT STATEMENT           |                            |      1 |        |      1 |00:00:00.09 |    2682 |   2581 |     81 |   
|   1 |  TEMP TABLE TRANSFORMATION |                            |      1 |        |      1 |00:00:00.09 |    2682 |   2581 |     81 |   
|   2 |   LOAD AS SELECT           |                            |      1 |        |      0 |00:00:00.06 |    2590 |   2500 |     81 |   
|   3 |    TABLE ACCESS FULL       | T                          |      1 |   8412 |  10000 |00:00:00.03 |    2506 |   2500 |      0 |   
|   4 |   SORT AGGREGATE           |                            |      1 |      1 |      1 |00:00:00.03 |      86 |     81 |      0 |   
|   5 |    VIEW                    |                            |      1 |   8412 |  10000 |00:00:00.03 |      86 |     81 |      0 |   
|   6 |     TABLE ACCESS FULL      | SYS_TEMP_0FD9D7090_3B7555B |      1 |   8412 |  10000 |00:00:00.01 |      86 |     81 |      0 |   
-------------------------------------------------------------------------------------------------------------------------------------   
                                                                                                                                        
Note                                                                                                                                    
-----                                                                                                                                   
   - dynamic statistics used: dynamic sampling (level=2)   


So personally I'd only select exactly what you need. But in many cases this is the last mile of tuning. So unless you're really tight on memory or have complicated SQL to get the data, I wouldn't worry about it too much. Usually there's much bigger gains to be made elsewhere in your application.

Code for the show_mem procedure:

create or replace procedure show_mem (
  details varchar2
) as
begin
  for mem in (
    select  vstt.name,
            max( vsst.value ) value
    from    v$sesstat vsst,
            v$statname vstt,
            v$session vses
    where   vstt.statistic# = vsst.statistic#
    and     vsst.sid = vses.sid
    and     vstt.name in (
      'session pga memory'
    )
    and     vses.username is not null
    and     vsst.sid = sys_context('USERENV', 'SID')
    group by vstt.name
    order by vstt.name
  ) loop
    dbms_output.put_line(
      details || ' ' || mem.name || ': ' || 
      round((mem.value/1024/1024), 3)
    );
  end loop;
end show_mem;
/

Rating

  (3 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

suggest create your own types instead of creating many variables

Joseph Charpak, November 17, 2017 - 3:33 pm UTC

Instead of using many variables and or "wasting" a good chunk of %rowtype, I suggest creating your own type in the code

create table test_table(
   col_01   varchar2(20),
   col_02   number,
   col_03   date,
   col_04   varchar2(1000)
);

insert into test_table(COL_01,COL_02,COL_03,COL_04) values ('test',1,sysdate,'this is a test this is only a test');

COMMIT;

create or replace package test_package
is
   type test_table_row_t  is RECORD(
      col_01   test_table.col_01%type,
      col_02   test_table.col_02%type
   );
   
   function get_test_table_row 
   return  test_table_row_t;
end;
/

create or replace package body test_package
is
   function get_test_table_row 
   return  test_table_row_t
   is
      l_test_table_row_t test_table_row_t;
   begin
      select COL_01,
             COL_02
        into l_test_table_row_t
        from test_table;
      return l_test_table_row_t;
   end;
end;
/

set serveroutput on

declare
   l_test_table_row_t test_package.test_table_row_t;
begin
   l_test_table_row_t :=  test_package.get_test_table_row;
   dbms_output.put_line(l_test_table_row_t.col_01||','||l_test_table_row_t.col_02);
end;
/

Chris Saxon
November 17, 2017 - 4:43 pm UTC

Yep, much easier to pass as parameters to other procedures/functions too!

Alternative

Racer I., November 23, 2017 - 2:10 pm UTC

Hi,

Also

DECLARE
  CURSOR CurSelect IS 
    SELECT 1 F1, 2 F2, 3 F3
    FROM   Dual;
  vRow CurSelect%ROWTYPE;
BEGIN
  DBMS_OUTPUT.ENABLE;
  OPEN CurSelect;
  FETCH CurSelect INTO vRow;
  DBMS_OUTPUT.PUT_LINE(vRow.F1 || '.' || vRow.F2 || '.' || vRow.F3);
  CLOSE CurSelect;
END;


regards,
Chris Saxon
November 24, 2017 - 8:08 am UTC

Yep, thanks for sharing

Readability

Marcus, November 27, 2017 - 6:14 am UTC

Whenever possible I use rowtypes. It increases readability and maintainability. This year I had to rewrite a package from scratch because the author (inexperienced in PL/SQL) used 300 global(!) variables and somehow got lost. Now there are four local rowtypes passed as parameters.
And instead of defining your own type you could select NULL from columns you don't use.
Chris Saxon
November 27, 2017 - 2:55 pm UTC

True, though if I saw a %rowtype variable I'd expect it to include all the columns from the table. Selecting null could cause confusion.

Racer's solution of cursor rowtypes is a better workaround in my opinion.

More to Explore

PL/SQL demos

Check out more PL/SQL tutorials on our LiveSQL tool.

PL/SQL docs

PL/SQL reference manual from the Oracle documentation library