Home > Software engineering >  PL/SQL dynamic query based on multiple rows
PL/SQL dynamic query based on multiple rows

Time:01-03

I am trying to write a PL/SQL Script that reads tables and generates a dynamic query based on rule_id and parameter_id.

My script should read from parameter_value based on rule id and parameter id and use parameter value in dynamic query.

So my table called rules looks like this:

enter image description here

Here is my script - what am I doing wrong? I am getting an error

ORA-01747 invalid user.table.column,table.column or column specification

declare 
v_rule_id number(10);
v_parameter_id number(10);
v_parameter_value varchar2(100);
v_source_table varchar2(100);
v_lookup_table varhcar2(100);
v_source_column varchar2(100);
v_lookup_column varchar2(100);
v_date varhchar2(100);
v_query varchar2(1000);

BEGIN
FOR RL IN (SELECT RULE_ID FROM RULE)
LOOP
FOR PRM IN (SELECT PARAMETER_ID,PARAMETER_VALUE FROM RULE)
LOOP
IF PRM.PARAM_ID = 1 THEN 
v_source_table:= PRM.PARAMETER_VALUE;
ELSIF PRM.PARAM_ID = 2 THEN
V_lookup_table := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAM_ID = 3 THEN
V_source_column := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAM_ID = 4 THEN
V_lookup_column := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAM_ID = 5 THEN
V_date := PRM.PARAMETER_VALUE;
END IF;
v_query := 'SELECT * FROM (SELECT DISTINCT A.' || v_source_column || ', count(*) as count from'|| v_source_table || ' A LEFT JOIN' || V_lookup_table || ' ON A.'||V_source_column ||' = B.'|| V_lookup_column || 'WHERE B.'||V_lookup_table||' IS NULL GROUP BY A.'||V_source_column  ||'ORDER BY 2 DESC' );
EXECUTE IMMEDIATE v_query;
END LOOP;
END LOOP;
END;

CodePudding user response:

Well... debug and you should find what is wrong.

  • Tip for all future stack overlow posts: check AND RUN your code before you paste it.
  • 2nd Tip: write a couple of lines, then test, fix if needed, then continue with some lines. Else you'll end up with an large number of errors and you'll lose overview.
  • 3rd Tip: use dbms_output.put_line (or a logging framework like logger) to instrument your code.

Enjoy the debugging process below !

Manually create sample data since poster provided a screenshot. Please provide this code yourself next time - this is be your job not ours.

CREATE TABLE rule (RULE_ID,PARAMETER_ID,PARAMETER_EXPLANATION,PARAMETER_VALUE) AS
SELECT 1,1,'TABLE_1','A' FROM DUAL UNION ALL 
SELECT 1,2,'TABLE_2','B' FROM DUAL UNION ALL 
SELECT 1,3,'COLUMN_1','X' FROM DUAL UNION ALL 
SELECT 1,4,'COLUMN_2','Y' FROM DUAL UNION ALL 
SELECT 1,5,'DATE','20221231' FROM DUAL UNION ALL 
SELECT 2,1,'TABLE_1','C' FROM DUAL UNION ALL 
SELECT 2,2,'TABLE_2','D' FROM DUAL UNION ALL 
SELECT 2,3,'COLUMN_1','Z' FROM DUAL UNION ALL 
SELECT 2,4,'COLUMN_2','Q' FROM DUAL UNION ALL 
SELECT 2,5,'DATE','20221231' FROM DUAL;

Table RULE created.

Run the code above:

run anonymous pl/sql block

ORA-06550: line 28, column 299:
PLS-00103: Encountered the symbol ")" when expecting one of the following:

   * & = -   ; < / > at in is mod remainder not rem
   <an exponent (**)> <> or != or ~= >= <= <> and or like like2
   like4 likec between || member submultiset
ORA-06550: line 31, column 5:
PLS-00103: Encountered the symbol "LOOP" when expecting one of the following:

   ;
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.

fix error on line 28, run block

Error report -
ORA-06550: line 6, column 16:
PLS-00201: identifier 'VARHCAR2' must be declared
ORA-06550: line 0, column 1:
PL/SQL: Compilation unit analysis terminated
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:

fix error on line 6, run block

ORA-06550: line 9, column 8:
PLS-00201: identifier 'VARHCHAR2' must be declared
ORA-06550: line 0, column 1:
PL/SQL: Compilation unit analysis terminated
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:

fix error on line 8, run block

Error report -
ORA-06550: line 17, column 8:
PLS-00302: component 'PARAM_ID' must be declared
ORA-06550: line 17, column 1:
PL/SQL: Statement ignored
06550. 00000 -  "line %s, column %s:\n%s"
*Cause:    Usually a PL/SQL compilation error.
*Action:

