Why does Execute Immediate throw this PLS-00201 error? - sql

I was trying to run the following code and it failed in the execute immediate block. So, am I going wrong with the syntax?
DECLARE
l_data long;
emp_rec EMP%rowtype;
begin
select * INTO emp_rec from EMP A WHERE A.EMP_NO = '001322';
for x in ( select column_name, data_type
from user_tab_columns
where table_name = 'EMP' )
loop
execute immediate
'begin
:x := emp_rec.' || x.column_name || ';
end;' using OUT l_data;
dbms_output.put_line( x.column_name || ' = ' || l_data );
end loop;
end;
I get this error
PLS-00201: Identifier EMP_REC.EMP_NO must be declared

Your emp_rec variable is a local PL/SQL record. When you do this, even with a static field name reference:
execute immediate 'begin :x := emp_rec.emp_no; end;'
the dynamic SQL runs in a separate context to the block that calls it. You then run a new anonymous PL/SQL block within that context.
Any variables from your outer anonymous block, specifically emp_rec here, are out of scope to the dynamic SQL context. They just do not exist to the code that is trying to assign the value to :x.
You could possibly do something with dbms_sql to make this dynamic, but if you know the table columns it would be easier to do:
declare
l_data varchar2(4000); -- long is deprecated; how big does this really need to be?
emp_rec EMP%rowtype;
begin
select * INTO emp_rec from EMP A WHERE A.EMP_NO = '001322';
for x in (
select column_name, data_type
from user_tab_columns
where table_name = 'EMP'
)
loop
case x.column_name
when 'EMP_NO' then
l_data := emp_rec.emp_no;
-- when clauses for each column in your real table
when 'FIRST_NAME' then
l_data := emp_rec.first_name;
when 'LAST_NAME' then
l_data := emp_rec.last_name;
-- list other columns and assignments
-- else ...
end case;
dbms_output.put_line( x.column_name || ' = ' || l_data );
end loop;
end;
/
although as #APC pointed out, the loop is now a bit pointless, since you can just do:
declare
emp_rec EMP%rowtype;
begin
select * INTO emp_rec from EMP A WHERE A.EMP_NO = '001322';
dbms_output.put_line( 'EMP_NO = ' || emp_rec.emp_no );
dbms_output.put_line( 'FIRST_NAME = ' || emp_rec.first_anme );
dbms_output.put_line( 'LAST_NAME = ' || emp_rec.last_name );
-- ... any other columns you want to show
end;
/

The emp_rec in the EXECUTE IMMEDIATE statement exists in a different namespace from the emp_rec in the calling code.
Not sure exactly what you're trying to achieve but it might be something like this:
DECLARE
l_data long;
emp_rec EMP%rowtype;
begin
select * INTO emp_rec from EMP A WHERE A.EMP_NO = '001322';
for x in ( select column_name, data_type
from user_tab_columns
where table_name = 'EMP' )
loop
execute immediate
'declare
lrec EMP%rowtype;
begin
lrec := :emp_rec;
:x := lrec.' || x.column_name || ';
end;' using emp_rec, OUT l_data;
dbms_output.put_line( x.column_name || ' = ' || l_data );
end loop;
end;
Note: I tested a version of this code in 12C and it does work there. Alas it doesn't work in 11gR2 (and presumably earlier versions too); it hurls PLS-00457. Still, 11gR2 is pretty much out of support except for folks with deep pockets, everybody ought to be using 12c by now:)

My guess would be that you have a hidden column in your emp table. Those are columns that've been marked as unused but not yet dropped, and as such, they are not available to be selected from.
You could update your cursor to use:
select column_name, data_type
from user_tab_cols
where table_name = 'EMP'
and hidden_column != 'NO'
or:
select column_name, data_type
from user_tab_columns
where table_name = 'EMP';
Note the different view name used in both queries - user_tab_columns doesn't output rows for hidden columns, whereas user_tab_cols does so you have to explicitly filter them out if you don't want to see them.

