Home > Software design >  Using for loop in where ORACLE SQL
Using for loop in where ORACLE SQL

Time:08-15

I have this query

SELECT DISTINCT
    i.id1,
    length(s.family_id) len,
    s.family_id
FROM
    tabel1 i,
    tabel2 s
WHERE
    i.id1 = s.member1
    OR i.id1 = s.member2
    OR i.id1 = s.member3
    OR i.id1 = s.member4
    OR i.id1 = s.member5
    OR i.id1 = s.member6
    OR i.id1 = s.member7
    OR i.id1 = s.member8
    OR i.id1 = s.member9
    OR i.id1 = s.member10
    OR i.id1 = s.member11
    OR i.id1 = s.member12
    OR i.id1 = s.member14
    OR i.id1 = s.member13
    OR i.id1 = s.member15
    OR i.id1 = s.member16
    OR i.id1 = s.member17
    OR i.id1 = s.member18;

but the number of members is not permanent (every run it can be changed this run it was 18 and the next run it can be 3 ) their is any way to make this where statement automatic ? (the members are coming from TABEL2 ) I thought about doing it with a for loop, but I couldn't make it happen.

CodePudding user response:

Shortly: that's an awful data model. Discard it, as soon as possible. Table - that contains MEMBER information - should be in master-detail relationship to tabel1 and current tabel2 (which doesn't have any member columns).

Mind you - currently, you have from member1 up to member18 columns. What if member19 has to be added? Will you modify all queries you've written, in all procedures, functions, views, forms, reports, ...? You can, but you should not.

I don't know what those tables contain, but - consider something like this:

SQL> CREATE TABLE tabel1
  2  (
  3     id1   NUMBER PRIMARY KEY
  4  );

Table created.

SQL> CREATE TABLE tabel2
  2  (
  3     id2         NUMBER PRIMARY KEY,
  4     id2_1       NUMBER REFERENCES tabel1 (id1),
  5     family_id   NUMBER
  6  );

Table created.

SQL> CREATE TABLE tabel3
  2  (
  3     id3      NUMBER PRIMARY KEY,
  4     id3_2    NUMBER REFERENCES tabel2 (id2),
  5     member   NUMBER REFERENCES tabel1 (id1)
  6  );

Table created.

Query would then look like this:

SQL> SELECT DISTINCT a.id1, LENGTH (b.family_id) len, b.family_id
  2    FROM tabel1 a
  3         JOIN tabel2 b ON b.id2_1 = a.id1
  4         JOIN tabel3 c
  5            ON     c.id3_2 = b.id2
  6               AND c.member = a.id1
  7   WHERE a.id1 = 1;

no rows selected

SQL>

Of course there's nothing returned, my tables are empty. But, with such a data model, you'd just add another row into tabel3, without modifying any code you've written so far.

CodePudding user response:

With this data structure and my understanding of the question...

  1. There is no link between data in those tables
  2. Table with member id always has just one row with different number of columns after every run
  3. The question is how to define Where clause to get just the member ids that are present in the particular run

There is no loop in Oracle's Where clause, so we have to think of workaround.
Simulated data in table containing member ids (1 row with 18 member id columns)

CREATE TABLE A_TABLE_OF_FAMILY_MEMBERS AS
   SELECT 'XXX' "FAMILY_ID", 'This is dummy column' "DUMMY", 
          1 "MEMBER1", 2 "MEMBER2", 3 "MEMBER3", 4 "MEMBER4", 5 "MEMBER5", 6 "MEMBER6", 7 "MEMBER7", 8 "MEMBER8", 9 "MEMBER9", 
          10 "MEMBER10", 11 "MEMBER11", 12 "MEMBER12", 13 "MEMBER13", 14 "MEMBER14", 15 "MEMBER15", 16 "MEMBER16", 17 "MEMBER17", 18 "MEMBER18"
    FROM Dual

Simulated data in table 1

WITH
    a_table AS
        (
          Select 1 "ID", 'Name 1' "NAME" From Dual Union All
          Select 2 "ID", 'Name 2' "NAME" From Dual Union All
          Select 3 "ID", 'Name 3' "NAME" From Dual Union All
          Select 4 "ID", 'Name 4' "NAME" From Dual Union All
          Select 5 "ID", 'Name 5' "NAME" From Dual Union All
          Select 6 "ID", 'Name 6' "NAME" From Dual 
        ),

Workaround for your WHERE clause - create function to get all the member id values in particular run
Just for demonstration there are three options of the function's return value. See the results in code below:

create or replace Function get_member_columns(p_What VarChar2 := 'cols') RETURN VarChar2 IS
BEGIN
    Declare
        mRet      VarChar2(512);
        cols      VarChar2(512);
    Begin
        If Lower(p_What) = 'len' Then                  -- return list of column names
            SELECT To_Char(Count(*)) INTO cols
            FROM
                (
                    SELECT  tc.COLUMN_NAME
                    FROM    all_tab_cols tc
                    WHERE   tc.TABLE_NAME = 'A_TABLE_OF_FAMILY_MEMBERS' AND tc.COLUMN_NAME LIKE('MEMBER%')
                );
            mRet := cols;
        ElsIf Lower(p_What) = 'cols' Then             -- return number of member columns
            SELECT  LISTAGG(tc.COLUMN_NAME, ', ') WITHIN GROUP (ORDER BY To_Number(Replace(tc.COLUMN_NAME, 'MEMBER', '')))
            INTO    cols
            FROM    all_tab_cols tc
            WHERE   tc.TABLE_NAME = 'A_TABLE_OF_FAMILY_MEMBERS' AND tc.COLUMN_NAME LIKE('MEMBER%');
            mRet := cols; 
        ElsIf Lower(p_What) = 'vals' Then           -- return list of column values
            Declare
                CURSOR  c  IS
                SELECT COLUMN_NAME 
                FROM all_tab_cols 
                WHERE TABLE_NAME =  'A_TABLE_OF_FAMILY_MEMBERS' AND COLUMN_NAME LIKE('MEMBER%')
                ORDER BY To_Number(Replace(COLUMN_NAME, 'MEMBER', ''));

                sq        VarChar2(1) := '''';      -- single quote character
                sql_txt   VarChar2(512);
                col       VarChar2(32);
                vals      VarChar2(512) := '';
                val       VarChar2(32) := '';
            Begin
                OPEN c;
                LOOP
                    FETCH c INTO col;
                    EXIT WHEN c%NOTFOUND;
                    sql_txt := 'SELECT ' || col || ' FROM A_TABLE_OF_FAMILY_MEMBERS';
                    Execute Immediate(sql_txt) INTO val;
                    vals := vals || sq || val || sq || ', ';
                END LOOP;
                CLOSE c;
                mRet := SubStr(vals, 1, Length(vals) - 2);
            End;
        End If;
        RETURN mRet;
    Exception
        WHEN NO_DATA_FOUND THEN RETURN 'ERR - [function: get_member_columns()] --> ' || SQLERRM;
        WHEN OTHERS THEN RETURN 'ERR - [function: get_member_columns()] --> ' || SQLERRM;
    End;
END get_member_columns;
--  
--  ------------------------------------------------------
--  Test function return options
SET SERVEROUTPUT ON
Begin
    DBMS_OUTPUT.PUT_LINE('Number of member_id columns          --->   ' || get_member_columns('len'));
    DBMS_OUTPUT.PUT_LINE('List of names of member_id columns   --->   ' || get_member_columns('cols'));
    DBMS_OUTPUT.PUT_LINE('List of values in member_id columns  --->   ' || get_member_columns('vals'));
End;
--  
--  TEST RESULTS
--  
--  anonymous block completed
--  Number of member_id columns          --->   18
--  List of names of member_id columns   --->   MEMBER1, MEMBER2, MEMBER3, MEMBER4, MEMBER5, MEMBER6, MEMBER7, MEMBER8, MEMBER9, MEMBER10, MEMBER11, MEMBER12, MEMBER13, MEMBER14, MEMBER15, MEMBER16, MEMBER17, MEMBER18
--  List of values in member_id columns  --->   '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18'

For unification of your WHERE clause we will need just the 'vals' option.
Create a CTE containing the values of member ids in the particular run and rewrite the main SQL

WITH
    member_vals AS
        ( Select get_member_columns('vals') "MEMBER_IDS" From Dual   )
--  
--  Main SQL
SELECT DISTINCT
    i.ID,
    s.FAMILY_ID,
    get_member_columns('len')  "NUMBER_OF_MEMBERS"
FROM
    a_table i
INNER JOIN
    A_TABLE_OF_FAMILY_MEMBERS s ON( 1 = 1 )
INNER JOIN
    member_vals m ON (INSTR(m.MEMBER_IDS, '''' || To_Char(i.ID) || '''') > 0)
ORDER BY i.ID

It is not a good way to do it at all, but if your situation is like described and you can't change the structure and logic of the data, it could help.
This way the function in every run would give you either list of member id column names or values, as well as the number of those columns if you need it. The values will be compared with the ID from table 1 and if there is a match a row will be returned.
With the data from this answer the result is as follows

--
--  R e s u l t :
--  
--          ID FAMILY_ID NUMBER_OF_MEMBERS   
--  ---------- --------- --------------------
--           1 XXX       18                  
--           2 XXX       18                  
--           3 XXX       18                  
--           4 XXX       18                  
--           5 XXX       18                  
--           6 XXX       18 
  • Related