select statement not working with variables, but does with fixed values - sql

So i have this statement:
execute immediate 'SELECT count(*) INTO counter FROM (SELECT table_a.NUMBERWANG FROM table_a MINUS SELECT table_b.NUMBERWANG FROM table_b )';
But if i try and make one of the values into a variable, the statement seems to no longer work.
If it try
execute immediate 'SELECT count(*) INTO counter FROM (SELECT table_a.NUMBERWANG FROM table_a MINUS SELECT table_b.NUMBERWANG FROM ' || variable_1 || ' )';
I get the missing keyword error.
Where it gets unusual is if i remove the "INTO COUNTER" it will run.
Is it my placement of the INTO that is wrong or is it something else entirely?

You can't have an into clause inside the string you are executing.
You need to move this out of the string and tell the execute immediate to do that:
declare
l_count integer;
l_sql varchar(4000);
begin
l_sql := 'SELECT count(*) FROM (SELECT table_a.NUMBERWANG FROM table_a MINUS SELECT table_b.NUMBERWANG FROM table_b )';
execute immediate l_sql into l_count;
end;
/

I don't think you can perform a SELECT...INTO... dynamically. When I try this:
DECLARE
counter NUMBER;
BEGIN
EXECUTE IMMEDIATE 'SELECT COUNT(*) INTO COUNTER FROM TEST_TABLE';
DBMS_OUTPUT.PUT_LINE('COUNTER=' || COUNTER);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
I get ORA-00905: missing keyword. I can get rid of the error by removing the INTO clause, but then of course I still don't get a value into counter.
If you want to execute this sort of statement dynamically and be able to retrieve the value(s) into variables you'll need to use a cursor, as in:
DECLARE
counter NUMBER;
c SYS_REFCURSOR;
BEGIN
OPEN c FOR 'SELECT COUNT(*) FROM TEST_TABLE';
FETCH c INTO counter;
CLOSE c;
DBMS_OUTPUT.PUT_LINE('COUNTER=' || COUNTER);
EXCEPTION
WHEN OTHERS THEN
DBMS_OUTPUT.PUT_LINE('Error: ' || SQLERRM);
END;
Share and enjoy.

Related

Oracle parameterize finding column count of a table - ORA-00905: missing keyword

I am adding a number of columns to multiple tables. Before I add them, I want to make sure they don't exist already.
SELECT count(*)
INTO myCount
FROM user_tab_cols
WHERE column_name = 'newCol'
and table_name = 'tableName';
Since the only variables are the tableName and columnName, I thought of parameterizing these two variables hoping to reuse it. So I wrote a pl/sql:
DECLARE
myCount NUMBER;
COUNT_COLUMN VARCHAR2(1000) :=
' SELECT count(*)
INTO myCount
FROM user_tab_cols
WHERE column_name = :1
and table_name = :2';
BEGIN
EXECUTE IMMEDIATE COUNT_COLUMN using 'columnName','tableName';
/*code to check count and add column*/
DBMS_OUTPUT.PUT_LINE(myCount);
EXCEPTION
WHEN
OTHERS THEN
DBMS_OUTPUT.PUT_LINE( 'sql failed: ' || SQLCODE || ': ' || SUBSTR(SQLERRM, 1, 200));
END;
I expected the myCount variable to contain number of columns in a table. But I get the following error.
sql failed: -905: ORA-00905: missing keyword
How do I get rid of this error? what other ways do I have to achieve this?
INTO cluase must not be included in SQL query but to be used in EXECUTE IMMEDIATE statement. So, You need to fix your dynamic SQL as below -
DECLARE
myCount NUMBER;
COUNT_COLUMN VARCHAR2(1000) :=
' SELECT count(*)
FROM user_tab_cols
WHERE column_name = :1
and table_name = :2';
BEGIN -- Add the BEGIN keyword to complete a PL/SQL block
EXECUTE IMMEDIATE COUNT_COLUMN INTO myCount using 'columnName','tableName';
/*code to check count and add column*/
DBMS_OUTPUT.PUT_LINE(myCount);
EXCEPTION
WHEN
OTHERS THEN
DBMS_OUTPUT.PUT_LINE( 'sql failed: ' || SQLCODE || ': ' || SUBSTR(SQLERRM, 1, 200));
END;

Dynamic sql with for loop PL/SQL

