PostgresQL: Loop over integer[] and insert current element into another table - sql

I have a function which takes a parameter named p_categories, of type smallint[].
How do I look through p_categories and execute the following command, where cat is the current smallint in p_categories?
INSERT INTO mytable (i_category) VALUES (cat)
Something like this (pseudocode) maybe?
FOR cat in SELECT p_categories
INSERT INTO mytable (i_category) VALUES (cat)
END LOOP;
That gives me an error: "invalid input syntax for integer: "{14,20}" when p_categories is '{14,20}'.

I think you're looking for unnest
INSERT INTO mytable (i_category)
SELECT unnest(p_categories);
The unnest array function just expands an array into its elements.
Or a more concrete example:
> create table t (i int not null);
> insert into t (i) select unnest(array[1,2]);
> select * from t;
i
---
1
2
(2 rows)

Related

How can I use If-Then statements in Snowflake - SnowSQL (Linux)

I' ve been trying to use If-Then statements in Snowflake - SnowSQL but I keep getting the error:
001003 (42000): SQL compilation error:
syntax error line 1 at position 0 unexpected 'IF'.
Here is my code:
#!/bin/ksh
export SNOWSQL_USER="my_usr"
export SNOWSQL_PWD="my_pwd"
export SNOWSQL_WAREHOUSE="my_wh"
export SNOWSQL_DATABASE="my_db"
export SNOWSQL_SCHEMA="my_schm"
export SNOWSQL_ACCOUNT="my_acct"
snowsql -o echo=true -o exit_on_error=True<<EOSQL
-- create a sample table
CREATE TEMPORARY TABLE sample_table (id INT, value INT);
-- populate the sample table
INSERT INTO sample_table VALUES (1, 10), (2, 20), (3, 30);
-- use an IF statement to conditionally update the value column
IF (SELECT COUNT(*) FROM sample_table WHERE value > 25) > 0 THEN
UPDATE sample_table SET value = value * 2 WHERE value > 25;
ELSE
UPDATE sample_table SET value = value * 3;
END IF;
-- view the updated table
SELECT * FROM sample_table;
EOSQL
Am I missing something that's necessary for the if statement to run?
Tried to write it in a different way.. nothing. Tried to use variables.. nothing.
To use control flow constructs like IF-ELSE you need Snowflake Scripting block:
EXECUTE IMMEDIATE $$
BEGIN
IF (EXISTS(SELECT * FROM sample_table WHERE value > 25)) THEN
UPDATE sample_table SET value = value * 2 WHERE value > 25;
ELSE
UPDATE sample_table SET value = value * 3;
END IF;
END;
$$;
Also:
(SELECT COUNT(*) FROM sample_table WHERE value > 25) > 0
=>
EXISTS(SELECT * FROM sample_table WHERE value > 25)
Related: Using Snowflake Scripting in SnowSQL and the Classic Web Interface

How to pass table column values to function

Table source contains integer column. Its values should be passed to Postgresql 12 function for selecting data from other table.
I tried to use array
CREATE OR REPLACE FUNCTION public.TestAddAssetTransactions(dokumnrs int[])
RETURNS int AS
$BODY$
with i1 as (
INSERT INTO bilkaib (dokumnr)
select dokumnr from dok where dokumnr in (select * from unnest(dokumnrs))
returning *
)
select count(*) from i1;
$BODY$ language sql;
create temp table bilkaib (dokumnr int ) on commit drop;
create temp table dok (dokumnr serial primary key ) on commit drop;
create temp table source (dokumnr int ) on commit drop;
insert into source values (1),(2);
select TestAddAssetTransactions( (select ARRAY[dokumnr] from source)::int[] )
but got error
ERROR: more than one row returned by a subquery used as an expression
How to pass single column values from table rows to function? Should array, table type or temp table used?
Using Postgresql 12+
You need to aggregate the ints in your function call. Otherwise you're just casting each value to a single-element array and they try to cast a column to an array of int.
select TestAddAssetTransactions( (select array_agg(dokumnr)::int[] from source) );
online demo

Can't save comma separated number string in varchar2()

