Cancel SQL *PLUS Execution without raising an error - sql

i have a tricky SQL Problem:
We have a huge SQL Script which installs our application on the DB server.
We want to skip the database installation if the latest update didn't change anything in the DB.
I have implemented following check, which is executed before the other SQL commands:
check_current_version_delta.sql:
DECLARE
v_deploy_version VARCHAR2(30) := '&db_deploy_version';
v_check BOOLEAN := FALSE;
BEGIN
DBMS_OUTPUT.PUT_LINE( '--------------------------------------------------------------------------------');
DBMS_OUTPUT.PUT_LINE( 'Check if we have a new DB version');
DBMS_OUTPUT.PUT_LINE( '--------------------------------------------------------------------------------');
FOR cu_version IN (SELECT version FROM &DB_CURRENT_USER..db_deployment WHERE version = v_deploy_version AND ROWNUM = 1) LOOP
v_check := TRUE;
END LOOP;
IF v_check THEN
RAISE_APPLICATION_ERROR( -20001, 'DB Version: '||v_deploy_version||' is already installed');
END IF;
END;
/
This is working very well but our installation team complains about the ORA-XXXXX Error in the log, because they have automated error checks this installation is marked as FAIL (though there was no actual error)
So now the actual problem:
I need to cancel the execution of the SQL without any errors in the LOG. Is this even possible?
Alternative would be to make the rest of the installation dependent on the outcome of the script above. But i'm not sure how to accomplish that.
Have you some suggestions on how to handle it the good way?

One possible way is to create three scripts: The first checks for the condition and either calls the second or the third. The second does the real job. The third is an empty dummy, to avoid the error message cause by calling a non-existent script
In code, it looks like this:
col SCRIPT1_COL new_val SCRIPT1
SELECT case
when version = '&db_deploy_version' then 'dummy.sql'
else 'upgrade_db.sql'
end as SCRIPT1_COL
FROM &DB_CURRENT_USER..db_deployment;
#&SCRIPT1
Alternatively, you could use the method shown above to load either a script "dummy.sql" that does nothing or a script "exit.sql" that just contains the exit command, and execute it before doing the real job.

Presumably you're already using whenever sqlerror so make it terminate when you raise that exception, and you're redirecting the output to your log file. If so you can just hide the text of the exception with set termout off:
whenever sqlerror exit success
set termout off
DECLARE
...
BEGIN
...
IF v_check THEN
RAISE_APPLICATION_ERROR( -20001,
'DB Version: '||v_deploy_version||' is already installed');
END IF;
END;
/
set termout on
whenever sqlerror exit failure
... the rest of your script
The script will stop if the exception is raised but produce no output. The success means anything that runs this won't decide it has errored independently of the log; the exit code from sqlplus will be zero.
You may be spooling to output instead; in which case just don't start the spool until after your check. Or if you have things before this that you do have to spool, turn the spool off and then on again afterwards with append.

Related

How to reproduce ORA-01940: cannot drop a user that is currently connected through Jenkins pipeline?

