How to call a private procedure which is body implemented inside package body but there is no package specification - package

What is the use of a package body without package specification in Oracle?
Tried in the mentioned link example.
below is the example:
create or replace package pkg_spec_test
AS
procedure p1;
v_1 NUMBER := 10;
END;
/
create or replace package body pkg_spec_test
AS
procedure p1 AS
BEGIN
dbms_output.put_line('HELLO. I can be called externally');
END p1;
procedure p2 AS
BEGIN
dbms_output.put_line('I''m private to package body.');
END;
procedure main as
begin
p2; --called within the body;
END main;
END pkg_spec_test;
/
Here the procedure p2 and main both have no specifications.
Below are the execution blocks
begin
pkg_spec_test.p1;
end;
/
above block is working fine.
now, executing the p2 procedure. it throws errors
begin
pkg_spec_test.p2;
end;
/
Error starting at line : 30 in command -
begin
pkg_spec_test.p2;
end;
Error report -
ORA-06550: line 2, column 15:
PLS-00302: component 'P2' must be declared
now, executing the main procedure it also throws the same errors as above.
begin
pkg_spec_test.main;
end;
/
Question: in the mentioned link shows executing the main procedure its does not throws any error. how it works ,Why p2 throws errors? here both p2 and main both are not in package specification.
if main procedure works then p2 also works right?.
even tried to execute main procedure it also throws same errors as p2.
How to execute private procedures? means without package specification.

Related

How to return return code from procedure but not error message when there is an error in SQL server Procedure

There is a Procedure like below
CRAETE PROCEDURE dbo.procname
AS
BEGIN
--BEGIN TRY
SELECT 1/0
--END TRY
--BEGIN CATCH
--END CATCH
END
We are calling this procedure in UNIX SQLCMD as below.
retval=`
DECLARE #val INT
EXEC #val=dbo.procname
SELECT #val`
echo $retval
When we using TRY CATCH block and call the procedure , #val is giving -6 but when we remove TRY CATCH block from procedure, then we are NOT getting any value in #val and it is displaying the SSMS generated Message like
Divide by zero error.
As it is known that, in SQL server, procedures return 0 by default if it runs successfully and Non zero value if it fails.
My requirement is to capture #val int value when it fails as I am using this value to proceed further in UNIX shell script.
So, is there any way that we can capture procedure return value in #val variable if we do not use TRY CATCH block in procedure.
We are working on Migration project and there are number of Procedure in it and we cannot check each and every proc to see if TRY CATCH block is present or not.

PLS-00201: identifier 'DBMS_AWM' must be declared on owbsys user

After upgrading oracle database from 11gR1 to 11gR2, owbsys user's procedure get the following compilation error.
PROCEDURE OWBSYS.WB_OLAP_LOAD_CUBE
On line: 1
PLS-00201: identifier 'DBMS_AWM' must be declared
Procedure Code:
CREATE OR REPLACE PROCEDURE OWBSYS.WB_OLAP_LOAD_CUBE(olap_aw_owner VARCHAR2, olap_aw_name VARCHAR2, olap_cube_owner VARCHAR2, olap_cube_name VARCHAR2, olap_tgt_cube_name VARCHAR2) AS v varchar2(32);
BEGIN
BEGIN
DBMS_AWM.CREATE_AWCUBELOAD_SPEC(olap_cube_name, olap_cube_owner, olap_cube_name, 'LOAD_DATA');
EXCEPTION WHEN OTHERS THEN NULL;
END;
DBMS_AWM.REFRESH_AWCUBE(olap_aw_owner, olap_aw_name, olap_tgt_cube_name, olap_cube_name);
DBMS_AW.EXECUTE('upd '||olap_aw_owner||'.'||olap_aw_name ||'; commit');
BEGIN
SELECT null into v from all_olap2_aw_cube_agg_specs where aw_owner=olap_aw_owner and aw_name=olap_aw_name and aw_cube_name=olap_tgt_cube_name and aw_aggspec_name=olap_cube_name;
EXCEPTION
WHEN OTHERS THEN RETURN;
END;
DBMS_AWM.AGGREGATE_AWCUBE(olap_aw_owner, olap_aw_name, olap_tgt_cube_name, olap_cube_name);
EXCEPTION
WHEN OTHERS THEN RAISE;
END;
/
The following is the workaround to fix the issue
Step 1: Login to database as SYSDBA
Step 2: Run the following grant command
GRANT EXECUTE ON DBMS_AWM TO OWBSYS;
Step 3: Re-compile your procedure.
Hopefully, after performing the above actions, your procedure will compile without errors.

CREATE/ALTER procedure using Delphi

