Search code examples
oracle-databaseplsqlnested-loops

Nested Loops to Retrieve a value from several tables in Oracle


Say I have several tables that all start with 'PLAYER_' and I am trying to loop through all of those tables to get tables names and then loop again to get a value of a column in all of these tables.

This column exists in all tables so I want to use nested FOR loops to achieve that.

Here is what I have so far but it does not seem to work:

DECLARE 
    LOG_ID NUMBER; 
    TBL_NME VARCHAR2(30); 
    V_STRNG VARCHAR2(4000); 
BEGIN 
    FOR i IN (SELECT TABLE_NAME FROM USER_TABLES WHERE TABLE_NAME LIKE 'PLAYER_%') LOOP
        TBL_NME := i.TABLE_NAME; 
        DBMS_OUTPUT.PUT_LINE('TABLE EXTRACTED IS ' || TBL_NME);
        FOR j IN(SELECT LOG_ID FROM i.TABLE_NAME) LOOP 
            V_EXEC_OBJ_STRNG := 'SELECT LOG_ID FROM ' || i.TABLE_NAME;
            EXECUTE IMMEDIATE V_STRNG INTO LOG_ID; 
            DBMS_OUTPUT.PUT_LINE('LOG_ID IS ' || LOG_ID || ' FOR TABLE ' || i.TABLE_NAME);
        END LOOP;
    END LOOP; 
END;
/

Solution

  • You can probably get away with just one loop ...

    Example

    create table player_01 ( id, name )
    as
    select level, dbms_random.string( 'x', 25 )
    from dual
    connect by level <= 10 ;
    
    create table player_02 ( id, name )
    as
    select level, dbms_random.string( 'x', 25 )
    from dual
    connect by level <= 11 ;
    
    create table player_03 ( id, name )
    as
    select level, dbms_random.string( 'x', 25 )
    from dual
    connect by level <= 12 ;
    

    Anonymous block:

    -- find all relevant tables and retrieve the highest id values
    declare 
      logid number := 0 ;
      tablename varchar2( 30 ) := '' ;
      v_string varchar2( 4000 ) := '' ;
    begin
      for r in (
        select table_name from user_tables
        where table_name like 'PLAYER%'
        order by table_name
      ) loop
      --  dbms_output.put_line( ' current table -> ' || r.table_name ) ;
        v_string := 'select max( id ) as logid from ' || r.table_name;
        execute immediate v_string into logid ;
        dbms_output.put_line( 'log id is ' || logid || '  for table ' || r.table_name ) ;
      end loop ;
    end ;
    /
    
    -- result
    log id is 10  for table PLAYER_01
    log id is 11  for table PLAYER_02
    log id is 12  for table PLAYER_03
    

    Dbfiddle here.

    According to your comment, there are several LOGIDs in each PLAYER_ table. Maybe the following example is closer to the "real thing". (And: the anonymous block has nested loops ... ( tested with Oracle 12c and 11g, dbfiddle here ).

    Tables

    create table player_01 ( id, details, logid )
    as
    select level, dbms_random.string( 'x', 25 ), abs( dbms_random.random() )
    from dual
    connect by level <= 3 ;
    
    create table player_02 ( id, details, logid  )
    as
    select level, dbms_random.string( 'x', 25 ), abs( dbms_random.random() )
    from dual
    connect by level <= 4 ;
    
    create table player_03 ( id, details, logid  )
    as
    select level, dbms_random.string( 'x', 25 ), abs( dbms_random.random() )
    from dual
    connect by level <= 4 ;
    

    Sample data in PLAYER_01 / PLAYER_02 / PLAYER_03

    select * from player_01 ;
    
    ID  DETAILS                     LOGID
    1   VZAQXPFCQK3U2F0RL32I31N40   699945134
    2   32QWFFMUCF1DL6E3Z5QM4DSWY   1635628934
    3   48GWBETOLUSDEFA3SMY061NUO   1237793316
    
    select * from player_02;
    ID  DETAILS                     LOGID
    1   HS827U4VCY853N8DKTI98J82D   1993524164
    2   XLYS0XPJG0IQP4BNKDQ0ZITPA   1665941353
    3   DWVVR5O6N5T1HP5MDYHVH3NZJ   1129581845
    4   L7N8HCPVTHP466WJ5TCQ04YHE   794237444
    
    select * from player_03;
    ID  DETAILS                     LOGID
    1   SYVX5G2FE5IC1MI6TCSAHNOUU   720476135
    2   4IQZIG6DAUCWW3APJY5OZ63TF   287457960
    3   525NMZFVGLWKIT7EIFA41C8MB   784891618
    4   0XHJXV2O4TCQQSITOTIQCO3AA   1578737054
    

    Anonymous block

    declare 
      logid number := 0 ;
      tablename varchar2( 30 ) := '' ;
      v_string1 varchar2( 4000 ) := '' ;
      v_string2 varchar2( 4000 ) := '' ;
      rowcount number := 0 ;
    begin
      for r in (
        select table_name from user_tables
        where table_name like 'PLAYER%'
        order by table_name
      ) loop
          v_string1 := 'select count(*) from ' || r.table_name ;
          execute immediate v_string1 into rowcount ;
          dbms_output.put_line( rowcount ) ;
    
          for rn in 1 .. rowcount
          loop 
            -- dbms_output.put_line( rn ) ;
            v_string2 := 'select logid from ( '
                      || 'select logid, row_number() over ( order by id ) rn '
                      || ' from ' || r.table_name || ' )'
                      || ' where rn = ' || rn;
            -- dbms_output.put_line( v_string2 ) ;
            execute immediate v_string2 into logid ;
            dbms_output.put_line( 'log id is ' || logid || '  for table ' || r.table_name ) ;
          end loop ;
    
      end loop ;
    end ;
    /
    

    dbms_output:

    3
    log id is 699945134  for table PLAYER_01
    log id is 1635628934  for table PLAYER_01
    log id is 1237793316  for table PLAYER_01
    4
    log id is 1993524164  for table PLAYER_02
    log id is 1665941353  for table PLAYER_02
    log id is 1129581845  for table PLAYER_02
    log id is 794237444  for table PLAYER_02
    4
    log id is 720476135  for table PLAYER_03
    log id is 287457960  for table PLAYER_03
    log id is 784891618  for table PLAYER_03
    log id is 1578737054  for table PLAYER_03
    

    The second query string (v_string2) looks a bit like this (maybe a bit easier to read than all the string parts and ||):

    select logid
    from (
      select 
        logid
      , row_number() over ( order by id ) rn
      from player_01 
    ) where rn = 1
    ;
    -- query result
    LOGID
    1338793259 
    

    Query in the inner loop (answering the question in your comment)

    The subquery uses row_number() - see documentation:

    "ROW_NUMBER is an analytic function. It assigns a unique number to each row to which it is applied (either each row in the partition or each row returned by the query), in the ordered sequence of rows specified in the order_by_clause, beginning with 1."

    We are using this to get consecutive numbers, numbering the LOGIDs as it were. Then, we use the RN values in the WHERE clause (of the outer select), and compare them to the inner FOR loop's "rn" value.

    select 
      logid
    , row_number() over ( order by id ) rn
    from player_01 ;
    
    -- result
    LOGID       RN
    1775991812  1
    262095022   2
    2090118607  3