How to pass field values as arguement to a stored procedure - sql

I have a DB table in which one column have an array of values.
Lets assume
Student table
"Name" character(10),
"Subject_studying" text[],
I created a stored procedure as follows
CREATE OR REPLACE FUNCTION ArrayLike(vals text[], v text) RETURNS integer AS $$
DECLARE
str text;
BEGIN
v := replace(replace(v, '%', '.*'), '_', '.');
FOREACH str IN ARRAY vals LOOP
IF str ~* v THEN
RETURN 1;
END IF;
END LOOP;
RETURN 0;
END; $$
LANGUAGE PLPGSQL;
Which returns all the subjects that I'm looking for.
How do I pass the parameter of the subjects to the stored procedure.
My query looks like
SELECT Name FROM Student WHERE ArrayLike('Subject_studying', 'english') = 1
The query is giving me an error
ERROR: FOREACH expression must not be null
CONTEXT: PL/pgSQL function Arraylike(text[],text) line 6 at FOREACH over array
********** Error **********
I guess the parameter 'Subject_studying' is not sent as a value but as a simple string. How do we pass the values in that field to the stored procedure?

The error is caused by passing the column name subject_studying as a string, as you already noted. Unquote it to make the error go away.
But you actually do not have to create your own function for this. You can use the built-in ANY operator:
SELECT Name FROM Student WHERE 'english' = ANY(subject_studying);

Related

SQL procedure loop on a key from JSON object

I am trying to insert a JSON object into multiple tables
CREATE TABLE images
(
id serial primary key ,
image_url varchar(255),
filename varchar(255)
);
Procedure:
CREATE OR REPLACE procedure example1(
images_json json
)
AS
$$
DECLARE i json;
BEGIN
RAISE NOTICE 'ITEM ID is %',images_json->>'item_id'; --will be used later on
FOR i IN SELECT * FROM json_array_elements(images_json->>'images')
LOOP
INSERT INTO images (image_url, filename) VALUES (i->>'url', i->>'filename');
end loop;
end;
$$
language plpgsql;
When I test it
call example1('{"item_id": 123,"images":[{ "url": "https://google.com","filename": "google.png"},
{ "url": "https://yahoo.com","filename":"yahoo.png"},
{"url": "https://www.bing.com","filename":"bing.png"}]}')
I get the following error
ERROR: function json_array_elements(text) does not exist
No function matches the given name and argument types. You might need to add explicit type casts.
Where: PL/pgSQL function example1(json) line 5 at FOR over SELECT rows
I want to insert each of the images array into images table.
Your immediate error is, that ->> returns a text value, but json_array_elements() expects a json value. You need to use the -> operator here:
FOR i IN SELECT * FROM json_array_elements(images_json -> 'images')
^ here
But you don't need a LOOP for this (or PL/pgSQL to begin with):
CREATE OR REPLACE procedure example1(images_json json)
AS
$$
BEGIN
RAISE NOTICE 'ITEM ID is %',images_json->>'item_id'; --will be used later on
INSERT INTO images (image_url, filename)
SELECT i->>'url', i->>'filename'
FROM json_array_elements(images_json -> 'images') as i;
end;
$$
language plpgsql;

Splitting comma separated string in PL/pgSQL function

