Sort issues with TArray<Myrectype> for large numbers - arrays

What is the reason why TArray.Sort<Mytype> does not work when I have large numbers in the comparison?
My code is as follows (Delphiy Tokyo):
Interface
Type
RCInd = record
Num : Integer;
Ger : Integer;
Confirmed : Boolean;
Total : Real;
End;
TArrInd = TArray<RCInd>;
Procedure SortInd (Var PArrayInd : TArrInd);
Implementation
Procedure SortInd (Var PArrayInd : TArrInd);
begin
TArray.Sort<RCInd>( PArrayInd,TComparer<RCInd>.Construct
( function (Const Rec1, Rec2 : RCInd) : Integer
begin
Result := - ( Trunc(Rec1.Total) - Trunc(Rec2.Total) );
end )
);
end;
......
When the values of Rec1.Total and Rec2.Total are within a Integer limits, this Sort works fine, BUT when values exceed Integer limits Sort procedure does not work! It generates a non sorted set of data in PArrayInd .
What is going on here?

The problem is one of overflow. The real values overflow the integer type.
The compare function is meant to return negative to indicate less than, positive to. Indicate greater than and zero to indicate equal. Your using arithmetic is the cause of your problem, leading as it does to overflow. Instead use comparison operators.
function(const Rec1, Rec2: RCInd): Integer
begin
if Rec1.Total < Rec2.Total then
Result := 1
else if Rec1.Total > Rec2.Total then
Result := -1
else
Result := 0;
end;
The problem in this question is trying to fit a real value into an integer, but even if you have integer data then arithmetic should not be used for compare functions. Consider the expression
Low(Integer) - 1
This results in overflow. As a general principle, always use comparison operators to implement compare functions.

Related

Recursive SQL function returning array has extra elements when self-invocation uses array function

