Is there any way to have sum of values of associate array in PL/SQL. normally the code is like this:
FOR i IN a.FIRST .. a.LAST
LOOP
IF a (i).weight > 0
THEN
flag := FALSE;
END IF;
END LOOP;
but it should be a way to do it without loop and using sum.
something like:
IF SUM(a.weight) > 0
THEN
flag := FALSE;
END IF;
Actually - normally the code above is wrong for looping through associative arrays. Try this.
declare
type t_array_rec is record(
weight number);
type t_array is table of t_array_rec index by pls_integer;
arr t_array;
li_idx int;
li_summ int := 0;
begin
arr(1).weight := 100;
arr(3).weight:= 200;
arr(5).weight := 150;
li_idx := arr.first;
while (li_idx is not null) loop
li_summ := li_summ + nvl(arr(li_idx).weight, 0);
li_idx := arr.next(li_idx);
end loop;
dbms_output.put_line(li_summ);
end;
This will count the min value. Also You can kindly have a look at the another answer on SO sorting the assotiative arrays .
In case of nested tables, You can use the table functions.
Assuming You have the TTI type on the database layer.
CREATE OR REPLACE TYPE TTI as table of int
You can aggragate it's values like shown bellow
declare
arr TTI := TTI(100, 200, 150);
li_summ number;
begin
select sum(column_value) into li_summ from table(arr);
dbms_output.put_line(li_summ);
end;
Related
Suppose given array is
1,2,3,4,5,6
It should give me 6 as an output.
This is incomplete code as I am stuck here.
SET SERVEROUTPUT ON;
DECLARE
TYPE maxarray IS VARRAY(10) OF NUMBER NOT NULL;
v_element maxarray := maxarray(1,2,3,4,5,6);
v_max NUMBER;
BEGIN
END;
You can loop through the array and find the maximum.
DECLARE
TYPE maxarray IS VARRAY(10) OF NUMBER NOT NULL;
v_element maxarray := maxarray(1,5,4,3,6,2);
v_max NUMBER;
idx PLS_INTEGER;
BEGIN
idx := v_element.FIRST;
LOOP
EXIT WHEN idx IS NULL;
IF v_max IS NULL OR v_max < v_element(idx) THEN
v_max := v_element(idx);
END IF;
idx := v_element.NEXT(idx);
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_max);
END;
/
The code above will work for VARRAY, nested table or an associative array (regardless of whether they are dense or sparse).
If you are just using a dense array then you can use:
DECLARE
TYPE maxarray IS VARRAY(10) OF NUMBER NOT NULL;
v_element maxarray := maxarray(1,5,4,3,6,2);
v_max NUMBER;
BEGIN
FOR i IN 1 .. v_element.COUNT LOOP
IF v_max IS NULL OR v_max < v_element(i) THEN
v_max := v_element(i);
END IF;
END LOOP;
DBMS_OUTPUT.PUT_LINE(v_max);
END;
/
db<>fiddle here
Just to add a 21c version using the more convenient values of syntax:
for i in values of myarray loop
v_max := greatest(v_max,i);
end loop;
This assumes v_max has been initialised with some non-null value from myarray, such as v_max number := myarray(myarray.first) or -binary_float_infinity.
procedure TfrmMain.createpnl(i: integer);
var
j,c: integer;
begin
c:=5;
top := pnlResult1.top;
for j := 1 TO i do
if (arrFound[j] <> -1) or (arrFound[j] <> 0) then
begin
with dmAll do
begin
tblHouses.First;
while not tblHouses.Eof do
begin
if tblHouses['ID'] = arrFound[j] then
begin
if j > 1 then
itop := j * pnlResult1.top + (j - 1) * pnlResult1.Height;
SetLength(arrpnl, c);
SetLength(arrimg, c);
SetLength(arrlbl[1], c);
SetLength(arrlbl[2], c);
SetLength(arrlbl[3], c);
SetLength(arrlbl[4], c);
SetLength(arrlbl[5], c);
SetLength(arrlbl[6], c);
{ the violation usually happes at arrlbl[6] but it has been in the neighboring area before }
/// ///////////dupe panels
arrpnl[c] := TPanel.Create(frmMain);
with arrpnl[c] do
begin
Parent := scbMain;
Width := pnlResult1.Width;
Height := pnlResult1.Height;
left := pnlResult1.left;
top := itop;
Visible := true;
Color := pnlResult1.Color;
end;
frmMain.Position:=poScreenCenter;
/// //////////dupe photos
arrimg[c] := TImage.Create(frmMain);
with arrimg[c] do
begin
Parent := arrpnl[c];
Width := Image1.Width;
Height := Image1.Height;
left := Image1.left;
top := Image1.top;
end;
{ i cut some spaghetti code to shorten question }
tblPhotos.First;
while NOT tblPhotos.Eof do
begin
if tblPhotos['HouseID'] = tblHouses['ID'] then
if fileexists(tblPhotos['photo']) then
begin
arrimg[c].Picture.LoadFromFile(tblPhotos['photo']);
arrimg[c].Stretch := true;
end
else
begin
if fileexists('H:\v0.1\not-found-image-15383864787lu.jpg') then
begin
arrimg[c].Picture.LoadFromFile
('H:\v0.1\not-found-image-15383864787lu.jpg');
arrimg[c].Stretch := true;
end;
end;
tblPhotos.Next
end;
tblOwners.First;
while NOT tblOwners.Eof do
begin
if tblOwners['ID'] = tblHouses['hOwner'] then
begin
arrlbl[4][c].caption := 'Email: ' + tblOwners['oEmail'] + #10 +
'Cell number: ' + tblOwners['oCell'];
end;
tblOwners.Next;
end;
inc(c);
bFound := true;
end;
tblHouses.Next;
end;
end;
end;
end;
I search through a database to find guesthouses that match the search criteria in an above procedure.
The search returns an array (arrFound) filled with ID's of houses that match search criteria.
I then make all duplicate results -1 and create TPanels dynamically to return the guesthouses as browseable results in a TScrollBox.
The dynamic array in question (arrlbl) is a 2D array of TLabels with a predetermined first value and a dynamic second value (depending on the amount of results).
I used 1D arrays but that gave the same error and I have a similar procedure on another form that doesn't give any errors.
It seems like you got the indices wrong.
In general, a dynamic array with n elements has indices 0..n - 1.
For example, if a is a dynamic array with 5 elements, the elements are a[0], a[1], a[2], a[3], and a[4]. There is no a[5].
I need to remove all duplicate values from an array of integer, yet maintain the order of the elements:
Example:
10,20,20(duplicate),10(duplicate),50
Becomes:
10,20,50
Create a dictionary with Integer as the key. The value type is immaterial.
Iterate through the input array. For each value in the input array, check whether or not that value is in the dictionary.
If yes, this is a duplicate, discard.
If no, this is the first time the value has been encountered. Retain the value, and add it to the dictionary.
The point of the dictionary is that it can perform O(1) lookup.
In pseudocode:
var
arr: TArray<Integer>; // input and output
Dict: TDictionary<Integer, Integer>;
SrcIndex, DestIndex: Integer;
....
DestIndex := 0;
for SrcIndex := 0 to high(arr) do begin
Value := arr[SrcIndex];
if not Dict.ContainsKey(Value) then begin
arr[DestIndex] := arr[SrcIndex];
Dict.Add(Value, 0);
inc(DestIndex);
end;
end;
SetLength(arr, DestIndex);
Obviously you need to create, and destroy, the dictionary. I'm assuming you know how to do that. And I've opted to modify the array in place but you could equally create a new array if you prefer.
heres a version without dictionary.
procedure TForm1.RemoveDuplicates;
var
i,j,k,tot,mov:integer;
arr:array of integer;
begin
arr := [10,20,30,40,30,20,10,10,50,10,20,40];
tot := 0;
for i := 0 to length(arr)-1 do
begin
if i >= length(arr)-tot-1 then
break;
for j := i + 1 to length(arr)-1-tot do
begin
if j >= length(arr)-tot-1 then
break;
mov := 0;
while arr[i] = arr[j] do
begin
inc(mov);
arr[j] := arr[j+mov];
end;
tot := tot + mov;
if mov>0 then
for k := j+1 to length(arr)-1-tot do
arr[k] := arr[k+mov];
end;
end;
SetLength(arr,length(arr)-tot-1);
end;
I have multiple arrays and they all start with integer fields, from 1 up to 5 fields, and these are like indexes that need to be sorted, from min to max:
TArrayA = record
Field1:integer;
Field2:integer;
Field3:integer;
Field4:integer;
Field5:integer;
... //other fields, strings, integers... up to 50 fields
end;
ArrayA:=array of TArrrayA;
Currently I use this approach to sort:
// sort by Field1
top:=Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if ArrayA[look].Field1 < ArrayA[min].Field1 then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
// now sort by Field2
top:=Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if (ArrayA[look].Field1 = ArrayA[min].Field1) And
(ArrayA[look].Field2 < ArrayA[min].Field2) then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
This does the job. Although is a bit slow when I need to sort all 5 fields,
and this is how I do it, field by field, so I sort the array 5 times. Is there any better, faster way?
Here is example:
procedure TForm1.Button8Click(Sender: TObject);
type
TArrayA = record
Field1: integer;
Field2: integer;
Field3: integer;
Field4: integer;
Field5: integer;
end;
var
ArrayA: array of TArrayA;
vTmpRecord: TArrayA;
top, counter, min, max, look: integer;
i,t1,t2:integer;
begin
SetLength(ArrayA,100000);
for i := 0 to 99999 do
begin
ArrayA[i].Field1:=1+Random(100);
ArrayA[i].Field2:=1+Random(100);
ArrayA[i].Field3:=1+Random(100);
ArrayA[i].Field4:=1+Random(100);
ArrayA[i].Field5:=1+Random(100);
end;
t1:=GetTickCount;
// sort by Field1
top := Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if ArrayA[look].Field1 < ArrayA[min].Field1 then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
// sort by Field2
top := Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if (ArrayA[look].Field1 = ArrayA[min].Field1) and
(ArrayA[look].Field2 < ArrayA[min].Field2) then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
// sort by Field3
top := Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if (ArrayA[look].Field1 = ArrayA[min].Field1) and (ArrayA[look].Field2 = ArrayA[min].Field2) and
(ArrayA[look].Field3 < ArrayA[min].Field3) then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
// sort by Field4
top := Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if (ArrayA[look].Field1 = ArrayA[min].Field1) and (ArrayA[look].Field2 = ArrayA[min].Field2) and (ArrayA[look].Field3 = ArrayA[min].Field3) and
(ArrayA[look].Field4 < ArrayA[min].Field4) then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
// sort by Field5
top := Length(ArrayA);
for counter := 0 to top do
begin
min := counter;
for look := counter + 1 to top do
if (ArrayA[look].Field1 = ArrayA[min].Field1) and (ArrayA[look].Field2 = ArrayA[min].Field2) and (ArrayA[look].Field3 = ArrayA[min].Field3) and (ArrayA[look].Field4 = ArrayA[min].Field4) and
(ArrayA[look].Field5 < ArrayA[min].Field5) then
min := look;
vTmpRecord := ArrayA[min];
ArrayA[min] := ArrayA[counter];
ArrayA[counter] := vTmpRecord;
end;
t2:=GetTickCount;
Button8.Caption:=IntToStr(t2-t1);
end;
You can use built in Quick sort method for sorting arrays with your custom comparer:
uses
System.Math,
System.Generics.Defaults,
System.Generics.Collections;
TArray.Sort<TArrayA>(ArrayA, TComparer<TArrayA>.Construct( function(const Left, Right: TArrayA): Integer
begin
if Left.Field1 = Right.Field1 then
begin
if Left.Field2 = Right.Field2 then
begin
Result := CompareValue(Left.Field3, Right.Field3);
end
else Result := CompareValue(Left.Field2, Right.Field2);
end
else Result := CompareValue(Left.Field1, Right.Field1);
end
));
I added code only for first three fields, but you will get the picture how to build your own comparer for more fields.
The most important thing for you to do is to separate the sort algorithm from the data. That way you can write, or use, a single sort algorithm again and again with different data
The classic way to do that is to use a comparison sort. They are sort algorithms that require a compare function that compares two items and returns a negative integer for less than, a positive integer for greater than, and zero when equal.
So, let's start by demonstrating such a compare function for your data. Storing multiple fields as you have makes it hard to write a general purpose comparer. Better to put the fields in an array. Once you have done so you can do the compare lexicographically using iteration like this:
function CompareIntegerArray(const lhs, rhs: array of Integer): Integer;
var
i: Integer;
begin
Assert(Length(lhs) = Length(rhs));
for i := low(lhs) to high(lhs) do
if lhs[i] < rhs[i] then
exit(-1)
else if lhs[i] > rhs[i] then
exit(1);
exit(0);
end;
With a lexicographic order we first compare the primary field. If they differ we have our answer, otherwise we move on to the secondary field. And so on. Such an algorithm is well suited to iteration as demonstrated above.
This overcomes a significant weakness in your approach, by sorting the array once only.
Once you have this compare function you need to wrap it in an outer compare function that extracts data from the record fields and populates arrays. Perhaps along these lines:
type
TMyArray = array [1..5] of Integer;
function GetMyArray(const Value: TArrayA): TMyArray;
begin
Result[1] := Value.Field1;
Result[2] := Value.Field2;
....
end;
function MyCompare(const lhs, rhs: TArrayA): Integer;
begin
Result := CompareIntegerArray(
GetMyArray(lhs),
GetMyArray(rhs)
);
end;
Now, as promised, you can use this compare function with a general purpose sort like TArray.Sort<T> from Generics.Collections. This is an implementation of Quicksort, a comparison sort with average complexity of O(n log n). That will typically yield a huge benefit over your O(n2) bubble sort.
Life would be simpler if you could replace the record with an actual array. Another option that might be useful would be to add a method to the record that returned an array of integer ready for use in the lexicographic compare function.
To recap:
Separate data, comparison and sorting to facilitate re-use and clarity.
Use arrays to enable lexicographic compare to be implemented with a loop.
Use an efficient sort algorithm such as Quicksort.
I declare an associate array like this
TYPE t_a_rec IS RECORD
(
a_id NUMBER,
a_name VARCHAR2 (500),
assigned NUMBER:= 0
);
TYPE t_a IS TABLE OF t_a_rec
INDEX BY PLS_INTEGER;
a t_a;
and in a part of code i need to reset all the assigned values to zero. so my code is:
IF flag = TRUE
THEN
FOR i IN a.FIRST .. a.LAST
LOOP
a(i).assigned := 0;
END LOOP;
END IF;
Is there any way to reset without loop?
In example something like:
IF flag = TRUE
THEN
a(:).assigned := 0; or a(all).assigned := 0;
END IF;
In case of associative arrays You don't have any option other than looping throgh it to reset one value from Your arrays element. Please read through the processing associative arrays in loops. You are looping the arryas wrong. Please consider the commented code in this example.
declare
type t_a_rec is record(
a_id number,
a_name varchar2(500),
assigned number := 0);
type t_a is table of t_a_rec index by pls_integer;
a t_a;
li_idx int;
begin
a(1).a_id := 1;
a(1).a_name := 'Test Name';
a(1).assigned := 100;
a(3).a_id := 2;
a(3).a_name := 'Test Name 2';
a(3).assigned := 200;
/* for i in a.first .. a.last loop
dbms_output.put_line(a(i).assigned);
end loop;*/
li_idx := a.first;
while (li_idx is not null) loop
dbms_output.put_line(a(li_idx).assigned);
li_idx := a.next(li_idx);
end loop;
end;
If You remove the comment You will get error, because You don't have values in Your arrays with 2 index. The for loop will iterate from the first value of it's attribute(a.first - which is 1 in the example above) and increment it up to the second value of it's attibute(a.last whichi is the 3). So You will have 1,2,3 for the looping indexes and in the second step You will get no data found error for the array.