I am using execute immediate statement in one of my queries.
procedure p1 (p_pk1_column, p_pk2_column , p_conv_table_name ,p_MODUE_NAME )
is
v_select_string := 'SELECT'''||p_MODUE_NAME||''',''' ||p_pk1_column || ''',''' || p_pk2_column ||''' FROM ' ||p_conv_table_name || v_where_condition;
execute immediate v_select_string ;
dbms_output.put_line('string:'||v_select_string );
end p1;
Here I am calling p1 procedure in another procedure p2
PROCEDURE P2 IS
v_pk1_column:='a';
v_pk2_columnm:='b';
v_mod_name:='mOD1';
p1(v_pk1_column,v_pk2_columnm);
end p2;
/
In p2 procedure a, b are the column names of p_conv_table_name . I want to execute the select statement like select p_mod_name, a, b from p_conv_table_name where condition; so that it should give values for a and b columns in p_conv_table_name .
But it is executing like select p_mod, p_pk1_col,p_pk2_col from p_conv_table_name where condition;
So simply column names are selecting instead of values in that column.
Please suggest some approach to achieve values in that column.
Thanks in advance
When the SELECT statement is built the column names are surrounded in single-quotes, which turns them into string literals. Change your procedure to something like:
CREATE OR REPLACE PROCEDURE P1 (p_pk1_column IN VARCHAR2,
p_pk2_column IN VARCHAR2,
p_conv_table_name IN VARCHAR2,
p_MODUE_NAME IN VARCHAR2)
IS
v_select_string VARCHAR2(2000);
v_where_condition VARCHAR2(2000) := ' WHERE SOMETHING = SOMETHING_ELSE';
csr SYS_REFCURSOR;
v_val_1 VARCHAR2(2000);
v_val_2 VARCHAR2(2000);
v_mod_name VARCHAR2(2000);
BEGIN
v_select_string := 'SELECT ' || p_MODUE_NAME || ',' ||
p_pk1_column || ',' ||
p_pk2_column ||
' FROM ' || p_conv_table_name ||
v_where_condition;
dbms_output.put_line('string:' || v_select_string);
OPEN csr FOR v_select_string;
LOOP
FETCH csr INTO v_mod_name, v_val_1, v_val_2;
EXIT WHEN csr%NOTFOUND;
DBMS_OUTPUT.PUT_LINE('v_mod_name=''' || v_mod_name || ''' ' ||
'v_val_1=''' || v_val_1 || ''' ' ||
'v_val_2=''' || v_val_2 || '''');
END LOOP;
CLOSE csr;
END P1;
I've also changed the code to OPEN and FETCH a cursor rather than using EXECUTE IMMEDIATE. OPEN and FETCH are generally more appropriate for use with a dynamic SELECT statement.
Share and enjoy.
Related
I m trying to execute the procedure but it is showing the errror of missing expression while executing this line execute immediate sqlQuery into test;
My query returns 6 columns which i need to store into the variables , please help to execute this query.
procedure getAttributeOptions(subID number, compID number,docNumber varchar2,transType varchar2 ,rowNum varchar2 ,
Attribute1 out varchar2,Attribute2 out varchar2,Attribute3 out varchar2,Attribute4 out varchar2,
Attribute5 out varchar2, Attribute6 out varchar2) is
sqlQuery varchar2(4000);
storageColumns varchar2(4000);
colName varchar2(1000);
tableName varchar2(1000);
primaryKey varchar2(1000);
test varchar2(4000);
begin
colName:=' ATTRIBUTE_OPTION_1,ATTRIBUTE_OPTION_2,ATTRIBUTE_OPTION_3,ATTRIBUTE_OPTION_4,ATTRIBUTE_OPTION_5,ATTRIBUTE_OPTION_6 ';
storageColumns:=' Attribute1,Attribute2,Attribute3,Attribute4,Attribute5,Attribute6 ';
if transType = TY_ISSUE then
tableName:='preq_master_detail';
primaryKey:='req_number';
elsif transType=TY_TRANSFER then
tableName:='pinv_transfer_detail';
primaryKey:='transfer_id';
elsif transType=TY_RECEIVE then
tableName:='ppo_master_detail';
primaryKey:='po_number';
elsif transType=TY_MANUAL then
tableName:='part_manual_adjustment';
primaryKey:='adjustment_id';
end if;
dbms_output.put_line(tableName);
if tableName is not null then
begin
sqlQuery:='select ' || colName || 'into ' || storageColumns || 'from ' || tableName ;
sqlQuery:=sqlQuery || ' where subscriber_id=' || subID;
sqlQuery:=sqlQuery || ' and company_id=' || compID;
sqlQuery:=sqlQuery || ' and ' || primaryKey ||'='|| '''' || docNumber || '''' || ' and row_number ='||rowNum;
dbms_output.put_line(sqlQuery);
execute immediate sqlQuery into test;--issue in this line
dbms_output.put_line(Attribute1);
dbms_output.put_line(Attribute2);
dbms_output.put_line(Attribute3);
dbms_output.put_line(Attribute4);
dbms_output.put_line(Attribute5);
dbms_output.put_line(Attribute6);
exception when NO_DATA_FOUND then return ;
end;
end if;
end;
You are placing the list of variables for your INTO clause inside the SQL, rather than outside in PL/SQL. You need to use put the INTO clause outside. Hopefully you can hard-code that clause:
sqlQuery:='select ' || colName || 'from ' || tableName ;
. . .
execute immediate sqlQuery into Attribute1,Attribute2,Attribute3,Attribute4,Attribute5,Attribute6;
While you are at it, you should use bind variables for your WHERE clause:
. . .
sqlQuery:=sqlQuery || ' where subscriber_id=:subid';
sqlQuery:=sqlQuery || ' and company_id=:compID';
sqlQuery:=sqlQuery || ' and ' || primaryKey ||'= :docno'|| ' and row_number =:rowNum';
execute immediate sqlQuery
INTO Attribute1,Attribute2,Attribute3,Attribute4,Attribute5,Attribute6
USING subid,compid,docNumber,rowNum;
That will make your DBA happy.
Let's assume following table:
drop table test1;
create table test1(
A number(10)
);
insert into test1 values (1);
insert into test1 values (10);
So as you can see table TEST1 is already populated. What i need to do is to change types of column A to varchar2. Since this column has values we can't just use following code:
alter table test1 modify A varchar2(10);
So i have wrote stored procedure which:
Renames column A to A1 ->
Then adds new column called A of type varchar2 ->
Then updates column A with values from column A1 ->
And ultimately drops old column A1.
Code which runs this process is following:
create or replace procedure change_col_type_to_varchar2(p_tab in varchar2, p_col in varchar2)
is
v_string clob;
cursor cur is
select column_name
from all_tab_columns
where table_name = upper(p_tab) and
column_name in (select regexp_substr(p_col,'[^,]+', 1, level) from dual
connect by regexp_substr(p_col, '[^,]+', 1, level) is not null);
begin
for i in cur loop
v_string := 'alter table ' || p_tab || ' rename column ' || i.column_name || ' to ' || i.column_name || '1' || ';';
dbms_lob.append(v_string,''||chr(10)||'');
dbms_lob.append(v_string, 'alter table ' || p_tab || ' add ' || i.column_name || ' varchar2(10);');
dbms_lob.append(v_string,''||chr(10)||'');
dbms_lob.append(v_string, 'update ' || p_tab || ' set ' || i.column_name || ' = ' || i.column_name || '1' || ';');
dbms_lob.append(v_string,''||chr(10)||'');
dbms_lob.append(v_string, 'alter table ' || p_tab || ' drop column ' || i.column_name || '1' || ';');
EXECUTE IMMEDIATE v_string;
DBMS_OUTPUT.PUT_LINE(v_string);
v_string := NULL;
end loop;
end;
I'am trying to apply this procedure to TEST1:
begin
change_col_type_to_varchar2('TEST1', 'A');
end;
And get error:
Error report -
ORA-23290: This operation may not be combined with any other operation
ORA-06512: at "YAVORSKYIY_DM.CHANGE_COL_TYPE_TO_VARCHAR2", line 19
ORA-06512: at "YAVORSKYIY_DM.CHANGE_COL_TYPE_TO_VARCHAR2", line 19
ORA-06512: at line 2
23290. 00000 - "This operation may not be combined with any other operation"
*Cause: ALTER TABLE RENAME COLUMN/CONSTRAINT operation was given in
conjunction with another ALTER TBALE Operation. This is not
allowed.
*Action: Ensure that RENAME COLUMN/CONSTRAINT is the only operation
specified in the ALTER TABLE.
But just typing :
alter table test1 rename column A to A1;
alter table test1 add A varchar2(100);
update test1 set A = A1;
alter table test1 drop column A1;
Works perfect.
Does anybody have any ideas about how to overcome this problem?
Appreciate your help.
the below will do what you asked for.
declare
procedure change_col_type_to_varchar2(p_tab in varchar2, p_col in varchar2)
is
v_string clob;
cursor cur is
select column_name
from all_tab_columns
where table_name = upper(p_tab) and
column_name in (select regexp_substr(p_col,'[^,]+', 1, level)
from dual
connect by regexp_substr(p_col, '[^,]+', 1, level) is not null);
begin
for i in cur loop
v_string := 'alter table ' || p_tab || ' rename column ' || i.column_name || ' to ' || i.column_name || '1';
execute immediate v_string;
v_string := 'alter table ' || p_tab || ' add ' || i.column_name || ' varchar2(10)';
execute immediate v_string;
v_string := 'update ' || p_tab || ' set ' || i.column_name || ' = ' || i.column_name || '1' ;
execute immediate v_string;
v_string := 'alter table ' || p_tab || ' drop column ' || i.column_name || '1' ;
execute immediate v_string;
v_string := NULL;
end loop;
end;
begin
DBMS_OUTPUT.PUT_LINE('Before calling');
change_col_type_to_varchar2('TEST1','A');
DBMS_OUTPUT.PUT_LINE('After calling');
end;
Well, execute each statement alone, instead of concatenating them. And you don't need LOBs, varchar2 for each one should be enough.
I propose other algorithm.
create new table 'table2' with varchar column;
select all values from table1.A and insert to table2 with to_char() conversion;
drop table1;
rename table2 to table1;
profit!
I am trying hard dynamic PL/SQL thing here.
I don't manage to fetch a column dynamic Query.
I am iterating on the name of the column to concatenate a full query in order to be executed on another table.
sql_req := 'select ';
for c in (SELECT name_col from TAB_LISTCOL)
loop
sql_req := sql_req || 'sum(' || c.name_col || '),';
end loop;
sql_req := sql_req || ' from ANOTHER_TAB ';
And when i try to execute it with EXECUTE IMMEDIATE or cursors or INTO/BULK COLLECT thing or just to fetch, i don't manage to iterate on the result.
I tried a lot.
Can you help me plz ? Or maybe it is not possible ?
ps : i know the coma is wrong but my code is more complexe than this : i didn't want to put more things
If you only want to get string columns, you can use listagg
select listagg(name_col, ',') WITHIN GROUP (ORDER BY null) from TAB_LISTCOL
Please see if this helps
In the absence of actual table structure and requirement, I'm creating dummy tables and query to illustrate an example:
SQL> create table another_tab
as
select 10 dummy_value1, 100 dummy_value2, 1000 dummy_value3 from dual union all
select 11 dummy_value1, 101 dummy_value2, 1001 dummy_value3 from dual union all
select 12 dummy_value1, 102 dummy_value2, 1003 dummy_value3 from dual
;
Table created.
SQL> create table tab_listcol
as select column_name from dba_tab_cols where table_name = 'ANOTHER_TAB'
;
Table created.
To reduce complexity in the final block, I'm defining a function to generate the dynamic sql query. This is based on your example and will need changes according to your actual requirement.
SQL> create or replace function gen_col_based_query
return varchar2
as
l_query varchar2(4000);
begin
l_query := 'select ';
for cols in ( select column_name cname from tab_listcol )
loop
l_query := l_query || 'sum(' || cols.cname || '), ' ;
end loop;
l_query := rtrim(l_query,', ') || ' from another_tab';
return l_query;
end;
/
Function created.
Sample output from the function will be as follows
SQL> select gen_col_based_query as query from dual;
QUERY
--------------------------------------------------------------------------------
select sum(DUMMY_VALUE1), sum(DUMMY_VALUE2), sum(DUMMY_VALUE3) from another_tab
Below is a sample block for executing a dynamic cursor using DBMS_SQL. For your ease of understanding, I've added comments wherever possible. More info here.
SQL> set serveroutput on size unlimited
SQL> declare
sql_stmt clob;
src_cur sys_refcursor;
curid number;
desctab dbms_sql.desc_tab; -- collection type
colcnt number;
namevar varchar2 (50);
numvar number;
datevar date;
l_header varchar2 (4000);
l_out_rows varchar2 (4000);
begin
/* Generate dynamic sql from the function defined earlier */
select gen_col_based_query into sql_stmt from dual;
/* Open cursor variable for this dynamic sql */
open src_cur for sql_stmt;
/* To fetch the data, however, you cannot use the cursor variable, since the number of elements fetched is unknown at complile time.
Therefore you use DBMS_SQL.TO_CURSOR_NUMBER to convert a REF CURSOR variable to a SQL cursor number which you can then pass to DBMS_SQL subprograms
*/
curid := dbms_sql.to_cursor_number (src_cur);
/* Use DBMS_SQL.DESCRIBE_COLUMNS to describe columns of your dynamic cursor, returning information about each column in an associative array of records viz., desctab. The no. of columns is returned in colcnt variable.
*/
dbms_sql.describe_columns (curid, colcnt, desctab);
/* Define columns at runtime based on the data type (number, date or varchar2 - you may add to the list)
*/
for indx in 1 .. colcnt
loop
if desctab (indx).col_type = 2 -- number data type
then
dbms_sql.define_column (curid, indx, numvar);
elsif desctab (indx).col_type = 12 -- date data type
then
dbms_sql.define_column (curid, indx, datevar);
else -- assuming string
dbms_sql.define_column (curid, indx, namevar, 100);
end if;
end loop;
/* Print header row */
for i in 1 .. desctab.count loop
l_header := l_header || ' | ' || rpad(desctab(i).col_name,20);
end loop;
l_header := l_header || ' | ' ;
dbms_output.put_line(l_header);
/* Loop to retrieve each row of data identified by the dynamic cursor and print output rows
*/
while dbms_sql.fetch_rows (curid) > 0
loop
for indx in 1 .. colcnt
loop
if (desctab (indx).col_type = 2) -- number data type
then
dbms_sql.column_value (curid, indx, numvar);
l_out_rows := l_out_rows || ' | ' || rpad(numvar,20);
elsif (desctab (indx).col_type = 12) -- date data type
then
dbms_sql.column_value (curid, indx, datevar);
l_out_rows := l_out_rows || ' | ' || rpad(datevar,20);
elsif (desctab (indx).col_type = 1) -- varchar2 data type
then
dbms_sql.column_value (curid, indx, namevar);
l_out_rows := l_out_rows || ' | ' || rpad(namevar,20);
end if;
end loop;
l_out_rows := l_out_rows || ' | ' ;
dbms_output.put_line(l_out_rows);
end loop;
dbms_sql.close_cursor (curid);
end;
/
PL/SQL procedure successfully completed.
Output
| SUM(DUMMY_VALUE1) | SUM(DUMMY_VALUE2) | SUM(DUMMY_VALUE3) |
| 33 | 303 | 3004 |
You have to use EXECUTE IMMEDIATE with BULK COLLECT
Below is an example of the same. For more information refer this link
DECLARE
TYPE name_salary_rt IS RECORD (
name VARCHAR2 (1000),
salary NUMBER
);
TYPE name_salary_aat IS TABLE OF name_salary_rt
INDEX BY PLS_INTEGER;
l_employees name_salary_aat;
BEGIN
EXECUTE IMMEDIATE
q'[select first_name || ' ' || last_name, salary
from hr.employees
order by salary desc]'
BULK COLLECT INTO l_employees;
FOR indx IN 1 .. l_employees.COUNT
LOOP
DBMS_OUTPUT.put_line (l_employees (indx).name);
END LOOP;
END;
If I understand correctly, you want to create a query and execute it and return the result to another function or some calling app. As the resulting query's columns are note before-known, I'd return a ref cursor in this case:
create function get_sums return sys_refcur as
declare
my_cursor sys_refcursor;
v_query varchar2(32757);
begin
select
'select ' ||
listagg('sum(' || name_col || ')', ', ') within group (order by name_col) ||
' from another_tab'
into v_query
from tab_listcol;
open my_cursor for v_query;
return v_query;
end get_sums;
DECLARE
CURSOR c_ddy (p_table_name VARCHAR2, p_table_name_2 VARCHAR2)
IS
(SELECT column_name
FROM ( SELECT column_name
FROM user_tab_columns
WHERE table_name = p_table_name
ORDER BY column_name));
TYPE t_content IS TABLE OF VARCHAR2 (9000)
INDEX BY BINARY_INTEGER;
a_content t_content;
l_sql VARCHAR2 (1000);
BEGIN
FOR r_ddy IN c_ddy ('TABLE_a', 'TABLE_B')
LOOP
l_sql :=
'SELECT '
|| r_ddy.column_name
|| ' FROM TABLE_A '
|| 'MINUS'
|| 'SELECT '
|| r_ddy.column_name
|| 'FROM TABLE_B';
EXECUTE IMMEDIATE l_sql BULK COLLECT INTO a_content;
DBMS_OUTPUT.put_line (l_sql);
END LOOP;
END;
Trying to output every column in TABLE_A minus every column in TABLE_B.
A space is missing in the SQL string.
Try this:
l_sql :=
'SELECT '
|| r_ddy.column_name
|| ' FROM TABLE_A '
|| ' MINUS '
|| ' SELECT '
|| r_ddy.column_name
|| ' FROM TABLE_B';
UPD: For some reason the spaces were gone before. Now I put them again.
This is first my stint with procedure and I am trying to execute below oracle procedure but facing some issue. Any inputs on this would be really helpful:
Issue:-----
I have a select query that returns two values:
src_Columns contains:
ID_ELEMENT
ID_ELEMENT_SA
Now, When I am trying to travesre a select-query(single column) result using "For Loop", I am not getting the values of the column rather I am getting its name only.
FOR columnItem IN (SELECT src_Columns FROM ELEM90_LNK_ELEM_BOSE)
LOOP
dbms_output.put_line('src_Columns 3: ' || columnItem.src_Columns);
query_test:= 'insert into ' || destination_Table || '(NAME,' || dest_Columns_Value || ') VALUES( ''' || src_name_Value || ''',''' || columnItem.dummyValue || ''')';
dbms_output.put_line('query_test:' || query_test);
execute immediate query_test;
END LOOP;
I mean the test query generated is following when i use variable name(src_columns):
insert into ATT_WTPART(NAME,STRINGVALUE) VALUES( 'ID_ELEMENT_SA','ID_ELEMENT_SA')
whereas if I use ID_ELEMENT_SA instead of src_Columns in FOR LOOP
FOR columnItem IN (SELECT ID_ELEMENT FROM ELEM90_LNK_ELEM_BOSE)
then I get proper values that are desired like
insert into ATT_WTPART(NAME,STRINGVALUE) VALUES( 'ID_ELEMENT_SA','ID05')
How can I make sure that I get the values even when I am using the variable name instead of any hard-coding
Below is the Complete Procedure:-------------
create or replace
PROCEDURE ELEM90_Lnk_Elem_ATT_WTPART_MK
AS
CURSOR targ_dest_relation IS
SELECT sourcecolumn FROM mapping where destinationtable='ATT_WTPART';
BEGIN
DECLARE
dest_Columns varchar2(1000);
src_Columns varchar2(1000);
src_Type varchar2(1000);
destination_Table varchar2(1000) := 'ATT_WTPART';
source_Table varchar2(1000) := 'ELEM90_LNK_ELEM_BOSE';
query_test varchar2(1000);
query_test2 varchar2(1000);
src_name varchar2(255);
src_Type_Value varchar2(255);
dest_Columns_Value varchar2(255);
src_name_Value varchar2(255);
for_query varchar2(1000);
for_query_data varchar2(1000);
dummyValue varchar2(1000);
BEGIN
FOR rec IN targ_dest_relation loop
dbms_output.put_line('destination_Table: ' || destination_Table);
dbms_output.put_line('source_Table: ' || source_Table);
src_Columns := rec.sourcecolumn;
dbms_output.put_line('src_Columns: ' || src_Columns);
src_Type := 'select data_type from user_tab_columns where table_name ='''||source_Table||'''and column_name='''|| src_Columns ||'''';
dbms_output.put_line('src_Type: ' || src_Type);
execute immediate src_Type INTO src_Type_Value;
dbms_output.put_line('src_Type_Value: ' || src_Type_Value);
dest_Columns := 'select DEST_COLUMN from ATT_PART_MAPPING where SOURCETYPE='''|| src_Type_Value || '''';
dbms_output.put_line('dest_Columns: ' || dest_Columns);
execute immediate dest_Columns INTO dest_Columns_Value;
dbms_output.put_line('dest_Columns_Value: ' || dest_Columns_Value);
src_name := 'select column_name from user_tab_columns where table_name ='''|| source_Table ||''' and column_name= ''' || src_Columns || '''';
dbms_output.put_line('src_name: ' || src_name);
execute immediate src_name INTO src_name_Value;
dbms_output.put_line('src_name_Value: ' || src_name_Value);
FOR columnItem IN (SELECT src_Columns FROM ELEM90_LNK_ELEM_BOSE)
LOOP
dbms_output.put_line('src_Columns 3: ' || columnItem.src_Columns);
query_test:= 'insert into ' || destination_Table || '(NAME,' || dest_Columns_Value || ') VALUES( ''' || src_name_Value || ''',''' || columnItem.dummyValue || ''')';
dbms_output.put_line('query_test:' || query_test);
execute immediate query_test;
END LOOP;
END loop;
END;
END;
The problem with the line
FOR columnItem IN (SELECT src_Columns FROM ELEM90_LNK_ELEM_BOSE)
is that src_Columns is a local variable. As a result, you end up selecting the same value for each row in ELEM90_LNK_ELEM_BOSE.
If you want the value of the local variable to be used as a column name in the query, you must use dynamic SQL instead. Try replacing FOR columnItem ... LOOP ... END LOOP with the following:
OPEN curs FOR 'SELECT ' || src_Columns || ' FROM ELEM90_LNK_ELEM_BOSE';
LOOP
FETCH curs INTO column_value;
EXIT WHEN curs%NOTFOUND;
dbms_output.put_line('src_Columns 3: ' || column_value);
query_test:= 'insert into ' || destination_Table || '(NAME,' || dest_Columns_Value || ') VALUES( ''' || src_name_Value || ''',''' || column_value || ''')';
dbms_output.put_line('query_test:' || query_test);
execute immediate query_test;
END LOOP;
CLOSE curs;
You will need to declare the following extra variables:
curs SYS_REFCURSOR;
column_value VARCHAR2(4000);
However, in truth it would probably be better to remove the loop entirely. You can replace it with an INSERT INTO ... SELECT ... FROM ... statement instead, such as the following:
EXECUTE IMMEDIATE 'INSERT INTO ' || destination_Table || ' (NAME,' ||
dest_Columns_Value || ') SELECT :name,' || src_Columns ||
' FROM ELEM90_LNK_ELEM_BOSE' USING src_name_Value;
This also gets rid of the need for the two local variables curs and column_value, and is also likely to be considerably faster, as there's no need to parse dynamic SQL once for each row in the destination table.
Is it working better if you try this one:
query_test:= 'insert into ' || destination_Table ||
'(NAME,'||dest_Columns_Value||') VALUES (:p1, :p2)';
execute immediate query_test USING src_name_Value, columnItem.dummyValue;
At least it should have a positive impact on performance.