How to use 'Bind Variable' with 'With clause' in a Dynamic query using PLSQL
-
05-03-2021 - |
سؤال
I have three tables with these structures:
1) z_test_a(c_num number , <other columns>)
2) z_test_b(c_num number , Amount number , <other columns>)
3) z_test_b(c_num number , Amount number , <other columns>)
Inspired by one the answers in this post: https://stackoverflow.com/questions/2514254/how-can-i-create-a-dynamic-where-clause , I've learned that one way to use Dynamic Query
and still use Bind variables
is to write your query with With clause
. I tried to apply this approach with Intersect
but I could not succeed and my procedure does not work.It ends up giving me this error:ORA-00904:"B"."AMOUNT1" invalid identifier
for this input parameter:
1)i_table_names:z_test_b,z_test_c
2)i_table_names:1000
3)i_amnt_second:1000
Is this even possible to use this approach when there is intersect or union or the query must be as simple as it is in the post mentioned above? And is there a better way to write this procedure?Maybe it can be written without dynamic query .
My procedure is:
create or replace procedure bind_variable_intersect(i_table_names in varchar2,
i_amnt_first in number,
i_amnt_second in number,
o_out out sys_refcursor) is
v_base_query varchar2(2000) := ' with binds as
(select :bind1 as amount1,
:bind2 as amount2
from dual)
select t.c_num
from z_test_a t , binds b where 1=1 ';
begin
-- Check input parameter " i_table_names "
if i_table_names like '%z_test_b%' then
v_base_query := v_base_query ||
' intersect select c_num from z_test_b where amount = b.amount1 ';
end if;
if i_table_names like '%z_test_c%' then
v_base_query := v_base_query ||
' intersect select c_num from z_test_c where amount = b.amount2 ';
end if;
-- Debug Code
dbms_output.put_line(v_base_query);
-- Execute
open o_out for v_base_query
using i_amnt_first,i_amnt_second;
end;
المحلول
How about constructing your dynamic query like this:
with params (amount1, amount2) as
( select 100, 200
from dual )
select z.*
from params p
cross apply (
select c_num from z_test_a
intersect
select c_num from z_test_b where amount = p.amount1 -- add dynamically
) z;
That last select
under the intersect
would be added dynamically according to the value of i_table_names
, as in your original version.
The procedure would then be:
create or replace procedure bind_variable_intersect
( i_table_names in varchar2
, i_amnt_first in number
, i_amnt_second in number
, o_out out sys_refcursor )
as
v_base_query varchar2(2000) :=
'with params (amount1, amount2) as
( select :b1, :b2
from dual )
select z.*
from params p
cross apply (
select c_num from z_test_a';
begin
-- Check input parameter " i_table_names "
if i_table_names like '%z_test_b%' then
v_base_query := v_base_query || chr(10) ||
'intersect select c_num from z_test_b where amount = p.amount1';
end if;
if i_table_names like '%z_test_c%' then
v_base_query := v_base_query || chr(10) ||
'intersect select c_num from z_test_c where amount = p.amount2';
end if;
v_base_query := v_base_query || chr(10) || ') z';
-- Debug Code
dbms_output.put_line(v_base_query);
-- Execute
open o_out for v_base_query
using i_amnt_first, i_amnt_second;
end bind_variable_intersect;
نصائح أخرى
You got it wrong, I'm afraid. This is what your query evaluates to:
with binds as
(select :bind1 as amount1,
:bind2 as amount2
from dual)
select t.c_num
from z_test_a t,
binds b --> this is "B"
where 1 = 1
intersect
select c_num
from z_test_b
where amount = b.amount1 --> you can't reference "B" here
Instead of intersect
, you could:
use
:bind1
in z_test_b, i.e.intersect select c_num from z_test_b where amount = :bind1
or join
z_test_b
withz_test_a
andbinds
(but I presume that former suggestion is simpler because of possiblewhere
clause; currently, it is useless (where 1 = 1
), but I presume that it is here just for illustration. Something like this:-- CTE you already have; remove WHERE from it with binds as (select :bind1 as amount1, :bind2 as amount2 from dual) select t.c_num from z_test_a t, binds b --> WHERE's not here any more; it is moved to join -- JOIN , z_test_b z where z.amount = b.amount1 --> moved here