I'm trying populate a multidimensional array on PostgreSQL, but it not work. Below my code:
CREATE OR REPLACE FUNCTION teste()
RETURNS void AS
$BODY$
DECLARE
tarifas NUMERIC[7][24];
a INTEGER;
b INTEGER;
BEGIN
FOR a IN 0..6 LOOP
RAISE NOTICE 'TESTE TESTE %', a;
FOR b IN 0..23 LOOP
RAISE NOTICE 'tarifas[%][%] = 0;', a, b;
tarifas[a][b] = 0;
END LOOP;
END LOOP;
END
$BODY$
LANGUAGE plpgsql VOLATILE;
Postgres has a dedicated function for that purpose exactly: array_fill():
returns an array initialized with supplied value and dimensions,
optionally with lower bounds other than 1
Use it:
CREATE OR REPLACE FUNCTION teste()
RETURNS void AS
$func$
DECLARE
tarifas numeric[7][24] := array_fill(0, ARRAY[7,24]);
a int;
b int;
BEGIN
-- do something
END
$func$ LANGUAGE plpgsql;
Notes
Array dimensions in numeric[7][24] are just documentation. The manual:
The current implementation does not enforce the declared number of
dimensions either. Arrays of a particular element type are all
considered to be of the same type, regardless of size or number of
dimensions. So, declaring the array size or number of dimensions in
CREATE TABLE is simply documentation; it does not affect run-time behavior.
About the assignment operator in plpgsql: := or =:
The forgotten assignment operator "=" and the commonplace ":="
It's generally not possible to write to an array element directly. You can concatenate or append / prepend elements. Or assign an array as a whole. Details in the manual. But a statement like this is not possible:
tarifas[%][%] = 0
Default lower bound of an array is 1, not 0. But you can define arbitrary array dimension. Example:
SELECT '[2:3][2:4]={{7,7,7},{7,7,7}}'::int[]
Related
I want to sort a TArray containing entries of a self defined TPair<>. I tried to follow this one, but the compiler always complains that he needs an object record or clastype (E2018):
How to sort a generic array containing records.
My code:
type
TFailureEntry = TPair<System.Word, TMyFailureRecord>;
procedure TMyClass.GetFailureAbbreviations;
var
FailureArray: TArray<TFailureEntry>;
Comparison: TComparison<TFailureEntry>;
begin
// derrive the array contents from a dictionary
FailureArray := FFailureDictionary.ToArray;
Comparison :=
function(const Left, Right: TFailureEntry): Integer
begin
Result := Left.Key-Right.Key;
end;
FailureArray.Sort(TComparer<TFailureEntry>.Construct(Comparison));
end;
The compiler complains at the .sort call.
An array cannot have a method - in this case no Sort method. Use TArray.Sort instead:
TArray.Sort<TFailureEntry>(FailureArray, TComparer<TFailureEntry>.Construct(Comparison));
Does PostgreSQL have some way for me to find if an array is contained within another array, but with the same ordering?
For example, I want to know if array1 is within array2 with matching elements in the same order.
array1[1, 3, 6, 8]
array2[3, 8, 2, 9, 10, 1, 6]
Obviously not the case in the example, but is there a built-in method for this in PostgreSQL or should I create my own function?
Version of PostgreSQL is 9.6. The actual numbers the query will run on are bigints.
General case
All elements of the second array are in the first, too. In the same order, but gaps are allowed.
I suggest this polymorphic PL/pgSQL function:
CREATE OR REPLACE FUNCTION array_contains_array_in_order(arr1 ANYARRAY
, arr2 ANYARRAY
, elem ANYELEMENT = NULL)
RETURNS bool AS
$func$
DECLARE
pos int := 1;
BEGIN
FOREACH elem in ARRAY arr2
LOOP
pos := pos + array_position(arr1[pos:], elem); -- see below
IF pos IS NULL THEN
RETURN FALSE;
END IF;
END LOOP;
RETURN true; -- all elements found in order
END
$func$ LANGUAGE plpgsql IMMUTABLE COST 3000;
As #a_horse commented, we can omit the upper bound in array subscripts to mean "unbounded" (arr1[pos:]). In older versions before 9.6 substitute with arr1[pos:2147483647] - 2147483647 = 2^31 - 1 being the theoretical max array index, the greatest signed int4 number.
This works for ...
any 1-dimensional array type, not just integer[].
arrays with NULL values, thanks to array_position() which works for NULL, too.
arrays with duplicate elements.
only for default array subscripts starting with 1. You can easily cover non-standard subscripts if needed:
Normalize array subscripts for 1-dimensional array so they start with 1
About the ANYELEMENT trick:
How do I get the type of an array's elements?
Performance
I ran a quick performance test comparing this function to the one #a_horse supplied. This one was around 5x faster.
If you use this a filter for a big table I strongly suggest you combine it with a (logically redundant) sargable filter like:
SELECT *
FROM tbl
WHERE arr #> '{2,160,134,58,149,111}'::int[]
AND array_contains_array_in_order(arr, '{2,160,134,58,149,111}')
This will use a GIN index on the array column like:
CREATE INDEX ON tbl USING gin (arr);
And only filter remaining (typically very few!) arrays that share all elements. Typically much faster.
Caveats with intarray module
Note: applies to integer[] exclusively, not smallint[] or bigint[] or any other array type!
Careful, if you have installed the intarray extension, which provides its own variant of the #> operator for int[]. Either you create an (additional) GIN index with its special operator class (which is a bit faster where applicable):
CREATE INDEX ON intarr USING gin (arr gin__int_ops);
Or, while you only have a GIN index with the default operator class, you must explicitly denote the standard operator to cooperate with the index:
WHERE arr OPERATOR(pg_catalog.#>) '{2,160,134,58,149,111}'::int[]
Details:
GIN index on smallint[] column not used or error "operator is not unique"
Simple case
As commented, your case is simpler:
The complete second array is included in the first (same order, no gaps!).
CREATE OR REPLACE FUNCTION array_contains_array_exactly(arr1 ANYARRAY, arr2 ANYARRAY)
RETURNS bool AS
$func$
DECLARE
len int := array_length(arr2, 1) - 1; -- length of arr2 - 1 to fix off-by-1
pos int; -- for current search postition in arr1
BEGIN
/* -- OPTIONAL, if invalid input possible
CASE array_length(arr1, 1) > len -- array_length(arr2, 1) - 1
WHEN TRUE THEN -- valid arrays
-- do nothing, proceed
WHEN FALSE THEN -- arr1 shorter than arr2
RETURN FALSE; -- or raise exception?
ELSE -- at least one array empty or NULL
RETURN NULL;
END CASE;
*/
pos := array_position(arr1, arr2[1]); -- pos of arr2's 1st elem in arr1
WHILE pos IS NOT NULL
LOOP
IF arr1[pos:pos+len] = arr2 THEN -- array slice matches arr2 *exactly*
RETURN TRUE; -- arr2 is part of arr1
END IF;
pos := pos + array_position(arr1[(pos+1):], arr2[1]);
END LOOP;
RETURN FALSE;
END
$func$ LANGUAGE plpgsql IMMUTABLE COST 1000;
Considerably faster than the above for longer arrays. All other considerations still apply.
I need to create a number of functions that deal with matrices of arbitrary size. I am familiar with the declare syntax used here, but this is for an university assignment, and my professor told me that using 'declare' there is overkill. I can't find anything relevant online, any help?
Basically I'm looking to get the matrix size via keyboard and then work with the resulting matrix, I'm stuck with the declare
Currently I have:
type myMatrix is array (Natural range <>, Natural range <>) of Integer;
type myVector is array (Natural range <>) of Integer;
And I use it as:
procedure Lab1 is
begin
declare A, B: myVector(1..5):=
(3, 14, 15, 92, 6);
which doesn't allow to specify the size at runtime, and as:
declare
int1:Integer:=generate_random_number(50)+2;
int3:Integer:=generate_random_number(50)+2; -- +2 so we don't get length/size 0 or 1
X, Y:myVector(1..int1):=(others=>generate_random_number(20));
MT:myMatrix(1..int1, 1..int3):=(others =>(others=>generate_random_number(10))); -- 'others' used for all the unmentioned elements, http://rosettacode.org/wiki/Array_Initialization
MS:myMatrix(1..int3, 1..int1):=(others =>(others=>generate_random_number(10)));
F3_result:myMatrix(1..int1, 1..int1);
begin
F3_result:=F3(X, Y, MT, MS);
end;
which uses a declare block. I might need to use the resulting array later on, and as I understand here F3 is a local variable, and therefore can't be reused?
Are there any other ways?
I agree with your teacher that putting a declare just after a begin will in general show that the declare block is unneeded (the only exception I can think of is because you want to handle exceptions that might arise as part of the declaration of the variables within the enclosing subprogram).
So you can just remove both begin and declare at the start of your subprogram, and that should work the same.
Can I specify an index (using some counter) to an element when adding elements to an array in PostgreSQL?
For example, I have this code from Oracle PL/SQL and I want to write it in PostgreSQL:
invoice_list.EXTEND;
invoice_list(counter):= cfp_cur.invoice_no; --adding some invoices from a loop to a text array
amount_list.EXTEND;
amount_list(counter) := inv_amount; --adding some amounts to a number array
counter := counter + 1; --increasing counter
PostgreSQL does not have an EXTEND method like Oracle does. PostgreSQL, however, can extend 1-dimensional arrays automatically by assigning array elements beyond the end of the current array length.
In your example, this becomes very simply:
CREATE FUNCTION some_function () RETURNS something AS $$
DECLARE
invoice_list text[];
amount_list float8[];
BEGIN
-- Do something
...
FOR counter IN 1 ... 10 LOOP
-- get current values for cfp_cur.invoice_no and inv_amount
invoice_list[counter] := cfp_cur.invoice_no;
amount_list[counter] := inv_amount;
END LOOP;
-- Do something with the arrays
...
RETURN;
END;
I asked another question about using records with operators. During testing, I've discovered an anomaly where two instances of this type seem to share the same memory.
The record has an array of Integer...
type
TVersion = record
Values: array of Integer;
function Count: Integer;
class operator implicit(aVersion: TVersion): String;
class operator implicit(aVersion: String): TVersion;
end;
class operator TVersion.implicit(aVersion: TVersion): String;
var
X: Integer;
begin
Result:= '';
for X := 0 to Length(aVersion.Values)-1 do begin
if X > 0 then Result:= Result + '.';
Result:= Result + IntToStr(aVersion.Values[X]);
end;
end;
class operator TVersion.implicit(aVersion: String): TVersion;
var
S, T: String;
I: Integer;
begin
S:= aVersion + '.';
SetLength(Result.Values, 0);
while Length(S) > 0 do begin
I:= Pos('.', S);
T:= Copy(S, 1, I-1);
Delete(S, 1, I);
SetLength(Result.Values, Length(Result.Values)+1);
Result.Values[Length(Result.Values)-1]:= StrToIntDef(T, 0);
end;
end;
function TVersion.Count: Integer;
begin
Result:= Length(Values);
end;
Now I try to implement this...
var
V1, V2: TVersion;
begin
V1:= '1.2.3.4';
V2:= V1;
ShowMessage(V1);
ShowMessage(V2);
V2.Values[2]:= 8;
ShowMessage(V1);
ShowMessage(V2);
end;
I expect that the V2 should be 1.2.8.4 and V1 to remain 1.2.3.4. However, V1 also changes to 1.2.8.4.
How do I keep these records independent when I assign them?
This behaviour is by design. A dynamic array variable is a pointer. When you assign a dynamic array variable you take a copy of the pointer and increase the reference count. Of course this also happens when you assign compound structures that contain dynamic arrays.
The documentation covers this:
If X and Y are variables of the same dynamic-array type, X := Y points X to the same array as Y. (There is no need to allocate memory for X before performing this operation.) Unlike strings and static arrays, copy-on-write is not employed for dynamic arrays, so they are not automatically copied before they are written to.
To get a mental model for this, this of dynamic arrays as being references in the same way as classes and interfaces. This is in contrast to simple types (integer, double etc.), strings, records, etc.
The standard way to deal with this is as follows:
Make the dynamic array private.
Expose the contents of the array through properties.
In the element setter, make sure that the reference to the dynamic array is unique.
That last step is the tricky part. Make a dynamic array reference unique by calling SetLength:
SetLength(arr, Length(arr));
One of the promises that SetLength makes is that it will always make its first argument be unique.
This has the effect of implementing copy-on-write. To the best of my knowledge it is not possible to implement copy-on-assign because the compiler gives you no hook into the assignment operator.
So the answer to:
How do I keep these records independent when I assign them?
is that you cannot. You need to use copy-on-write instead.