The following query needs to convert to dynamic SQL without hard code cursor SQL,
using l_query, I do not know the l_query it will come as a parameter.
Inside the loop, I need to execute another insert query ( l_insert_query) that also comes as a parameter.
Your counsel would be much appreciated
DECLARE
CURSOR cust
IS
SELECT *
FROM customer
WHERE id < 500;
BEGIN
l_query := 'SELECT * FROM customer WHERE id < 5';
l_insert_query :=
'insert into data ( name, mobile) values ( cust.name,cust.mobile)';
FOR r_cust IN cust
LOOP
EXECUTE IMMEDIATE l_insert_query;
END LOOP;
END;
You could do this with a dynamic PL/SQL block:
declare
l_query varchar2(100) := 'SELECT * FROM customer WHERE id < 5';
l_insert varchar2(100) := 'insert into data ( name, mobile) values ( cust.name,cust.mobile)';
l_plsql varchar2(4000);
begin
l_plsql := '
begin
for cust in (' || l_query || ') loop
' || l_insert || ';
end loop;
end;
';
dbms_output.put_line(l_plsql);
execute immediate l_plsql;
end;
/
The l_plsql statement ends up as a generated PL/SQL block using the cursor query and insert statement:
begin
for cust in (SELECT * FROM customer WHERE id < 5) loop
insert into data ( name, mobile) values ( cust.name,cust.mobile);
end loop;
end;
db<>fiddle
But that you can do this doesn't mean you should. This is vulnerable to SQL injection, and doesn't seem like a very safe, sensible or efficient way to handle data manipulation in your system.

Oracle procedure to delete records in all tables of an owner

I am trying to create an Oracle procedure to delete records from multiple tables of an owner based upon a distinct count condition:
Firstly I am trying to obtain the tables for which I want to delete those records with this query:
SELECT * FROM ALL_TABLES WHERE OWNER = 'Lorik' AND TABLE_NAME LIKE 'UT_%';
This results in a total of 300 tables, now all of those tables have a column named: DATE_INC
I am trying to delete records from all of the tables if this COUNT(DISTINCT DATE_INC) > 5.
Assuming that one of those 300 tables is named UT_NAMES:
SELECT COUNT(DISTINCT DATE_INC) FROM Lorik.UT_NAMES;
So if the count exceeds 5, then I want to delete the records with the minimum date:
DELETE MIN(DATE_INC) FROM Lorik.UT_NAMES;
Can someone please link these steps together so I can loop through each table of that owner and obtain the distinct date count and delete records based upon the above cited condition.
Thanks in advance!
You can use 'EXECUTE IMMEDIATE' in PL/SQL to accomplish your goal:
DECLARE
strTable VARCHAR2(32767);
nCount NUMBER;
BEGIN
FOR aRow IN (SELECT *
FROM ALL_TABLES
WHERE OWNER = 'Lorik' AND
TABLE_NAME LIKE 'UT_%')
LOOP
strTable := aRow.OWNER || '.' || aRow.TABLE_NAME;
EXECUTE IMMEDIATE 'SELECT COUNT(DISTINCT DATE_INC) FROM ' || strTable
INTO nCount;
IF nCount > 5 THEN
EXECUTE IMMEDIATE 'DELETE FROM ' || strTable ||
' WHERE DATE_INC = (SELECT MIN(DATE_INC) ' ||
'FROM ' || strTable || ')';
END IF;
END LOOP;
END;
Not tested on animals - you'll be first! :-)
As pointed out by #Andrew this seems to be very basic processes.
Declare some variables.
Open a cursor.
Use dynamic sql to count
Use dynamic sql to delete
Add some log information
_
declare
v_cnt number;
v_sql varchar2(1000);
begin
for cur in (SELECT * FROM ALL_TABLES WHERE OWNER = 'HR' AND TABLE_NAME LIKE 'E%')
loop
v_sql := 'select count(distinct department_id||''date_inc'') from '||cur.owner||'. '||cur.table_name;
execute immediate v_sql into v_cnt;
dbms_output.put_line (cur.table_name || ': ' || v_cnt);
if v_cnt > 5 then
v_sql := 'delete from '||cur.owner||'. '||cur.table_name || ' where date_inc = (select min (date_inc) from ' ||cur.owner||'. '||cur.table_name || ')';
dbms_output.put_line (v_sql);
-- execute immediate v_sql;
end if;
end loop;
rollback;
-- commit;
end;
A little bit simpler dynamic SQL for deleting rows, as it uses a local variable:
declare
l_cnt number; -- counter variable
l_min_date date; -- MIN(date_inc)
begin
for cur_t in (select table_name from all_tables
where owner = 'Lorik' -- are you sure it is not uppercase, LORIK?
and table_name like 'UT%' -- underline is wildcard character so you don't need it
)
loop
execute immediate 'select count(distinct date_inc), min(date_inc) from ' ||
cur_t.table_name into l_cnt, l_min_date;
if l_cnt > 5 then
execute immediate 'delete from ' || cur_t.table_name ||
' where date_inc = ' || l_min_date_inc;
end if;
end loop;
end;
/

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;
/

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;