How can I use %TYPE with Oracle associative arrays? - arrays

I have declared an associative array type in some Oracle package header like:
TYPE ParamArray IS TABLE OF VARCHAR2(4096) INDEX BY VARCHAR2(512);
In my package body I would like to iterate through the array without repeating the string sizes to avoid mismatches when updating the package header.
My try was:
PROCEDURE IterateArray( Params ParamArray )
AS
v_ParamName ParamArray%TYPE;
BEGIN
v_ParamName := Params.First;
WHILE v_ParamName IS NOT NULL LOOP
-- do something with the array entry
v_ParamName := Params.Next(v_ParamName);
END LOOP;
END;
But this didn't work on my Oracle 10g test server.

You can define you own type to handle the varchar2 and the use your type, with no need to repeat the size.
For example:
CREATE OR REPLACE PACKAGE testpck AS
SUBTYPE myType IS VARCHAR2(4096); /* a type for the values */
SUBTYPE myIndexType is VARCHAR2(512); /* a type for the index */
TYPE ParamArray IS TABLE OF myType
INDEX BY myIndexType;
PROCEDURE IterateArray(Params ParamArray);
END;
CREATE OR REPLACE PACKAGE BODY testpck AS
PROCEDURE IterateArray(Params ParamArray) AS
v_ParamName myType;
v_index myIndexType;
BEGIN
v_index := Params.FIRST;
WHILE v_index IS NOT NULL
LOOP
-- do something with the array entry
v_ParamName := Params(v_index);
dbms_output.put_line('value of ' || v_index || ' is ' || v_ParamName);
v_index := Params.NEXT(v_index);
END LOOP;
END;
END;
The call:
declare
myArray testpck.ParamArray;
myValue testpck.myType;
myIndex testpck.myIndexType;
begin
myIndex := 'ONE';
myValue := 'VALUE OF ONE';
myArray(myIndex) := myValue;
--
myIndex := 'TWO';
myValue := 'VALUE OF TWO';
myArray(myIndex) := myValue;
--
testpck.IterateArray(myArray);
end;

Related

Delphi TFDStoredProc parameter default values missing (MS SQL)

I am migrating from ADO to FireDAC. After replacing TADOStoredProc to TFDStoredProc I have the following issue. My _OpenStp procedure opens a stored procedure having default values in its parameter list, and I don't want to pass all those parameters. E.g.
CREATE PROCEDURE [dbo].[usp_SearchDocument]
#User_Id INT
, #Window_Id INT = 10
, #Page INT = 1
...
The core of my procedure:
procedure _OpenStp(
const AConnection: TFDConnection;
var AStp: TFDStoredProc;
const AStpName: string;
const AParamNameA: array of string;
const AParamValueA: array of Variant);
var
i: Integer;
begin
if AStp <> nil then
begin
if AStp.Active then
AStp.Close;
end
else
AStp := TFDStoredProc.Create(nil);
AStp.Connection := AConnection;
AStp.StoredProcName := AStpName;
AStp.Prepare;
for i := Low(AParamNameA) to High(AParamNameA) do
AStp.Params.ParamByName(AParamNameA[i]).Value := AParamValueA[i];
AStp.Open;
end;
The Delphi code of the call:
_OpenStp(SomeConnection, SomeStp, 'usp_SearchDocument',
['User_Id'], [150]);
According to SQL Server Profiler the call was:
exec [dbo].[usp_SearchDocument]
#User_Id=150,
#Window_Id=NULL,
#Page=NULL
TFDStoredProc.Prepare doesn't seem to query the default values of the sp parameters. When I was using the ADO counterpart of my _OpenStp procedure, the TADOStoredProc.Parameters.Refresh method did that job:
procedure _OpenStp(
const AConnection: TADOConnection;
var AStp: TADOStoredProc;
const AStpName: string;
const AParamNameA: array of string;
const AParamValueA: array of Variant);
begin
if AStp <> nil then
begin
if AStp.Active then
AStp.Close;
end
else
AStp := TADOStoredProc.Create(nil);
AStp.Connection := AConnection;
AStp.ProcedureName := AStpName;
AStp.Parameters.Refresh;
for i := 0 to Length(AParamNameA) - 1 do
AStp.Parameters.ParamByName(AParamNameA[i]).Value := AParamValueA[i];
AStp.Open;
end;
SQL Server Profiler:
exec usp_SearchDocument 150,default,default
Unfortunately it isn't an option to rewrite the code to pass all of the parameters, I have to rely on sp parameter default values. Is there a way to modify the FireDAC version of my _OpenStp procedure to achieve this goal?
Edit: I don't even have information about the type of the parameters (see the _OpenStp procedure), I only know their names and the values to be set, so I can't create the TFDParams programmatically.
Edit#2: An EArgumentOutOfRangeException was thrown after deleting the unnecessary parameters:
for i := AStp.ParamCount - 1 downto 0 do
if AStp.Params[i].Name <> '#RETURN_VALUE' then
begin
ExistsInArray := False;
for j := Low(AParamNameA) to High(AParamNameA) do
if Char.ToLower(AStp.Params[i].Name) = Char.ToLower(Format('#%s', [AParamNameA[j]])) then
begin
ExistsInArray := True;
Break;
end;
if not ExistsInArray then
AStp.Params.Delete(i);
end;

