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...
Related
How to take where clause conditions from table column in oracle plsql.
E.g. data in table
Condition
1.sourceSystemId = 'SN'
2.AND(coverageType='AD',amountType1='PREMIUM',premiumFrequency='REGULAR',yearOfPremium='1')
e.g query:
select * from xyz where rule='abc' and "sourceSystemId = 'SN'"
select * from xyz where rule='abc' AND(coverageType='AD',amountType1='PREMIUM',premiumFrequency='REGULAR',yearOfPremium='1')
Not entirely sure what you're asking here, but I would imagine that
select * from xyz where rule='abc' AND(coverageType='AD',amountType1='PREMIUM',premiumFrequency='REGULAR',yearOfPremium='1')
would become
select * from xyz
where rule='abc'
AND coverageType='AD'
and amountType1='PREMIUM'
and premiumFrequency='REGULAR'
and yearOfPremium='1'
I suppose you want something like :
DECLARE
l_query VARCHAR2(2000) := 'select * from xyz where rule=''abc''';
l_result xyz%ROWTYPE;
l_cursor SYS_REFCURSOR;
BEGIN
dbms_output.put_line(l_query);
FOR clause IN (SELECT condition
FROM conditions)
LOOP
l_query := l_query||' AND '||clause.condition;
END LOOP;
OPEN l_cursor FOR l_query;
LOOP
FETCH l_cursor INTO l_result;
EXIT WHEN l_cursor%NOTFOUND;
..
-- your processing
END LOOP;
CLOSE l_cursor;
END;
Here is example of SQL solution. I used justt first and last condition but you can get them all...
WITH
xyz As
(
Select 1 "ID", 'abc' "RULE", 'AD' "COVERAGETYPE", 'PREMIUM' "AMOUNTTYPE1", 'REGULAR' "PREMIUMFREQUENCY", '1' "YEAROFPREMIUM" From Dual
UNION
Select 2 "ID", 'abc' "RULE", 'BF' "COVERAGETYPE", 'ORDINARY' "AMOUNTTYPE1", 'EXTRA' "PREMIUMFREQUENCY", '2' "YEAROFPREMIUM" From Dual
UNION
Select 3 "ID", 'abc' "RULE", 'AD' "COVERAGETYPE", 'PREMIUM' "AMOUNTTYPE1", 'REGULAR' "PREMIUMFREQUENCY", '1' "YEAROFPREMIUM" From Dual
),
conditions As
(
SELECT UPPER('coverageType=AD,amountType1=PREMIUM,premiumFrequency=REGULAR,yearOfPremium=1') "CND" From Dual
)
SELECT
x.ID, x.RULE, x.COVERAGETYPE, x.AMOUNTTYPE1, x.PREMIUMFREQUENCY, x.YEAROFPREMIUM
FROM
xyz x
INNER JOIN
conditions c ON(1=1)
WHERE
x.RULE = 'abc' And
x.COVERAGETYPE = CASE WHEN InStr(c.CND || ',', 'COVERAGETYPE=') = 0 THEN x.COVERAGETYPE
ELSE SubStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'COVERAGETYPE=') + Length('COVERAGETYPE=')), 1, InStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'COVERAGETYPE=') + Length('COVERAGETYPE=') + 1), ',')) END And
x.YEAROFPREMIUM = CASE WHEN InStr(c.CND || ',', 'YEAROFPREMIUM=') = 0 THEN x.YEAROFPREMIUM
ELSE SubStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'YEAROFPREMIUM=') + Length('YEAROFPREMIUM=')), 1, InStr(SubStr(c.CND || ',', InStr(c.CND || ',', 'YEAROFPREMIUM=') + Length('YEAROFPREMIUM=') + 1), ',')) END
Result:
ID RULE COVERAGETYPE AMOUNTTYPE1 PREMIUMFREQUENCY YEAROFPREMIUM
1 abc AD PREMIUM REGULAR 1
3 abc AD PREMIUM REGULAR 1
In 1 PL/SQL block have to use multiple SELECT query and one block statement. In this block statement we have to take counts before insert query and once insert statement run then after have to take its after_counts of the id value that mentioned below.
set heading off
set colsep '|'
set feedback off
set sqlformat csv
set trimspool on
spool output.txt
declare
ln_rec tab1%rowtype;
lv varchar(20);
sid tab.id%type;
b_cnts number;
a_cnts number;
type sh_id is varray(10) of tab.col1%type;
id sh_id := sh_id(1, 3, 5, 7, 9, 11, 13, 15, 17, 19);
begin
select a.id, count(b.sub_id) into sid, b_cnts as "before_counts" from tab a, tab1 b;
for i in (select distinct b.sub_id from tab a, tab1 b where a.id in (1, 3, 5, 7, 9, 11, 13, 15, 17, 19))
loop
select * into ln_rec from tab1 where sub_id = i.sub_id;
insert into new_tab values(id, i.sub_id, lv);
commit;
end loop;
select a.id, count(b.sub_id) into sid, a_cnts as "after_counts" from tab a, tab b;
end;
spool off
But when i execute it then got error because of above SET system variable summary and in the insert statement due to id. I want output in the csv format or output format like where 3 columns should be generated as id, before_counts, after_counts & its proper value. Like this:-
<id> <before_counts> <after_counts> -- This heading should not appear because above used heading off
1 135 138
3 246 250
5 298 302
7 389 399
.........
Something like this:
DECLARE
type sh_id is varray(10) of tab.col1%type;
a_cnts number;
b_cnts number;
lv varchar2(20) := NULL; -- This is never modified in your code.
ids sh_id := sh_id(1, 3, 5, 7, 9, 11, 13, 15, 17, 19);
BEGIN
FOR i IN 1 .. ids.COUNT LOOP
SELECT count(b.sub_id)
INTO b_cnts
FROM tab a
INNER JOIN tab1 b
ON ( <some join conditions> ) -- you need to specify the join
WHERE a.id = ids(i);
INSERT INTO new_tab
SELECT DISTINCT
ids(i),
b.sub_id,
lv
FROM tab a
INNER JOIN tab1 b
ON ( <some join conditions> ) -- you need to specify the join
WHERE a.id = ids(i);
-- Assuming you have a trigger to populate tab or tab1 from new_tab then
a_cnts := b_cnts + SQL%ROWCOUNT;
-- Otherwise:
SELECT count(b.sub_id)
INTO a_cnts
FROM tab a
INNER JOIN tab1 b
ON ( <some join conditions> ) -- you need to specify the join
WHERE a.id = ids(i);
DBMS_OUTPUT.PUT_LINE( ids(i) || CHR(9) || b_cnts || CHR(9) || a_cnts );
END LOOP;
-- Commit outside the loop
COMMIT;
END;
/
I have a table in oracle and I want to convert it in matrix form
table 1 : I have two type of users with corresponding weights
User_name M_User Total
user 1 user 2 7
user 1 user 3 19
user 1 user 7 5
user 3 user 2 1
user 2 user 7 1
The final result should be something like this: user 1 - > user two has weight 7 so this value appears in that cell and so on
user 1 user 2 user 3 user 7
user 1 0 7 19 5
user 3 0 1 0 0
user 2 0 0 0 1
user 7 0 0 0 0
After a bit of research I found Pivot function and used it.
SELECT *
FROM (SELECT USER_NAME, M_USER, TOTAL
FROM TEST)
PIVOT (MAX(TOTAL) FOR (M_USER) IN ('user 2' AS User2, 'user 3' AS User3 , 'user7' AS User7))
First problem is that it is showing null values for 'User 7' and it shouldn't, second problem is that I have lot of data in my file (107k records,including duplicates) for limited data like above I can use 'user 2' AS User2, 'user 3' AS User3 , 'user7' AS User7 after IN command in case of such big data how can i write this line? of course I can't write 100k records after IN
UPDATE:
ran the commands in sql developer as "run script"
Error starting at line 2 in command:
EXEC :rc := getusers;
Error report:
ORA-06550: line 1, column 13:
PLS-00905: object SYSTEM.GETUSERS is invalid
ORA-06550: line 1, column 7:
PL/SQL: Statement ignored
06550. 00000 - "line %s, column %s:\n%s"
*Cause: Usually a PL/SQL compilation error.
*Action:
rc
This may be helpful for you. I have used CASE WHEN THEN END blocks to achieve the PIVOT.
SELECT USERS.USER_NAME
, MAX(COALESCE(TEST.USER1, 0)) USER1
, MAX(COALESCE(TEST.USER2, 0)) USER2
, MAX(COALESCE(TEST.USER3, 0)) USER3
, MAX(COALESCE(TEST.USER7, 0)) USER7
FROM (
SELECT DISTINCT USER_NAME
FROM (
SELECT USER_NAME FROM TEST
UNION ALL
SELECT M_USER FROM TEST
)
) USERS
LEFT OUTER JOIN (
SELECT
USER_NAME
, M_USER
, CASE WHEN M_USER = 'user 1' THEN TOTAL ELSE 0 END AS USER1
, CASE WHEN M_USER = 'user 2' THEN TOTAL ELSE 0 END AS USER2
, CASE WHEN M_USER = 'user 3' THEN TOTAL ELSE 0 END AS USER3
, CASE WHEN M_USER = 'user 7' THEN TOTAL ELSE 0 END AS USER7
FROM TEST
) TEST ON USERS.USER_NAME = TEST.USER_NAME
GROUP BY USERS.USER_NAME
ORDER BY USERS.USER_NAME
UPDATE
I could not find a way write this in a single query. After some analysis i found this.
CREATE OR REPLACE FUNCTION GETUSERS RETURN SYS_REFCURSOR AS
QUERY VARCHAR2(32767);
RC SYS_REFCURSOR;
BEGIN
QUERY := 'SELECT USERS.USER_NAME ';
FOR TMP IN (SELECT DISTINCT UPPER(REPLACE(USER_NAME, ' ', '')) USER_NAME FROM (SELECT USER_NAME FROM TEST UNION ALL SELECT M_USER FROM TEST) ORDER BY USER_NAME)
LOOP
QUERY := QUERY || ' , MAX(COALESCE(TEST.' || TMP.USER_NAME || ' , 0)) ' || TMP.USER_NAME;
END LOOP;
QUERY := QUERY || ' FROM ( ';
QUERY := QUERY || ' SELECT DISTINCT USER_NAME ';
QUERY := QUERY || ' FROM ( ';
QUERY := QUERY || ' SELECT USER_NAME FROM TEST ';
QUERY := QUERY || ' UNION ALL ';
QUERY := QUERY || ' SELECT M_USER FROM TEST ';
QUERY := QUERY || ' ) ';
QUERY := QUERY || ' ) USERS ';
QUERY := QUERY || ' LEFT OUTER JOIN ( ';
QUERY := QUERY || ' SELECT USER_NAME';
FOR TMP IN (SELECT DISTINCT USER_NAME, REPLACE(USER_NAME, ' ', '') USER_COL_NM FROM (SELECT USER_NAME FROM TEST UNION ALL SELECT M_USER FROM TEST))
LOOP
QUERY := QUERY || ', CASE WHEN M_USER = ''' || TMP.USER_NAME
|| ''' THEN TOTAL ELSE 0 END AS ' || TMP.USER_COL_NM ;
END LOOP;
QUERY := QUERY || ' FROM TEST';
QUERY := QUERY || ' ) TEST ON USERS.USER_NAME = TEST.USER_NAME ';
QUERY := QUERY || 'GROUP BY USERS.USER_NAME ';
QUERY := QUERY || 'ORDER BY USERS.USER_NAME';
OPEN RC FOR QUERY;
RETURN RC;
END;
/
Created function which dynamically creates the SQL and returns SYS_REFCURSOR. This can be run in SQL*Plus or SQL Developer (with 'run as a script'),
VAR RC REFCURSOR;
EXEC :RC := GETUSERS;
PRINT RC
I have following example of table. Thera can be unlimited branch and customers. I need group this branches and count their customers, then show it's with different columns.
BRANCHNAME CUSTOMERNO
100 1001010
100 1001011
103 1001012
104 1001013
104 1001014
104 1001015
105 1001016
105 1001017
106 1001018
Note that there can be unlimited branch and customers, the query must work not only this case.
In this case the accepted result is:
100 103 104 105 106
2 1 3 2 1
Example SQL DATA
select '100' BranchName,'1001010' CustomerNo from dual UNION ALL
select '100' BranchName,'1001011' CustomerNo from dual UNION ALL
select '103' BranchName,'1001012' CustomerNo from dual UNION ALL
select '104' BranchName,'1001013' CustomerNo from dual UNION ALL
select '104' BranchName,'1001014' CustomerNo from dual UNION ALL
select '104' BranchName,'1001015' CustomerNo from dual UNION ALL
select '105' BranchName,'1001016' CustomerNo from dual UNION ALL
select '105' BranchName,'1001017' CustomerNo from dual UNION ALL
select '106' BranchName,'1001018' CustomerNo from dual
I think it is possible, though quite complicated, to write a pipelined table function that returns a variable structure. Your pipeline table function will use the Oracle Data Cartridge interface and the magic of the AnyDataSet type to return a dynamic structure at runtime. You can then use that in subsequent SQL statements as if it was a table, i.e.
SELECT *
FROM TABLE( your_pipelined_function( p_1, p_2 ));
A couple more references that discuss the same sample implementation
Dynamic SQL Pivoting
The Implementing the Interface Approach section of the Oracle Data Cartridge Developer's Guide
Method4. After downloading and installing the open source PL/SQL code, here is a complete implementation:
--Create sample table.
create table branch_data as
select '100' BranchName,'1001010' CustomerNo from dual UNION ALL
select '100' BranchName,'1001011' CustomerNo from dual UNION ALL
select '103' BranchName,'1001012' CustomerNo from dual UNION ALL
select '104' BranchName,'1001013' CustomerNo from dual UNION ALL
select '104' BranchName,'1001014' CustomerNo from dual UNION ALL
select '104' BranchName,'1001015' CustomerNo from dual UNION ALL
select '105' BranchName,'1001016' CustomerNo from dual UNION ALL
select '105' BranchName,'1001017' CustomerNo from dual UNION ALL
select '106' BranchName,'1001018' CustomerNo from dual;
--Create a dynamic pivot in SQL.
select *
from table(method4.dynamic_query(
q'[
--Create a select statement
select
--The SELECT:
'select'||chr(10)||
--The column list:
listagg(
replace(q'!sum(case when BranchName = '#BRANCH_NAME#' then 1 else 0 end) "#BRANCH_NAME#"!', '#BRANCH_NAME#', BranchName)
, ','||chr(10)) within group (order by BranchName)||chr(10)||
--The FROM:
'from branch_data' v_sql
from
(
--Distinct BranchNames.
select distinct BranchName
from branch_data
)
]'
));
If you just want to report the results somewhere, you may use a cursor for the select statement:
select branchname, count(*) from test group by branchname order by branchname asc;
Looping through the cursor you may get your values.
here is my sample:
declare
v_b varchar2(1000);
v_t varchar2(1000);
begin
for i in (select branchname, count(*) total from test group by branchname order by branchname asc)
loop
v_b := v_b || i.branchname || ' ';
v_t := v_t || i.total || ' ';
end loop;
dbms_output.put_line(v_b);
dbms_output.put_line(v_t);
end;
This will get it in rows (rather than columns):
SELECT branchname,
COUNT( DISTINCT customerno ) AS customers
FROM your_table
GROUP BY branchname;
(Note: you can omit the DISTINCT keyword if there will never be repeats of the branchname, customerno pair.)
Without knowing what the branch names are you are could only do a dynamic pivot.
It would be much simpler to take the output of the above query (in row format) and transpose it in whatever front-end you are using to access the database.
From comments:
I need a report in this format, and don't want write some application , wants to do with sql for easily export to excell in such format
No, you don't need it in column format in SQL. You can put it into excel in row format and then use excel's TRANSPOSE function to convert it (very simply) to columns without having to implement a complicated dynamic SQL solution.
What about this solution. Without no table creation, just set the v_sql parameter.
SET SERVEROUTPUT ON SIZE 100000
DECLARE
v_cursor sys_refcursor;
CURSOR get_columns
IS
SELECT EXTRACTVALUE (t2.COLUMN_VALUE, 'node()') VALUE
FROM (SELECT *
FROM TABLE (XMLSEQUENCE (v_cursor))) t1,
TABLE (XMLSEQUENCE (EXTRACT (t1.COLUMN_VALUE, '/ROW/node()'))) t2;
v_column VARCHAR2 (1000);
v_value VARCHAR2 (1000);
v_counter NUMBER (3) := 0;
v_sql VARCHAR2 (4000);
BEGIN
v_sql :=
'SELECT branchname, COUNT (DISTINCT customerno) AS customers'
|| ' FROM (SELECT 100 branchname, 1001010 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 100 branchname, 1001011 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 103 branchname, 1001012 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 104 branchname, 1001013 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 104 branchname, 1001014 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 104 branchname, 1001015 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 105 branchname, 1001016 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 105 branchname, 1001017 customerno'
|| ' FROM DUAL'
|| ' UNION ALL'
|| ' SELECT 106 branchname, 1001018 customerno'
|| ' FROM DUAL)'
|| ' GROUP BY branchname';
OPEN v_cursor FOR v_sql;
FOR v_record IN get_columns
LOOP
IF v_counter = 0
THEN
v_column := v_column || v_record.VALUE || ' ';
v_counter := 1;
ELSIF v_counter = 1
THEN
v_value := v_value || v_record.VALUE || ' ';
v_counter := 0;
END IF;
END LOOP;
DBMS_OUTPUT.put_line (v_column);
DBMS_OUTPUT.put_line (v_value);
END;
/
And the output is
100 105 104 103 106
2 2 3 1 1
with src as
(select '100' BranchName,'1001010' CustomerNo from dual UNION ALL
select '100' BranchName,'1001011' CustomerNo from dual UNION ALL
select '103' BranchName,'1001012' CustomerNo from dual UNION ALL
select '104' BranchName,'1001013' CustomerNo from dual UNION ALL
select '104' BranchName,'1001014' CustomerNo from dual UNION ALL
select '104' BranchName,'1001015' CustomerNo from dual UNION ALL
select '105' BranchName,'1001016' CustomerNo from dual UNION ALL
select '105' BranchName,'1001017' CustomerNo from dual UNION ALL
select '106' BranchName,'1001018' CustomerNo from dual )
SELECT * FROM
(select BranchName from src)
PIVOT XML
(COUNT(*) FOR (BranchName)
IN
(SELECT DISTINCT BranchName FROM SRC))
This query gives the output in xml format. The whole xml data will be contained in the field that the query results(The query has only single row-sinlge column output). The next step is to parse the xml data and display it in tabular form.
You can use this selection:
SELECT branchname, count(*)
FROM test
GROUP BY branchname
In general it is not professional to use selection for every number in branchname.
I'm wondering if it possible to debug similar statements in an easy way.
When I save the 'select string' in a variable , it become 'long' and I would need to split it in more variables. I' presenting the very simplified sample:
OPEN o_recordset FOR
'SELECT distinct
a, b, c
FROM t1,t2
WHERE'
|| CASE
WHEN i_use_ctr_id = 1 then ' a = b'
END
|| ' ORDER BY 1 ASC , DECODE('''||i_sort_order||''',null, '''', ''a'', '' NULLS LAST '', ''b'' ,'',2 ASC NULLS LAST'')'
;
I wish to see the select like this (i_use_ctr_id = 1, i_sort_order = a)
SELECT distinct
a, b, c
FROM t1,t2
WHERE a = b
END
ORDER BY 1 ASC , DECODE('a',null, '''', ''a'', '' NULLS LAST '', ''b'' ,'',2 ASC NULLS LAST'')'
;
Use a debug procedure that either writes to a file or inserts in a table (with an autonomous transaction).
For instance:
CREATE TABLE debug_t (ts timestamp default systimestamp, data VARCHAR2(4000));
CREATE OR REPLACE PROCEDURE debug_p (p VARCHAR2) IS
PRAGMA autonomous_transaction;
BEGIN
-- you should split p if length is > 4000
INSERT INTO debug_t (data) VALUES (p);
COMMIT;
END;
/
Then you can debug values by inserting a single line of code:
SQL> DECLARE
2 l_sql VARCHAR2(4000);
3 i_use_ctr_id NUMBER;
4 i_sort_order NUMBER;
5 BEGIN
6 l_sql := 'SELECT distinct
7 a, b, c
8 FROM t1,t2
9
10 WHERE'
11 || CASE
12 WHEN i_use_ctr_id = 1 then ' a = b'
13 END
14 || ' ORDER BY 1 ASC , DECODE('''||i_sort_order
15 ||''',null, '''', ''a'', '' NULLS LAST '', ''b'' ,'',2 ASC NULLS LAST'')'
16 ;
17 debug_p(l_sql); -- debug before opening cursor
18 END;
19 /
PL/SQL procedure successfully completed
SQL> select * from debug_t;
TS DATA
----------------- --------------------------------------------------------------------------------
11/09/13 11:52:30 SELECT distinct
a, b, c
FROM t1,t2
WHERE ORDER BY 1 ASC , DECODE('',null, '', 'a', ' NULLS LAST ', 'b' ,',2 ASC