I am trying to write a function that takes an ID as an input and update some fields on that given ID.
So far, it looks like this:
CREATE FUNCTION update_status(p_id character varying,
p_status character varying DEFAULT NULL::character varying) RETURNS character varying
LANGUAGE plpgsql
AS
$$
DECLARE
v_row_count bigint DEFAULT 0;
v_result varchar(255);
BEGIN
IF p_id IS NOT NULL THEN
SELECT count(user_id)
INTO v_row_count
FROM test
WHERE user_id = p_id;
END IF;
IF v_row_count <= 0 THEN
v_result = 'User not found';
RETURN v_result;
ELSE
IF p_id NOT LIKE '%,%' THEN
UPDATE test
SET status = p_status,
updated_by = 'admin'
WHERE user_id IN (p_id);
ELSE
--Here comes split and pass multiple IDs into an IN() operator
END IF;
END IF;
END
$$;
ALTER FUNCTION update_status(varchar, varchar) OWNER TO postgres;
Now, it is supposed to accept only one ID at a time but I wonder if I can get it to also accept multiple IDs -maybe even hundreds- once by splitting that single string into an array of IDs if it has a comma delimiter, then pass those to an IN() operator. How can I get split a string into an array so I can feed it to an IN() operator?
Blue Star already mentioned that there is a built-in function to convert a comma separated string into an array.
But I would suggest to not pass a comma separated string to begin with. If you want to pass a variable number of IDs use a variadic parameter.
You also don't need to first run a SELECT, you can ask the system how many rows were updated after the UPDATE statement.
CREATE FUNCTION update_status(p_status text, p_id variadic integer[])
RETURNS character varying
LANGUAGE plpgsql
AS
$$
DECLARE
v_row_count bigint DEFAULT 0;
BEGIN
UPDATE test
SET status = p_status,
updated_by = 'admin'
WHERE user_id = any (p_id);
get diagnostics v_row_count = row_count;
if v_row_count = 0 then
return 'User not found';
end if;
return concat(v_row_count, ' users updated');
END
$$;
You can use it like this:
select update_status('active', 1);
select update_status('active', 5, 8, 42);
If for some reason, you "have" to pass this as a single argument, use a real array instead:
CREATE FUNCTION update_status(p_status text, p_id integer[])
Then pass it like this:
select update_status('active', array[5,8,42]);
or
select update_status('active', '{5,8,42}');
There's a function for that, see docs.
SELECT string_to_array('str1,str2,str3,str4', ',');
string_to_array
-----------------------
{str1,str2,str3,str4}
Note that once it's an array, you'll want your condition to look like this -
WHERE user_id = ANY(string_to_array(p_id, ',');

PostgreSQL - Creating a loop with a SELECT

I have these Stored Procedure logic to be implemented:
CREATE OR REPLACE FUNCTION get_array(arraynumbers integer[])
RETURNS TABLE (name text) AS $$
DECLARE
index integer := 0
BEGIN
FOREACH index < arraynumbers
LOOP
SELECT e.name as empname FROM employee as e
WHERE e.id = arraynumbers[index]
LIMIT 1
name.push(empname)
ENDLOOP;
RETURN name;
END;
$$
LANGUAGE PLPGSQL;
The goal is to loop based on the length of the array parameter and every index of the parameter will be the condition for retrieving a record and push it to a variable and return the variable as table.
What is the correct way of writing it in PostgreSQL Stored Procedure?
It's unclear to me what exactly the result should be, but as far as I can tell, you don't need a loop or a PL/pgSQL function:
CREATE OR REPLACE FUNCTION get_array(arraynumbers integer[])
RETURNS TABLE (name text)
AS
$$
SELECT e.name
FROM employee as e
WHERE e.id = any(arraynumbers);
$$
LANGUAGE SQL;
This will return one row for each id in arraynumbers that exist in the employee table. As the function is declared as returns table there is no need to collect the values into a single variable (which you didn't declare to begin with)

how to get current row value in plpgsql function

I need to create plpgsql methods which use current row value without passing in parameter in update command.
I tried
create temp table test ( test text, result text ) on commit drop;
insert into test values ('1','');
CREATE OR REPLACE FUNCTION public.gettest() RETURNS text AS $$
DECLARE
comp text := NULL;
BEGIN
EXECUTE 'SELECT ''Current row is '' ||test.test' INTO comp;
RETURN comp;
END; $$ LANGUAGE plpgsql STRICT STABLE;
update test set result = 'Result: ' || gettest();
but got exception
ERROR: missing FROM-clause entry for table "test"
LINE 1: SELECT 'Current row is ' ||test.test
^
QUERY: SELECT 'Current row is ' ||test.test
CONTEXT: PL/pgSQL function gettest() line 6 at EXECUTE statement
********** Error **********
ERROR: missing FROM-clause entry for table "test"
SQL state: 42P01
Context: PL/pgSQL function gettest() line 6 at EXECUTE statement
How to fix ?
How to fix without passing vaue to plpgsql method parameter ?
There is no such thing as an "implicit current row". You have to pass the the function whatever it needs as a parameter. You can however pass a complete row if you want to:
create temp table test (val text, result text ) on commit drop;
insert into test values ('1','');
insert into test values ('2','');
CREATE OR REPLACE FUNCTION gettest(p_test test) RETURNS text AS $$
DECLARE
comp text := NULL;
BEGIN
comp := 'Current row is '||p_test.val;
RETURN comp;
END; $$ LANGUAGE plpgsql STRICT STABLE;
update test set result = 'Result: ' || gettest(test);
I had to rename the column test to something else, otherwise the call gettest(test) would refer to the column not the whole table (=row) and thus it didn't work.

postgresql function error: column name does not exist

i've implemented a function that check if a value appears in a specific row of a specific table:
CREATE FUNCTION check_if_if_exist(id INTEGER, table_name character(50), table_column character(20) ) RETURNS BOOLEAN AS $$
DECLARE res BOOLEAN;
BEGIN
SELECT table_column INTO res
FROM table_name
WHERE table_column = id;
RETURN res;
END;
$$ LANGUAGE plpgsql
i've create and fill a simple test table for try this function:
CREATE TABLE tab(f INTEGER);
and i call function like
SELECT check_if_exist(10, tab, f);
but i occurs in this error:
ERROR: column "prova" does not exist
LINE 1: SELECT check_if_exist(10, tab, f);
^
********** Error **********
ERROR: column "tab" does not exist
SQL state: 42703
Character: 27
why?
In addition to Elmo response you must be careful with types. You have got:
ERROR: column "tab" does not exist
because SQL parser do not know how to deal with tab which is without quote. Your query must be like:
SELECT check_if_exist(10, 'tab', 'f');
As Elmo answered you use dynamic query, so even if you quote tab you will got error:
ERROR: relation "table_name" does not exist
so you can use EXECUTE, example:
CREATE OR REPLACE FUNCTION check_if_exist(id INTEGER, table_name varchar, table_column varchar) RETURNS BOOLEAN AS $$
DECLARE
sql varchar;
cnt int;
BEGIN
sql := 'SELECT count(*) FROM ' || quote_ident(table_name) || ' WHERE ' || quote_ident(table_column) || '=$1';
RAISE NOTICE 'sql %', sql;
EXECUTE sql USING id INTO cnt;
RETURN cnt > 0;
END;
$$ LANGUAGE plpgsql
You can also use VARCHAR instead of character(N) in function arguments and use CREATE OR REPLACE FUNCTION ... instead of just CREATE FUNCTION ... which is very handy at debugging.
Your code has no chance to work - when dealing with different tables in PLPGSQL you need to utilize dynamic queries, so EXECUTE is required - http://www.postgresql.org/docs/current/static/plpgsql-statements.html#PLPGSQL-STATEMENTS-EXECUTING-DYN
But first of all - there is nothing bad in using PostgreSQL EXISTS - http://www.postgresql.org/docs/current/static/functions-subquery.html#AEN15284 instead of inventing your own - performance of your solution will be significantly worse than using included batteries...
Hopefully this is helpful. Good luck.