Related

PL/SQL Dynamic SQL : Table name not valid

I'm currently learning PL/SQL. I need to create a PL/SQL block to create a backup of all my tables like this : myTable -> myTable_old.
Here's what I got right now :
DECLARE
Cursor c IS SELECT table_name
FROM user_tables
WHERE table_name NOT LIKE '%_old';
sql_slc VARCHAR2(200);
sql_drp VARCHAR2(200);
sql_crt VARCHAR2(200);
row_count NUMBER;
t_name user_tables.table_name%type;
t_backup_name user_tables.table_name%type;
BEGIN
sql_drp := 'DROP TABLE :1 CASCADE';
sql_crt := 'CREATE TABLE :1 AS SELECT * FROM :2';
sql_slc := 'SELECT COUNT(*) FROM user_tables WHERE table_name = :1';
OPEN c;
LOOP
FETCH c INTO t_name;
EXIT WHEN (c%NOTFOUND);
t_backup_name := t_name || '_old';
dbms_output.put_line(t_name || ' ' || t_backup_name);
EXECUTE IMMEDIATE sql_slc INTO row_count USING t_backup_name;
IF row_count > 0 THEN
dbms_output.put_line(t_backup_name || ' dropped');
EXECUTE IMMEDIATE sql_drp USING t_backup_name;
END IF;
dbms_output.put_line(t_backup_name || ' created');
EXECUTE IMMEDIATE sql_crt USING t_backup_name, t_name;
COMMIT;
END LOOP;
CLOSE c;
END;
/
Here's the error :
OUVRAGE OUVRAGE_old
OUVRAGE_old created
DECLARE
*
ERROR on line 1 :
ORA-00903: table name not valid
ORA-06512: on line 29
I don't understand why this error is coming up, can someone help me ?
The issue is that you can not use bind variables for table names; Oracle documentation:
The database uses the values of bind variables exclusively and does
not interpret their contents in any way.
You should edit your code to use concatenation instead:
DECLARE
Cursor c IS SELECT table_name
FROM user_tables
WHERE table_name NOT LIKE '%_OLD'; /* OLD, upper case */
sql_slc VARCHAR2(200);
--sql_drp VARCHAR2(200);
--sql_crt VARCHAR2(200);
row_count NUMBER;
t_name user_tables.table_name%type;
t_backup_name user_tables.table_name%type;
BEGIN
-- sql_drp := 'DROP TABLE :1 CASCADE';
-- sql_crt := 'CREATE TABLE :1 AS SELECT * FROM :2';
sql_slc := 'SELECT COUNT(*) FROM user_tables WHERE table_name = :1';
OPEN c;
LOOP
FETCH c INTO t_name;
EXIT WHEN (c%NOTFOUND);
t_backup_name := t_name || '_OLD'; /* OLD, upper case */
DBMS_OUTPUT.put_line (t_name || ' ' || t_backup_name);
EXECUTE IMMEDIATE sql_slc INTO row_count USING t_backup_name;
IF row_count > 0
THEN
DBMS_OUTPUT.put_line (t_backup_name || ' dropped');
-- EXECUTE IMMEDIATE sql_drp USING t_backup_name;
EXECUTE IMMEDIATE ' drop table ' || t_backup_name; /* concatenation and not bind variables */
END IF;
DBMS_OUTPUT.put_line (t_backup_name || ' created'); /* concatenation and not bind variables */
-- EXECUTE IMMEDIATE sql_crt USING t_backup_name, t_name;
EXECUTE IMMEDIATE 'create table ' || t_backup_name || ' as select * from ' || t_name;
COMMIT;
END LOOP;
CLOSE c;
END;
Also, notice that, if not double quoted, object names always are uppercase, so you have to look for t_name || '_OLD' and not t_name || '_old'

How to declare %rowtype dynamically?