I've got a list of items I want to add in a single click, for this purpose I created a table with a column with a type varchar2(4000), in this column I want to list id's that refer to the other table so I can paste the value of this column as a parameter. ex. select t.* from table_name t where t.point_id in (varchar2 string of comma seprated point_ids).
The problem I've got is that when I put more than 1 id in the varchar2 field I get ORA-06502: PL/SQL: numeric or value error: character to number conversion error
How can I avoid this error? My field is varchar2, not number and I don't want it to be converted. I need the value I'm parsing to be saved. ex. (11, 12)
Picture of my Table:
EDIT: Note - My select is working okay, the problem I'm having is with saving the information.
My Insert :
procedure lab_water_pointsgroup (v_group_id lab_water_pointsgroups.group_name%type,
v_group_name lab_water_pointsgroups.group_code%type,
v_group_code lab_water_pointsgroups.lab_points_ids%type,
v_lab_points_ids lab_water_pointsgroups.group_id%type) as
begin
update lab_water_pointsgroups
set group_name = v_group_name,
group_code = v_group_code,
lab_points_ids = v_lab_points_ids
where group_id = v_group_id;
if ( SQL%RowCount = 0 ) then
insert into lab_water_pointsgroups
(group_id, group_name, group_code, lab_points_ids)
values
(v_group_id, v_group_name, v_group_code, v_lab_points_ids);
end if;
end;
Not sure how exactly I can help you here as you gave no example. Have a look at the below demo, maybe the contruct with xmltable solves your problem. HTH KR
create table testtab (id number);
insert into testtab values (1);
select * from testtab where id in ('1'); -- works
select * from testtab where id in (1); -- works
select * from testtab where id in (1,2); -- works
select * from testtab where id in ('1,2'); -- ORA-01722: invalid number
select * from testtab where id in (select to_number(xt.column_value) from xmltable('1,2') xt); -- works
Here is how you defined parameters for your procedure:
v_group_id lab_water_pointsgroups.group_name%type,
v_group_name lab_water_pointsgroups.group_code%type,
v_group_code lab_water_pointsgroups.lab_points_ids%type,
v_lab_points_ids lab_water_pointsgroups.group_id%type
I suspect that you made mistake with types, because id has name type, name has code type etc. So it should be:
v_group_id lab_water_pointsgroups.group_id%type,
v_group_name lab_water_pointsgroups.group_name%type,
v_group_code lab_water_pointsgroups.group_code%type,
v_lab_points_ids lab_water_pointsgroups.lab_points_ids%type
And I suggest to use merge instead of this update / insert, but it's not what you asked for :)
Your error is in that you don't make difference between variable containing comma separated numbers and actual enumeration in the 'in' operator. After your code analize and preparation to execution your statement will be like .. id in ('1,2,3') instead of ..id in (1,2,3), did you notice differnce ? So you need to transform comma separated values into array or in this case into collection. Your code should be like this:
select t.*
from table_name t
where t.point_id in
(select regexp_substr(YOUR_VARCHAR2_COLUMN_VALUE, '[^,]+', 1, level)
from dual
connect by regexp_substr(YOUR_VARCHAR2_COLUMN_VALUE, '[^,]+', 1, level) is not null)

Turn SQL statement in a function

How to replace a concrete value with a variable in Oracle SQL? I have
select 5, min(id) from my_table where id > 5 --AND ..
UNION ALL
select 6, min(id) from my_table where id > 6 --AND ..
UNION ALL
....
| 5 | 6 |
| 6 | 8 |
...
How to wrap it in a function executing pseudocode below?
for ( $i in ( select id from my_table)){
UNION ALL
select $i, min(id) from my_table where id > $i
}
Edit: To make it clear, I am looking for a general method to turn a select with hard wired values into a function that accepts variables. Note the --AND part.
Edit2:
Let me translate it in Java.
Qu estion: I have code
System.out.println(1+2+" = 1+2"+ " ");
that returns a sum of two numbers. How to replace concrete 1+2 with any a and b?
Answer:
You need to define
int getSum(int a, int b){ return a+b;}
Now you can write
for(int a : setA){
for(int b : setB){
System.out.println(" " +a+"+"+b+" = "+ getSum(a,b)+" ");
}}
This way you can iterate over all elements of setA and all elements of setB instead of providing concrete values 1 and 2.
I have exactly the same question for SQL. If I have a query that returns a result for a concrete value (Java example: 1,2; SQL example: where id > 6 ) - how to modify it so that SQL iterates over all possible values (obtained by select id from t group by id)?
It looks like you want to get the next ID for each id. That can be done simply by using the lead function:
select
t.id,
lead(t.id) over (order by t.id) as next_id
from
my_table t
If you do want to return it from a function, you would probably need a table function returning a custom table type, because the rowtype to return doesn't match your table structure, so you can't use my_table%rowtype.
The snippet below would create such a row type and matching table type, and a function that would return the same as above query. The last lines contain the same query, now using the function instead of the table:
-- Define a row type (an object).
create or replace type my_row_type as object (
id int,
next_id int
);
-- Define a table type of that row type
create type my_table_type as table of my_row_type;
-- Create a function that returns a table.
create or replace function my_function
return my_table_type
is
result my_table_type;
begin
select
-- Use the rowtype constructor to put the id and next_id into a row object
my_row_type(
t.id,
lead(t.id) over (order by t.id))
-- use bulk collect into to query all rows into the table variable 'result'
bulk collect into
result
from
my_table t;
-- Don't forget to actually return it.
return result;
end;
/
-- Query it by 'casting' the function result to a table.
select
id,
next_id
from
table(my_function);
For more info on table functions, I think this tutorial/article is a good start:
http://stevenfeuersteinonplsql.blogspot.com/2015/04/table-functions-introduction-and.html

How to get an INSERT's returned value in a SQL function and use it in that same function?

here's an example to complete the title,
this is the inner of my function (i am using postgresql but i guess this is pure SQL language)
$$
INSERT INTO foo VALUES (DEFAULT, $1) RETURNING id;
INSERT INTO link_foo_to_bar VALUES (1, <?>);
$$ language SQL;
two things to notice here, I RETURNING the id of the first insert, how to catch it as to use the returned id as the second argument of the second insert marked as < ? > ?
You can use a CTE to capture the rows inserted from the first insert:
with firstinsert as (
INSERT INTO foo VALUES (DEFAULT, $1) RETURNING id
)
INSERT INTO link_foo_to_bar
select 1, id
from firstinsert;
If the first column is a serial/primary key, I would use CURRVAL function:
$$
INSERT INTO foo VALUES (DEFAULT, $1);
INSERT INTO link_foo_to_bar VALUES (1, CURRVAL('foo_id_seq'));
$$ language SQL;