Use SQL Server table-valued parameters without creating a type on the SQL Server

I'm trying to pass several values on a single parameter (table-valued parameter) and it works fine following the sample
C:\Users\Public\Documents\Embarcadero\Studio\21.0\Samples\Object Pascal\Database\FireDAC\Samples\DBMS Specific\MSSQL\TVP
SQL:
create type TVPType as table(Code integer, Name varchar(100), RegDate datetime, Notes varchar(max))
go
create table TVPTab(Code integer, Name varchar(100), RegDate datetime, Notes varchar(max))
go
Delphi:
procedure TForm1.btnQryManualSetupClick(Sender: TObject);
var
oDS: TFDMemTable;
i: Integer;
begin
Start;
FDQuery2.SQL.Text := 'insert into TVPTab (Code, Name, RegDate, Notes) ' +
'select Code, Name, RegDate, Notes from :t';
oDS := TFDMemTable.Create(nil);
oDS.FieldDefs.Add('Code', ftInteger);
oDS.FieldDefs.Add('Name', ftString, 100);
oDS.FieldDefs.Add('RegDate', ftTimeStamp);
oDS.FieldDefs.Add('Notes', ftMemo);
FDQuery2.Params[0].DataTypeName := 'TVPType';
FDQuery2.Params[0].AsDataSet := oDS;
(FDQuery2.Params[0].AsDataSet as TFDMemTable).EmptyDataSet;
for i := 1 to C_Recs do
with FDQuery2.Params[0].AsDataSet do begin
Append;
Fields[0].AsInteger := i;
Fields[1].AsString := 'str' + IntToStr(i * 10);
Fields[2].AsDateTime := Now() + i;
Fields[3].AsString := StringOfChar('x', 1000);
Post;
end;
FDConnection1.StartTransaction;
FDQuery2.Execute;
FDConnection1.Commit;
Done('TVP Qry manual setup');
end;
The problem with this code is that it forces me to create a type (in this case the TVPType type) on the server, and I would like to use these table-valued parameters without having to create any object on the server.
Can it be done ?. I have tried defining the parameter as ftDataset but I get an error: The data type READONLY cannot be found.
procedure TForm1.btnQryManualSetupClick(Sender: TObject);
var
oDS: TFDMemTable;
i: Integer;
begin
Start;
FDQuery2.SQL.Text := 'insert into TVPTab (Code, Name, RegDate, Notes) ' +
'select Code, Name, RegDate, Notes from :t';
oDS := TFDMemTable.Create(nil);
oDS.FieldDefs.Add('Code', ftInteger);
oDS.FieldDefs.Add('Name', ftString, 100);
oDS.FieldDefs.Add('RegDate', ftTimeStamp);
oDS.FieldDefs.Add('Notes', ftMemo);
//FDQuery2.Params[0].DataTypeName := 'TVPType';
FDQuery2.Params[0].DataType := ftDataset;
FDQuery2.Params[0].AsDataSet := oDS;
(FDQuery2.Params[0].AsDataSet as TFDMemTable).EmptyDataSet;
for i := 1 to C_Recs do
with FDQuery2.Params[0].AsDataSet do begin
Append;
Fields[0].AsInteger := i;
Fields[1].AsString := 'str' + IntToStr(i * 10);
Fields[2].AsDateTime := Now() + i;
Fields[3].AsString := StringOfChar('x', 1000);
Post;
end;
FDConnection1.StartTransaction;
FDQuery2.Execute;
FDConnection1.Commit;
Done('TVP Qry manual setup');
end;
No. If your multiple values are just a single row you can look into passing them as a comma-separated string and then use the STRING_SPLIT function, or using temp or permanent tables to store multi-row values.

