I'm using iBatis' ScriptRunner to execute scripts on an Oracle database. The first script is executed fine, but the second one which has triggers in it returns:
Cause: java.sql.SQLSyntaxErrorException: ORA-00900: invalid SQL statement
The part of the script which returns this error is executed without any errors on SQL Developer:
.
.
.
create table MG_MSGALR
(
ID VARCHAR2(30) not null,
V_GRAV VARCHAR2(3),
constraint PK_MG_MSGALR primary key (ID) using index tablespace B_INDEX
);
CREATE OR REPLACE TRIGGER UC_JAR_LST_TRIGGER
BEFORE INSERT
ON UC_JAR_LST
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT UC_JAR_LST_SEQ.nextval INTO :NEW.ID FROM dual;
END;
/
CREATE OR REPLACE TRIGGER UC_UPD_LST_TRIGGER
BEFORE INSERT
ON UC_UPD_LST
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT UC_UPD_LST_SEQ.nextval INTO :NEW.ID FROM dual;
END;
/
Here's how I execute the script from my side:
Boolean procedure = StringUtils.endsWith(FilenameUtils.getBaseName(file.getName()), "procedure") || StringUtils.endsWith(FilenameUtils.getBaseName(file.getName()), "trigger");
runner.setSendFullScript(procedure);
runner.runScript(new FileReader(file));
I noticed that the Boolean procedure's value is always false even when the script has triggers in it, and so I tried to force ScriptRunner to send it as a full script just to see if it goes through or not and i got the following error instead:
CREATE OR REPLACE TRIGGER UC_UPD_LST_TRIGGER
BEFORE INSERT
ON UC_UPD_LST
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT UC_UPD_LST_SEQ.nextval INTO :NEW.ID FROM dual;
END;
/
. Cause: java.sql.SQLSyntaxErrorException: ORA-00933: SQL command not properly ended
Could somebody please tell me what am I doing wrong here? Should add some sort of delimiter in the file right before when the trigger creation is supposed to start (which is now at the very end of the file).
In case someone else runs into the same problem. When you have a hybrid script (which means it has both normal queries and procedures or triggers), and if you're trying to execute it using myBatis from Java, all you need to do is leave all of your procedures at the end of your script and put delimiters before and after them to let SQL know it should be executed as a block and not line by line. So here's how I added my delimiters:
-- Change the delimiter to '$'
-- #DELIMITER $
CREATE OR REPLACE TRIGGER UC_JAR_LST_TRIGGER
BEFORE INSERT
ON UC_JAR_LST
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT UC_JAR_LST_SEQ.nextval INTO :NEW.ID FROM dual;
-- Change the delimiter back to ';'
-- #DELIMITER ;
END;
-- Change the delimiter to '$'
-- #DELIMITER $
CREATE OR REPLACE TRIGGER UC_UPD_LST_TRIGGER
BEFORE INSERT
ON UC_UPD_LST
REFERENCING NEW AS NEW
FOR EACH ROW
BEGIN
SELECT UC_UPD_LST_SEQ.nextval INTO :NEW.ID FROM dual;
-- Change the delimiter back to ';'
-- #DELIMITER ;
END;
And the execution ended without errors.
Related
We are using Postgres 13.0 version with Spring-Boot .sql file as an initial step.
I need to run an UPDATE script but only if the table itself already exists.
After some effort to understand what is the correct syntax I came with the following script:
ALTER TABLE IF EXISTS ONLY scm_repos ADD COLUMN IF NOT EXISTS token_id BIGINT;
DO '
BEGIN
IF EXISTS
(SELECT 1 FROM scm_repos WHERE id = 1)
THEN
UPDATE scm_repos repos SET token_id=(SELECT token_id FROM scm_orgs orgs WHERE repos.org_id=orgs.id);
END IF ;
END;
' ;
My intention is simple - to run the UPDATE script only if the scm_repos table does exists, but whatever I tried, I'm still getting the following error:
Failed to execute SQL script statement #5 of URL [jar:file:/app/cx-integrations-datastore.jar!/BOOT-INF/classes!/schema.sql]: DO '
BEGIN
IF EXISTS
(SELECT 1 FROM scm_repos WHERE id = 1)
THEN
UPDATE scm_repos repos SET token_id=(SELECT token_id FROM scm_orgs orgs WHERE repos.org_id=orgs.id);
END IF ;
END;
' ; nested exception is org.********ql.util.PSQLException: ERROR: relation "scm_repos" does not exist
Where: PL/pgSQL function inline_code_block line 3 at IF
What am I missing here?
13.0 has known unfixed bugs. 13.4 is the latest release of 13. There is almost never a good reason to run an old minor release version. Not that that seems to be relevant here.
But what you are missing here is that at the top level, EXISTS checks to see if a SELECT returns any rows. It does not check to see if tables mentioned in the FROM list of the SELECT exist or not, it assumes they do.
You could change your query so that it queries the catalog to see if the table exists, something like:
IF EXISTS
(SELECT 1 FROM pg_class where relname=$J$scm_repos$J$)
...
We have a database migration scripts, which are run trough FlyWay, which is part of Java app. The problem is that some customers have the older version of app, where some table and data are presented, while others will be installing this product as new solution. These scripts must run everytime (solution problem and historical problem).
In fact, we handle this in SQL with simple check, if table exist and then execute the rest of script. It works quite fine for other databases (Postgres, MySQL, MS SQL) but not for Oracle. And after few days of trying and googling I am starting to rip my hair off of my head. First problem was executing DDL statements (already learned I can't do that) and now this.
What are we traing to achieve:
Run script if main table exists for current user (that's how we
determine, if all other table exists too)
For every table we need to create a backup table and copy all rows in it (thats why we do TRUNCATE and INSERT INTO and if fails,
we do CREATE TABLE AS) - this is what presented code should do, block of code inside IF table_count > 0 THEN and END IF; is repeated per every table presented (11x)
We then run the cleanup script, which checks for bad data and clean them (we must have original values stored in case something
goes bad).
Cleaned datas are copied into new tables (prepared in previous script).
There is 10 scripts in total, but this one is constantly failing.
Here is the code:
DECLARE
table_count NUMBER;
curr_user VARCHAR2(100);
BEGIN
SELECT
sys_context('USERENV','SESSION_USER') INTO curr_user
FROM dual;
SELECT count(*)
INTO table_count
FROM all_objects
WHERE object_type IN ('TABLE', 'VIEW')
AND object_name = 'main_table'
AND owner = curr_user;
IF table_count > 0 THEN
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE some_table_backup';
EXECUTE IMMEDIATE 'INSERT INTO some_table_backup
SELECT *
FROM some_table';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942
THEN
RAISE;
ELSE
EXECUTE IMMEDIATE 'CREATE TABLE some_table_backup AS SELECT * FROM some_table';
END IF;
END;
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE some_table_backup2';
EXECUTE IMMEDIATE 'INSERT INTO some_table_backup2
SELECT *
FROM some_table2';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942
THEN
RAISE;
ELSE
EXECUTE IMMEDIATE 'CREATE TABLE some_table_backup2 AS SELECT * FROM some_table2';
END IF;
END;
.
.
.
END IF;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line(SQLERRM);
END;
The error is following:
ORA-06550: line 30, column 8:
PLS-00103: Encountered the symbol "end-of-file" when expecting one of the following:
Line 30 is END; after END IF; in first partition of script.
It looks like that your version of FlyWay does not like nested PL/SQL Blocks. I would try to add anonymous block labels:
DECLARE
table_count NUMBER;
curr_user VARCHAR2(100);
BEGIN
SELECT
sys_context('USERENV','SESSION_USER') INTO curr_user
FROM dual;
SELECT count(*)
INTO table_count
FROM all_objects
WHERE object_type IN ('TABLE', 'VIEW')
AND object_name = UPPER('main_table') --here I added UPPER
AND owner = curr_user;
IF table_count > 0 THEN
<<name1>>
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE some_table_backup';
EXECUTE IMMEDIATE 'INSERT INTO some_table_backup
SELECT *
FROM some_table';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942
THEN
RAISE;
ELSE
EXECUTE IMMEDIATE 'CREATE TABLE some_table_backup AS SELECT * FROM some_table';
END IF;
END name1;
<<name2>>
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE some_table_backup2';
EXECUTE IMMEDIATE 'INSERT INTO some_table_backup2
SELECT *
FROM some_table2';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942
THEN
RAISE;
ELSE
EXECUTE IMMEDIATE 'CREATE TABLE some_table_backup2 AS SELECT * FROM some_table2';
END IF;
END name2;
END IF;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line(SQLERRM);
END;
/
Alternatively you could avoid nested PL/SQL block by changing approach.
Instead of trying to do something first and handle exception, you could easily check if table exist or not and then do the action. That way there is no need for nested PL/SQL blocks at all.
Second alternative is to use dynamic SQL:
DECLARE
table_count NUMBER;
curr_user VARCHAR2(100);
BEGIN
SELECT
sys_context('USERENV','SESSION_USER') INTO curr_user
FROM dual;
SELECT count(*)
INTO table_count
FROM all_objects
WHERE object_type IN ('TABLE', 'VIEW')
AND object_name = UPPER('main_table')
AND owner = curr_user;
IF table_count > 0 THEN
EXECUTE IMMEDIATE q'{
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE some_table_backup';
EXECUTE IMMEDIATE 'INSERT INTO some_table_backup
SELECT *
FROM some_table';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942
THEN
RAISE;
ELSE
EXECUTE IMMEDIATE 'CREATE TABLE some_table_backup AS SELECT * FROM some_table';
END IF;
END;}';
EXECUTE IMMEDIATE q'{
BEGIN
EXECUTE IMMEDIATE 'TRUNCATE TABLE some_table_backup2';
EXECUTE IMMEDIATE 'INSERT INTO some_table_backup2
SELECT *
FROM some_table2';
EXCEPTION
WHEN OTHERS THEN
IF SQLCODE != -942
THEN
RAISE;
ELSE
EXECUTE IMMEDIATE 'CREATE TABLE some_table_backup2 AS SELECT * FROM some_table2';
END IF;
END;}';
END IF;
EXCEPTION
WHEN OTHERS
THEN
DBMS_OUTPUT.put_line(SQLERRM);
END;
Please note the usage of q'{}' text literal that allow to handle ' without duplicating them.
With FlyWay 4.2.0(called from command-line) both samples work as it should.
I have an anonymous pl/sql block in an sql file and I want to execute and assign values to it in Java. My sql block looks like this
DECLARE
someInput1 NUMBER(1);
someInput2 NUMBER(2);
someString1 VARCHAR(100);
BEGIN
someInput1 := ‘&1’;
someInput2 := ‘&2’;
--get name in table A
BEGIN
SELECT a.value INTO someString1
FROM TABLE_A a
WHERE a.id = someInput1;
END;
UPDATE TABLE_B b
SET b.someStringRow = someString1
WHERE b.someIntRow = someInput2;
COMMIT;
END;
/
exit;
What I am planning to do is load the sql file in a Java String, change ‘&1’ to ?1 and execute it as a CallableStatement. However, I am getting
PLS-00103: Encountered the symbol “” when expecting one of the following
begin function package pragma procedure subtype us <an identifier> <a double quoted delimited identifier> form current cursor
Error occured at lines in DECLARE section
Is my approach an acceptable solution, if yes, what may be wrong in my approach?
Are there other better solution for my problem? Thanks
you have to remove quote marks, and replace &1 with :1, and you should be fine, also, you will need remove '/' and exit; and they relate to sqlplus and not to pl/sql directly
I am using Oracle 11g, I am executing Oracle sql script through java code. My SQL script may contain SQL statements(DDL or DML) or PL/SQL blocks, so I don't want to parse the script in my java code but used This solution to execute complete script at once. Following is the sample code, where SQLExec class is in ant jar.
This solution worked for most cases except that if sql script contains create or replace trigger it fails with java.sql.SQLSyntaxErrorException: ORA-00900: invalid SQL statement. I have also specified snippet of sql script which fails.
Please note that if I run same script through SQL Developer, it runs fine.
Following is the Java code:
private void executeSql(String sqlFilePath) {
final class SqlExecuter extends SQLExec {
public SqlExecuter() {
Project project = new Project();
project.init();
setProject(project);
setTaskType("sql");
setTaskName("sql");
}
}
SqlExecuter executer = new SqlExecuter();
executer.setSrc(new File(sqlFilePath));
executer.setDriver(args.getDriver());
executer.setPassword(args.getPwd());
executer.setUserid(args.getUser());
executer.setUrl(args.getUrl());
executer.execute();
}
SQL Script snippet:
......
......
CREATE OR REPLACE TRIGGER MY_TRG
BEFORE INSERT ON MY_TABLE
FOR EACH ROW
BEGIN
:NEW.MYNUMBER := MY_SEQUENCENUM.NEXTVAL;
END;
Following is the Exception trace:
Exception in thread "main" java.sql.SQLSyntaxErrorException: ORA-00900: invalid SQL statement
at org.apache.tools.ant.taskdefs.SQLExec.execute(SQLExec.java:398)
at com.kuldeep.OracleConnectionTest.executeSql(OracleConnectionTest.java:160)
at com.kuldeep.OracleConnectionTest.main(OracleConnectionTest.java:25)
Caused by: java.sql.SQLSyntaxErrorException: ORA-00900: invalid SQL statement
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:439)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:395)
at oracle.jdbc.driver.T4C8Oall.processError(T4C8Oall.java:802)
at oracle.jdbc.driver.T4CTTIfun.receive(T4CTTIfun.java:436)
at oracle.jdbc.driver.T4CTTIfun.doRPC(T4CTTIfun.java:186)
at oracle.jdbc.driver.T4C8Oall.doOALL(T4C8Oall.java:521)
at oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:194)
at oracle.jdbc.driver.T4CStatement.executeForRows(T4CStatement.java:1000)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1307)
at oracle.jdbc.driver.OracleStatement.executeInternal(OracleStatement.java:1882)
at oracle.jdbc.driver.OracleStatement.execute(OracleStatement.java:1847)
at oracle.jdbc.driver.OracleStatementWrapper.execute(OracleStatementWrapper.java:301)
at org.apache.tools.ant.taskdefs.SQLExec.execSQL(SQLExec.java:499)
at org.apache.tools.ant.taskdefs.SQLExec.runStatements(SQLExec.java:470)
at org.apache.tools.ant.taskdefs.SQLExec$Transaction.runTransaction(SQLExec.java:664)
at org.apache.tools.ant.taskdefs.SQLExec$Transaction.access$000(SQLExec.java:627)
at org.apache.tools.ant.taskdefs.SQLExec.execute(SQLExec.java:370)
In the documentation it says:
Multiple statements can be provided, separated by semicolons (or the defined delimiter).
Therefore, using the semicolon character (;) as the default delimiter, SQLEXEC interprets the CREATE TRIGGER statement of your script as two statements, giving this error message as the result.
As #Reza Goodarzi mentioned the cause of invalid SQL statement is semicolon being used as the statement separator. So to solve my issue I am separating each statement with slash(/) as delimiter and followed these rules which I created myself:
Each SQL statement (not part of PL/SQL block) and PL/SQL block must end with a forwarded slash (/) in a new line.
SQL statement (not part of PL/SQL blocks) should not end with semicolon (;). I just removed semicolon from the end of statements.
For PL/SQL block do not remove the semicolon(;) from end of the block as well as from any statement contained within the block.
And by making these changes in my SQL Scripts I executed (using jdbc) each PL/SQL block and each SQL statement (not part of PL/SQL block) at a time by parsing the file myself instead of using SQLExec or any other external api/library.
I think you need to change your trigger to set your new ID
create or replace trigger MY_TRG
BEFORE insert MY_TABLE
for each row
begin
if (:new.MYNUMBER is null) then
select MY_SEQUENCENUM.nextval
into :new.MYNUMBER
from DUAL;
end if;
end;
/
or this:
create or replace trigger TG_BIU_TABLE1
before insert or update on TABLE1
for each row
begin
if (:new.ID1 is null) then
select SQ_TABLE1.nextval
into :new.ID1
from DUAL
end if
end
/
You can also add a delimiter in the execute statement, as so:
......
......
DELIMITER $$
CREATE OR REPLACE TRIGGER MY_TRG
BEFORE INSERT ON MY_TABLE
FOR EACH ROW
BEGIN
:NEW.MYNUMBER := MY_SEQUENCENUM.NEXTVAL;
END; $$
......
I also left the final part of the script just for the triggers and procedures, as the delimiter is used onward.
That did the trick for me. Courtesy of SQL Developer´s Migration tool.
I have Oracle DB 11g Enterprise Edition and I want to create a table by reading the sql script from a file.Through java code I am reading following sql script from a file and storing it in a String sqlBlock:
CREATE SEQUENCE VerHist_SeqNum
START WITH 1
INCREMENT BY 1;
CREATE TABLE VerHist
(
SequenceNumber NUMBER(10,0) NOT NULL,
SQLFileName VARCHAR2(100) NOT NULL,
STATUS VARCHAR2(10) NOT NULL,
AppliedDate DATE NOT NULL,
DateCreated DATE
DEFAULT (SYSDATE),
DateUpdated DATE
DEFAULT (SYSDATE),
CONSTRAINT PK_VerHist PRIMARY KEY( SequenceNumber ),
CONSTRAINT UC_VerHist_SQLFileNa UNIQUE( SQLFileName )
);
CREATE OR REPLACE TRIGGER VerHist_SeqNum_TRG
BEFORE INSERT
ON VerHist
FOR EACH ROW
BEGIN
SELECT VerHist_SeqNum.NEXTVAL INTO :NEW.SequenceNumber
FROM DUAL;
END;
When I execute this query it gives
java.sql.SQLException: ORA-00911: invalid character\n
at
oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:112)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:331) at
oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:288) at
oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:743) at
oracle.jdbc.driver.T4CStatement.doOall8(T4CStatement.java:207) at
oracle.jdbc.driver.T4CStatement.executeForRows(T4CStatement.java:946)
at
oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1168)
at
oracle.jdbc.driver.OracleStatement.executeInternal(OracleStatement.java:1687)
at
oracle.jdbc.driver.OracleStatement.execute(OracleStatement.java:1653)
Following is my code to execute the sql block:
Statement stmt = conn.createStatement();
String sqlBlock = //"this contains the content of the file (it contains \n charters)";
stmt.execute(sqlBlock);
Is the newline charter invalid here, if yes, how to get this working otherwise?
Please note that when I copy paste the contents of this file and run the script through Oracle SQL Developer it runs fine.
I think the \n reference is a red-herring, and an artefact of how the error is being logged. You're trying to run two SQL statements, separated by a semi-colon, in one execute. That is not allowed. The semi-colon is a statement separator in SQL*Plus, not in SQL, and will generate an ORA-00911 even with a single statement. And execute has to be a single statement.
If you were doing DML you could wrap the statements in a PL/SQL block and execute that, but since this is DDL you can't do that unless you resort to dynamic SQL, which is going to be overly complicated and messy for what you're trying to do.
You need to put each statement in a separate file (without the trailing semi-colon on the create sequence; you still need it on the create trigger because there it is ending the trigger's PL/SQL block, not acting as a statement separator - confusing, I know), and read and execute them separately. Then each execute has a single statement, and will be much happier.
As an aside, you don't need to select your sequence value into your variable in 11g; you can now do it like this:
CREATE OR REPLACE TRIGGER VerHist_SeqNum_TRG
BEFORE INSERT
ON VerHist
FOR EACH ROW
BEGIN
:NEW.SequenceNumber := VerHist_SeqNum.NEXTVAL;
END;
When you copy paste the contains of the file to browser. Browser will treat \n as new line.
Whereas for the code /n is character only.
Try to replace \n with single space and then run it will work
sqlBlock = sqlBlock.replaceAll("\n"," ");
Remove the \n from the query. If you want the query to be formatted in sql developer, you can select the query and press Ctrl+F7