How to handle cases where array is empty in postgresql? - sql

I have a function written in plpythonu.
CREATE OR REPLACE FUNCTION temp(t_x integer[])
RETURNS void AS
$BODY$
.
.
.
x=plpy.execute("""select array_to_string(select id from A where id=any(array%s) ), ',')"""%t_x)
On some cases when t_x is empty i get an error:
ERROR: cannot determine type of empty array
How do I fix it?

if-else statement
Pseudocode:
if t_x is empty
x=null
else
x=plpy.execute....
cast
x=plpy.execute("""select array_to_string(select id from A where id=any(array%s::integer[]) ), ',')"""%t_x)
Why cast help?
postgres=# select pg_typeof(array[1,2]);
pg_typeof
-----------
integer[]
array[1,2] has type integer[]
postgres=# select array[];
ERROR: cannot determine type of empty array
LINE 1: select array[];
array[] has no type.
postgres=# select pg_typeof(array[]::integer[]);
pg_typeof
-----------
integer[]
array[]::integer[] has type integer[]

Maybe this can help someone in future.
Table function may receive NULL or Empty Array in that case (equivalent to ALL):
CREATE OR REPLACE FUNCTION temp(t_x integer[])
RETURNS void AS
$BODY$
.
.
.
x=plpy.execute("""select array_to_string(select id from A where id=any(array%s) or cardinality(coalesce(array%s,array[]::integer[])) = 0), ',')"""%t_x)

Related

Why do we need to Treat() MDSYS.ST_GEOMETRY as ST_LINESTRING to use ST_PointN(1)?

MDSYS.ST_GEOMETRY; Oracle 18c:
The following query works. It extracts the first point from an MDSYS.ST_GEOMETRY:
--Source: https://www.spdba.com.au/using-oracles-st_geometry-type-hierarchy-with-sdo_geometry-st_pointn-and-st_numpoints/
with cte as (
select treat(mdsys.st_geometry.from_wkt('LINESTRING(10 10, 20 20)',26917) as mdsys.st_linestring) as shape
from dual
)
select
(shape).st_pointn(1) as first_point
from
cte
Result:
MDSYS.ST_POINT(MDSYS.SDO_GEOMETRY(2001, 26917, MDSYS.SDO_POINT_TYPE(10, 10, NULL), NULL, NULL))
I don't understand why we need to Treat() the ST_GEOMETRY supertype as an ST_LINESTRING subtype in order to use ST_PointN() to get the point.
For example, if I remove the Treat(... as ST_LINESTRING), then I get an error:
with cte as (
select mdsys.st_geometry.from_wkt('LINESTRING(10 10, 20 20)',26917) as shape
from dual
)
select
(shape).st_pointn(1) as first_point
from
cte
Error:
ORA-00904: "MDSYS"."ST_GEOMETRY"."ST_POINTN": invalid identifier
Why do I get that error when I remove Treat()?
Why do I get that error when I remove Treat()?
ST_LINESTRING is a sub-type of ST_CURVE which, in turn, is a sub-type of ST_GEOMETRY.
ST_POINTN is a member function declared on the sub-type ST_CURVE and ST_LINESTRING inherits this function.
ST_POINTN is not declared as a member function on the parent type ST_GEOMETRY.
The ST_GEOMETRY.FROM_WKT() function returns an ST_GEOMETRY instance that, in this case is a actually a ST_LINESTRING sub-type but the return type of the function is ST_GEOMETRY as it could return any child sub-type.
When you remove TREAT() then you are trying to call the ST_POINTN member function on the parent type ST_GEOMETRY and, as the error message states "MDSYS"."ST_GEOMETRY"."ST_POINTN" is an invalid identifier because the type does not have that member function.
When you include TREAT() then you cast the super-type to the sub-type and then call the member function on that sub-type and the member function does exist so it works.
A similar example is:
CREATE TYPE parent_type IS OBJECT (
x NUMBER,
y NUMBER
) NOT FINAL;
CREATE TYPE child_type UNDER parent_type (
MEMBER FUNCTION get_x RETURN NUMBER
);
CREATE TYPE BODY child_type IS
MEMBER FUNCTION get_x RETURN NUMBER
IS
BEGIN
RETURN self.x;
END;
END;
/
Then:
CREATE FUNCTION create_parent RETURN PARENT_TYPE
IS
BEGIN
RETURN child_type(1, 2);
END;
/
If you use:
SELECT create_parent().get_x() FROM DUAL;
Then the function declares it returns a PARENT_TYPE and so the member function is called on that type, even though the actual returned value is a CHILD_TYPE, so raises the error:
ORA-00904: "SCHEMA_NAME"."PARENT_TYPE"."GET_X": invalid identifier
If you use TREAT to cast the returned parent to its actual child type:
SELECT TREAT(create_parent() AS child_type).get_x() FROM DUAL;
Then the output is:
TREAT(CREATE_PARENT()ASCHILD_TYPE).GET_X()
1
db<>fiddle here