Goal: write a function in PostgreSQL SQL that takes as input an integer array whose each element is either 0, 1, or -1 and returns an array of the same length, where each element of the output array is the sum of all adjacent nonzero values in the input array having the same or lower index.
Example, this input:
{0,1,1,1,1,0,-1,-1,0}
should produce this result:
{0,1,2,3,4,0,-1,-2,0}
Here is my attempt at such a function:
CREATE FUNCTION runs(input int[], output int[] DEFAULT '{}')
RETURNS int[] AS $$
SELECT
CASE WHEN cardinality(input) = 0 THEN output
ELSE runs(input[2:],
array_append(output, CASE
WHEN input[1] = 0 THEN 0
ELSE output[cardinality(output)] + input[1]
END)
)
END
$$ LANGUAGE SQL;
Which gives unexpected (to me) output:
# select runs('{0,1,1,1,1,0,-1,-1,-1,0}');
runs
----------------------------------------
{0,1,2,3,4,5,6,0,0,0,-1,-2,-3,-4,-5,0}
(1 row)
I'm using PostgreSQL 14.4. While I am ignorant of why there are more elements in the output array than the input, the cardinality() in the recursive call seems to be causing it, as also does using array_length() or array_upper() in the same place.
Question: how can I write a function that gives me the output I want (and why is the function I wrote failing to do that)?
Bonus extra: For context, this input array is coming from array_agg() invoked on a table column and the output will go back into a table using unnest(). I'm converting to/from an array since I see no way to do it directly on the table, in particular because WITH RECURSIVE forbids references to the recursive table in either an outer join or subquery. But if there's a way around using arrays (especially with a lack of tail-recursion optimization) that will answer the general question (But I am still very very curious why I'm seeing the extra elements in the output array).
Everything indicates that you have found a reportable Postgres bug. The function should work properly, and a slight modification unexpectedly changes its behavior. Add SELECT; right after $$ to get the function to run as expected, see Db<>fiddle.
A good alternative to a recursive solution is a simple iterative function. Handling arrays in PL/pgSQL is typically simpler and faster than recursion.
create or replace function loop_function(input int[])
returns int[] language plpgsql as $$
declare
val int;
tot int = 0;
res int[];
begin
foreach val in array input loop
if val = 0 then tot = 0;
else tot := tot + val;
end if;
res := res || tot;
end loop;
return res;
end $$;
Test it in Db<>fiddle.
The OP wrote:
this input array is coming from array_agg() invoked on a table column and the output will go back into a table using unnest().
You can calculate these cumulative sums directly in the table with the help of window functions.
select id, val, sum(val) over w
from (
select
id,
val,
case val
when 0 then 0
else sum((val = 0)::int) over w
end as series
from my_table
window w as (order by id)
) t
window w as (partition by series order by id)
order by id
Test it in Db<>fiddle.

Warning: function result variable of a managed type does not seem to be initialized

The task I have requires me to create two routines one of which reads in data from a terminal and the other outputs data to the terminal, and another two routines which utilize an array to loop through these two routines to perform them multiple times.
The issue I am having is that the terminal crashes after one run through of the ReadComputer function instead of looping multiple times. The compiler is also providing me the following warning:
"Warning: function result variable of a managed type does not seem to be initialized"
although after extensive research and due to the fact that no one uses pascal I cannot find a solution. Any help is much appreciated! :)
I have provided a copy of my code here for reference:
program CompupterProgram;
uses TerminalUserInput;
type
Computer = Record
id: integer;
manafacturer: String;
year: integer;
warranty: integer;
end;
type Computers = Array of Computer;
function ReadComputer(): Computer;
begin
ReadComputer.id := ReadInteger('PLease Enter Computer Id:');
ReadComputer.manafacturer := ReadString('PLease Enter Computer Manafacturer:');
ReadComputer.year := ReadInteger('PLease Enter Computer Year:');
ReadComputer.warranty := ReadInteger('PLease Enter Computer Warranty:');
result := ReadComputer;
end;
procedure WriteComputer(c: Computer);
begin
WriteLn('Computer ID: ', c.id);
WriteLn('Computer Manafacturer ', c.manafacturer);
WriteLn('Computer Year ', c.year);
WriteLn('Computer Warranty ', c.warranty);
ReadLn();
end;
function ReadAllComputers(count: Integer): Computers;
var i: Integer;
begin
for i := 0 to count do
begin
ReadAllComputers[i] := ReadComputer();
end;
result := ReadAllComputers;
end;
procedure WriteAllComputers(computerArray: Computers);
var i: Integer;
begin
for i:= 0 to (length(computerArray)) do
begin
WriteComputer(computerArray[i]);
end;
end;
procedure Main();
var computers: Array of Computer;
index: Integer;
begin
computers := ReadAllComputers(3);
WriteAllComputers(computers);
end;
begin
Main();
end.
Computers is a dynamic array, and you need to set its length before use in ReadAllComputers with SetLength().
All dynamic arrays are zero based, so you need to count from zero to Length(aDynArray)-1 in a couple of places. Or use the High(aDynArray) function to express the highest possible value of it's index.
Note: The Result use in ReadComputer is superfluous. Either use the function name or the Result variable to return the function result. The latter is to prefer, since code will be more clear.
In freepascal the Result variable is defined only in ObjFPC or Delphi mode.

set cap length for concatenated string

