I have been trying to create PL/SQL statement that creates a dynamic query in order to get dynamic columns. Since I don't have much idea about these Oracle PL/SQL statements; I am confused about few things.
Is it mandatory to have stored procedure for creating dynamic queries ?
The following query does not throw any error and even results nothing. What I am trying to do in the following query is to get sum of FKOD_AMOUNT according to FKBAB_SOURCE_ID which is foreign key for PFS_SOURCE_ID.
declare
sql_query varchar2(3000) := 'select FKOM_OFFICE_ID,FKBAM_BUDGET_ID ';
begin
for x in (select distinct PFS_SOURCE_ID,PFS_SOURCE_ENG from PBS_FC_SOURCE WHERE PFS_UPPER_SOURCE_ID!=0 )
loop
sql_query := sql_query ||
' , sum(case when FKBAB_SOURCE_ID = '||x.PFS_SOURCE_ID||' then FKOD_AMOUNT ELSE 0 end) as '||x.PFS_SOURCE_ENG;
dbms_output.put_line(sql_query);
end loop;
sql_query := sql_query || ' FROM FMS_K_OFFICEWISE_DTL
JOIN FMS_K_OFFICEWISE_MST ON FMS_K_OFFICEWISE_MST.FKOM_OFFICE_MST_ID=FMS_K_OFFICEWISE_DTL.FKOD_OFFICE_MST_ID
JOIN FMS_K_BUDGET_ALLOCATION_DTL ON FMS_K_BUDGET_ALLOCATION_DTL.FKBAD_BUDGET_ALLOC_DTL_ID=FMS_K_OFFICEWISE_DTL.FKOD_BUDGET_ALLOC_AD_ID
JOIN FMS_K_BUDGET_ALLOCATION_MST ON FMS_K_BUDGET_ALLOCATION_MST.FKBAM_BUDGET_ALLOC_ID=FMS_K_BUDGET_ALLOCATION_DTL.FKBAB_BUDGET_ALLOC_ID
JOIN PBS_FC_BUDGET ON PBS_FC_BUDGET.PFB_BUDGET_ID=FMS_K_BUDGET_ALLOCATION_MST.FKBAM_BUDGET_ID
GROUP BY FKOM_OFFICE_ID,FKBAM_BUDGET_ID ';
dbms_output.put_line(sql_query);
end;
How can I execute 'sql_query' ?
To have an answer(removed my comments), this is pl/sql, not a select statement, so you can't just run it.
With execute immediate you can use INTO clause to store the results into some variables or arrays.
But you may make the query a cursor, put the cursor into a pipelined function and then
select * from table(your_pipelined_function)
Related
I am creating a temp table
CREATE TABLE TMP_PRTimeSum AS
SELECT DISTINCT p.employee,
SUM(p.wage_amount) AS wage_sum,
SUM(p.hours) AS hour_sum
I then want to do a select from that temp table and show the results. I want to do it all in one query. It works if I run them as two separate queries. Is there any way to do this in one query?
CREATE GLOBAL TEMPORARY TABLE a
ON COMMIT PRESERVE ROWS
AS
select * from b;
(add where 1=0 too if you didn't want to initially populate it for the current session with all the data from b).
Is there any way to do this in one query?
No, you need to use one DDL statement (CREATE TABLE) to create the table and one DML statement (SELECT) to query the newly created table.
If you want to be really pedantic then you could do it in a single PL/SQL statement:
DECLARE
cur SYS_REFCURSOR;
v_employee VARCHAR2(20);
v_wage NUMBER;
v_hours NUMBER;
BEGIN
EXECUTE IMMEDIATE
'CREATE TABLE TMP_PRTimeSum (employee, wage_sum, hour_sum) AS
SELECT P.EMPLOYEE,
SUM(P.WAGE_AMOUNT),
SUM(P.HOURS)
FROM table_name p
GROUP BY p.Employee';
OPEN cur FOR 'SELECT * FROM TMP_PRTimeSum';
LOOP
FETCH cur INTO v_employee, v_wage, v_hours;
EXIT WHEN cur%NOTFOUND;
DBMS_OUTPUT.PUT_LINE(v_employee || ', ' || v_wage || ', ' || v_hours);
END LOOP;
END;
/
db<>fiddle here
However, you are then wrapping the two SQL statements in a PL/SQL anonymous block so, depending on how you want to count it then it is either one composite statement, two SQL statements or three (2 SQL and 1 PL/SQL) statements.
I have two type of select statement with the same complicated where clause.
One - Returns transaction detailed data
select field_1, field_2, field_3, ... , field_30 from my_table where my_where_clause
Second - Returns transaction data grouped by(distinct) merchants
select distinct field_1, field_2, field_8 from my_table where my_where_clause
Statements are called separately.
I want to simplify my code and not to repeat this complicated where clause in both statements without loosing performance
In dynamic SQL it's possible but I don't want to use dynamic SQL.
Any suggestions?
Suggestion: you can try GROUPING SETS expression.
It allows you to selectively specify the set of groups that you want to create within a GROUP BY clause. In
In your case, you can specify 2 sets, one group by set for all fields from 1 to 30 and another set for fields 1,2&8.
Link- https://docs.oracle.com/cd/E40518_01/server.761/es_eql/src/reql_aggregation_grouping_sets.html
However, it will return the output of both the groups in a single resultset, not sure if this fits in your design.
So you could encapsulate this statement, in a view or function, e,g,:
create or replace view view_1 as
select field_1, field_2, field_3, ... , field_30
from my_table
where my_where_clause
Then your second query could be
select distinct * from view_1;
You said that you are using this query from java. Try this.
create or replace function get_cursor(p_type varchar2 default null/* other paramethers*/ ) return sys_refcursor
is
result_curosr sys_refcursor;
begin
open result_curosr for 'select '||p_type||' object_type,status from user_objects' /* where clausele */ ;
return result_curosr;
end;
And usage of this from java.
Connection con = ...
CallableStatement callableStatement = con.prepareCall("declare c sys_refcursor; begin ? := get_cursor(?); end ; ");
callableStatement.registerOutParameter(1, OracleTypes.CURSOR);
callableStatement.setString(2, "Distinct"); // for distinct
or
callableStatement.setNull(2, OracleTypes.VARCHAR); // for full results
callableStatement.executeUpdate();
ResultSet rs = (ResultSet) callableStatement.getObject(1);
while(rs.next()) {
System.err.println(rs.getString(1));
}
rs.close();
con.close();
Other solution.
Add one more parameter and do simple deduplication using all columns from query. But i don't see any advantages.
select object_type,status from
(select object_type,status, row_number() over( partition by object_type,status order by 1) rn from user_objects /* your_where_clusue */
) where rn = case when 'DISTIINCT'/* <- paramete here :isDistinct */ = 'DISTIINCT' then 1 else rn end;
You can make dynamic SQL more readable by using multi-line strings, alternative quotes, and templates.
declare
v_select varchar2(32767);
v_where varchar2(32767);
v_code varchar2(32767) := '
##SELECT##
##WHERE##
';
begin
--Populate the clauses.
if ... then
v_select := 'select field_1, field_2, field_3, ... , field_30 from my_table';
else
v_select := 'select distinct field_1, field_2, field_8 from my_table';
end if;
if ... then
v_where :=
q'[
where field_1 = 'foo'
and field_2 = :bind1
...
]';
else
v_where :=
q'[
where field_2 = 'bar'
and field_2 = :bind2
...
]';
end if;
--Fill in the code.
v_code := replace(v_code, '##SELECT##', v_select);
v_code := replace(v_code, '##WHERE##', v_where);
--Print the code to check the formatting. Remove after testing.
dbms_output.put_line(v_code);
--Run it.
execute immediate v_code using ...;
end;
/
It's not perfect but it prevents ugly concatenation. And it's much better than the anti-patterns needed to avoid dynamic SQL at all costs. In most languages features like polymorphism and reflection are better than dynamic code. PL/SQL does not have good support for those advanced features so it's usually better to build the code as a string.
I have a database with many users that have the same table (by same I mean same columns but different data). I would like to run the same query on these tables and bulk collect the results into a temporary table or any other way of viewing.
So far I have the code shown below:
DECLARE
TYPE PDTABLE_12SEGTBL IS TABLE OF MTO_SG2420.PDTABLE_12%ROWTYPE;
COLLECTIONTBL PDTABLE_12SEGTBL;
LoopIteration pls_integer;
CompTblName varchar2(61);
CURSOR MTO_Cursor IS
SELECT owner, table_name
FROM ALL_TABLES
WHERE OWNER LIKE 'MTO_K%'
AND TABLE_NAME = 'PDTABLE_12';
BEGIN
LoopIteration :=1;
FOR item IN MTO_Cursor
LOOP
CompTblName := item.owner || '.pdtable_12';
DBMS_OUTPUT.PUT_LINE('Loop Iteration ' || LoopIteration || ' CompTblName' || CompTblName);
LoopIteration := LoopIteration + 1;
END LOOP;
END;
The tables that I would like to run the query on look like this:
MTO_K01.pdtable_12
MTO_K02.pdtable_12
MTO_K03.pdtable_12
MTO_K04.pdtable_12
MTO_K05.pdtable_12
In the CompTblName variable, I store the complete table name including username successfully through each iteration.
My question is how can I add a query to the code above that runs a select statement on the variable CompTblName and pushes the results into the table that I created (COLLECTIONTBL). I searched in this forum and other places and saw that I can do this using fetch command. However, fetch command needs to be placed within a cursor which gives an error every time I place it in a loop. It is important to note that I would like to concatenate the results from all iterations into COLLECTIONTBL.
You need to use execute immediate statement, which allows to create and run dynamic SQL:
FOR item IN MTO_Cursor LOOP
CompTblName := item.owner || '.pdtable_12';
execute immediate 'insert into COLLECTIONTBL select * from ' || CompTblName;
END LOOP;
I need to open a cursor while table name, columns and where clause are varying. The table name etc will be passed as parameter. For example
CURSOR batch_cur
IS
SELECT a.col_1, b.col_1
FROM table_1 a inner join table_2 b
ON a.col_2 = b.col_2
WHERE a.col_3 = 123
Here, projected columns, table names, join criteria and where clause will be passed as parameters. Once opened, i need to loop through and process each fetched record.
You need to use dynamic SQL something like this:
procedure dynamic_proc
( p_table_1 varchar2
, p_table_2 varchar2
, p_value number
)
is
batch_cur sys_refcursor;
begin
open batch_cur for
'select a.col_1, b.col_1
from ' || p_table_1 || ' a inner join || ' p_table_2 || ' b
on a.col_2 = b.col_2
where a.col_3 = :bind_value1';
using p_value;
-- Now fetch data from batch_cur...
end;
Note the use of a bind variable for the data value - very important if you will re-use this many times with different values.
From your question i guess you need a dynamic cursor. Oracle provides REFCURSOR for dynamic sql statements. Since your query will be built dynamically you need a refcursor to do that.
create procedure SP_REF_CHECK(v_col1 number,v_col2 date,v_tab1 number,v_var1 char,v_var2 varchar2)
is
Ref_cur is REF CURSOR;
My_cur Ref_cur;
My_type Table_name%rowtype;
stmt varchar2(500);
begin
stmt:='select :1,:2 from :3 where :4=:5';
open My_cur for stmt using v_col1,v_col2,v_tab1,v_var1,v_var2;
loop
fetch My_cur into My_type;
//do some processing //
exit when My_cur%notfound;
end loop;
close My_cur;
end;
Check this link for more http://docs.oracle.com/cd/B10500_01/appdev.920/a96624/11_dynam.htm
As of now I am using IF ELSE to handle this condition
IF INPUT_PARAM IS NOT NULL
SELECT ... FROM SOMETABLE WHERE COLUMN = INPUT_PARAM
ELSE
SELECT ... FROM SOMETABLE
Is there any better way to do this in a single query without IF ELSE loops. As the query gets complex there will be more input parameters like this and the amount of IF ELSE required would be too much.
One method would be to use a variant of
WHERE column = nvl(var, column)
There are two pitfalls here however:
if the column is nullable, this clause will filter null values whereas in your question you would not filter the null values in the second case. You could modify this clause to take nulls into account but it turns ugly:
WHERE nvl(column, impossible_value) = nvl(var, impossible_value)
Of course if somehow the impossible_value is ever inserted you will run into some other kind of (fun) problems.
The optimizer doesn't understand correctly this type of clause. It will sometimes produce a plan with a UNION ALL but if there are more than a couple of nvl, you will get full scan even if perfectly valid indexes are present.
This is why when there are lots of parameters (several search fields in a big form for example), I like to use dynamic SQL:
DECLARE
l_query VARCHAR2(32767) := 'SELECT ... JOIN ... WHERE 1 = 1';
BEGIN
IF param1 IS NOT NULL THEN
l_query := l_query || ' AND column1 = :p1';
ELSE
l_query := l_query || ' AND :p1 IS NULL';
END IF;
/* repeat for each parameter */
...
/* open the cursor dynamically */
OPEN your_ref_cursor FOR l_query USING param1 /*,param2...*/;
END;
You can also use EXECUTE IMMEDIATE l_query INTO l_result USING param1;
This should work
SELECT ... FROM SOMETABLE WHERE COLUMN = NVL( INPUT_PARAM, COLUMN )