How to set composite type array containing JSONB elements with a literal value?

I have this type:
CREATE TYPE public.user_type AS (
text_1 VARCHAR(512),
text_2 VARCHAR(1000),
jsonb_1 JSONB,
jsonb_2 JSONB
);
And need to know how to format a literal value to set an array of this type for a unit test. I keep getting malformed array literal errors when I try to set it.
SQL Error [22P02]: ERROR: malformed array literal: "{{"(text 1,text 2,{"key_1":"value_1"},{"key_2":"value_2"})"}"
Detail: Unexpected array element.
Where: PL/pgSQL function inline_code_block line 4 during statement block local variable initialization
with this snippet:
DO $$
DECLARE
user_type public.user_type[] = '{{"(text 1,text 2,{"key_1":"value_1"},{"key_2":"value_2"})"}';
BEGIN
END $$;
How do I form a literal string to set this composite type?
For other unit tests I can declare composite types without JSONB elements and set them with literal values. For example this works:
For this type:
CREATE TYPE public.user_type_2 AS (
text_1 VARCHAR(512),
text_2 VARCHAR(1000)
);
This snippet will return multiple rows:
DO $$
DECLARE
user_type_2 public.user_type_2[] = '{{"(string_1a,string_1b)"},{"(string_2a,string_2b)"}}';
BEGIN
DROP TABLE IF EXISTS _results;
CREATE TEMPORARY TABLE _results AS
SELECT * FROM UNNEST(user_type_2) x (text_1, text_2);
END $$;
SELECT * FROM _results;
as I would expect
+-----------+-----------+
| text_1 | text_2 |
+-----------+-----------+
| string_1a | string_1b |
| string_2a | string_2b |
+-----------+-----------+
You have to use several levels of escaping for that:
The array element has to be surrounded by ", because it contains ,, { and }.
Each of the " inside the array element has to be escaped to \".
The JSON elements in the composite type have to be surrounded by " (now \") because they contain ,.
The " for JSON strings have to be doubled to escape the " from the previous point, so they eventually become \"\".
Your assignment could look like this:
user_type public.user_type[] := '{"(text 1,text 2,\"{\"\"key_1\"\": \"\"value_1\"\"}\",\"{\"\"key_2\"\": \"\"value_2\"\"}\")"}';
Yuck.

PostgreSQL: How to pass and array to a function and using it in a query with the IN operator

I have a problem, I want to pass an array to a postgres function and use that array so returns values in a SELECT IN clause.
But It shows me this error:
An error occurred executing the SQL command :
SELECT
*
FROM
get_invtransferences_porders_fporders (300001300 , array [ 300093753 , 300094126 , 300093349 , 300093838 , 300094128 ] ...
ERROR: operator does not exist : integer = integer [ ]
Hint : No operator matches the name and type of arguments. You may need to add explicit type conversions .
Where : PL / pgSQL get_invtransferences_porders_fporders (numeric , integer []) function on line 8 FOR loop around rows of a SELECT
This is my function:
CREATE OR REPLACE FUNCTION public.get_invtransferences_porders_fporders(p_product_id numeric, P_INVTRANSFERENCES_IDS integer[])
RETURNS SETOF record
LANGUAGE plpgsql
AS
$body$
DECLARE
PORDER_PRODUCT RECORD;
COMPONENT RECORD;
COMPONENT2 RECORD;
COMPONENT3 RECORD;
BEGIN
FOR PORDER_PRODUCT IN (
SELECT
'porder' AS "operation_type"
,porders.id AS "porder_id"
,porders.user_id AS "porder_user_id"
,(SELECT name FROM users WHERE users.id = porders.user_id) AS "porder_user_name"
,porders.delivery_datetime AS "porder_delivery_datetime"
,porders_products.requested AS "product_requested"
,porders_products.produced AS "product_produced"
,products.code AS "product_code"
,products.NAME AS "product_name"
,(
SELECT products.name
FROM products
WHERE id = product_components.component_product_id
) AS "component_product_name"
,product_components.quantity AS "component_quantity"
,(
SELECT products.um_id
FROM products
WHERE id = product_components.component_product_id
) AS "component_um_id"
,(product_components.quantity / products.production_base) * porders_products.requested AS "total"
FROM porders
,porders_products
,products
,product_components
WHERE porders.id = porders_products.porder_id
AND porders_products.product_id = products.id
AND porders_products.product_id = product_components.product_id
AND porders.id IN (
SELECT rawm_audit_porders.porder_id
FROM rawm_audit_invtransferences
,rawm_audits
,rawm_audit_porders
WHERE rawm_audit_invtransferences.rawm_audits_id = rawm_audits.id
AND rawm_audit_porders.rawm_audits_id = rawm_audits.id
AND rawm_audit_invtransferences.invtransference_id IN
(
SELECT
invtransferences.id
FROM invtransferences
,invtransferences_products
,products
WHERE invtransferences.id = invtransferences_products.invtransference_id
AND products.id = invtransferences_products.product_id
AND invtransferences.id IN (P_INVTRANSFERENCES_IDS)
)
)
AND product_components.component_product_id = p_product_id
) LOOP
IF(PORDER_PRODUCT.porder_id IS NOT NULL)THEN
RETURN NEXT PORDER_PRODUCT;
END IF;
END LOOP;
RETURN;
END;
$body$
VOLATILE
COST 100
ROWS 1000
I think the error it here `invtransferences.id IN (P_INVTRANSFERENCES_IDS)
This is the select that calls the function:
SELECT
*
FROM
get_invtransferences_porders_fporders(300001300 , array[300093753, 300094126, 300093349, 300093838, 300094128] )
AS
(
"operation_type" varchar,
"porder_id" numeric,
"porder_user_id" numeric,
"porder_user_name" varchar,
"porder_delivery_datetime" date,
"product_requested" numeric,
"product_produced" numeric,
"product_code" varchar,
"product_name" varchar,
"component_product_name" varchar,
"component_quantity" numeric,
"component_um_id" varchar,
"total" numeric
)
ORDER BY
"porder_id";
EDIT: I remove the VARIADIC words that were in the function and in the select that calls the function
Can you hep me Please.
You don't need to declare your function as VARIADIC to pass array to it.
Try this
CREATE OR REPLACE FUNCTION xxx(
p_product_id integer,
P_INVTRANSFERENCES_IDS integer[])
RETURNS SETOF record
LANGUAGE sql
AS
$body$
select p_product_id = ANY(P_INVTRANSFERENCES_IDS)
$body$;
Note there is no VARIADIC before P_INVTRANSFERENCES_IDS.
You also need to use ANY instead of IN to check membership in array.
SqlFiddle

PostgreSQL: get count of occurrences of specified element in array

I need to calculate the count of occurrences of specified element in array, something like:
elem_occurrences_count(ARRAY[a,b,c,a,a], a) = 3
elem_occurrences_count(ARRAY[a,b,c], d) = 0
Is there any function in PostgreSQL that can be used to solve the problem? Any help is appreciated.
You will need to unnest the array and then count the occurrences.
with elements (element) as (
select unnest(ARRAY['a','b','c','a','a'])
)
select count(*)
from elements
where element = 'a';
This can easily be embedded into a function:
create or replace function count_elements(elements text[], to_find text)
returns bigint
as
$body$
select count(*)
from unnest(elements) element
where element = to_find;
$body$
language sql;
Update
Since Postgres 9.5 this can also be done using array_positions() which returns an array of positions where an element was found. The length of that array is the number of occurrences:
select cardinality(array_positions(ARRAY['a','b','c','a','a'], 'a'));
9.5+
There is an easier method now
SELECT
sArray,
c,
coalesce(array_length( array_positions(sArray, c), 1 ),0) AS count
FROM ( VALUES
(ARRAY['a','b','c','a','a'], 'a'),
(ARRAY['a','b','c'], 'd')
) AS t(sArray,c);
sarray | c | count
-------------+---+-------
{a,b,c,a,a} | a | 3
{a,b,c} | d | 0
(2 rows)
The occurrence of all elements in an array can be found with this query:
SELECT count(id), UNNEST(array) as element
FROM myTable
GROUP BY element;
To count the occurrence of a specific element, for example 'c', add a WHERE clause:
SELECT count(id), UNNEST(array) as element
FROM myTable
WHERE EXISTS (SELECT * FROM UNNEST(array) AS x WHERE x='c')
GROUP BY element;
You can use the FILTER clause to count the occurrences.
Let's say we have a questions table with tags (array type) and you want to count the questions with postgresql tag:
SELECT COUNT(*) FILTER (WHERE '{postgresq}' <# (tags)) as tagCount
FROM posts;
More generic function is here;
CREATE FUNCTION count_array_elements (
i_elements pg_catalog.anyarray,
i_element_to_find pg_catalog.anyelement,
out count bigint
)
RETURNS bigint AS
$body$
BEGIN
SELECT count(*) INTO count
FROM unnest(i_elements) u
WHERE u = i_element_to_find;
END;
$body$
LANGUAGE 'plpgsql'
IMMUTABLE
RETURNS NULL ON NULL INPUT;
With this way, we can query like this one below;
SELECT * FROM count_array_elements(array [ TRUE, TRUE, FALSE, FALSE, FALSE ], TRUE);
Thanks to all contributors here, I learnt a few things.
I am building on work of others in this thread and others in stackoverflow.
I tried to create a function that will count for all the unique elements in the array.
I was targeting returning a json but it seems you can only return as SETOF.
result of count_element_3
CREATE OR REPLACE FUNCTION count_element_3(str_array text[])
RETURNS setof text
AS
$$
DECLARE
unique_element_array text[];
cardinality_array int[];
retArray text[];
BEGIN
-- Find unique items first
unique_element_array := array(select distinct unnest(str_array));
FOR I IN array_lower(unique_element_array, 1)..array_upper(unique_element_array, 1)
LOOP
cardinality_array[I] := (select cardinality(array_positions(str_array, unique_element_array[I])));
retArray[I] := concat(unique_element_array[I],':',cardinality_array[I]);
END LOOP;
RETURN QUERY SELECT(retArray::text);
END;
$$
LANGUAGE plpgsql
VOLATILE
RETURNS NULL ON NULL INPUT;
with t1 as (SELECT
sArray,
c,
coalesce(array_length( array_positions(sArray, c), 1 ),0) AS count
FROM ( VALUES
(ARRAY['a','b','c','a','a'], 'a'),
(ARRAY['a','b','c'], 'd')
) AS t(sArray,c)
)
select sarray, count_element_3(sarray) from t1
sarray count_element_3
text[] text
-------------------------------------
"{a,b,c,a,a}" "{c:1,a:3,b:1}"
"{a,b,c}" "{c:1,a:1,b:1}"

ORACLE SQL Method Produces Warning

I'm having trouble getting the following member method to compile (count_single_buses). Would appreciate any advice on what might be wrong syntactically with my code.
CREATE OR REPLACE TYPE BodyModel2_Type AS OBJECT(
ModelID INTEGER,
ModelName VARCHAR2(45),
FloorType VARCHAR2(45),
Manufacturer VARCHAR2(45),
Length NUMBER(8,2),
Width NUMBER(8,2),
NoOfAxles INTEGER,
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER);
/
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN INTEGER IS
N INTEGER;
BEGIN
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
RETURN N;
END count_single_buses;
END;
--EDIT--
Thanks to #Ravi, I managed to solve the issue my correcting my SQL syntax and setting the resultset to a NUMBER, instead of INTEGER.
CREATE OR REPLACE TYPE BODY BodyModel_Type AS
MEMBER FUNCTION count_single_buses(thisModelID INTEGER) RETURN NUMBER IS
NUM NUMBER;
BEGIN
SELECT COUNT(S.BODYMODELREF) INTO NUM FROM SINGLEDECKBUS_TABLE S WHERE S.BODYMODELREF.MODELID = thisModelID;
RETURN NUM;
END count_single_buses;
END;
/
Still not sure why #Ravi's exact code still produced the warning, and thought that resultset when returning a count value could go into an integer. At any rate, the code works now. Thanks all.
Your BodyModel2_Type Type definition looks okay. However, the body definition is syntactically incorrect.
You cannot define a SQL statement directly to a variable, thus making this statement wrong.
N := (SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S
WHERE S.BODYMODELREF = ModelID);
You will have to use Select... into statement in order to assign the result set of your SQL query into a variable. So, the right syntax should look like this
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID
AFAIK you don't have END the Type followed by the Type name like this END count_single_buses. It'll produce an error. So, overall your Type body specification should look like this:
CREATE OR REPLACE TYPE BODY BodyModel2_Type AS
MEMBER FUNCTION count_single_buses(ModelID INTEGER) RETURN NUMBER IS
N NUMBER;
BEGIN
SELECT COUNT(BODYMODELREF) FROM SINGLEDECKBUS_TABLE S INTO N
WHERE S.BODYMODELREF = ModelID;
RETURN (N);
END;
END;
/
I'm writing this off without any live environment available right now so please let me know if you come across any error in the above code.
Cheers.