EDIT: I'm having alot of trouble formatting this code for some reason please bear with me. Also i'm aware some code is missing. This is just one portion of the code.
I am simulating a batch load that is run nightly to do some load testing. The problem I face is that my auto-generated PK's exceed the columns datalength after 100 or so inserts. How would I cap off my Strings without violating the unique constraint while inserting around 20,000 rows per table. my goal is to get rid of the random strings due to a change in requirements.Below is the portion of code I'm having trouble with.
declare
l_cnt integer := 0;
t_cnt integer := 0;
c_cnt integer := 0;
f_cnt integer := 0;
i integer := 0;
TYPE T_EMPL_NO IS TABLE OF VARCHAR2(1000) INDEX BY BINARY_INTEGER;
TAB_EMPL_NO T_EMPL_NO;
TAB_SEC_PK T_EMPL_NO;
TAB_THR_PK T_EMPL_NO;
TAB_FTH_PK T_EMPL_NO;
begin
dbms_output.put_line('START LOAD TEST');
LOOP
i := i + 1;
TAB_EMPL_NO(l_cnt) := 'JB'||i;
TAB_SEC_PK(t_cnt) := dbms_random.string('L',6);
TAB_THR_PK(c_cnt) := dbms_random.string('L',1);
TAB_FTH_PK(f_cnt) := dbms_random.string('L',20);
Insert into AOMS.PARTS_MONTH_CLOSE(
NAMES OF COLUMNS HERE
) Values (
TAB_EMPL_NO(l_cnt),
TAB_SEC_PK(t_cnt),
TAB_THR_PK(c_cnt),
TAB_FTH_PK(f_cnt)
);
l_cnt := l_cnt + SQL%ROWCOUNT;
EXIT WHEN l_cnt = 100; -- change to record count 22k
END LOOP;
dbms_output.put_line('P2ACCTMO :Rows inserted: ' || l_cnt);
END;
/
As a bonus If I wanted the script to run for an hour but not exceed the amount of records that insert during a loop how would I do that? Thanks so much for any help.
To define a numeric datatype with a given range (let's say 1 to 1000, for the sake of argument) you can use a user-defined PL/SQL subtype. To do this you'd use something like
SUBTYPE MY_ZERO_TO_1K_SUBTYPE IS NUMBER(4,0) RANGE 0..1000;
and then define variables of this subtype just as you would any other variable:
nLimited_number MY_ZERO_TO1K_SUBTYPE;
You can set a variable of this type to 0, 1, 2, ..., 998, 999, 1000. However, if you set it to a negative value or a value greater than 1000 an ORA-06502: PL/SQL: numeric or value error exception will be raised.
I would suggest to go for SEQUENCES rather than using Random string
generation. In this way you wlll not face unique key violation and
also you can set the max seq limit to the datatype limit of the
column. Hope this information helps.

PostgreSQL PL/pgSQL random value from array of values

How can I declare an array like variable with two or three values and get them randomly during execution?
a := [1, 2, 5] -- sample sake
select random(a) -- returns random value
Any suggestion where to start?
Try this one:
select (array['Yes', 'No', 'Maybe'])[floor(random() * 3 + 1)];
Updated 2023-01-10 to fix the broken array literal. Made it several times faster while being at it:
CREATE OR REPLACE FUNCTION random_pick()
RETURNS int
LANGUAGE sql VOLATILE PARALLEL SAFE AS
$func$
SELECT ('[0:2]={1,2,5}'::int[])[trunc(random() * 3)::int];
$func$;
random() returns a value x where 0.0 <= x < 1.0. Multiply by 3 and truncate it with trunc() (slightly faster than floor()) to get 0, 1, or 2 with exactly equal chance.
Postgres indexes are 1-based by default (as per SQL standard). This would be off-by-1. We could increment by 1 every time, but for efficiency I declare the array index to start with 0 instead. Slightly faster, yet. See:
Normalize array subscripts so they start with 1
The manual on mathematical functions.
PARALLEL SAFE for Postgres 9.6 or later. See:
PARALLEL label for a function with SELECT and INSERT
When to mark functions as PARALLEL RESTRICTED vs PARALLEL SAFE?
You can use the plain SELECT statement if you don't want to create a function:
SELECT ('[0:2]={1,2,5}'::int[])[trunc(random() * 3)::int];
Erwin Brandstetter answered the OP's question well enough. However, for others looking for understanding how to randomly pick elements from more complex arrays (like me some two months ago), I expanded his function:
CREATE OR REPLACE FUNCTION random_pick( a anyarray, OUT x anyelement )
RETURNS anyelement AS
$func$
BEGIN
IF a = '{}' THEN
x := NULL::TEXT;
ELSE
WHILE x IS NULL LOOP
x := a[floor(array_lower(a, 1) + (random()*( array_upper(a, 1) - array_lower(a, 1)+1) ) )::int];
END LOOP;
END IF;
END
$func$ LANGUAGE plpgsql VOLATILE RETURNS NULL ON NULL INPUT;
Few assumptions:
this is not only for integer arrays, but for arrays of any type
we ignore NULL data; NULL is returned only if the array is empty or if NULL is inserted (values of other non-array types produce an error)
the array don't need to be formatted as usual - the array index may start and end anywhere, may have gaps etc.
this is for one-dimensional arrays
Other notes:
without the first IF statement, empty array would lead to an endless loop
without the loop, gaps and NULLs would make the function return NULL
omit both array_lower calls if you know that your arrays start at zero
with gaps in the index, you will need array_upper instead of array_length; without gaps, it's the same (not sure which is faster, but they shouldn't be much different)
the +1 after second array_lower serves to get the last value in the array with the same probability as any other; otherwise it would need the random()'s output to be exactly 1, which never happens
this is considerably slower than Erwin's solution, and likely to be an overkill for the your needs; in practice, most people would mix an ideal cocktail from the two
Here is another way to do the same thing
WITH arr AS (
SELECT '{1, 2, 5}'::INT[] a
)
SELECT a[1 + floor((random() * array_length(a, 1)))::int] FROM arr;
You can change the array to any type you would like.
CREATE OR REPLACE FUNCTION pick_random( members anyarray )
RETURNS anyelement AS
$$
BEGIN
RETURN members[trunc(random() * array_length(members, 1) + 1)];
END
$$ LANGUAGE plpgsql VOLATILE;
or
CREATE OR REPLACE FUNCTION pick_random( members anyarray )
RETURNS anyelement AS
$$
SELECT (array_agg(m1 order by random()))[1]
FROM unnest(members) m1;
$$ LANGUAGE SQL VOLATILE;
For bigger datasets, see:
http://blog.rhodiumtoad.org.uk/2009/03/08/selecting-random-rows-from-a-table/
http://www.depesz.com/2007/09/16/my-thoughts-on-getting-random-row/
https://blog.2ndquadrant.com/tablesample-and-other-methods-for-getting-random-tuples/
https://www.postgresql.org/docs/current/static/functions-math.html
CREATE FUNCTION random_pick(p_items anyarray)
RETURNS anyelement AS
$$
SELECT unnest(p_items) ORDER BY RANDOM() LIMIT 1;
$$ LANGUAGE SQL;