Below is the sample code in which I have stored all the table names in one table (table_config) and trying to insert one record of every table into its temporary table and trying to get the particular rowid for further need.
So I need every table rowtype to make this work, something dynamic. Could you please help me with this?
DECLARE
l_row table_name%ROWTYPE;
l_rowid ROWID;
l_table_name all_tab_partitions.table_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
BEGIN
FOR tab IN
(select table_name from
Table_config)
LOOP
l_table_name:= tab.table_name;
l_temp_table_name:= 'TEMP_'||l_table_name;
SELECT * INTO l_row
FROM l_table_name
WHERE ROWNUM=1;
INSERT INTO l_temp_table_name VALUES l_row
RETURNING ROWID INTO l_rowid;
COMMIT;
END LOOP;
END;
Thank you,
Pradeep
Without coding the complete answer for you.
Why don't you do something like this?
FOR tab IN
(select table_name from
Table_config)
EXECUTE_IMMEDIATE(
'declare
l_row '||table_name||'%ROWTYPE;
begin
INSERT INTO '||l_temp_table_name
SELECT * FROM '||l_table_name||' WHERE ROWNUM=1;
end;');
EXECUTE_IMMEDIATE ('SELECT ROWID FROM '||l_table_name)
INTO l_rowid;
END LOOP;
it assumes target table is empty to begin with with only one record inserted during the process.
You can't do that as already mentioned in the comment by OldProgrammer above.
You'll have to use Dynamic SQL to achieve what you're trying to achieve.
DECLARE
temp_table VARCHAR2(255);
source_table VARCHAR2(255);
sql_stmt VARCHAR2(255);
CURSOR c1 IS
SELECT table_name FROM user_Tables;
BEGIN
FOR c1_Rec IN c1 LOOP
temp_table := 'TEMP_'||c1_rec.table_name;
source_table := c1_rec.table_name;
sql_stmt := 'INSERT INTO '||temp_table||' SELECT * FROM '||source_table||' WHERE rownum = 1';
EXECUTE IMMEDIATE sql_stmt;
END LOOP;
END;
/
Below is solution. What do you need this rowids for? I would be much simpler without it, as you cannot use returning with insert as select
DECLARE
l_rowid ROWID;
l_table_name all_tab_partitions.table_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
v_sql1 varchar2(4000);
v_sql2 varchar2(4000);
BEGIN
FOR tab IN (select table_name from Table_config) LOOP
l_table_name:= tab.table_name;
l_temp_table_name:= 'TEMP_'||l_table_name;
v_sql1 := 'select rowid from ' || l_table_name || ' where rownum =1 for update';
v_sql2 := 'insert into ' || l_temp_table_name || ' select * from ' || l_table_name || ' where rownum = 1';
execute immediate v_sql1 into l_rowid;
execute immediate v_sql2;
commit;
END LOOP;
END;
/
You should investigate EXECUTE IMMEDIATE INTO. I think this would be an excellent way to get the ROWID when combined with some dynamic SQL examples from above. Here's an example:
DECLARE
DYN_SQL VARCHAR(4000) := 'SELECT 1 FROM DUAL';
INTO_VAR NUMBER(1);
BEGIN
EXECUTE IMMEDIATE DYN_SQL INTO INTO_VAR;
DBMS_OUTPUT.PUT_LINE(INTO_VAR);
END;
Thank you guys for your response. Actually I was trying to implement partition exchange on interval partitioned tables. I achieved it by using Dynamic Sql now. Initially I was trying to implement it by using rowid which is ok when I hard coded for one table, but when I thought of configuring it and using it for multiple tables I got stuck at that %ROWTYPE.
In the below code I have hard coded table name in few places which can be modified as dynamic but the problem is how to get the %ROWTYPE for the every table we pass.
DECLARE
l_table_name table_config.table_name%TYPE;
l_query_temp VARCHAR2(1000);
l_part_table_name all_tab_partitions.table_name%TYPE;
l_part_name all_tab_partitions.partition_name%TYPE;
l_temp_table_name all_tab_partitions.table_name%TYPE;
l_row test_archival%ROWTYPE;
l_rowid ROWID;
l_arch_table_name all_tab_partitions.table_name%TYPE;
l_arch_part_name VARCHAR2(30);
l_query_arch VARCHAR2(1000);
l_query_source VARCHAR2(1000);
BEGIN
<<outer_loop>>
FOR tab IN
(SELECT table_name FROM
table_config)
LOOP
l_table_name:= tab.table_name;
<<inner_loop>>
FOR part IN
(SELECT table_name, partition_position, partition_name FROM
(SELECT table_name, partition_position, partition_name,
DENSE_RANK() OVER (PARTITION BY table_name ORDER BY partition_position DESC) AS RANK
FROM all_tab_partitions
WHERE table_name=l_table_name
) WHERE RANK NOT IN(1, 2) ORDER BY partition_position)
LOOP
l_part_table_name:= part.table_name;
l_part_name:= part.partition_name;
l_temp_table_name := 'TEMP_'||l_part_table_name;
l_arch_table_name := 'ARCH_'||l_part_table_name;
l_query_temp := 'ALTER TABLE '
|| l_part_table_name
|| ' EXCHANGE PARTITION '
|| l_part_name
|| ' WITH TABLE '
|| l_temp_table_name
||' INCLUDING INDEXES WITHOUT VALIDATION';
EXECUTE IMMEDIATE l_query_temp;
COMMIT;
SELECT * INTO l_row FROM temp_test_archival WHERE ROWNUM = 1;
INSERT INTO arch_test_archival VALUES l_row RETURNING ROWID INTO l_rowid;
COMMIT;
SELECT subobject_name
INTO l_arch_part_name FROM user_objects
WHERE data_object_id = dbms_rowid.rowid_object(l_rowid);
DELETE from arch_test_archival where rowid=l_rowid;
COMMIT;
l_query_arch := 'ALTER TABLE '
||'ARCH_TEST_ARCHIVAL'
||' EXCHANGE PARTITION '
||l_arch_part_name
||' WITH TABLE '
||'TEMP_TEST_ARCHIVAL'
||' INCLUDING INDEXES WITHOUT VALIDATION';
EXECUTE IMMEDIATE l_query_arch;
END LOOP;
END LOOP;
END;
/

Checking for the existence of a column in a view before searching for a value

I am trying to search every view in the database for a specific value of something like '%THIS%'
I came up with this plsql code
DECLARE
match_count INTEGER;
BEGIN
FOR t IN (SELECT name FROM user_dependencies where type = 'VIEW') LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM '|| t.name || ' Where Column_Name LIKE ''%THIS%'' '
INTO match_count;
IF match_count > 0 THEN
dbms_output.put_line( t.name ||' '||match_count );
END IF;
END LOOP;
END;
But when I try to run it, I get an invalid identifier error for the column name in the execute immeadiate query.
The problem to me is obvious that not every view has the Column_Name, but I can't figure out how I can check to see if the column exists before running the query as I loop through all of the views.
I was also able to use a slightly modified version of this to run through all of the tables, and while they do not all have that column, I did not run in to this issue.
Edit: I am including the plsql code that I was able to use to loop through the tables.
DECLARE
match_count INTEGER;
BEGIN
FOR t IN (SELECT table_name, column_name FROM all_tab_columns
where table_name LIKE 'THIS%' and data_type = 'VARCHAR2' AND column_name = 'Column_name') LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM '||t.table_name || ' Where Column_name LIKE ''%THIS%'' '
INTO match_count;
IF match_count > 0 THEN
dbms_output.put_line( t.table_name ||' '||t.column_name||' '||match_count );
END IF;
END LOOP;
END;
I'm assuming this is Oracle since you tagged pl/sql. You can use Oracle's metadata tables to see what columns a table/view has. Specifically USER_TAB_COLUMNS.
select count(*)
from user_tab_columns
where table_name = [name of view]
and column_name = [name of column]
This should query every view that has a column_name like '%START%'
declare
cursor getViews is
select table_name, column_name from user_tab_cols where table_name in
(select view_name from user_views)
and column_name like '%START%';
myResult number;
BEGIN
for i in getViews
LOOP
execute immediate 'select count(*) from '||i.table_name into myResult;
END LOOP;
END;

query each column of a table in a loop - Oracle Database

I'm working with an oracle database and what I basically need to do is to count the number of NULL fields per column in a certain table.
something like that:
DECLARE
BlankCount number(20);
i number(2) := 1;
BEGIN
loop that would take each column individualy and exit after the last one
SELECT COUNT(*) INTO BlankCount FROM name_of_my_table
DBMS_OUTPUT.PUT_LINE('Column '||i||' has '||BlankCount||' empty cells');
i := i + 1;
END LOOP;
END;
I just couldn't find anything that would do the loop part.
It would also be nice if instead of just numbering them (with the i) I could display the column name (but that is not very important).
Thank you!
Something like this:
declare
mytable varchar(32) := 'MY_TABLE';
cursor s1 (mytable varchar2) is
select column_name
from user_tab_columns
where table_name = mytable
and nullable = 'Y';
mycolumn varchar2(32);
query_str varchar2(100);
mycount number;
begin
open s1 (mytable);
loop
fetch s1 into mycolumn;
exit when s1%NOTFOUND;
query_str := 'select count(*) from ' || mytable || ' where ' || mycolumn || ' is null';
execute immediate query_str into mycount;
dbms_output.put_line('Column ' || mycolumn || ' has ' || mycount || ' null values');
end loop;
end;
Try using cursor approach and Dynamic SQL as mentioned in this thread: How to loop through columns in an oracle pl/sql cursor
HTH.

Oracle Nested cursors

I want to get the distinct dates in a column called "YMDH" from each table in a schema where that column exists. I figured that I needed to use nested cursors (something I've not done before) and came up with the following code:
CREATE OR REPLACE PROCEDURE DistinctDates AS
sql_statement1 varchar2(200);
sql_statement2 varchar2(200);
results varchar2(15);
ColumnExist integer;
BEGIN
for cursor_rec in (SELECT * FROM user_objects WHERE object_type='TABLE'
AND object_name NOT LIKE 'TM%') loop
sql_statement1 := 'select count (*) from user_tab_columns where table_name=' || '''' || cursor_rec.object_name || '''' || ' and column_name=' || '''' ||'YMDH' || '''';
execute immediate sql_statement1 into ColumnExist;
if ColumnExist = 1 then
for inner_cursor_rec in (select distinct(ymdh) from cursor_rec.object_name) loop
null;
end loop;
end if;
end loop;
END DistinctDates;
SQL Developer is complaining about the select statement for the inner cursor. The error message is:
Error(18,32): PL/SQL: SQL Statement ignored
Error(18,70): PL/SQL: ORA-00942: table or view does not exist
So it's not recognizing the reference to the outer cursor. How do I pass the table name (which is the cursor_rec.object_name) to the inner cursor?
You have used dynamic SQL where it is not needed, and have not used it where it is needed!
The check to see if the table has a column called 'YMDH' can be incorporated into the first query, giving this code:
CREATE OR REPLACE PROCEDURE DistinctDates AS
sql_statement varchar2(200);
rc sys_refcursor;
ymdh_value ????; -- Appropriate data type
BEGIN
for cursor_rec in (SELECT t.table_name
FROM user_tables t
JOIN user_tab_columns c ON c.table_name = t.table_name
WHERE t.table_name NOT LIKE 'TM%'
AND c.column_name='YMDH')
loop
sql_statement := 'select distinct(ymdh) from ' || cursor_rec.table_name;
open rc for sql_statement;
loop
fetch rc into ymdh_value;
exit when rc%notfound;
null;
end loop;
close rc;
end loop;
END DistinctDates;