Through Jenkins pipeline, if I get error ORA-01940: cannot drop a user that is currently connected, I want to perform some action.
FOr testing I should get that message. If I create user and log in with it to sql developer and try to drop it through pipeline it results into above error -
sh '''
sqlplus -s /nolog <<-EOF
connect system/system#orcl
DROP USER TEST_USER;
exit
EOF
'''
Is there any way I can connect using that user and drop it through pipeline so results into above error?
If you are running that part through a PIPELINE in Jenkins using shell script, there are some options:
Get a specific Return Code of the Operation
In this case, you might do the following
sh '''
sqlplus -s /nolog <<-EOF
connect system/system#orcl
whenever sqlerror exit 99
DROP USER TEST_USER;
exit
EOF
'''
This operation will return 99 in case you got the error. You must evaluate the the return of the operation using $?
Example
Session 1 ( as system )
SQL> create user test identified by Oracle_1 ;
User created.
SQL> grant connect to test ;
Grant succeeded.
I connect with the user test, and then with my system user I will try to drop it
SQL> whenever sqlerror exit 99
SQL> drop user test ;
drop user test
*
ERROR at line 1:
ORA-01940: cannot drop a user that is currently connected
Disconnected from Oracle Database 12c Enterprise Edition Release 12.2.0.1.0 - 64 bit Production
[ftpcpl#scglvdoracp0040 ~]$ echo $?
99
Using PLSQL Block to return code or message
However, if you want something different than the option before, like having the code displayed automatically without evaluation, then you can get the message display by the output using an anonymous PL/SQL block.
In your case, I would use something like this:
sh '''
sqlplus -s /nolog <<-EOF
connect system/system#orcl
set serveroutput on feedback off
declare
user_is_conn exception;
verror varchar2(100);
pragma exception_init ( user_is_conn , -1940 );
begin
execute immediate q'[ DROP USER TEST ]';
exception
when user_is_conn then
verror := sqlerrm ; -- the whole error message
-- verror := sqlcode ; -- only the code 1940
dbms_output.put_line(verror); -- message
when others then
raise;
end;
/
exit
EOF
'''
I commented verror := sqlcode in the section before to show the whole message. An example of this running in Linux would be:
$ $ORACLE_HOME/bin/sqlplus -S "/ as sysdba" << eof
set serveroutput on feedback off
declare
user_is_conn exception;
verror varchar2(100);
pragma exception_init ( user_is_conn , -1940 );
begin
execute immediate 'DROP USER TEST' ;
exception
when user_is_conn then
verror := sqlerrm ;
dbms_output.put_line(verror);
when others then
raise;
end;
/
eof
ORA-01940: cannot drop a user that is currently connected
Be aware that this PL/SQL block is ended OK. I mean, from OS perspective the operation return code is 0. If you want that as a error, you need to raise the error, but in that case the message would be different.

sqlplus variable value is not accepting in EOF block in shell

I am unable to give input to the variable "y" written in below code. Can any one help us Please.
Code :
#!/bin/bash
${ORACLE_HOME}/bin/sqlplus -s abcd/passabcd <<EOF
declare
res varchar2(9) := '&y';
begin
insert into abc values(10);
if res in ('commit;')
then
EXECUTE IMMEDIATE 'commit;';
elsif res in ('rollback;')
then
EXECUTE IMMEDIATE 'rollback;';
else
DBMS_OUTPUT.PUT_LINE('Enter Correct Input');
end if;
EXCEPTION
WHEN OTHERS THEN
dbms_output.put_line('Error msg ' || substr(sqlerrm,1,200));
end;
/
EOF
if i run this, below is the screen. automatically i am getting sp2-0546 error without entering any input.
[oracle#ssmz861 ~]$ ./mm.sh
Enter value for y:SP2-0546: User requested Interrupt or EOF detected.
[oracle#ssmz861 ~]$
here is the scenario: I have a pl/sql code i need to give to sqlplus to execute in following manner like <EOF .....EOF. in that one i have to see the output of one query and decide whether need to commit or rollback by giving input.
thanks in advance
Siva

SQLPlus is trying to drop package twice

While executing scripts in SQLPlus I've encountered a problem:
script.sql contains the following lines
#some_pkg.pks
#some_pkg.pkb
drop package some_pkg;
/
After calling
> sqlplus user/password#dbname #script.sql
the following messages are in console:
Package created.
Package body created.
Package dropped.
drop package some_pkg;
*
ERROR at line 1:
ORA-04043: object SOME_PKG does not exist
Please, explain what's happening here. Looks like the package is being dropped twice. Is it possible to avoid the error?
The rules of SQLplus command execution basically are:
Execute the current text when you encounter a semi-colon. Thus if a line doesn't end with a semi-colon, the current text continues to be collected.
If you encounter DECLARE or BEGIN, collect all the text and do not execute on semi-colons
If you encounter a slash (/), execute the collected text.
So what happens in your cases is, that both the semi-colon and the slash execute the DROP statements.
To fix it, remove the slash.
You only need the slash if you have a block of PL/SQL, which always with an END statement. Use semicolons for everything else.
Note: the above rules are simplified. It's more complex in practice.
Some examples will help to understand rules:
Below code will be executed once
begin
dbms_output.put_line('executed');
end;
/
Below code will not execute (missing semicolon)
begin
dbms_output.put_line('executed')
end
/
Below code will be executed twice
begin
dbms_output.put_line('executed');
end;
/
/
Below code will be executed once
select 1 from dual
/
Below code will be executed once
select 1 from dual;
Below code will be executed twice
select 1 from dual;
/

How does "if xx > 0then" execute but not compile?

I've got a PL/SQL-Block which looks like this:
declare
L_Count number := 10;
begin
if L_Count > 0then
dbms_output.put_line('l_Count > 0');
else
dbms_output.put_line('l_Count <= 0');
end if;
exception
when others then
dbms_output.put_line('exception occurred');
end;
Note the fourth line containing 0then instead of 0 then.
Using PL/SQL-Developer, I can execute this block as an SQL statement, which actually outputs l_Count > 0. Using a "Program Window" and compiling this, PL/SQL-Developer says the following error:
Unable to perform operation due to errors in source code
How can this statement execute but not compile?
Thank you for your hints!
The behavior is not what I would expect either. However, both the PL/SQL Developer SQL Window and Command Window modes execute this consistently with how SQL*Plus behaves. So, this is either:
Not a bug, or
An Oracle bug but not an Allround Automations bug.
Execution and compilation are two separate things. The code block is an anonynous block and it cannot be compiled. However you can execute the block. Execution would show you l_Count > 0.
Thanks,
Aditya

SQL Server - stop or break execution of a SQL script

Is there a way to immediately stop execution of a SQL script in SQL server, like a "break" or "exit" command?
I have a script that does some validation and lookups before it starts doing inserts, and I want it to stop if any of the validations or lookups fail.
The raiserror method
raiserror('Oh no a fatal error', 20, -1) with log
This will terminate the connection, thereby stopping the rest of the script from running.
Note that both severity level 20 or higher and the WITH LOG option are necessary for it to work this way.
This even works with GO statements, eg.
print 'hi'
go
raiserror('Oh no a fatal error', 20, -1) with log
go
print 'ho'
Will give you the output:
hi
Msg 2745, Level 16, State 2, Line 1
Process ID 51 has raised user error 50000, severity 20. SQL Server is terminating this process.
Msg 50000, Level 20, State 1, Line 1
Oh no a fatal error
Msg 0, Level 20, State 0, Line 0
A severe error occurred on the current command. The results, if any, should be discarded.
Notice that 'ho' is not printed.
CAVEATS:
This only works if you are logged in as admin ('sysadmin' role), and also leaves you with no database connection.
If you are NOT logged in as admin, the RAISEERROR() call itself will fail and the script will continue executing.
When invoked with sqlcmd.exe, exit code 2745 will be reported.
Reference: http://www.mydatabasesupport.com/forums/ms-sqlserver/174037-sql-server-2000-abort-whole-script.html#post761334
The noexec method
Another method that works with GO statements is set noexec on (docs). This causes the rest of the script to be skipped over. It does not terminate the connection, but you need to turn noexec off again before any commands will execute.
Example:
print 'hi'
go
print 'Fatal error, script will not continue!'
set noexec on
print 'ho'
go
-- last line of the script
set noexec off -- Turn execution back on; only needed in SSMS, so as to be able
-- to run this script again in the same session.
Just use a RETURN (it will work both inside and outside a stored procedure).
If you can use SQLCMD mode, then the incantation
:on error exit
(INCLUDING the colon) will cause RAISERROR to actually stop the script. E.g.,
:on error exit
IF NOT EXISTS (SELECT * FROM sys.objects WHERE object_id = OBJECT_ID(N'[dbo].[SOMETABLE]') AND type in (N'U'))
RaisError ('This is not a Valid Instance Database', 15, 10)
GO
print 'Keep Working'
will output:
Msg 50000, Level 15, State 10, Line 3
This is not a Valid Instance Database
** An error was encountered during execution of batch. Exiting.
and the batch will stop. If SQLCMD mode isn't turned on, you'll get parse error about the colon. Unfortuantely, it's not completely bulletproof as if the script is run without being in SQLCMD mode, SQL Managment Studio breezes right past even parse time errors! Still, if you're running them from the command line, this is fine.
I would not use RAISERROR- SQL has IF statements that can be used for this purpose. Do your validation and lookups and set local variables, then use the value of the variables in IF statements to make the inserts conditional.
You wouldn't need to check a variable result of every validation test. You could usually do this with only one flag variable to confirm all conditions passed:
declare #valid bit
set #valid = 1
if -- Condition(s)
begin
print 'Condition(s) failed.'
set #valid = 0
end
-- Additional validation with similar structure
-- Final check that validation passed
if #valid = 1
begin
print 'Validation succeeded.'
-- Do work
end
Even if your validation is more complex, you should only need a few flag variables to include in your final check(s).
In SQL 2012+, you can use THROW.
THROW 51000, 'Stopping execution because validation failed.', 0;
PRINT 'Still Executing'; -- This doesn't execute with THROW
From MSDN:
Raises an exception and transfers execution to a CATCH block of a TRY…CATCH construct ... If a TRY…CATCH construct is not available, the session is ended. The line number and procedure where the exception is raised are set. The severity is set to 16.
I extended the noexec on/off solution successfully with a transaction to run the script in an all or nothing manner.
set noexec off
begin transaction
go
<First batch, do something here>
go
if ##error != 0 set noexec on;
<Second batch, do something here>
go
if ##error != 0 set noexec on;
<... etc>
declare #finished bit;
set #finished = 1;
SET noexec off;
IF #finished = 1
BEGIN
PRINT 'Committing changes'
COMMIT TRANSACTION
END
ELSE
BEGIN
PRINT 'Errors occured. Rolling back changes'
ROLLBACK TRANSACTION
END
Apparently the compiler "understands" the #finished variable in the IF, even if there was an error and the execution was disabled. However, the value is set to 1 only if the execution was not disabled. Hence I can nicely commit or rollback the transaction accordingly.
You can alter the flow of execution using GOTO statements:
IF #ValidationResult = 0
BEGIN
PRINT 'Validation fault.'
GOTO EndScript
END
/* our code */
EndScript:
you could wrap your SQL statement in a WHILE loop and use BREAK if needed
WHILE 1 = 1
BEGIN
-- Do work here
-- If you need to stop execution then use a BREAK
BREAK; --Make sure to have this break at the end to prevent infinite loop
END
Further refinig Sglasses method, the above lines force the use of SQLCMD mode, and either treminates the scirpt if not using SQLCMD mode or uses :on error exit to exit on any error
CONTEXT_INFO is used to keep track of the state.
SET CONTEXT_INFO 0x1 --Just to make sure everything's ok
GO
--treminate the script on any error. (Requires SQLCMD mode)
:on error exit
--If not in SQLCMD mode the above line will generate an error, so the next line won't hit
SET CONTEXT_INFO 0x2
GO
--make sure to use SQLCMD mode ( :on error needs that)
IF CONTEXT_INFO()<>0x2
BEGIN
SELECT CONTEXT_INFO()
SELECT 'This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!'
RAISERROR('This script must be run in SQLCMD mode! (To enable it go to (Management Studio) Query->SQLCMD mode)\nPlease abort the script!',16,1) WITH NOWAIT
WAITFOR DELAY '02:00'; --wait for the user to read the message, and terminate the script manually
END
GO
----------------------------------------------------------------------------------
----THE ACTUAL SCRIPT BEGINS HERE-------------
I use RETURN here all the time, works in script or Stored Procedure
Make sure you ROLLBACK the transaction if you are in one, otherwise RETURN immediately will result in an open uncommitted transaction
Is this a stored procedure? If so, I think you could just do a Return, such as "Return NULL";
Wrap your appropriate code block in a try catch block. You can then use the Raiserror event with a severity of 11 in order to break to the catch block if you wish. If you just want to raiserrors but continue execution within the try block then use a lower severity.
TRY...CATCH (Transact-SQL)
None of these works with 'GO' statements. In this code, regardless of whether the severity is 10 or 11, you get the final PRINT statement.
Test Script:
-- =================================
PRINT 'Start Test 1 - RAISERROR'
IF 1 = 1 BEGIN
RAISERROR('Error 1, level 11', 11, 1)
RETURN
END
IF 1 = 1 BEGIN
RAISERROR('Error 2, level 11', 11, 1)
RETURN
END
GO
PRINT 'Test 1 - After GO'
GO
-- =================================
PRINT 'Start Test 2 - Try/Catch'
BEGIN TRY
SELECT (1 / 0) AS CauseError
END TRY
BEGIN CATCH
SELECT ERROR_MESSAGE() AS ErrorMessage
RAISERROR('Error in TRY, level 11', 11, 1)
RETURN
END CATCH
GO
PRINT 'Test 2 - After GO'
GO
Results:
Start Test 1 - RAISERROR
Msg 50000, Level 11, State 1, Line 5
Error 1, level 11
Test 1 - After GO
Start Test 2 - Try/Catch
CauseError
-----------
ErrorMessage
--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
Divide by zero error encountered.
Msg 50000, Level 11, State 1, Line 10
Error in TRY, level 11
Test 2 - After GO
The only way to make this work is to write the script without GO statements. Sometimes that's easy. Sometimes it's quite difficult. (Use something like IF #error <> 0 BEGIN ....)
you can use RAISERROR.
This was my solution:
...
BEGIN
raiserror('Invalid database', 15, 10)
rollback transaction
return
END
You can use GOTO statement. Try this. This is use full for you.
WHILE(#N <= #Count)
BEGIN
GOTO FinalStateMent;
END
FinalStatement:
Select #CoumnName from TableName
Thx for the answer!
raiserror() works fine but you shouldn't forget the return statement otherwise the script continues without error! (hense the raiserror isn't a "throwerror" ;-)) and of course doing a rollback if necessary!
raiserror() is nice to tell the person who executes the script that something went wrong.
If you are simply executing a script in Management Studio, and want to stop execution or rollback transaction (if used) on first error, then the best way I reckon is to use try catch block (SQL 2005 onward).
This works well in Management studio if you are executing a script file.
Stored proc can always use this as well.
Enclose it in a try catch block, then the execution will be transfered to catch.
BEGIN TRY
PRINT 'This will be printed'
RAISERROR ('Custom Exception', 16, 1);
PRINT 'This will not be printed'
END TRY
BEGIN CATCH
PRINT 'This will be printed 2nd'
END CATCH;
Back in the day we used the following...worked best:
RAISERROR ('Error! Connection dead', 20, 127) WITH LOG
Many thanks to all the other people here and other posts I have read.
But nothing was meeting all of my needs until #jaraics answered.
Most answers I have seen disregard scripts with multiple batches. And they ignore dual usage in SSMS and SQLCMD. My script is fully runable in SSMS -- but I want F5 prevention so they don't remove an existing set of objects on accident.
SET PARSEONLY ON worked well enough to prevent unwanted F5. But then you can't run with SQLCMD.
Another thing that slowed me down for a while is how a Batch will skip any further commands when there is an error - so my SET NOCOUNT ON was being skipped and thus the script still ran.
Anyway, I modified jaraics' answer just a bit: (in this case, I also need a database to be active from commandline)
-----------------------------------------------------------------------
-- Prevent accidental F5
-- Options:
-- 1) Highlight everything below here to run
-- 2) Disable this safety guard
-- 3) or use SQLCMD
-----------------------------------------------------------------------
set NOEXEC OFF -- Reset in case it got stuck ON
set CONTEXT_INFO 0x1 -- A 'variable' that can pass batch boundaries
GO -- important !
if $(SQLCMDDBNAME) is not null
set CONTEXT_INFO 0x2 -- If above line worked, we're in SQLCMD mode
GO -- important !
if CONTEXT_INFO()<>0x2
begin
select 'F5 Pressed accidentally.'
SET NOEXEC ON -- skip rest of script
END
GO -- important !
-----------------------------------------------------------------------
< rest of script . . . . . >
GO
SET NOEXEC OFF
print 'DONE'
I have been using the following script and you can see more details in my answer here.
RAISERROR ( 'Wrong Server!!!',18,1) WITH NOWAIT RETURN
print 'here'
select [was this executed]='Yes'