Delphi: using BigInts from a database

I´m using Delphi 7 with devart dbExpress to connect to SQLServer.
The problem is that when I add a bigInt field to a ClientQuery it comes as TFMTBCDField.
And the TFMTBCDField don´t have a method to get the 64 bit value.
I can use the Field.AsVariant or the StrToInt64(Field.AsString) to pick this 64 bits value.
Is there a better way to pick/use this value?
Maybe add a TLargeIntField manualy to dataset, set it's FieldName to appropriate name and use such code:
SomeInt64Value := (qryMyQuery.FieldByName('blahblah') as TLargeIntField).AsLargeInt;
Do not remember exactly types, but it worked this way in Delphi6.
You can convert the BCD to Variant and than to int64 with VarFMTBcdCreate from unit FMTBcd.
Try this:
var value64 : int64;
...
value64 := VarFMTBcdCreate(Field.Value);
The data format for TFMTBCDField is the TBcd record from the FMTBcd unit. You can get that raw value by reading the field's Value or AsBCD properties.
Depending on what you need the value for, TBcd might be sufficient. That is, you might not need to convert it to an Int64. The FMTBcd unit provides functions to add, subtract, multiply, and divide TBcd values.
The unit provides no conversions to Int64. There are conversions to Variant, string, Currency, Double, and Integer. If we were going to write an Int64 conversion, the Integer conversion is probably a good place to start, so let's take a look at how it's implemented:
function BcdToInteger(const Bcd: TBcd; Truncate: Boolean = False): Integer;
var
ABcd: TBcd;
begin
if Truncate and (BcdScale(Bcd) > 0) then
NormalizeBcd(Bcd, ABcd, Bcd.Precision, 0)
else
ABcd := Bcd;
Result := StrToInt(BcdToStr(ABcd));
end;
So, the VCL itself doesn't provide any more direct way to convert a TBcd to an Integer than to go through string. Therefore, it looks like your idea to call StrToInt64 on the string version of the field is fine.
I dont have Delphi 7 installed here anymore, but looking in the help, I see you can get as Float (Double), like this:
function GetFieldAsInt64(Field: TField): Int64;
begin
Result:= Int64(Round(Field.GetAsFloat));
end;
And then, call the function:
var
Value: Int64;
begin
Value:= GetFieldAsInt64(MyFMTBCDField);
end;

Resources