I've got a small problem that I could not find an answer to. I need to create/alter some procedures from Delphi.
So this is my code that take the code from a file and tries to execute it.
procedure TfrmMainApp.actRulezaScriptExecute(Sender: TObject);
var
j: Int32;
sql: string;
commandFile: TextFile;
Linie: string;
affRows: Int32;
err: string;
begin
for j := 0 to filesToExecute.Count - 1 do
begin
sql := 'USE ' + DBName + #10#13;
sql := sql + ' GO ' + #10#13;
AssignFile(commandFile, filesToExecute[j]);
Reset(commandFile);
while not EOF(commandFile) do
begin
Readln(commandFile, Linie);
sql := sql + #10#13 + Linie;
end;
dmMainScriptRun.ExecuteCommand(sql, err, affRows);
if err <> '' then
break;
Memo1.Lines.Add('Affected rows:' + IntToStr(affRows));
end;
end;
function TdmMainScriptRun.ExecuteCommand(sqlCommand: string; var err: string;
var affRows: Int32): Boolean;
begin
err := '';
try
with cmd do
begin
CommandText := sqlCommand;
Execute(affRows, EmptyParam);
end;
except
on E: Exception do
begin
err := E.Message;
end;
end;
end;
So my file looks like this
CREATE PROCEDURE sp_TestSebi
AS
print ('testSebi')
My command looks like this ( it was taken from the SQL Server Profiler )
USE Test
GO
CREATE PROCEDURE sp_TestSebi
AS
print ('testSebi')
Executin this command returns err Incorrect syntax near 'GO' running the script without the GO statement return err CREATE/ALTER PROCEDURE' must be the first statement in a query batch because of the USE clause.
Is there a way I can create a procedure from Delphi? I need the use statement because i'm trying to execute a script on multiple databases.
The way you are attempting this can be improved in several ways.
Firstly, try this:
Create a new VCL project
Drop a TAdoConnection and a TAdoQuery on the form. Also drop a TButton on it.
Connect the TAdoQuery to the TAdoConnection.
Set the TAdoConnection to connect to your Sql Server
in the code below, modify the scUse constant to refer to your target database
and scCreateView to refer to a non-existing view and a valid table in your db. This is to ensure that the Create View will not fail because the view already exists or the table does not exist.
Run the code and you should get a complaint that the Create View refers to an invalid object name, because the AdoConnection isn't connected to your target database when the Create View executes.
Then change KeepConnection to True and retest. This time the view should be successfully created.
const
scUse ='use m4common';
scCreateView = 'create view vwtest as select * from btnames';
procedure TForm1.Button1Click(Sender: TObject);
begin
AdoConnection1.DefaultDatabase := 'Master';
// The point of changing the default database in the line above
// is to ensure for demo purposes that the 'Use' statement
// is necessary to change to the correct db.
AdoConnection1.KeepConnection := False; // retest with this set to True
AdoQuery1.SQL.Text := scUse;
AdoQuery1.ExecSQL;
AdoQuery1.SQL.Text := scCreateView;
AdoQuery1.ExecSQL;
end;
So, the point of KeepConnection is to allow you to execute two or more Sql batches in the same connection context and to satisfy the server that the Create View statement (or similar) can be the first statement in the batch, and at the same time, the database to which the Create View applies is the same one as you "USEd" in the previous batch.
Secondly, your AssignFile ... while not Eof is unnecessarily long-winded and error-prone. Try something like this instead:
var
TL : TStringList;
begin
TL := TStringList.Create;
try
for j := 0 to filesToExecute.Count - 1 do
begin
sql := 'USE ' + DBName + #13#10;
sql := sql + ' GO ' + #13#10;
TL.BeginUpdate; // for better performance with longer scripts
TL.LoadFromFile(filesToExecute[j]);
TL.Insert(0, sql);
TL.EndUpdate;
// execute the Sql here etc
end;
finally
TL.Free;
end;
end;
Note that I've reversed the order of your #10 and #13 so that it is correct.
The other point is that as the Lines property of your Memo already has a LoadFromFile method, you don't really need my temporary TStringList, TL, because you could do the load into your memo (though you might prefer to keep the two uses separate).
You can drop the connection each time and specify the USE in the params. In fact if you are clever you can establish one connection per db and parallelize the whole thing.

Is it possible one procedure can call another procedure inside a package body?

