i have following structure of a procedures inside a package:
PROCEDURE proc_All
(
start IN date := null,
end IN date := null,
cursor1 OUT sys_refcursor,
cursor1 OUT sys_refcursor,
cursor1 OUT sys_refcursor
);
PROCEDURE proc_1
(
start1 IN date := null,
end1 IN date := null,
cursor1_1 OUT sys_refcursor,
);
PROCEDURE proc_3
(
start2 IN date := null,
end2 IN date := null,
cursor1_2 OUT sys_refcursor,
);
PROCEDURE proc_3
(
start3 IN date := null,
end3 IN date := null,
cursor1_3 OUT sys_refcursor,
);
Now my requirement is that i want first procedure proc_All to call all three next procedure, as i want proc_All to be called by program. how can i pass variables to a procedure when calling inside other procedure (weather they are IN or OUT type)
can anyone please help.. thanx in advance
This should work (though I am not sure you are allowed to use the keyword "end" as a parameter name):
PROCEDURE proc_All
(
start IN date := null,
end IN date := null,
cursor1 OUT sys_refcursor,
cursor1 OUT sys_refcursor,
cursor1 OUT sys_refcursor
)
IS
BEGIN
proc_1
(
start1 => start,
end1 => end,
cursor1_1 => cursor1
);
proc_2
(
start2 => start,
end2 => end,
cursor1_2 => cursor2
);
proc_3
(
start3 => start,
end3 => end,
cursor1_3 => cursor3
);
END;
Related
I am trying to update a stored procedure in an Oracle package. Originally, SP was set to assume a single item id would be passed to it but now there is possibility of set of item ids to be passed in the form of comma separated string.
I am trying to split this string into a set of strings and loop through it. I managed to split the string but can't seem to be able to assign it to a variable that I can use to loop through.
PROCEDURE USPGETSOMTHING
(
IPSITEMIDS VARCHAR2,
....,
CUR_OUT OUT GETDATACURSOR
)
IS
...;
V_NEWITEM VARCHAR2(4000);
V_NEWITEMLIST VARCHAR2(4000);
-- Tried declaring an array; not good!
type array_t is table of varchar(20);
V_NEWITEMLIST array_t;
BEGIN
-- string passed to SP is in the form of "'LP060500105','EM060500103'"
V_NEWITEM := REPLACE(IPSITEMIDS, '''', '');
-- This comma-separated string will actually be V_NEWITEM
SELECT REGEXP_SUBSTR('LP060500105,EM060500103', '[^,]+', 1, LEVEL) INTO V_NEWITEMLIST FROM DUAL
CONNECT BY instr('LP060500105,EM060500103', ',',1, LEVEL-1) > 0;
The above does not work, I get:
ORA-01422: exact fetch returns more than requested number of rows
Next issue I need to tackle is returning the result in a data table.
Currently, I use:
IF (...)
....
LVSQUERY:='SELECT '''|| V_NEWITEM ||''' AS ITEMID, '''|| V_OUTCOME ||''' AS OUTCOME FROM DUAL';
END IF;
OPEN CUR_OUT FOR LVSQUERY;
How would I make sure that resulting data table will have as many rows as number of item IDs, the result of splitting the item id string?
You could try a CURSOR and loop through the records.
PROCEDURE USPGETSOMTHING
(
IPSITEMIDS VARCHAR2,
....,
CUR_OUT OUT GETDATACURSOR
)
IS
...;
V_NEWITEM VARCHAR2(4000);
V_NEWITEMLIST VARCHAR2(4000);
CURSOR cur IS
SELECT REGEXP_SUBSTR('LP060500105,EM060500103', '[^,]+', 1, LEVEL) V_NEWITEMLIST
FROM DUAL
CONNECT BY instr('LP060500105,EM060500103', ',',1, LEVEL-1) > 0;
-- Tried declaring an array; not good!
type array_t is table of varchar(20);
V_NEWITEMLIST array_t;
BEGIN
-- string passed to SP is in the form of "'LP060500105','EM060500103'"
V_NEWITEM := REPLACE(IPSITEMIDS, '''', '');
FOR rec IN cur LOOP
--- whatever you want to do referencing the field in the cursor by rec.V_NEWITEMLIST
END LOOP;
You can also try the OPEN ... FOR
PROCEDURE uspgetsomthing
( IPSITEMIDS VARCHAR2,
CUR_OUT OUT SYS_REFCURSOR
)
IS
BEGIN
OPEN CUR_OUT FOR
SELECT REGEXP_SUBSTR('LP060500105,EM060500103', '[^,]+', 1, LEVEL) V_NEWITEMLIST
FROM DUAL
CONNECT BY instr('LP060500105,EM060500103', ',',1, LEVEL-1) > 0;
END; -- Procedure
You want to use BULK COLLECT INTO rather than just INTO:
SELECT REGEXP_SUBSTR('LP060500105,EM060500103', '[^,]+', 1, LEVEL)
BULK COLLECT INTO V_NEWITEMLIST
FROM DUAL
CONNECT BY instr('LP060500105,EM060500103', ',',1, LEVEL-1) > 0;
However, a pure PL/SQL implementation that does not need a context switch to SQL and does not use (slow) regular expressions is:
CREATE OR REPLACE FUNCTION split_String(
i_str IN VARCHAR2,
i_delim IN VARCHAR2 DEFAULT ','
) RETURN SYS.ODCIVARCHAR2LIST DETERMINISTIC
AS
p_result SYS.ODCIVARCHAR2LIST := SYS.ODCIVARCHAR2LIST();
p_start NUMBER(5) := 1;
p_end NUMBER(5);
c_len CONSTANT NUMBER(5) := LENGTH( i_str );
c_ld CONSTANT NUMBER(5) := LENGTH( i_delim );
BEGIN
IF c_len > 0 THEN
p_end := INSTR( i_str, i_delim, p_start );
WHILE p_end > 0 LOOP
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, p_end - p_start );
p_start := p_end + c_ld;
p_end := INSTR( i_str, i_delim, p_start );
END LOOP;
IF p_start <= c_len + 1 THEN
p_result.EXTEND;
p_result( p_result.COUNT ) := SUBSTR( i_str, p_start, c_len - p_start + 1 );
END IF;
END IF;
RETURN p_result;
END;
/
(You can replace SYS.ODCIVARCHAR2LIST with a user-defined collection if you prefer.)
and then you can call that as:
V_NEWITEMLIST := split_string( 'LP060500105,EM060500103' ); -- or V_NEWITEM
When I execute the following code I get an error when executing the last script.
The error is:
"Cannot open the file "c:\FiredacDemo\las.sql"
I cannot understand why it executes fine named scripts #fdrop_dables, #first, #second and only for the very last it tries to executes instead an sql file.
procedure TForm1.btnMasterScriptClick(Sender: TObject);
begin
with FDScript.SQLScripts do begin
Clear;
with Add do begin
Name := 'root';
SQL.Add('#drop_tables');
SQL.Add('#first');
SQL.Add('#second');
SQL.Add('#last');
end;
with Add do begin
Name := 'drop_tables';
SQL.Add('drop TABLE T1;');
SQL.Add('drop TABLE T2;');
SQL.Add('drop TABLE T3;');
SQL.Add('drop TABLE T4;');
end;
with Add do begin
Name := 'first';
SQL.Add('CREATE TABLE T1 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
SQL.Add('CREATE TABLE T2 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
end;
with Add do begin
Name := 'second';
SQL.Add('CREATE TABLE T3 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
SQL.Add('CREATE TABLE T4 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
end;
with Add do begin
Name := 'last';
SQL.Add(format('INSERT INTO T1 (ID, FNAME, CREATED) VALUES(%d, %s, current_timestamp);', [ 1, 'fabio', now]));
SQL.Add('commit;');
end;
end;
FDScript.ValidateAll;
FDScript.ExecuteAll;
end;
EDIT: This one works!
procedure TForm1.btnMasterScriptClick(Sender: TObject);
begin
FDScript.SQLScriptFileName := '';
FDScript.Arguments.Clear;
with FDScript.SQLScripts do begin
Clear;
with Add do begin
Name := 'root';
SQL.Add('#drop_tables');
SQL.Add('#first_script');
SQL.Add('#second_script');
SQL.Add('#third_script');
SQL.Add('#last_script');
SQL.Add('#'); // Added this -> see below
end;
with Add do begin
Name := 'drop_tables';
SQL.Add('drop TABLE T1;');
SQL.Add('drop TABLE T2;');
SQL.Add('drop TABLE T3;');
SQL.Add('drop TABLE T4;');
end;
with Add do begin
Name := 'first_script';
SQL.Add('CREATE TABLE T1 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
SQL.Add('CREATE TABLE T2 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
end;
with Add do begin
Name := 'second_script';
SQL.Add('CREATE TABLE T3 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
SQL.Add('CREATE TABLE T4 (ID INTEGER NOT NULL, NAME VARCHAR(50), CREATED TIMESTAMP);');
end;
with Add do begin
Name := 'third_script';
SQL.Add(Format('INSERT INTO T1 (ID, NAME) VALUES(%d, %s);', [ 1, '''fabio''']));
end;
with Add do begin
Name := 'last_script';
SQL.Add(Format('INSERT INTO T1 (ID, NAME) VALUES(%d, %s);', [ 2, '''stefano''']));
end;
with Add do begin // addes this (see above)
Name := ''; // please note the empty '' Name
end; //
end;
FDScript.ValidateAll;
FDScript.ExecuteAll;
end;
I want to store images in a database using sql but cant seem to get it to work:
qry.SQL.Clear;
qry.Sql.Add('update tbl set pic = :blobVal where id = :idVal');
qry.Parameters.ParamByName('idVal')._?:=1;
.Parameters has no .asinteger like .Param has but .Param isn't compatible with a TADOquery - to workaround I tried:
a_TParameter:=qry.Parameters.CreateParameter('blobval',ftBlob,pdinput,SizeOf(TBlobField),Null);
a_TParam.Assign(a_TParameter);
a_TParam.asblob:=a_Tblob;
qry.ExecSql;
This also doesnt work:
qry.SQL.Clear;
qry.Sql.Add('update tbl set pic = :blobVal where id = 1')
qry.Parameters.ParamByName('blobVal').LoadFromStream(img as a_TFileStream,ftGraphic);//ftblob
//or
qry.Parameters.ParamByName('blobVal').LoadFromFile('c:\sample.jpg',ftgrafic);//ftblob
qry.ExecSql;
Should be something like:
qry.Parameters.Clear;
qry.Parameters.AddParameter.Name := 'blobVal';
qry.Parameters.ParamByName('blobVal').LoadFromFile('c:\sample.jpg', ftBlob);
// or load from stream:
// qry.Parameters.ParamByName('blobVal').LoadFromStream(MyStream, ftBlob);
qry.Parameters.AddParameter.Name := 'idVal';
qry.Parameters.ParamByName('idVal').Value := 1;
qry.SQL.Text := 'update tbl set pic = :blobVal where id = :idVal';
qry.ExecSQL;
To read the BLOB back from the DB:
qry.SQL.Text := 'select id, pic from tbl where id = 1';
qry.Open;
TBlobField(qry.FieldByName('pic')).SaveToFile('c:\sample_2.jpg');
I'm using Lazarus, not Delphi, but I guess its usually the same syntax. If so, here's a slight improvement on kobiks suggestion:
Parameters are added automatically if the SQL.Text is assigned before trying to assign values to the parameters. Like this:
qry.Parameters.Clear;
qry.SQL.Text := 'update tbl set pic = :blobVal where id = :idVal';
qry.Parameters.ParamByName('blobVal').LoadFromFile('c:\sample.jpg', ftBlob);
qry.Parameters.ParamByName('idVal').Value := 1;
qry.ExecSQL;
I wrote this as an answer to this q,
Delphi save packed record as blob in a sql database
which is currently flagged as a duplicate, possibly incorrectly because the technique
used by the OP as described in comments appears to be correct. So, the cause of the problem may lie elsewhere.
If the Duplicate flag gets removed, I'll re-post this answer there.
The following code works fine for me against a Sql Server table defined as shown below.
The data from Rec1 is saved into the table and correctly read back into Rec2.
(* MS Sql Server DDL
CREATE TABLE [blobs] (
[id] [int] NOT NULL ,
[blob] [image] NULL ,
CONSTRAINT [PK_blobs] PRIMARY KEY CLUSTERED
(
[id]
) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
GO
*)
TForm1 = class(TForm)
ADOConnection1: TADOConnection;
qBlobInsert: TADOQuery;
qBlobRead: TADOQuery;
Button1: TButton;
procedure Button1Click(Sender: TObject);
[...]
type
TMyRecord = packed record
FontName: string[30];
FontSize: word;
FontColor: integer;
FontStyle: word;
Attachement: string[255];
URL: string[255];
end;
const
scInsert = 'insert into blobs(id, blob) values(:id, :blob)';
scSelect = 'select * from blobs where id = %d';
procedure TForm1.Button1Click(Sender: TObject);
begin
TestInsert;
end;
procedure TForm1.TestInsert;
var
Rec1,
Rec2 : TMyRecord;
MS : TMemoryStream;
begin
FillChar(Rec1, SizeOf(Rec1), #0);
FillChar(Rec2, SizeOf(Rec2), #0);
Rec1.FontName := 'AName';
Rec1.URL := 'AUrl';
MS := TMemoryStream.Create;
try
// Save Rec1 using an INSERT statement
MS.Write(Rec1, SizeOf(Rec1));
MS.Seek(0, soFromBeginning);
qBlobInsert.Parameters[0].Value := 1;
qBlobInsert.Parameters[1].LoadFromStream(MS, ftBlob);
qBlobInsert.SQL.Text := scInsert;
qBlobInsert.ExecSQL;
// Read saved data back into Rec2
qBlobRead.SQL.Text := Format(scSelect, [1]);
qBlobRead.Open;
MS.Clear;
TBlobField(qBlobRead.FieldByName('blob')).SaveToStream(MS);
MS.Seek(0, soFromBeginning);
MS.Read(Rec2, MS.Size - 1);
Caption := Rec2.FontName + ':' + Rec2.URL;
finally
MS.Free;
end;
end;
Extract from DFM
object qBlobInsert: TADOQuery
Connection = ADOConnection1
Parameters = <
item
Name = 'id'
DataType = ftInteger
Value = Null
end
item
Name = 'blob'
DataType = ftBlob
Value = Null
end>
Left = 56
Top = 32
end
I have a TOraQuery with SQL defined something like this
SELECT ML.ID, Ml.detail1, Ml.detail2
FROM MY_LIST ML
WHERE
ML.ID in (:My_IDS)
If I was to build this query on the fly, I'd naturally end up with something like this:
SELECT ML.ID, Ml.detail1, Ml.detail2
FROM MY_LIST ML
WHERE
ML.ID in (14001,14002,14003)
However, I'd like to pass in 14001,14002,14003 as a parameter.
myListQuery.Active := False;
myListQuery.ParamByName('My_IDS').AsString := '14001,14002,14003';
myListQuery.Active := True;
But of course that generates an ORA-01722: invalid number. Do I have any other option other than building up the query on the fly.
AFAIK, it is not possible directly.
You'll have to convert the list into a SQL list in plain text.
For instance:
function ListToText(const Args: array of string): string; overload;
var
i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+QuotedStr(Args[i])+',';
result[length(result)] := ')';
end;
function ListToText(const Args: array of integer): string; overload;
var
i: integer;
begin
result := '(';
for i := 0 to high(Args) do
result := result+IntToStr(Args[i])+',';
result[length(result)] := ')';
end;
To be used as such:
SQL.Text := 'select * from myTable where intKey in '+ListToText([1,2,3]);
SQL.Text := 'select * from myTable where stringKey in '+ListToText(['a','b','c']);
Or in your case:
myListQuery.SQL.Text := 'SELECT ML.ID, Ml.detail1, Ml.detail);
myListQuery.SQL.Add('FROM MY_LIST ML ');
myListQuery.SQL.Add('WHERE ');
myListQuery.SQL.Add('ML.ID in ') + ListToText([14001,14002,14003]);
You can do it but it requires some additional setup. Hopefully this works with your version of Oracle.
Create a table type
Create a function that converts your string to your table type
Use CAST in the subquery. Pass your value to the bind variable using the same thing you have in your code (i.e. ParamByName('').AsString).
create or replace type myTableType as table of varchar2 (255);
create or replace function in_list( p_string in varchar2 ) return myTableType as
l_string long default p_string || ',';
l_data myTableType := myTableType();
n number;
begin
loop
exit when l_string is null;
n := instr( l_string, ',' );
l_data.extend;
l_data(l_data.count) :=
ltrim( rtrim( substr( l_string, 1, n-1 ) ) );
l_string := substr( l_string, n+1 );
end loop;
return l_data;
end;
select * from THE ( select cast( in_list(:MY_BIND_VARIABLE) as mytableType ) from dual ) a
If this works for you, credit for the answer and example code goes to Tom Kyte from Oracle who runs asktom.com. https://asktom.oracle.com/pls/asktom/f?p=100:11:0%3a%3a%3a%3aP11_QUESTION_ID:210612357425
You can use a "Macro"
Not quite what I was looking for, but it's a hair closer to a parameter than building the SQL on the fly.
Create the TOraQuery like this
SELECT ML.ID, Ml.detail1, Ml.detail2
FROM MY_LIST ML
WHERE
ML.ID in (&My_IDS)
Now I can pass in 14001,14002,14003 as a macro.
myListQuery.Active := False;
myListQuery.MacroByName('My_IDS').value := '14001,14002,14003';
myListQuery.Active := True;
I have been banging my head against the desk with this. I have a simple table with 2 columns, like so:
CREATE TABLE [dbo].[MiscInitializers](
[PKey] [int] IDENTITY(1,1) NOT NULL,
[Value] [text] NULL,
CONSTRAINT [PK_MiscInitializers] PRIMARY KEY CLUSTERED
(
[PKey] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY] TEXTIMAGE_ON [PRIMARY]
I am trying to update a row with a procedure like this:
function TdmSQL.SetInitializer(Value: string; var Key: string): boolean;
const
UpdateCmd =
'update MiscInitializers set Value = :theValue where PKey = :theKey';
InsertCmd = 'insert into MiscInitializers (Value) values (:Param1)';
var
tmp: integer;
rsTmp: TADODataSet;
foo: TParameter;
sTmp: string;
begin
Result := false;
adoGenericCommand.CommandText := '';
adoGenericCommand.Parameters.Clear;
if Key <> '' then
begin
// attempt update
if not TryStrToInt(Key, tmp) then
exit;
adoGenericCommand.CommandText := UpdateCmd;
adoGenericCommand.Prepared := true;
adoGenericCommand.Parameters.Refresh;
// some debug stuff
sTmp := Format('Num Params: %d', [adoGenericCommand.Parameters.Count]);
ShowMessageBox(sTmp);
for tmp := 0 to adoGenericCommand.Parameters.Count - 1 do
begin
sTmp := Format('Param %d: Name %s',
[tmp, adoGenericCommand.Parameters.Items[tmp].Name]);
ShowMessageBox(sTmp);
end;
// end debug stuff
foo := adoGenericCommand.Parameters.ParamByName('theValue');
foo.Value.AsString := Value;
foo := adoGenericCommand.Parameters.ParamByName('theKey');
foo.Value := Key;
rsTmp.Recordset := adoGenericCommand.Execute;
Result := rsTmp.RecordCount = 1;
exit;
// etc
What I see happening (with those debug messagebox calls) is that the update command gets 2 parameters, but their names are Param1 and Param2, not theValue and theKey.
Is there a way to set up the parameters at runtime so the ParamByName calls will work with the names I actually want, rather than the Param*N* that I'm getting?
You can use ParseSQL to generate the Parameters
const
UpdateCmd = 'update MiscInitializers set Value = :theValue where PKey = :theKey';
var
ds: TADODataSet;
I: Integer;
begin
ds := TADODataSet.Create(nil);
try
ds.CommandText := UpdateCmd;
ds.Parameters.ParseSQL(ds.CommandText, True);
for I := 0 to ds.Parameters.Count - 1 do
ShowMessage(ds.Parameters.Items[I].name);
finally
ds.Free;
end;
end;
Don't call Refresh on the 'Parameters' after you assign the 'CommandText'. When you call 'Refresh', the VCL turns to the provider for parameter information, and if the returned information does not contain parameter names then the VCL makes up them on the fly.