RETURN cannot have a parameter in function with OUT parameters

I did the db migration from oracle to pgsql and got the code like below:
CREATE OR REPLACE FUNCTION PKG_UTIL_BD_LOGISTICS_getsignerinfo (
i_opCode T_MQ_LOGIC_TRACK_HEAD_LOG.LP_CODE%TYPE, i_remark T_MQ_LOGIC_TRACK_HEAD_LOG.REMARK%TYPE, i_acceptTime T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNED_TIME%TYPE, i_signer T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNER%TYPE, i_lpcode T_MQ_LOGIC_TRACK_HEAD_LOG.LP_CODE%TYPE,
o_signer OUT T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNER%TYPE, o_signerTime OUT T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNED_TIME%TYPE, o_status OUT T_MQ_LOGIC_TRACK_HEAD_LOG.STATUS%TYPE )
RETURNS RECORD AS $body$
DECLARE
v_signer T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNER%TYPE;
v_signerTime T_MQ_LOGIC_TRACK_HEAD_LOG.SIGNED_TIME%TYPE;
v_status T_MQ_LOGIC_TRACK_HEAD_LOG.STATUS%TYPE;
BEGIN
IF i_lpcode = 'SF' THEN
IF i_opCode = '8000' THEN
IF POSITION(':back' in i_remark) > 0 THEN
v_status := '3';
ELSE
v_status := '7';
v_signerTime := i_acceptTime;
v_signer := SUBSTR(i_remark, POSITION(':' in i_remark) + 1);
END IF;
ELSIF i_opCode = '9999' THEN
v_status := '3';
ELSIF i_opCode = '80' THEN
v_status := '7';
v_signerTime := i_acceptTime;
ELSIF i_opCode = 70 THEN
v_status := i_opCode;
ELSE
v_status := '1';
END IF;
ELSE
IF i_opCode = 'signed' THEN
v_signerTime := i_acceptTime;
v_signer := i_signer;
v_status:='7';
ELSE
v_status:='1';
END IF;
END IF;
o_status := v_status;
o_signer := v_signer;
o_signerTime := v_signerTime;
RETURN 1;
EXCEPTION
WHEN OTHERS THEN
RAISE EXCEPTION '%', 'PKG_UTIL_BD_LOGISTICS.getSignerInfo fetch parameters' || i_remark || 'value error:' || SQLERRM;
END;
$body$
LANGUAGE PLPGSQL
SECURITY DEFINER
when i executed the code,i got the error:RETURN cannot have a parameter in function with OUT parameters.Can someone help?I am new to pgsql.
The result of function with OUT parameters is specified by values of OUT parameters and only by these values. Although syntax of OUT parameters is similar between PostgreSQL and Oracle, a implementation is maximally different.
Oracle uses reference for OUT parameters - so you can write something like:
CREATE FUNCTION foo(a int, OUT b int)
RETURN boolean IS
BEGIN
b := a;
RETURN true;
END;
This function returns boolean value and as "side" effect it modifies second parameter passed by reference.
PostgreSQL doesn't support passing parameters by reference. All parameters are passed by value only. When You use OUT parameter, then there is not passed reference, but the returned values is taken from result composite. Result composite is composed only from OUT parameters. There are no space for some any other. So code:
CREATE OR REPLACE FUNCTION foo(a INT, OUT b int)
RETURNS boolean AS $$
BEGIN
b := a;
RETURN true;
END; $$ LANGUAGE plpgsql
is invalid, because real result of foo function is scalar int value, what is in contradiction with declared boolean. RETURN true is wrong too, because result is based on OUT parameters only, and then RETURN should be without any expression.
Equivalent translation of function foo from Oracle to Postgres is:
CREATE OR REPLACE FUNCTION foo(a INT, OUT b int, OUT result boolean)
RETURNS record AS $$
BEGIN
b := a;
result := true;
RETURN;
END; $$ LANGUAGE plpgsql
Easy rule - when function has OUT variables in Postgres, then RETURN statement is used only for ending execution - not for returned value specification. This values is based by OUT parameters.
Consider the following example :
CREATE OR REPLACE FUNCTION generate_taskcode(IN classIdVar character varying,IN appIdInt integer, OUT pagecoderet character varying) RETURNS character varying AS $$
DECLARE
updateCode character varying;
BEGIN
update mytable set lastcodeused = to_char(cast(lastcodeused as INTEGER)+1, 'FM999999999999999999') where
classid = classIdVar and appid= appIdInt
RETURNING concat(pageName,lastcodeused) as pageName
into updateCode;
return updateCode;
END;
$$ LANGUAGE plpgsql
notice the "updatecode" in between begin and end. If you'll try to execute this you'll get the same error as you've mentioned as you can't return variable "** return updateCode**" when you've OUT in function parameters. So the correct def would be as follows :
CREATE OR REPLACE FUNCTION generate_taskcode(IN classIdVar character varying,IN appIdInt integer, OUT pagecoderet character varying) RETURNS character varying AS $$
DECLARE
updateCode character varying;
BEGIN
update mytable set lastcodeused = to_char(cast(lastcodeused as INTEGER)+1, 'FM999999999999999999') where
classid = classIdVar and appid= appIdInt
RETURNING concat(pageName,lastcodeused) as pageName
into updateCode;
pagecoderet = updateCode;
return;
END;
$$ LANGUAGE plpgsql