replace occurrences of PARAM_ID with PARAMETER_ID, run block

Error report -
ORA-01747: invalid user.table.column, table.column, or column specification
ORA-06512: at line 29
ORA-06512: at line 29
01747. 00000 -  "invalid user.table.column, table.column, or column specification"
*Cause:    
*Action:

ah... we got the error !

This is the code that gives the original error:

declare 
v_rule_id number(10);
v_parameter_id number(10);
v_parameter_value varchar2(100);
v_source_table varchar2(100);
v_lookup_table varchar2(100);
v_source_column varchar2(100);
v_lookup_column varchar2(100);
v_date varchar2(100);
v_query varchar2(1000);

BEGIN
FOR RL IN (SELECT RULE_ID FROM RULE)
LOOP
FOR PRM IN (SELECT PARAMETER_ID,PARAMETER_VALUE FROM RULE)
LOOP
IF PRM.PARAMETER_ID = 1 THEN 
v_source_table:= PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 2 THEN
V_lookup_table := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 3 THEN
V_source_column := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 4 THEN
V_lookup_column := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 5 THEN
V_date := PRM.PARAMETER_VALUE;
END IF;
v_query := 'SELECT * FROM (SELECT DISTINCT A.' || v_source_column || ', count(*) as count from'|| v_source_table || ' A LEFT JOIN' || V_lookup_table || ' ON A.'||V_source_column ||' = B.'|| V_lookup_column || 'WHERE B.'||V_lookup_table||' IS NULL GROUP BY A.'||V_source_column  ||'ORDER BY 2 DESC';
EXECUTE IMMEDIATE v_query;
END LOOP;
END LOOP;
END;
/

Now it's time to do the proper debugging. Comment out the EXECUTE IMMEDIATE v_query; and replace add dbms_output.put_line(v_query); to see what you're trying to execute. Result: lots of rows like:

SELECT * FROM (SELECT DISTINCT A., count(*) as count fromA A LEFT JOIN ON A. = B.WHERE B. IS NULL GROUP BY A.ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A., count(*) as count fromA A LEFT JOINB ON A. = B.WHERE B.B IS NULL GROUP BY A.ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count fromA A LEFT JOINB ON A.X = B.WHERE B.B IS NULL GROUP BY A.XORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count fromA A LEFT JOINB ON A.X = B.YWHERE B.B IS NULL GROUP BY A.XORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count fromA A LEFT JOINB ON A.X = B.YWHERE B.B IS NULL GROUP BY A.XORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count fromC A LEFT JOINB ON A.X = B.YWHERE B.B IS NULL GROUP BY A.XORDER BY 2 DESC

etc...

The sql statements are (1) incomplete , (2) keywords are concatenated and there are way too many rows. Both inner loop and outer loop do a full select.

...some work...

Final solution:

set serveroutput on size 999999
clear screen
declare 
v_rule_id number(10);
v_parameter_id number(10);
v_parameter_value varchar2(100);
v_source_table varchar2(100);
v_lookup_table varchar2(100);
v_source_column varchar2(100);
v_lookup_column varchar2(100);
v_date varchar2(100);
v_query varchar2(1000);

BEGIN
FOR RL IN (SELECT RULE_ID FROM RULE)
LOOP
FOR PRM IN (SELECT PARAMETER_ID,PARAMETER_VALUE FROM RULE WHERE rule_id = rl.rule_id)
LOOP
IF PRM.PARAMETER_ID = 1 THEN 
v_source_table:= PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 2 THEN
V_lookup_table := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 3 THEN
V_source_column := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 4 THEN
V_lookup_column := PRM.PARAMETER_VALUE;
ELSIF PRM.PARAMETER_ID = 5 THEN
V_date := PRM.PARAMETER_VALUE;
END IF;
END LOOP;
v_query := 'SELECT * FROM (SELECT DISTINCT A.' || v_source_column || ', count(*) as count from '|| v_source_table || ' A LEFT JOIN ' || V_lookup_table || ' ON A.'||V_source_column ||' = B.'|| V_lookup_column || ' WHERE B.'||V_lookup_table||' IS NULL GROUP BY A.'||V_source_column  ||' ORDER BY 2 DESC' ;
dbms_output.put_line(v_query);
--EXECUTE IMMEDIATE v_query; --uncomment if all tables exist.
END LOOP;
END;
/

SELECT * FROM (SELECT DISTINCT A.X, count(*) as count from A A LEFT JOIN B ON A.X = B.Y WHERE B.B IS NULL GROUP BY A.X ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count from A A LEFT JOIN B ON A.X = B.Y WHERE B.B IS NULL GROUP BY A.X ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count from A A LEFT JOIN B ON A.X = B.Y WHERE B.B IS NULL GROUP BY A.X ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count from A A LEFT JOIN B ON A.X = B.Y WHERE B.B IS NULL GROUP BY A.X ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.X, count(*) as count from A A LEFT JOIN B ON A.X = B.Y WHERE B.B IS NULL GROUP BY A.X ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.Z, count(*) as count from C A LEFT JOIN D ON A.Z = B.Q WHERE B.D IS NULL GROUP BY A.Z ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.Z, count(*) as count from C A LEFT JOIN D ON A.Z = B.Q WHERE B.D IS NULL GROUP BY A.Z ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.Z, count(*) as count from C A LEFT JOIN D ON A.Z = B.Q WHERE B.D IS NULL GROUP BY A.Z ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.Z, count(*) as count from C A LEFT JOIN D ON A.Z = B.Q WHERE B.D IS NULL GROUP BY A.Z ORDER BY 2 DESC
SELECT * FROM (SELECT DISTINCT A.Z, count(*) as count from C A LEFT JOIN D ON A.Z = B.Q WHERE B.D IS NULL GROUP BY A.Z ORDER BY 2 DESC

This will execute successfully if all tables in each select statement actually exist in the database.

CodePudding user response:

What if you could get your queries without PL/SQL - just plain SQL?
Lets say that your two tables look like below:

CREATE TABLE
  A_TBL_1 (ID, TXT, SOME_COL, COL_1_T1, DATE_T1) AS
      (
          SELECT 1,   'TEXT for ID 1', 'Something else 1 in tbl_1', 'X',  To_Date('20221231', 'yyyymmdd') From Dual Union All
          SELECT 2,   'TEXT for ID 2', 'Something else 2 in tbl_1', 'Y',  To_Date('20221231', 'yyyymmdd') From Dual Union All
          SELECT 3,   'TEXT for ID 3', 'Something else 3 in tbl_1', 'Z',  To_Date('20221231', 'yyyymmdd') From Dual
      );
CREATE TABLE     
  A_TBL_2 (ID, TXT, SOME_COL, COL_1_T2) AS
      (
          SELECT 11,   'TEXT for ID 11', 'Something else 11 in tbl_2', 'X' From Dual Union All
          SELECT 12,   'TEXT for ID 12', 'Something else 12 in tbl_2', 'Y' From Dual Union All
          SELECT 13,   'TEXT for ID 13', 'Something else 13 in tbl_2', 'X' From Dual
      );

... and that your rules are set up like here

CREATE TABLE
    A_RULE_TBL (RULE_ID, PAR_ID, PAR_EXPL, PAR_VAL) AS
      (
        SELECT 1, 1, 'A_TBL_1',     'a'     FROM DUAL UNION ALL 
        SELECT 1, 2, 'A_TBL2',      'b'     FROM DUAL UNION ALL 
        SELECT 1, 3, 'COL_1_T1',    'X'     FROM DUAL UNION ALL 
        SELECT 1, 4, 'COL_1_T2',    'X'     FROM DUAL UNION ALL 
        SELECT 1, 5, 'DATE_T1',     '20221231' FROM DUAL UNION ALL 
        SELECT 2, 1, 'A_TBL_1',     'a'     FROM DUAL UNION ALL 
        SELECT 2, 2, 'A_TBL_2',     'b'     FROM DUAL UNION ALL 
        SELECT 2, 3, 'COL_1_T1',    'Y'     FROM DUAL UNION ALL 
        SELECT 2, 4, 'COL_1_T2',    'Y'     FROM DUAL UNION ALL 
        SELECT 2, 5, 'DATE_T1',      '20221231' FROM DUAL
      );

If we Pivot And Unpivot the rules using a CTE (named params)_

WITH
    params AS
        (   Select    *
            From      A_RULE_TBL 
            PIVOT (
                    Max(CASE WHEN PAR_ID = 1 THEN PAR_EXPL END) "SRC_TBL",
                    Max(CASE WHEN PAR_ID = 2 THEN PAR_EXPL END) "LKP_TBL",
                    Max(CASE WHEN PAR_ID = 3 THEN PAR_EXPL END) "SRC_COL",
                    Max(CASE WHEN PAR_ID = 4 THEN PAR_EXPL END) "LKP_COL",
                    Max(CASE WHEN PAR_ID = 5 THEN PAR_EXPL END) "DATE",
                    --
                    Max(CASE WHEN PAR_ID = 1 THEN PAR_VAL END) "SRC_TBL_VAL",
                    Max(CASE WHEN PAR_ID = 2 THEN PAR_VAL END) "LKP_TBL_VAL",
                    Max(CASE WHEN PAR_ID = 3 THEN PAR_VAL END) "SRC_COL_VAL",
                    Max(CASE WHEN PAR_ID = 4 THEN PAR_VAL END) "LKP_COL_VAL",
                    Max(CASE WHEN PAR_ID = 5 THEN PAR_VAL END) "DATE_VAL"
                    FOR RULE_ID IN(1 "ID1", 2 "ID2")    )
        
            UNPIVOT(  (SRC_TBL, SRC_COL, LKP_TBL, LKP_COL, A_DATE, SRC_TBL_VAL, SRC_COL_VAL, LKP_TBL_VAL, LKP_COL_VAL, DATE_VAL) 
                        FOR RULE_ID
                        IN  (
                            (ID1_SRC_TBL, ID1_SRC_COL, ID1_LKP_TBL, ID1_LKP_COL, ID1_DATE, ID1_SRC_TBL_VAL, ID1_SRC_COL_VAL, ID1_LKP_TBL_VAL, ID1_LKP_COL_VAL, ID1_DATE_VAL ) as 1,
                            (ID2_SRC_TBL, ID2_SRC_COL, ID2_LKP_TBL, ID2_LKP_COL, ID2_DATE, ID2_SRC_TBL_VAL, ID2_SRC_COL_VAL, ID2_LKP_TBL_VAL, ID2_LKP_COL_VAL, ID2_DATE_VAL ) as 2   )
                  )
            ORDER BY RULE_ID
      )
--  
--  R e s u l t
--     RULE_ID SRC_TBL  SRC_COL  LKP_TBL  LKP_COL  A_DATE   SRC_TBL_VAL SRC_COL_VAL LKP_TBL_VAL LKP_COL_VAL DATE_VAL
--  ---------- -------- -------- -------- -------- -------- ----------- ----------- ----------- ----------- --------
--           1 A_TBL_1  COL_1_T1 A_TBL_2  COL_1_T2 DATE_T1  a           X           b           X           20221231 
--           2 A_TBL_1  COL_1_T1 A_TBL_2  COL_1_T2 DATE_T1  a           Y           b           Y           20221231

Resulting dataset has everything you need to construct different sql commands. Here for RULE_ID = 1 there will be SQL for left joining tables and selecting rows that doesn't match. For RULE_ID = 2 rows that does match.

SELECT 
    'Select ' || SRC_TBL_VAL || '.' || SRC_COL || ', Count(*) "CNT" ' || Chr(10) ||
    'From ' || SRC_TBL || ' ' || SRC_TBL_VAL || ' ' ||  Chr(10) ||
    'Left Join ' || LKP_TBL || ' ' || LKP_TBL_VAL || ' ON(' || LKP_TBL_VAL || '.' || LKP_COL || ' = ' || SRC_TBL_VAL || '.' || SRC_COL || ')' ||  Chr(10) ||
    'Where ' || LKP_TBL_VAL || '.' || LKP_COL || ' Is ' || CASE RULE_ID WHEN 2 THEN 'Not' ELSE '' END   || ' Null ' ||  Chr(10) ||
    'Group By ' || SRC_TBL_VAL  || '.' || SRC_COL || ' ' ||  Chr(10) ||
    'Order By Count(*) DESC' "SQL_COMMANDS"
FROM      params
ORDER BY  RULE_ID
/*  R e s u l t :
SQL_COMMANDS                                       
--------------------------------------------------
Select a.COL_1_T1, Count(*) "CNT"                 
From A_TBL_1 a                                    
Left Join A_TBL_2 b ON(b.COL_1_T2 = a.COL_1_T1)   
Where b.COL_1_T2 Is  Null                         
Group By a.COL_1_T1                               
Order By Count(*) DESC                            

Select a.COL_1_T1, Count(*) "CNT"                    
From A_TBL_1 a                                       
Left Join A_TBL_2 b ON(b.COL_1_T2 = a.COL_1_T1)      
Where b.COL_1_T2 Is Not Null                         
Group By a.COL_1_T1                                  
Order By Count(*) DESC                         
*/

The first query, if run against above sample data, results as:

--  COL_1_T1        CNT
--  -------- ----------
--  Z                 1

... while second results as:

--  COL_1_T1        CNT
--  -------- ----------
--  X                 2 
--  Y                 1

You can select some or all of other columns and you can construct the sql commands with different joins and where conditions, groupings, orderings etc...

  • Related