I'm working on pl/sql procedure(X) at database(A) it must pass parameters to another procedure(Y) at database(B) both databases are oracle,
I already create database link from (A) to (B) and it's work fine.
Problem is when procedure(X) executed it return ORA-02064 ( distributed operation not supported) which it caused by miss co-ordering in commit mechanism between (A) and (B).
I try to use pragma autonomous_transaction inside the calling as follow:
begin
v_sql :=
'declare
pragma autonomous_transaction;
begin '
|| schema_name
|| '.procedure_Y#'
|| db_link
|| '(:1, :2, :3); end;'
execute immediate v_sql using in x , in y, out z;
end;
I already use pragma in the declaration of procedure(X) and there is no change still get same error,
knowing that I can't modify any object in database(B).
Related
I have a volatile table (say, vtTempTableForPI) that I am creating inside a procedure. Once the volatile table gets created, I am inserting rows into it.
Once I have records in the volatile table, I need to take MAX(ModifiedDatetime) from this volatile table (vtTempTableForPI) into a local variable. However, I am unable to find a way to do so.
Here is a piece of code from my procedure for some context:
SELECT MAX(ModifiedDatetime)
FROM vtTempTableForPI
INTO lvMaxUpdateDateTime
WHERE Template = 'Schedule_Stream'
;
And then I want to use this local variable to insert into another volatile table, like below:
SET lvQuery = '
INSERT INTO vtScheduleVersionUpdatedTime
SELECT
''Schedule_Stream''
, ''' || CAST(lvMaxUpdateDateTime AS VARCHAR(19)) || '''
';
EXECUTE IMMEDIATE lvQuery;
I can't use this cursor on volatile table since its definition will not be present in DBC when we compile the procedure, so it will give an error. If I try to use dynamic query in order to avoid this error, I run into another error like below:
SET lvMaxModifiedDateQuery = '
SELECT MAX(ModifiedDatetime)
FROM vtTempTableForPI
WHERE Template = ''Schedule_Tank''
';
PREPARE stMaxModifiedDateQuery03 FROM lvMaxModifiedDateQuery;
OPEN crGetMaxModifiedDate03;
FETCH crGetMaxModifiedDate03 INTO lvMaxUpdateDateTime;
--CLOSE crGetMaxModifiedDate;
SET lvQuery = '
INSERT INTO vtScheduleVersionUpdatedTime
SELECT
''Schedule_Tank''
, ''' || CASt(lvMaxUpdateDateTime AS VARCHAR(19)) || '''
';
EXECUTE IMMEDIATE lvQuery;
Below is the error that I am getting:
Failure occured while Creating Dynamic Query
SQL State:T7688,
SQL Code:7688,
SQL SESSION: 252898254,
Execution Start Time:2019-01-13 21:44:44,
Execution End Time:2019-01-13 21:44:54,
ERROR Message: Error occurred generating Evl code for dynamic fetch.
Need help!
What's your Teradata release? This is working fine for me:
REPLACE PROCEDURE SP_test(
IN v_CALC_BASIS VARCHAR(100))
BEGIN
DECLARE lvMaxUpdateDateTime INT;
DECLARE v_LogStmt VARCHAR(5000);
SELECT Max(ModifiedDatetime)
FROM vtTempTableForPI
INTO lvMaxUpdateDateTime
WHERE Template = 'Schedule_Stream'
;
END;
CALL SP_test ('bla');
CALL Failed. [3807] SP_TEST:Object 'vtTempTableForPI' does not exist.
Either create the Volatile Table before you compile it or simply switch to a Global Temporary Table instead (recommended) .
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.
I'd like to create users through procedure on another database via database link.I am getting error while executing procedure.
Here are the code which i used.
create or REPLACE PROCEDURE hostname
(host_name in varchar2,user_name in VARCHAR2, pass_word in VARCHAR2,
table_space in varchar2,pro_file in varchar2)
as
db_link_name varchar2(30);
begin
select db_link into db_link_name from all_db_links where host=host_name;
EXECUTE IMMEDIATE 'dbms_utility.exec_ddl_statement#'||db_link_name||('CREATE USER '||user_name||' IDENTIFIED BY '||pass_word||'
DEFAULT TABLESPACE '||table_space||' PROFILE '|| pro_file||' ACCOUNT UNLOCK');
EXECUTE IMMEDIATE 'dbms_utility.exec_ddl_statement#'||db_link_name||
('GRANT CONNECT,RESOURCE,EXECUTE_CATALOG_ROLE,Create table,create session,create view,create sequence,create procedure,create job,create synonym TO '||user_name);
end;
/
execute hostname('orcl1','rahul1','rahul','users','default');
Error: ORA-00900: invalid SQL statement ORA-06512: at
"SYS.HOSTNAME", line 6 ORA-06512: at line 1
00900. 00000 - "invalid SQL statement"
*Cause:
*Action:
I think you may have got a little confused by some of the advice provided by the commenter mustaccio. What he meant to say is that the SQL strings in your EXECUTE IMMEDIATE statements need to use PL/SQL blocks in order to call stored procedures.
In other words, instead of writing
EXECUTE IMMEDIATE 'dbms_utility.exec_ddl_statement ... ';
you should write
EXECUTE IMMEDIATE 'BEGIN dbms_utility.exec_ddl_statement ... ; END;';
For your procedure, I would recommend doing introducing a local variable for the strings you are passing to EXECUTE IMMEDIATE. These can be notoriously tricky to get right, and if you've assigned them to a local variable you can easily output them using dbms_output.put_line. In fact, I did exactly this while fixing up the problems in your procedure. I would recommend continuing to do this if you wish to modify or extend your procedure.
Anyway, here's what I ended up with, which appeared to work:
create or REPLACE PROCEDURE hostname
(host_name in varchar2,user_name in VARCHAR2, pass_word in VARCHAR2,
table_space in varchar2,pro_file in varchar2)
as
db_link_name varchar2(30);
l_ddl_sql varchar2(4000);
begin
select db_link into db_link_name from all_db_links where host=host_name;
l_ddl_sql := 'begin dbms_utility.exec_ddl_statement#'||db_link_name||'(''CREATE USER '||user_name||' IDENTIFIED BY '||pass_word||'
DEFAULT TABLESPACE '||table_space||' PROFILE '|| pro_file||' ACCOUNT UNLOCK''); END;';
EXECUTE IMMEDIATE l_ddl_sql;
l_ddl_sql := 'begin dbms_utility.exec_ddl_statement#'||db_link_name||
'(''GRANT CONNECT,RESOURCE,EXECUTE_CATALOG_ROLE,Create table,create session,create view,create sequence,create procedure,create job,create synonym TO '||user_name || '''); END;';
EXECUTE IMMEDIATE l_ddl_sql;
end;
/
(For readers who don't use Delphi: Although the following is couched in terms of Delphi coding, my actual technical question isn't Delphi-specific, but is about how to find out how the Sql Server will "understand" a TransactSql batch submitted to it. A "TAdoQuery" is a Delphi class which basically wraps an ADO Command and a RecordSet and submits a TSql batch to a Sql Server. Usually, using the TAdoQuery, the batch is a single statement, but my q is specifically concerned with the possibility that the batch may contain more than one statement.)
Suppose I have a TAdoQuery whose Sql.Text contains a TransactSql batch comprising
one or more statements S1[...Sn].
What I'm trying to do is to find out without executing the batch whether a) the first (or only) statement, S1, in the batch will return a result set (even if empty) e.g by dint of it being a SELECT statement or an invocation of a stored procedure or table function, or whatever, AND b) how many statements the server thinks there are in the batch.
Regular users of Delphi's TAdoQuery will know that it's easy but slightly messy to test whether the first (or only) statement in a batch returns a result set just by calling TAdoQuery.Open. If it does, then it retrieves that result set, but if doesn't, then calling .Open will provoke an exception.
So, instead, I do something like this:
type
TMyDataSet = class(TDataSet);
procedure TForm1.Button1Click(Sender: TObject);
begin
if AdoQuery1.Active then
AdoQuery1.Close;
AdoQuery1.FieldDefs.Clear;
TMyDataSet(AdoQuery1).OpenCursor(True);
AdoQuery1.FieldDefList.Update;
//AdoQuery1.FieldList.Update;
//Listbox1.Items.Assign(AdoQuery1.FieldList);
end;
The call to .OpenCursor with its InfoQuery param set to true causes the AdoQuery's FieldDefs
to be populated iff the first statement in its Sql would return a result set, but, unlike calling .Open, it will not cause the batch to be executed.
So far, so good. Here's my question:
How (via the AdoQuery or otherwise) do I get the Sql Server to tell me how many
statements it thinks the batch contains? (I think I may have stumbled on a way (subject to a lot more testing), but am interested in whether anyone knows an "official" technique for doing this.)
Btw, for now I'm using an antique (Sql Server 2000!) server via its OleDB driver for Sql Server.
You can use the SET SHOWPLAN_ALL function to analyze the statement in SQL server instead of executing the query. Please note that you can't use this functionality with TADOQuery but only with a TADOCommand object (Like TADOConnection.Execute).
test table:
USE [TestCustomer]
GO
CREATE TABLE [dbo].[Tbl_test](
[Id] [int] NULL,
[col1] [varchar](50) NULL
) ON [PRIMARY]
GO
small demo program:
program SO27007086;
{$APPTYPE CONSOLE}
uses
ActiveX,
Db,
AdoDb,
SysUtils;
var
DbConn : TADOConnection;
function GetNumberOfStatements(SQLQuery: String): Integer;
var
Rs : _RecordSet;
LastId : Integer;
begin
Result := 0;
LastId := -1;
DbConn.Execute('SET SHOWPLAN_ALL ON');
Rs := DbConn.Execute(SQLQuery, cmdText, []);
while not Rs.EOF do
begin
if Rs.Fields['StmtId'].Value <> LastId then
begin
Inc(Result);
LastId := Rs.Fields['StmtId'].Value;
end;
if Rs.Fields['Parent'].Value = 0 then
Writeln(Rs.Fields['Type'].Value);
Rs.MoveNext;
end;
DbConn.Execute('SET SHOWPLAN_ALL OFF');
end;
begin
try
try
CoInitialize(nil);
DbConn := TADOConnection.Create(nil);
try
DbConn.ConnectionString := 'Provider=SQLOLEDB;Integrated Security=SSPI;Initial Catalog=TestCustomer;Data Source=localhost\SQLEXPRESS;MARS Connection=True;';
DbConn.Connected := True;
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test'));
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test DELETE FROM Tbl_test WHERE 1 = 2'));
Writeln(GetNumberOfStatements('SELECT * FROM Tbl_test INSERT INTO Tbl_Test (Id, Col1) VALUES (3, ''c''),(4, ''d'')'#13#10'DELETE FROM Tbl_test WHERE 1 = 2'));
finally
DbConn.Free;
end;
finally
CoUninitialize;
end;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
Readln;
end.
Output:
SELECT
1
SELECT
DELETE
2
SELECT
INSERT
DELETE
3
i have 3 oracle databases: db1, db2, db3.
I have created database links from db1 to db2 and db3, called db002link and db003link.
Now i have a procedure which takes as input a date and takes different actions on tables according to that input. One of them though requires to connect to one of the db2 or db3 databases. Before the execution of the procedure, i don't not know to which one, as it depends on the data gathered by the procedure itself in previous steps.
So i need to concatenate some variables to create the db link and then connect through it.
i have the variable v_dbnumber which is varchar(3) and looks like '003' for instance and is the result of a select from a table. I have tried the following:
v_dbconn := 'db'||v_dbnumber||'link'
But then the next step, select * from s1.t1#v_dbconn gets a compilation error for the procedure: ORA-04052, ORA-00604, ORA-02019 referring to the non existing connection. But the object is shown as:
#v_dbconn instead of #db003link.
Can somebody please help me with this?
If you need the statement to be dynamic, you'd need to use dynamic SQL.
If you just want to open a cursor using a dynamically generated SQL statement, you can do something like
DECLARE
l_sql_stmt varchar2(1000);
l_dblink varchar2(100) := 'db002link';
l_rc sys_refcursor;
BEGIN
l_sql := 'select * from s1.t1#' || l_dblink;
open l_rc for l_sql;
END;
Normally, though, you're doing something with the data that you're selecting. That would generally involve using either dbms_sql or EXECUTE IMMEDIATE to execute the statement and fetch data into some local variable or collection. Assuming that the table definitions are the same in each of the databases, you could do something like
EXECUTE IMMEDIATE l_sql
BULK COLLECT INTO <<some appropriate collection>>
My solution is very similar to Justin's, though I am using a procedure with dynamic sql.
APPS#tst> CREATE OR REPLACE PROCEDURE test_dblink(
2 db_link VARCHAR2 )
3 AS
4 v_sql VARCHAR2(500);
5 v_test dual.dummy%TYPE;
6 BEGIN
7 v_sql := 'select dummy from dual#'|| db_link;
8 EXECUTE IMMEDIATE v_sql INTO v_test;
9 DBMS_OUTPUT.PUT_LINE(v_test);
10 END;
11 /
Procedure created.
APPS#tst> commit;
Commit complete.
APPS#tst>
APPS#tst>
APPS#tst>
APPS#tst> begin
2 test_dblink('db003link');
3 end;
4 /
X
PL/SQL procedure successfully completed.
This does not have error handling and it assumes one record will be returned (typically not a good assumption).