简体   繁体   中英

PL/SQL dynamic query based on multiple rows

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:

在此处输入图像描述

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;

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.

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...

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM