Postgresql: Custom Type + Arrays combination - arrays

I'm unable to find where the issue is for the below program. the values of the custom type are displaying without any errors when I use RAISE NOTICE statements at the end. When I run the final select statement, the error is Array value must start with "{" or dimension information. Please help me with the select statement on how to call the package/function.
create
or
replace TYPE t_col_foo as object
(
ID NUMBER
, CLUSTERNAME VARCHAR2(300)
, "1200AM" varchar2(10));
create
or
replace TYPE T_COL_R AS TABLE OF t_col_foo;
CREATE OR REPLACE PACKAGE foo_avail_pkg
IS
FUNCTION foo_slots
(
p_ref_data anyarray
)
RETURN t_col_r[];
END foo_avail_pkg;
CREATE OR REPLACE PACKAGE BODY foo_avail_pkg
IS
FUNCTION foo_slots
(
p_ref_data anyarray
)
RETURN t_col_r[]
IS
-- declare
r_target_data t_col_foo:=t_col_foo(null,null,null);
r_target_data_1 t_col_foo;
r_source_data text[];
t_return t_col_tab1;
BEGIN
t_return:=t_col_tab1();
select
array
(
select
unnest( p_ref_data )
)
into r_source_data
;
-- r_target_data = '{}';
for i in coalesce(array_lower(r_source_data,1),0) .. coalesce(array_upper(r_source_data,1),0)
LOOP
r_target_data.ID := substr(r_source_data[i],1,instr(r_source_data[i],',',1,1)-1);
r_target_data.CLUSTERNAME := substr(r_source_data[i],length(r_target_data.ID)+2,(instr(r_source_data[i],',',length(r_target_data.ID)+1,2) - instr(r_source_data[i],',',1,1))-1);
r_target_data."1200AM" := 3;
r_target_data_1 :=row(r_target_data.ID ,r_target_data.CLUSTERNAME,r_target_data."1200AM") :: t_col_foo;
END LOOP;
-- dbms_output.put_line(r_target_data_1);
RETURN r_target_data_1;
end;
END foo_avail_pkg;
This is how I have to call
select * from foo_avail_pkg.foo_SLOTS(array
(
select
ID
||','
||CLUSTER_NAME
||','
||LOB
from
y limit 1
));
And the error is
ERROR: malformed array literal: "(1398,Sanity20feb,3)"
DETAIL: Array value must start with "{" or dimension information.

Related

Get value from from a json_array in oracle

i need the values of a json_array. I tried this:
DECLARE
l_stuff json_array_t;
BEGIN
l_stuff := json_array_t ('["Stirfry", "Yogurt", "Apple"] ');
FOR indx IN 0 .. l_stuff.get_size - 1
LOOP
INSERT INTO t_taböe (name, type)
VALUES(l_stuff.get(i), 'TEXT');
END LOOP;
END;
You are passing the position as i instead of indx; but you need a string so use get_string(indx) as #Sayan said.
But if you try to use that directly in an insert you'll get "ORA-40573: Invalid use of PL/SQL JSON object type" because of a still-outstanding (as far as I know) bug.
To work around that you can assign the string to a variable first:
l_name := l_stuff.get_string(indx);
INSERT INTO t_taböe (name, type)
VALUES(l_name, 'TEXT');
db<>fiddle
You do not need PL/SQL and can do it in a single SQL statement:
INSERT INTO t_taböe (name, type)
SELECT value,
'TEXT'
FROM JSON_TABLE(
'["Stirfry","Yogurt","Apple"]',
'$[*]'
COLUMNS (
value VARCHAR2(50) PATH '$'
)
);
db<>fiddle here
First convert the JSON array into an ordinary PL/SQL array, then use a bulk insert.
Here is a reproducible example:
create table tab (name varchar2 (8), type varchar2 (8))
/
declare
type namelist is table of varchar2(8) index by pls_integer;
names namelist;
arr json_array_t := json_array_t ('["Stirfry", "Yogurt", "Apple"]');
begin
for idx in 1..arr.get_size loop
names(idx) := arr.get_string(idx-1);
end loop;
forall idx in indices of names
insert into tab (name, type) values (names(idx), 'TEXT');
end;
/
The query and outcomes:
select * from tab
/
NAME TYPE
-------- --------
Stirfry TEXT
Yogurt TEXT
Apple TEXT
Just use get_string:
DECLARE
l_stuff json_array_t;
BEGIN
l_stuff := json_array_t ('["Stirfry", "Yogurt", "Apple"] ');
FOR indx IN 0 .. l_stuff.get_size - 1
LOOP
--INSERT INTO t_taböe (name, type)
-- VALUES(l_stuff.get_string(indx), 'TEXT');
dbms_output.put_line(l_stuff.get_string(indx));
END LOOP;
END;

How do I call a declared array variable in a where clause in postgres?

I am trying to build a declared array from all the dogs that share the same family_id and query the dog_characteristics table using the array.
CREATE OR REPLACE FUNCTION update_dog_characteristics_guarantor_id()
RETURNS trigger AS $$
DECLARE dog_ids INT[];
BEGIN
SELECT id into dog_ids FROM dogs WHERE dogs.family_id = OLD.id;
IF ((OLD.family_id IS NOT NULL) && ((SELECT COUNT(*) FROM dog_ids) > 0)) THEN
UPDATE
dog_characteristics
SET
guarantor_id = NEW.guarantor_id
WHERE
dog_characteristics.account_id = OLD.account_id
AND dog_characteristics.dog_id IN ANY(dog_ids);
RETURN NULL;
END IF;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
What I have tried
AND dog_characteristics.dog_id = ANY(dog_ids);
AND dog_characteristics.dog_id = ANY(dog_ids::int[]);
AND dog_characteristics.dog_id IN (dog_ids::int[]);
AND dog_characteristics.dog_id IN (dog_ids);
AND dog_characteristics.dog_id IN (ARRAY(dog_ids));
AND dog_characteristics.dog_id IN ARRAY(dog_ids);
AND dog_characteristics.dog_id IN implode( ',', dog_ids);
Most common error
ERROR: malformed array literal: "672"
DETAIL: Array value must start with "{" or dimension information.
CONTEXT: PL/pgSQL function update_dog_characteristics_guarantor_id() line 5 at SQL statement
There are multiple errors in your trigger function.
As dog_ids is declared as an array, the result of the first select has to be an array as well. To do that, you need to aggregate all IDs that are returned from the query.
So the first select statement should be
select array_agg(id) --<< aggregate all IDs into an array
into dog_ids
FROM dogs
WHERE dogs.family_id = OLD.id;
To check if an array has elements, you can't use select count(*), you need to use use array_length() or cardinality().
The && is not the "AND" operator in SQL - that's AND - so the if should be:
IF OLD.family_id IS NOT NULL AND cardinality(dog_ids) > 0 THEN
...
END IF;
The where condition on the array should be:
AND dog_characteristics.dog_id = ANY(dog_ids);

PL/SQL Cursor and Array of different size

I want to store the content of a cursor in an associative array (Table index by binary_integer). But in the same array I also want to store an additional variable, say a boolean.
My cursor has n elements per row and the array is defined to have n+1 elements (n with the same %type as the cursorelements), the last one being the boolean.
What I whant is something like this
for cursorrow in cursor(...)
loop
array(row i) := cursorrow, boolean_variable;
end loop;
|1|2|...|n|n+1| := |1|2|...|n|, |1|
Unfortunately I can't get it to work.
Anybody knows how to do it?
How about defining a composite record type consisting of a %rowtype record and a Boolean?
Test setup:
create table demo_table
( some_id integer primary key
, some_name varchar2(30) not null unique
, some_type varchar2(10) not null );
insert all
into demo_table values (1, 'One', 'X' )
into demo_table values (2, 'Two', 'Y' )
into demo_table values (3, 'Three', 'Z' )
select * from dual;
Test:
(Edit: added dbms_output messages within loop)
declare
subtype demo_rectype is demo_table%rowtype;
type demo_rec is record
( details demo_rectype
, somecheck boolean );
type demo_tt is table of demo_rec index by pls_integer;
t_demo demo_tt;
cursor c_demo is select * from demo_table;
begin
for r in c_demo
loop
t_demo(c_demo%rowcount).details := r;
t_demo(c_demo%rowcount).somecheck := dbms_random.value < 0.5;
dbms_output.put_line
( t_demo(c_demo%rowcount).details.some_name || ': ' ||
case t_demo(c_demo%rowcount).somecheck
when true then 'TRUE'
when false then 'FALSE'
else 'NULL'
end );
end loop;
dbms_output.new_line();
dbms_output.put_line('Array t_demo contains ' || t_demo.count || ' items.');
end;
Output:
One: FALSE
Two: FALSE
Three: TRUE
Array t_demo contains 3 items.
Since the record type differs by 1 field from the table structure (i.e. the boolean), you can not assign the cursor row to the record type in 1 assignment statement. You need to do it for every table column individually:
for cursorrow in cursor(...)
loop
array(row i).col1 := cursorrow.col1;
array(row i).col2 := cursorrow.col2;
...
array(row i).coln := cursorrow.coln;
array(row i).boolean_variable := some_boolean_value;
end loop;
As mentioned in comments,you can create a record and do it. You can use the below procedure to achieve your requirement.
create or replace procedure proc_test
as
cursor cur_tab is
select a --have selected only 1 column..you can choose many
from test;
TYPE var_temp IS TABLE OF cur_tab%ROWTYPE INDEX BY PLS_INTEGER;
v_var var_temp;
/**You can add columns selected in you cursor here in your record****/
TYPE abc IS RECORD
(
id varchar2(100),
orig_name boolean
);
TYPE xx IS TABLE OF abc ;
-- initialization of record
v_xx xx := xx() ;
t boolean:=TRUE;
begin
open cur_tab;
fetch cur_tab bulk collect into v_var;
close cur_tab;
for i in 1..v_var.count
loop
v_xx.extend;
v_xx(i).id := v_var(i).a;
v_xx(i).orig_name := t;
dbms_output.put_line (v_xx(i).id ||'----'||sys.diutil.bool_to_int(v_xx(i).orig_name));
---OR
dbms_output.put_line (v_xx(i).id ||'----'||case when v_xx(i).orig_name = true then 'TRUE' ELSE 'FALSE' end );
end loop;
exception
when others then
null;
end;
Call:
execute proc_test;

Oracle PL/SQL Parameter value from parameter name in String

Hi I need to retrieve a parameter value where the parameter name is in different parameter.
Lets say the proc is as below
PROCEDURE findValue
(
p_date IN VARCHAR2,
p_name IN VARCHAR2,
p_class IN VARCHAR2,
p_paramname IN VARCHAR2,
)
IS
Now lets say i want to pass p_paramname as p_date and further use the value of p_date parameter in PL/SQL block, how do i use it ?
If you can afford PL/SQL table inputs then try the logic below:
CREATE TYPE param_tbl_type IS TABLE OF VARCHAR2(255);
CREATE OR REPLACE FUNCTION
(p_param_names param_tbl_type,
p_param_values param_tbl_type)
RETURN VARCHAR2
IS
l_dyn_func_str VARCHAR2(4000);
l_ret_val VARCHAR2(4000);
vCursor integer;
fdbk PLS_INTEGER;
BEGIN
-- make sure you have equal number of params and currespondin values.
-- Index much match too. i.e. if p_param_names(0) is 'p_currency' then p_param_value(0) must be 'USD' (value of currency)
IF p_param_names.COUNT <> p_param_values.COUNT THEN
raise_application_error(-2000,'Incorrect number of arguments');
END IF;
-- use this variable to generate anonymous block code
l_dyn_fun_str:='BEGIN :retval := function_tobe_called (';
-- loop through each parameter and add to the parameter list
FOR i in 1..p_param_names.COUNT LOOP
IF i=0 THEN
l_dyn_fun_str:=l_dyn_fun_str||':'||l_param_name(i);
ELSE
l_dyn_fun_str:=l_dyn_fun_str||','||':'||l_param_name(i);
END IF;
END LOOP;
l_dyn_fun_str:=l_dyn_fun_str||'); END;'
-- open cursor and associate with function call string (l_dyn_fun_str)
vCursor:=DBMS_SQL.OPEN_CURSOR;
DBMS_SQL.PARSE(vCursor,l_dyn_fun_str);
-- loop through parameter values and associate them with bind variables
DBMS_SQL.BIND_VARIABLE(vCursor,':retval',l_ret_val);
FOR j in 1..p_param_values.COUNT LOOP
DBMS_SQL.BIND_VARIABLE(vCursor, ':'||l_param_names(j), l_param_values(j));
END LOOP;
-- execute function
fdbk := DBMS_SQL.EXECUTE (vCursor);
-- get output of function
DBMS_SQL.VARIABLE_VALUE (vCursor, 'retval', l_ret_val);
RETURN l_ret_val;
END;
Note: The code syntax may not be perfect but the pseudo should still work for your requirement.
Just check the value of P_PARAMNAME against the possible parameter names and branch accordingly. E.g.,
CREATE OR REPLACE PROCEDURE matt_test1 (p_date IN VARCHAR2,
p_name IN VARCHAR2,
p_class IN VARCHAR2,
p_paramname IN VARCHAR2) IS
BEGIN
CASE p_paramname
WHEN 'P_NAME' THEN
DBMS_OUTPUT.put_line ('Did something with P_NAME. Value was ' || p_name);
WHEN 'P_DATE' THEN
DBMS_OUTPUT.put_line ('Did something with P_DATE. Value was ' || p_date);
WHEN 'P_CLASS' THEN
DBMS_OUTPUT.put_line ('Did something with P_CLASS. Value was ' || p_class);
ELSE
raise_application_error (-20001, 'Invalid parameter name: ' || p_paramname);
END CASE;
END matt_test1;
begin
matt_test1(SYSDATE,'Fred','English 101', 'P_DATE');
matt_test1(SYSDATE,'Fred','English 101', 'P_NAME');
matt_test1(SYSDATE,'Fred','English 101', 'P_CLASS');
end;
Output:
Did something with P_DATE. Value was 11-Aug-2015
Did something with P_NAME. Value was Fred
Did something with P_CLASS. Value was English 101

Passing an array to function and use it in WHERE IN clause

I want to use array(which is passed to function) under Where in clause
Here is what i tried
First created the array type
create or replace type p_emp_arr as table of number
Function is
create or replace
FUNCTION getEmployee_func ( empId_arr IN p_emp_arr)
RETURN number IS
total number(2) := 0;
BEGIN
IF(empId_arr is null)
THEN
empIdClause := '';
ELSE
empIdClause := 'AND Employee.empId in (select column_value from table('||empId_arr||'))';
END IF;
....
RETURN total;
END;
But gives error
Error(17,23): PLS-00306: wrong number or types of arguments in call to '||'
The error is because CONCAT (||) operator accepts only scalar variables (string/number), you cannot pass an array to it.
You need to execute this as a dynamic PL/SQL block.
In case you want to bind the array dynamically, try something like this.
Bind the variables using IN and OUT keywords appropriately.
In your Anonymous block string, prefix the to-be-bind variables with colon (:)
EXECUTE IMMEDIATE '
BEGIN
SELECT
COLUM1,COLUMN2..
INTO
:VAR1, :VAR2..
FROM .... WHERE...
AND Employee.empId in (select column_value from table(:empId_arr));
END;
'
USING OUT VAR1, OUT VAR2... IN empId_arr;
It can also be Simply,
OPEN EMP_CURSOR FOR
'SELECT * FROM Employee
where empId in SELECT COLUMN_VALUE FROM TABLE(:empId_arr)'
USING empId_arr ;
If you take the output as a cursor;
AS Wernfried mentioned.. Using MEMBER OF operator.
OPEN EMP_CURSOR FOR
'SELECT * FROM Employee
where empId member of :empId_arr'
USING empId_arr ;

Resources