Oracle pl/sql map with value is array

I need create associative array where key is VARCHAR(20) and value is array of varchars.
For example:
customer_by_locations: {
"London": {
"John Doe", "Samantha Simpson", "Nicolas Darcula"
},
"Stambul": {
"Abdula Ibn Rahim", "Another Abdula"
}
}
I have created query:
declare
type customers is varray(10) of varchar2(20);
type locations is table of customers index by varchar2(20);
list_locations locations;
list_customers varchar(20);
begin
list_locations('London') := ("John Doe", "Samantha Simpson", "Nicolas Darcula");
list_locations('Stambul') := ("Abdula Ibn Rahim", "Another Abdula");
for i in 1 .. list_locations loop
DBMS_OUTPUT.PUT_LINE('Total ' || list_locations(i));
//Something do
end loop;
end;
But I have error
PLS-00382: expression is of wrong type
Please, tell me, how i will declare array as value and assign values in it in oracle pl/sql.
declare
type customers is varray(10) of varchar2(20);
type locations is table of customers index by varchar2(20);
list_locations locations;
list_customers varchar(20);
v_location varchar2(20);
begin
list_locations('London') := customers('John Doe', 'Samantha Simpson', 'Nicolas Darcula');
list_locations('Stambul') := customers('Abdula Ibn Rahim', 'Another Abdula');
v_location := list_locations.first;
loop
exit when v_location is null;
for i in 1 .. list_locations(v_location).count loop
dbms_output.put_line(v_location||': '||list_locations(v_location)(i));
end loop;
v_location := list_locations.next(v_location);
end loop;
end;
/

How do I get the types and values of an array of const?

In my Delphi development,
I want to pass an "array of const"(that can contains class too) to a procedure, and in procedure loop on elements and detect type of element as bellow.
Procedure Test(const Args : array of const);
begin
end;
and in my code call it with some variables
Procedure Test();
begin
cls := TMyObject.create;
i := 123;
j := 'book';
l := False;
Test([i,j,l, cls, 37.8])
end;
How loop on sent array elements and detect it's type?
Assuming you are using Unicode Delphi (otherwise, you have to alter the string case):
procedure test(const args: array of const);
var
i: Integer;
begin
for i := low(args) to high(args) do
case args[i].VType of
vtInteger: ShowMessage(IntToStr(args[i].VInteger));
vtUnicodeString: ShowMessage(string(args[i].VUnicodeString));
vtBoolean: ShowMessage(BoolToStr(args[i].VBoolean, true));
vtExtended: ShowMessage(FloatToStr(args[i].VExtended^));
vtObject: ShowMessage(TForm(args[i].VObject).Caption);
// and so on
end;
end;
procedure TForm4.FormCreate(Sender: TObject);
begin
test(['alpha', 5, true, Pi, Self]);
end;
for I := Low(Args) to High(Args) do
case TVarRec(Args[I]).VType of
vtInteger:
...
end;

Resources