So simple... I need to retrieve an item of an array-of-arrays, so the item must be a simple array. Example:
WITH t AS (SELECT '{{1,2,3},{333,444,555}}'::int[][] as a, '{{{{1,2}}}}'::int[] as b)
SELECT
a[2], -- returns null (!), why not works?
a[2:2], -- returns array-into-array, not simple array
b[1][1], -- also null
b[1][1][1:2], -- another ugly array-into-array-into...-array
a[1][1] -- is correct, but only works with scalars
FROM t;
The result is NULL,{{333,444,555}},NULL,{{{{1,2}}}},1... I need a simple array for a[2], {333,444,555}... How to access it as it?
PS: the guide and google-examples only show slices... Perhaps it is so obvious but I not remember why a[2] is invalid in PostgreSQL.
I had forgotten what we discussed here: it is a restricted form of "array of arrays"... named "multidimensional array"... But I persisted in thinking in ambiguous way, that is, thinking sometimes in array of arrays...
The best answer comes from Tom Lane at the pgsql-hackers.at.postgresql.org forum, "Multi-dimensional arrays in Postgres are not 'arrays of arrays'".
So, in the "multidimensional array" world, not exist that kind of "access to an array-item" that I expected.
As #VinΓciusGobboA.deOliveira commented here, the only solution is to write a function, through PL/SQL (low performance) or C (high performance).
a[2:2] will return integer[] type, while non-colon notation a[2] will return integer type, which in fact in your case should be an array. Based on the documentation:
An array slice is denoted by writing lower-bound:upper-bound for one
or more array dimensions.
Check the code below:
WITH test AS (
SELECT
'{
{11,12,13},
{21,22,23},
{31,32,33},
{41,42,43}
}'::INTEGER[][] as vv
)
SELECT
array_dims(vv) dimensions,-- [1:4][1:3] - get all dimensions
vv[1:4] all_slices, -- entire 2d-array
vv[2:3] second_and_third_slices, -- {{21,22,23},{{31,32,33}}
vv[2:2] second_slice, -- {{21,22,23}} - get specific slice
pg_typeof(vv[2:2]) second_slice_type, -- type of slice above is INTEGER[]!
pg_typeof(vv[2]) vv_type, -- type is INTEGER
vv[2:2][2:2] second_slice_second_element, -- 22 - get specific element
vv[2:2][2:3] second_slice_second_and_third_elements -- {{22,23}}
FROM test;
Related
I recently tried my Array #a = 'a'..'z'; and my Array #a = #('a'..'z');.
Both will produce the following error:
Type check failed in assignment to #a; expected Array but got Str ("a")
in block <unit> at <unknown file> line 1
However initializing without the type works and seems to ultimately produce an Array:
> my #a = 'a'..'z';
> #a.^name
Array
Why is this the case?
TL;DR I provide a relatively simple answer in Why is this the case? However, that explanation may be inadequate1 so I review some alternatives in Declare and initialize a typed array from a range.
Why is this the case?
my #a; declares a new Array (initialized to be empty) and "binds" it to the symbol #a. Thus my #a; say #a.^name returns Array. There is no need to use the word Array in a declaration or initialization of an array -- the # is enough.2
my #a = 'a'..'z' attempts to copy each value in the range 'a' thru 'z', one at a time, into #a[0], #a[1], etc. The new array bound to #a has a type constraint for each of its elements (explained in the next section); it will be checked for each value (and will succeed).
my Array #a declares an Array with an Array type constraint on its elements (so it's an array of arrays). my Array #a; say #a.^name returns Array[Array] to indicate this. my Array #a = 'a'..'z'; fails when copying the first value ("a") because it is a Str value not an Array.
Declare and initialize a typed array from a range
my #a = 'a'..'z';
The my #a part of this statement declares a variable that's bound to (refers to) a new array of type Array. Because no element type constraint was specified, the new array's elements are constrained to be consistent with Mu, the Most unassuming type in P6. In other words it's an empty array ready to contain whatever values you want to put in it. (One could say that say #a.^name displays Array rather than Array[Mu] because the [Mu] is considered Most uninteresting.)
... = 'a'..'z' initializes the new array. The initialization has no impact on the array's already established type constraints. It just delivers copies of the strings 'a', 'b' etc. into the array (which auto-expands to receive them into #a[0], #a[1] etc.).
I recommend devs avoid adding explicit type constraints on variables and explicit coercions of values unless they're confident they're desirable. (cf my parenthetical remarks at the end of an earlier SO answer.) That said, you can choose to do so:
my Str #a = 'a'..'z'; # `Array` elements constrained to `Str`
my Str #a = (0..99)>>.Str; # Coerce value to match constraint
Alternatively, P6 supports explicit binding, rather than assignment, of a value or list of values. The most common way to do this is to use := instead of =:
my #a := 'a'..'z'; say #a.WHAT; say #a[25]; # (Range)β€z
Note how the explicit binding of #a means #a has not been bound to a new Array but instead to the Range value. And because a Range can behave as a Positional, positional subscripting still works.
The following statements would imo be grossly redundant explicit typing but would both work and yield exactly the same outcome as each other, though the first one would be faster:
my Str #a := Array[Str].new('a'..'z');
my Str #a = Array[Str].new('a'..'z');
There's more to discuss about this topic but perhaps the foregoing provides enough for this question/answer. If not, please ask further questions in comments under your original question and/or below.
Footnotes
1 An earlier version of this answer began with:
my Array #a ...
# My array of thoughts raised by this declaration
# and your questing "why?" in this SO question
# began with wry thoughts about complicated answers
# about reasons your array is awry and pedances
(I made up the word "pedances" to mean something that appears to be pedantic but flows nicely when used correctly -- which will happen naturally once you've become familiar with its apparently idiosyncratic but actually helpful nature. More importantly, I needed something that rhymes with "answers".)
2 Here are a couple of mnemonics for the meaning of # in P6:
It looks like a zero digit (0) with an π (Mathematical Italic Small A) inside it -- and a #foo variable is by default a 0 indexed πππππ¦ (or #ππππ¦).
It sounds like the word "at". An array has elements at indices.
Set the type of the element in the array:
my Str #a = 'a'..'z';
say #a; #[a b c d e f g
To see, what type it is, you can use .WHAT
my Str #a = 'a'..'z';
say #a.WHAT #(Array[Str])
To test if it is an array, you can smartmatch
my Str #a = 'a'..'z';
say 'is array' if #a ~~ Array; #is array
say 'is str array' if #a ~~ Array[Str]; #is str array
say 'is str array' if #a ~~ Array[Int]; #
The #('a'..'z') will make a List not an Array. Changing it to
['a'..'z'] will give an Array.
But that will still give you a type error when assigning it to my Array #a as you have done.
If you want to assign an entire Array to an element of another array, you have to itemise it somehow eg:
$[1,2,3]; #Still an array but treated as single item
[1,2,3], ; #Note the list operator (comma), this give you a list of lists
So in your case:
'a'..'z'; is a range, needs to be converted to an array, so
['a'..'z']; range is now evaluated into an array
$['a'..'z']; range is now evaluated into an array and taken as a single item
my Array #a=$['a'..'z'];
say #a;
#OUTPUT:
#[[a b c d e f g h i j k l m n o p q r s t u v w x y z]]
# Which is an array of arrays;
Not sure if it is what you are after but it removes the type error.
When you declare an array, you can easily specify what type the elements of the array are.
my Str #a = 'a'..'z';
You can specify the exact class that is used for the storage as well.
The following line is exactly the same as the above line; since Array is the default storage class.
my #a is Array[Str] = 'a'..'z';
You can even combine the two.
This following is also exactly the same.
my Str #a is Array = 'a'..'z';
When you wrote the following line.
my Array #a = 'a'..'z';
What you were actually saying was:
my #a is Array[Array] = 'a'..'z';
I assume you thought you were writing this.
my #a is Array = 'a'..'z';
This can be useful if you don't want the elements to change.
my #a is List = 'a'..'z';
Or if you have specific needs that aren't satisfied by the default Array class.
use Array::Unique; # doesn't really exist yet (afaik)
my #a is Array::Unique[Str] = 'a'..'z';
my Str #b is Array::Unique = 'a'..'z';
I'm trying to pass data around as a multidimensional array, and I'm getting behavior that seems odd to me. Specifically I'm trying to get a single element out of a 2 dimensional array (so a 1 dimensional array out of my 2 dimension array), and it doesn't work the way I'd expect.
In the following examples #2, 4, & 5 work the way I'd expect, but 1 & 3 do not.
db=> select s.col[2] from (select array[[1,2,3],[4,5,6]] as col) s;
col
-----
(1 row)
db=> select s.col[2:2] from (select array[[1,2,3],[4,5,6]] as col) s;
col
-----
{{4,5,6}}
(1 row)
db=> select array[s.col[2]] from (select array[[1,2,3],[4,5,6]] as col) s;
array
--------
{NULL}
(1 row)
db=> select array[s.col[2:2]] from (select array[[1,2,3],[4,5,6]] as col) s;
array
-------------
{{{4,5,6}}}
(1 row)
db=> select s.col[2][1] from (select array[[1,2,3],[4,5,6]] as col) s;
col
-----
4
(1 row)
Is there doc on this? I have something that's working well enough for me right now, but it's ugly and I worry it won't do the things I want to do next. Technically I'm getting a 2 dimensional array, where 1 dimension only has 1 element. I'd rather just get an array.
I've read (among others):
http://www.postgresql.org/docs/9.1/static/arrays.html
http://www.postgresql.org/docs/9.1/static/functions-array.html
http://www.postgresql.org/docs/9.1/static/sql-expressions.html#SQL-SYNTAX-ARRAY-CONSTRUCTORS
And I'm just not seeing what I'm looking for.
Postgres array elements are always base elements, i.e. non-array types. Sub-arrays are not "elements" in Postgres. Array slices retain original dimensions.
You can either extract a base element, with element data type. Or you can extract an array slice, which retains the original array data type, and also original array dimensions.
Your idea to retrieve a sub-array as "element" would conflict with that and is just not implemented.
The manual might be made clearer in its explanation. But at least we can find:
If any dimension is written as a slice, i.e., contains a colon, then
all dimensions are treated as slices. Any dimension that has only a
single number (no colon) is treated as being from 1 to the number
specified. For example, [2] is treated as [1:2] ...
Your 1st example tries to reference a base element, which is not found (you'd need a subscript with two array indexes in a 2D array). So Postgres returns NULL.
Your 3rd example just wraps the resulting NULL in a new array.
To flatten an array slice (make it a 1D array) you can unnest() and feed the resulting set to a new ARRAY constructor. Either in a correlated subquery or in a LATERAL join (requires pg 9.3+). Demonstrating both:
SELECT s.col[2:2][2:3] AS slice_arr
, x.lateral_arr
, ARRAY(SELECT unnest(s.col[2:2][2:3])) AS corr_arr
FROM (SELECT ARRAY[[1,2,3],[4,5,6]] AS col) s
, LATERAL (
SELECT ARRAY(SELECT * FROM unnest(s.col[2:2][2:3])) AS lateral_arr
) x;
Be sure to read the current version of the manual. your references point to Postgres 9.1, but chances are you are actually using Postgres 9.4.
Related:
How to select 1d array from 2d array?
Unnest array by one level
I have this array:
a = {{4,2,2,6}, {2,1,1,2}}
How can I retrieve an index from that array to a C program?
For example:
a[1] -- {4,2,2,6}
a[1][2] -- 2
You can use the lua_gettable method. There are a few important notes, however:
Lua arrays start at index 1, not 0.
You'll need to push the index onto the lua stack via lua_pushinteger.
The key is "replaced" with the indexed element.
Try this:
lua_getglobal(L,"a")
lua_rawgeti(L,-1,1)
lua_rawgeti(L,-1,2)
After this, the value of a[1][2] will be on the top of the stack. The stack will also contain a and a[1], which you may want to pop when you're done (they're left on the stack in case you want to retrieve multiple values).
I'm a total Perl newb, but still cannot believe I cannot figure this out with all the info I've read through online, but, I've burned too much time and am suffering from block at this point. Hoping to learn something based on my real life example...
Ok, I think I have an array of arrays, created like this:
my #array1 = ();
my #array2 = ();
my $ctr1 = 0;
my $col;
[sql query]
while(($col)=$sth->fetchrow_array() ) {
$array1[$ctr1]=$col;
$ctr1++;
}
print STDERR "#array1";
##results in 10 rows, a mac address in each
##00:00:00:00:00:00 00:11:11:11:11:11 22:22:22:22:22:22 33:33:33:33:33:33 ...
Now I do another query. While looping through results, I am looking for those 10 mac addresses. When I find one, I add a row to array2 with the mac and the sequential number accumulated to the point, like this:
[sql query]
while(($col)=$sth->fetchrow_array() ) {
$ctr2++;
if( my ($matched) = grep $_ eq $col, #array1 ) {
push( #array2, ($col,$ctr2) );
}
}
print STDERR "#array2";
##results in 10 rows, a mac address and an integer in each
##00:00:00:00:00:00 2 00:11:11:11:11:11 24 22:22:22:22:22:22 69 33:33:33:33:33:33 82 ...
Now the easy part. I want to loop through array2, grabbing the mac address to use as part of a sql query. Therein lies the problem. I am so ignorant as to exactly what I am doing that even though I had it almost working, I can't get back to that point. Ignorance is definitely not bliss.
When I loop through array2, I am getting a host of errors, based on the different forms of the statement. The one I think is right is listed below along with the error message...
my $ctr3 = 0;
foreach $ctr3 (#array2) {
my $chkmac = $array2[$ctr3][0]; <--- gacks here with the error below - line 607
[SQL query]
[Thu May 30 14:05:09 2013] [error] Can't use string ("00:66:55:77:99:88") as an ARRAY ref while "strict refs" in use at /path/to/test.cgi line 607.\n
I believe the issue is that my array of arrays is not an array of arrays. If it were, it would work as coded, or so I think from the reading... That said, I cannot fathom what I am dealing with otherwise. This will be a head slapper I'm all but sure, but I am stumped.... Little help, please?
TIA
O
For an array of arrays you want to use an array reference, e.g.
push #array2, [$col, $ctr2];
When accessing an element within an array refernce, you'll want to use the -> operator. Also, when looping through an array, it's not necessary to index back into that same array. So the last part would look more like:
foreach $ctr3 (#array2) {
my $chkmac = $ctr3->[0];
....
When you do the foreach there, $ctrl3 won't have the index in it, it'll have the value. So you should just need to do $ctrl3->[0]. Note the -> which dereferences the array reference (#array2 is actually an array of array references).
EDIT: As AKHolland pointed out, #array2 actually isn't an array of array references, although that's what it should be. You also need to change:
push( #array2, ($col, $ctr2) );
To
push( #array2, [$col, $ctr2] );
This makes an array reference, rather than a list. A list in this context just collapses down into regular arguments to push, meaning you're pushing two separate strings into #array2.
You are correct that your array of arrays is not an array of arrays, since in Perl there is no such thing. So what do you have instead? There's two ways to see.
First, when you print #array2, you come up with a string composed of alternating MACs and counts, separated by spaces. Since the spaces sort-of-signify the division between array elements, we might surmise that what we've got is a single array of heterogeneous elements, such that element 0 is a MAC, element 1 is a count, element 2 is another MAC, and so on.
The other perspective is to look at how #array2 is constructed:
push( #array2, ($col,$ctr2) );
From the documentation for push, we find that push ARRAY LIST works by appending the elements of LIST to the end of ARRAY. This has the effect of flattening the list into the array such that its original identity as a list is lost. You can add all the parentheses you want, when Perl expects a list it flattens all of them away.
So how do you achieve the effect you want? The List-of-Lists documentation has a detailed treatment, but the short answer is that you make a list of array references. Array references are scalars and are therefore legal elements in an array. But they retain their identify as array references!
The anonymous array reference constructor is the square bracket []. In order to push an array reference containing the elements $col and $ctr2 onto the end of #array2, you simply do this:
push( #array2, [$col, $ctr2] );
The code you wrote for accessing a particular element of the array-reference-in-an-array now works. But since I've already written a bunch of paragraphs on the subject, let me finish by explaining what was wrong originally and how changing the push statements suddenly makes it work.
The expression $array2[$ctr3][0] is sometimes written as $array2[$ctr3]->[0] to clarify what it's actually doing. What it does is to take the value of $array2[$ctr3] and treat it as an array reference, taking its 0 element. If we take $ctr3 to be 0 (as it would be at the top of the loop) the value of $array2[$ctr3] is the first element, 00:00:00:00:00:00. When you then subsequently ask Perl to treat 00:00:00:00:00:00 as an array reference, Perl dies because it doesn't know how to treat a string as an array reference.
When instead the value of $array2[$ctr3] is an array reference because that is what you pushed onto #array2 when constructing it, Perl is able to do as you ask, dereferencing the array reference and looking at element 0 of the resulting array, whose value happens to be 00:00:00:00:00:00.
The PostgreSQL Documentation on arrays provides an example using [-1] to access what appears to be the last element of an array; however while SELECT arr[2:3]; produces {5,9}, arr[2:-1] results in {}.
How can the last element of an array be obtained in PostgreSQL?
Edit: Windows, PostgreSQL v9.2.1
For any array "arr", to fetch the last element of array arr use
SELECT arr[array_upper(arr, 1)];
I think you're misinterpreting the example. PostgreSQL arrays don't have to be indexed from 1 to n, that's just the default:
By default PostgreSQL uses a one-based numbering convention for arrays, that is, an array of n elements starts with array[1] and ends with array[n].
The example you're looking at is this:
SELECT f1[1][-2][3] AS e1, f1[1][-1][5] AS e2
FROM (SELECT '[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[] AS f1) AS ss;
But those negative numbers aren't indexing from the end of the arrays as in languages such as Perl. In the FROM (SELECT ... part, they're specifying the starting and ending indexes so the -1 in f1[1][-1][5] is just a plain old index. Consider this array_dims result:
=> SELECT array_dims('[1:1][-2:-1][3:5]={{{1,2,3},{4,5,6}}}'::int[]);
array_dims
-------------------
[1:1][-2:-1][3:5]
If you're using the default 1-based arrays then you can get the last element with a simple arr[array_length(arr, 1)]. If you're not using the default [1:n] arrays then you'll have to mess around with array_lower and array_upper to get the first and last elements; or, depending on the circumstances, you might be able to use unnest to unpack the array then work with the array as a rowset.
If someone is using Postgre 9.5, the documentation says:
-> int
Get JSON array element (indexed from zero, negative integers count from the end)
So this works for me:
to_json(arr)->-1