(Just to preempt - I know how hacky and flawed this approach is. It's just going to be a personal use thing to make a certain task easier.)
For this example, I want to create two dynamic SQL insert statements which insert the first two results from ALL_TABLES into GT_DUMMY, padded with an input value.
CREATE GLOBAL TEMPORARY TABLE GT_DUMMY
( Test_Field VARCHAR(100)
);
CREATE OR REPLACE PROCEDURE LOAD_GT_DUMMY
( Insert_String VARCHAR
)
IS
BEGIN
FOR col IN
( SELECT 'INSERT INTO GT_DUMMY VALUES(' || CHR(39) || Insert_String || Table_Name || CHR(39) || ');' AS insertStatement
FROM ALL_TABLES
WHERE ROWNUM <= 2
) LOOP
DBMS_OUTPUT.put_line(col.insertStatement);
-- Result of EXEC LOAD_GT_DUMMY('SOMETHING'); :
-- INSERT INTO GT_DUMMY VALUES('SOMETHINGDUAL');
-- INSERT INTO GT_DUMMY VALUES('SOMETHINGSYSTEM_PRIVILEGE_MAP');
-- This command fails when
EXECUTE IMMEDIATE col.insertStatement;
END LOOP;
END;
The inserts are well formed and will execute if I just run them standalone, but the EXECUTE IMMEDIATE col.insertStatement; command is not working. The procedure compiles, but when I try to run
EXEC LOAD_GT_DUMMY('SOMETHING');
I get an error
ORA-00933: SQL command not properly ended
ORA-06512: at "MY_SCHEMA.LOAD_GT_DUMMY", line 14
ORA-06512: at line 1
Any thoughts? Is my syntax off?
Best.
Remove the terminating semi-colon; dynamic SQL doesn't like it.
SELECT 'INSERT INTO GT_DUMMY VALU... <snip> ... || CHR(39) || ');'
^
|
here
Try with INTO v_result in the end of EXECUTE statement
EXECUTE IMMEDIATE col.insertStatement INTO v_result;
P.S. don't forget to declare v_result variable
You can check similar example down here
Remove the colon in sql statement and execute it will work
FOR col IN
( SELECT 'INSERT INTO GT_DUMMY VALUES(' || CHR(39) || Insert_String || Table_Name || CHR(39) || ')' AS insertStatement
FROM ALL_TABLES
WHERE ROWNUM <= 2
) LOOP
Related
I am stuck not able to iterate through values output from select statement.
My code :
CREATE OR replace PROCEDURE getdetails ( v_tb in VARCHAR2 )
AS
BEGIN
FOR i IN
(
SELECT table_name
FROM all_tables
WHERE table_name LIKE ''''
|| v_tb
|| '%'
|| '''' )
LOOP
DBMS_OUTPUT.PUT_LINE(i);
/*
My logic will be coming that I cannot share ...
*/
END LOOP;
END;
Calling
BEGIN
Getdetails('DEMO');
END;
Issue : The select statement is not getting initialized with v_tb
SELECT table_name
FROM all_tables
WHERE table_name LIKE ''''
|| v_tb
|| '%'
|| ''''
Any solution is much appreciated !!!
When I compile your code I get:
14/8 PL/SQL: Statement ignored
14/8 PLS-00306: wrong number or types of arguments in call to 'PUT_LINE'
Errors: check compiler log
The reason is that "i" cannot be used as argument in dbms_output because it isn't a scalar value, it is a record variable. The solution is to reference the actual column that is selected in the statement
DBMS_OUTPUT.PUT_LINE(i.table_name);
The reason that the statement never returns any rows is that the quotes are double escaped. Escape a quote with a 2nd quote. 4 quotes to escape a single quote is too much in a plain pl/sql statement.
So, putting it all together gives:
CREATE OR replace PROCEDURE getdetails ( v_tb in VARCHAR2 )
AS
BEGIN
FOR i IN
(
SELECT table_name
FROM all_tables
WHERE table_name LIKE ''
|| v_tb
|| '%'
|| '' )
LOOP
DBMS_OUTPUT.PUT_LINE(i.table_name);
/*
My logic will be coming that I cannot share ...
*/
END LOOP;
END;
/
Procedure GETDETAILS compiled
set serveroutput on size 999999
clear screen
BEGIN
Getdetails('BOO');
END;
/
BOOK
BOOK_TITLE
PL/SQL procedure successfully completed.
/
If you need quotes in your string, you should write the SQL query as follows:
SELECT table_name
FROM all_tables
WHERE table_name LIKE ''''
|| v_tb
|| '%'
|| '''some text'''
some text will be concatenated as 'some text'
I am trying to change all PK constraint names. Below is the code I am trying to run:
BEGIN
FOR i IN (
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME
FROM ALL_CONSTRAINTS
WHERE OWNER = 'SOME_SCHEME'
AND TABLE_NAME NOT LIKE 'flyway%'
AND TABLE_NAME NOT LIKE 'BIN%'
AND CONSTRAINT_TYPE = 'P')
LOOP
dbms_output.put_line(i.CONSTRAINT_NAME || i.table_name);
EXECUTE IMMEDIATE 'ALTER TABLE i.table_name rename constraint i.constraint_name to i.table_name || ''_PK''';
END LOOP;
END;
I am getting the following error:
SQL Error [946] [42000]: ORA-00946: missing TO keyword
ORA-06512: at line 17
ORA-06512: at line 17
Why does it say TO is missing when it is not?
How can this problem be solved?
If you use dbms_output to display the statement before you execute it, you'll see it's malformed; whatever the table and constraint, the statement you are currently trying to execute is, literally:
ALTER TABLE i.table_name rename constraint i.constraint_name to i.table_name || '_PK'
The i.* parts are not variables or values, they are literally those strings. Running that statement manually gets the same error, of course.
You need to concatenate the loop variable values into the statement; you also need to specify the owner, since you're looking at all_tables, and you might as well quote the names:
DECLARE
l_stmt varchar2(4000);
BEGIN
FOR ...
LOOP
dbms_output.put_line(i.CONSTRAINT_NAME || i.table_name);
l_stmt := 'ALTER TABLE SOME_SCHEME."' || i.table_name || '"'
|| ' rename constraint "' || i.constraint_name || '"'
|| ' to "' || i.table_name || '_PK"';
dbms_output.put_line(l_stmt);
EXECUTE IMMEDIATE l_stmt;
END LOOP;
END;
/
which generates something more like:
ALTER TABLE SOME_SCHEME."SDO_DATUMS" rename constraint "DATUM_PRIM" to "SDO_DATUMS_PK"
db<>fiddle
I've hard-coded the SOME_SCHEME name you use in the loop query; you could also get that from the query itself by adding OWNER to the select list, and then concatenate that in as well (again, quoted to be overly cautious). Or set that in a local variable and use that in both places, as #MTO suggested in a comment.
If you will only run this as the SOME_SCHEME user then you can query user_constraints instead:
DECLARE
l_stmt varchar2(4000);
BEGIN
FOR i IN (
SELECT CONSTRAINT_NAME, CONSTRAINT_TYPE, TABLE_NAME
FROM USER_CONSTRAINTS
WHERE TABLE_NAME NOT LIKE 'flyway%' -- are these really quoted lower-case?
AND TABLE_NAME NOT LIKE 'BIN%'
AND CONSTRAINT_TYPE = 'P'
AND CONSTRAINT_NAME != TABLE_NAME || '_PK'
)
LOOP
l_stmt := 'ALTER TABLE "' || i.table_name || '"'
|| ' RENAME constraint "' || i.constraint_name || '"'
|| ' TO "' || i.table_name || '_PK"';
dbms_output.put_line(l_stmt);
EXECUTE IMMEDIATE l_stmt;
END LOOP;
END;
/
db<>fiddle with example tables, with quoted and unquoted identifiers.
You might also want your loop query to include:
AND CONSTRAINT_NAME != TABLE_NAME || '_PK'
... so you don't touch constraints that already have the name pattern you want.
I'm trying to create a generic procedure to synchronize sequences.
I want to call the procedure and pass name of table, column and sequence but my procedure won't run due to an error.
Procedure:
CREATE OR REPLACE PROCEDURE INCREMENT_SEQ(table_name in varchar2 , id_column in varchar2 , sequence_name in varchar2)
AS
current_value number;
seq_val number := -1;
begin
EXECUTE IMMEDIATE 'select max(' || table_name || '.' || id_column || ') into current_value from ' || table_name ;
WHILE current_value >= seq_val
LOOP
EXECUTE IMMEDIATE 'select ' || sequence_name || '.nextval into seq_val from dual';
end loop;
end;
when I run the script I'm having the following error:
Error at line 2
ORA-00905: missing keyword
ORA-06512: at "TGC100_DEV.INCREMENT_SEQ", line 6
ORA-06512: at line 1
Script Terminated on line 16.
But I have no idea how to solve. Any advice would be helpfull.
You should put INTO clause outside the query:
CREATE OR REPLACE PROCEDURE INCREMENT_SEQ(table_name in varchar2 , id_column in varchar2 , sequence_name in varchar2)
AS
current_value number;
seq_val number := -1;
begin
EXECUTE IMMEDIATE 'select max(' || table_name || '.' || id_column || ') from ' || table_name into current_value;
WHILE current_value >= seq_val
LOOP
EXECUTE IMMEDIATE 'select ' || sequence_name || '.nextval from dual' into seq_val;
end loop;
end;
EXECUTE IMMEDIATE 'select max(' || table_name || '.' || id_column || ') into current_value from ' || table_name ;
It is syntactically incorrect. The INTO clause should be outside of EXECUTE IMMEDIATE statement.
Something like,
EXECUTE IMMEDIATE 'your SQL statement' INTO variable USING value;
UPDATE It is better to have the dynamic SQL as a variable to avoid confusions with so many single quotes and concatenation in the EXECUTE IMMEDIATE statement itself.
The other answer by Aramilo was posted before my answer, but I got confused to see the INTO clause already outside the statement.
For developers, it is always a good practice to first check the dynamic SQL using DBMS OUTPUT before actually executing it. Thus, it saves a lot of time to debug the whole bunch of PL/SQL code. Once confirmed that the dynamic SQL formed is correct, remove the DBMS_OUTPUT and execute the PL/SQL code.
I have just started to learn PL/SQL. Following a post in stackoverflow, I have written a script to search a certain value in Oracle as below:
DECLARE
match_count INTEGER;
v_owner VARCHAR2(255);
v_search_value NUMBER;
BEGIN
v_owner:='USERA USERB';
v_search_value:=4823.0;
EXECUTE IMMEDIATE
'INSERT INTO TMP SELECT owner, table_name, column_name FROM all_tab_cols WHERE instr(:1, owner)>0 AND data_type like ''%NUMBER%''' USING v_owner;
commit;
FOR t IN (SELECT owner, table_name, column_name FROM TMP) LOOP
EXECUTE IMMEDIATE
'SELECT COUNT(*) FROM '||t.owner||'.'||t.table_name||' WHERE '||t.column_name||'= :1'
INTO match_count
USING v_search_value;
IF match_count > 0 THEN
EXECUTE IMMEDIATE
'INSERT INTO RESULT VALUES(:1, :2, :3, :4)'
USING t.owner, t.table_name, t.column_name, match_count;
commit;
END IF;
END LOOP;
END;
The table TMP and RESULT have been properly created before executing the script. However, when I run the script, I get an ORA-01788 error saying that "connect by clause required" on line 12. I wonder why the code cause this error and how to change the script to be executed properly. Thanks!
You probably have a table with a column called level (as Florin suggested while I wasn't looking), which is a reserved word.
It may be in any case - not just level, but LEVEL, or Level - so to check you could do:
select owner, table_name, column_name
from all_tab_columns
where upper(column_name) = 'LEVEL';
Or to look for any reserved words in use (though this won't always find all potential problems):
select atc.owner, atc.table_name, atc.column_name, vrw.keyword
from all_tab_columns atc
join v$reserved_words vrw on vrw.keyword = upper(atc.column_name);
You could run similar queries against all_tables or all_objects to find potential problems elsewhere.
If I run a modified version of your query which doesn't bother with the TMP table - not sure why you'd want that; doesn't use dynamic SQL for the inserts; doesn't commit (which is an odd thing to do in a block - you don't need to do it after every insert anyway); and just for my benefit just displays the matches instead of needing to build a results table:
declare
match_count integer;
v_owner varchar2(255);
v_search_value number;
begin
v_owner := 'USERA USERB';
v_search_value := 4823.0;
for t in (
select owner, table_name, column_name
from all_tab_cols
where instr(v_owner, owner) > 0
and data_type like '%NUMBER%'
) loop
execute immediate
'select count(*) from ' || t.owner ||'.'|| t.table_name
|| ' where ' || t.column_name || ' = :1'
into match_count using v_search_value;
if match_count > 0 then
-- insert into results values (t.owner, t.table_name,
-- t.column_name, match_count);
dbms_output.put_line('Matched ' || t.owner ||'.'|| t.table_name
||'.'|| t.column_name ||': '|| match_count);
end if;
end loop;
end;
/
... this completes successfully. If I add a table with a level column:
create table t42 ("LEVEL" number);
... and then run the same block again then I also get:
ORA-01788: CONNECT BY clause required in this query block
ORA-06512: at line 14
01788. 00000 - "CONNECT BY clause required in this query block"
*Cause:
*Action:
Aside from avoiding reserved words in object names, you can make this work by enclosing all of your object names in double-quotes:
execute immediate
'select count(*) from "' || t.owner ||'"."'|| t.table_name
|| '" where "' || t.column_name || '" = :1'
into match_count using v_search_value;
This will also cope with mixed-case object names, which you should also avoid.
I want to the the number of records in all tables that match a specific name criteria. Here is the SQL I built
Declare SQLStatement VARCHAR (8000) :='';
BEGIN
SELECT 'SELECT COUNT (*) FROM ' || Table_Name || ';'
INTO SQLStatement
FROM All_Tables
WHERE 1=1
AND UPPER (Table_Name) LIKE UPPER ('MSRS%');
IF SQLStatement <> '' THEN
EXECUTE IMMEDIATE SQLStatement;
END IF;
END;
/
But I get the following error:
Error at line 1
ORA-01422: exact fetch returns more than requested number of rows
ORA-06512: at line 3
Script Terminated on line 1.
How do I modify this so that it runs for all matching tables?
Update:
Based on an answer received, I tried the following but I do not get anything in the DBMS_OUTPUT
declare
cnt number;
begin
for r in (select table_name from all_tables) loop
dbms_output.put_line('select count(*) from CDR.' || r.table_name);
end loop;
end;
/
declare
cnt number;
begin
for r in (select owner, table_name from all_tables
where upper(table_name) like ('%MSRS%')) loop
execute immediate 'select count(*) from "'
|| r.owner || '"."'
|| r.table_name || '"'
into cnt;
dbms_output.put_line(r.owner || '.' || r.table_name || ': ' || cnt);
end loop;
end;
/
If you're selecting from all_tables you cannot count on having been given the grants necessary to select from the table name. You should therefore check for the ORA-00942: table or view does not exist error thrown.
As to the cause for your error: You get this error because the select statement returns a result set with more than one row (one for each table) and you cannot assign such a result set to a varchar2.
By the way, make sure you enable dbms_output with SET SERVEROUT ON before executing this block.