Question-: Is it possible one procedure can call another procedure inside a package body?(Let's i want to declare two procedure inside a package body(Not in Package Specification). P1 & P2 are my procedures.Is it possible P1 can call P2 inside that package body?)
Yes, otherwise packages would lose a lot of their functionality. A procedure that is defined in the package body but not in the specification is private, and cannot be invoked from outside the package; but of course can be from within.
However, the called procedure has to be defined before the caller within the package body source:
create or replace package p42 as
end p42;
/
Package P42 compiled
create or replace package body p42 as
procedure p2 is
begin
null;
end;
procedure p1 is
begin
p2;
end;
end p42;
/
Package body P42 compiled
No errors.
If you have them the other way around it won't compile:
create package body p42 as
procedure p1 is
begin
p2;
end;
procedure p2 is
begin
null;
end;
end p42;
/
Package body P42 compiled
Errors: check compiler log
Errors for PACKAGE BODY STACKOVERFLOW.P42:
LINE/COL ERROR
-------- ------------------------------------------
5/3 PLS-00313: 'P2' not declared in this scope
5/3 PL/SQL: Statement ignored
If you don't want to define them in that order, or can't because you have lots of procedures with dependencies that can't be organised, you can also declare the called procedure any time before it's used - still within the body, and using the same syntax as you would for a public procedure in a specification:
create or replace package body p42 as
-- declare private procedure so it can be called before it is fully defined
procedure p2;
procedure p1 is
begin
p2;
end;
procedure p2 is
begin
null;
end;
end p42;
/
Package body P42 compiled
No errors.
This is summarised in the documentation:
Before invoking a procedure, you must declare and define it. You can either declare it first (with procedure_declaration) and then define it later in the same block, subprogram, or package (with procedure_definition) or declare and define it at the same time (with procedure_definition).
As with a package specification, if your procedure has arguments then the declaration has to exactly match. And this is also all true for functions as well, of course.
Yes, it's possible for a procedure to call another.

Error handling when executing SQL script with multiple INSERT, UPDATE,... statements using ADO in Delphi

I experience difficulties with the Delphi/ADO error handling when executing an SQL script containing more than one INSERT, UPDATE,... statement. Only when the first SQL statement of the script fails, I get an exception in Delphi. If the first statement passes, there will be no exception in Delphi, whatever happens further in the script.
This is the Delphi code I use:
var
DataSet: TADOQuery;
begin
...
try
DataSet.Close;
DataSet.ParamCheck := true;
DataSet.SQL.LoadFromFile(FileName);
DataSet.Prepared := true;
try
DataSet.ExecSQL;
finally
DataSet.Close;
end;
except
on E: Exception do
Logging.AddText(E.ClassName + ' error raised when executing ' + FileName + '. Message: ' + E.Message);
end;
...
end;
For testing I used this simple script:
INSERT INTO TESTTABLE
VALUES ('John', 24);
INSERT INTO TESTTABLE
VALUES ('Ed', '32');
where TESTTABLE is just a simple table containing two columns: Name NVARCHAR(50) and Age INT.
When you replace, for example, 24 by 'twentyfour' in the first INSERT statement and run the script with the Delphi code, Delphi/ADO will raise an exception. But when you replace, for example, 32 by 'thirtytwo' in the second INSERT statement, there will be no exception.
I tried to solve this by putting the script in a stored procedure "dbo.ErrorHandling" and sending
EXEC dbo.ErrorHandling
to ADO, but it did not help.
CREATE PROCEDURE dbo.ErrorHandling
AS
BEGIN
INSERT INTO TESTTABLE
VALUES ('John', 24);
INSERT INTO TESTTABLE
VALUES ('Ed', '32');
END
I can solve the problem by using TRY and CATCH in the script, and letting it log the errors to a LOGGING table. Delphi can check this table for new errors after each script execution.
However, is it possible to catch all SQL server errors in Delphi, or do I have to execute INSERTS, UPDATES,... one by one?
I use Delphi XE6 and SQLServer 2008 R2
I would t take a look at the Errors Collection of the AdoConnection.
TAdoConnection.Errors
I've never found any way of reliably processing several queries at once and sensibly detecting problems. I believe the correct solution is to execute the statements one at a time.
This is code from one of my programs doing exactly what J__ describes in his comment. It processes an SQL Server style script with a GO after every statement. You could replace the "GO" detector with some other indication of the end of a statement. It batches the whole thing up as a single all-or-nothing transaction. The last finally has some code to save the last query into an on screen memo so you can see what failed if there was an exception.
procedure TDupFrame.LoadButtonClick(Sender: TObject);
var
Query: TADOQuery;
Reader: TStreamReader;
Line: string;
begin
Query := TADOQuery.Create(nil);
try
Query.Connection := ConfModule.ADOConnection;
Query.Connection.BeginTrans;
if ScriptOpenDialog.Execute(Self.Handle) then
begin
Reader := TStreamReader.Create(ScriptOpenDialog.FileName);
try
Query.SQL.BeginUpdate;
while not Reader.EndOfStream do
begin
Line := Reader.ReadLine;
if not SameText(Line, 'GO') then
begin
Query.SQL.Add(Line);
end
else
begin
Query.SQL.EndUpdate;
Query.ExecSQL;
Query.SQL.Clear;
Query.SQL.BeginUpdate;
end;
end;
Query.SQL.EndUpdate;
if Query.SQL.Count > 0 then Query.ExecSQL;
finally
Reader.Free;
end;
Query.Connection.CommitTrans;
end;
finally
SQLMemo.Lines.Assign(Query.SQL);
// rollback if we have missed the commit (ie an exception occurred)
if Query.Connection.InTransaction then
Query.Connection.RollbackTrans;
Query.Free;
end;
end;
You need to configure the TADOQuery or TADOCommand with the ExecuteNoRecords Option:
Query := TADOQuery.Create(nil);
Query.ExecuteOptions := [eoExecuteNoRecords];
It's the same in .NET. If you use ExecuteReader() or ExecuteScalar() on a SqlCommand, no exception is thrown if the first of a sequence of statements within a single command succeeds - no matter of how many subsequent statements fail.
If you call ExecuteNonQuery() however, a proper exception is being thrown in any case.

Resources