I have a table in Postgres that looks something like:
TABLE Product (
id serial,
name varchar(512),
price numeric,
...
)
I am trying to create a procedure that would take an array of json objects and insert each element as a row into the table.
I am doing something like this:
CREATE OR REPLACE PROCEDURE insert_into_product(entries json[])
LANGUAGE plpgsql AS $$
DECLARE _elem json;
BEGIN
FOREACH _elem IN ARRAY entries
LOOP
INSERT INTO product
SELECT id, name, price
FROM json_populate_record (NULL::product,
format('{
"id": "%d",
"name": "%s",
"price": "%f"
}', _elem->id, _elem->name, _elem->price)::json
);
END LOOP;
END;
$$
And I'm getting an error "column "id" does not exist"
Is the problem here that id is serial, and in my json object it's an int? What's the right way to insert row from a json object here?
Your _elem variable is already a JSON value, so you can pass that directly to the json_populate_record() function:
CREATE OR REPLACE PROCEDURE insert_into_product(entries json[])
LANGUAGE plpgsql
AS $$
DECLARE
_elem json;
BEGIN
FOREACH _elem IN ARRAY entries
LOOP
INSERT INTO product
SELECT id, name, price
FROM json_populate_record (NULL::product, _elem);
END LOOP;
END;
$$
;
But you don't need a LOOP or even PL/pgSQL for this:
CREATE OR REPLACE PROCEDURE insert_into_product(entries json[])
LANGUAGE sql
AS $$
INSERT INTO product
SELECT (json_populate_record(null::product, u.entry)).*
FROM unnest(entries) as u(entry);
$$
;
Related
How to return a dynamic result set from a PLPGSQL function? What can be used instead of RETURNS SETOF table_name?
Example:
CREATE OR REPLACE FUNCTION dynamic_query
(
table_name varchar
)
RETURNS **SETOF table_name**
LANGUAGE PLPGSQL
AS $$
DECLARE
sql varchar;
BEGIN
/*some code*/
sql = 'SELECT * FROM '|| table_name;
/*some code*/
RETURN QUERY EXECUTE sql;
END $$;
You cannot do that. Your only choices are:
use RETURNS SETOF record.
That works, but you will have to specify the table structure in the query that uses the function, as shown in the documentation.
use RETURNS SETOF jsonb and use to_jsonb in the query to convert the result rows to jsonb
You can return a set of text representation of the record and cast it with your existing type in the select query.
-- Create and fill sample
DROP TABLE IF EXISTS test_table;
CREATE TABLE test_table
(
id BIGSERIAL PRIMARY KEY,
int_val INTEGER,
text_val TEXT,
date_val DATE
);
INSERT INTO test_table(int_val, text_val, date_val)
VALUES (1, 'TEST1, TEST', CURRENT_DATE),
(2, 'TEST2, TEST', CURRENT_DATE),
(3, 'TEST3, TEST', CURRENT_DATE),
(4, 'TEST4, TEST', CURRENT_DATE);
--Create Dynamic query
DROP FUNCTION IF EXISTS dynamic_select(p_table_name TEXT);
CREATE OR REPLACE FUNCTION dynamic_select(IN p_table_name TEXT)
RETURNS SETOF TEXT
AS
$$
DECLARE
v_dynamic_query TEXT;
BEGIN
--Create query
v_dynamic_query = FORMAT('SELECT t::TEXT FROM %I t', p_table_name);
--Execute
RETURN QUERY EXECUTE v_dynamic_query;
END;
$$ LANGUAGE plpgsql;
-- Returns text representation of row values of test table
SELECT *
FROM dynamic_select('test_table');
-- Cast to defined type
SELECT (t::TEST_TABLE).*
FROM dynamic_select('test_table') t;
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;
I need help to complete the following requirements:
[x] Profile table with columns id, username, ...
[x] Comment table with columns id, content, ...
[x] CommentReference table with columns id, profile_id, comment_id, ...
When a new comment is created, before inserting:
[ ] Check if NEW.content have references to usernames, like #someusername
[ ] Check if each reference exists in the profile table
[ ] For references that exist, insert into CommentReferences the profile and comment
For now, what I have is the following code:
PS: the following code has errors, I need help to fix it. I'm using postgres version 12.
CREATE FUNCTION create_comment_usernames_references()
RETURNS trigger AS $$
DECLARE usernames TEXT[];
DECLARE username TEXT;
DECLARE profile_id TEXT; -- profile_id is of type uuid, is it correct to use TEXT here?
BEGIN
-- verify if there are usernames in the comment.content with the username regex
SELECT DISTINCT(
regexp_matches(
NEW.content,
'#(([a-z0-9]*((?<=[a-z0-9])[-|_|\.](?=[a-z0-9]))[a-z0-9]*)*|[a-z0-9]*)',
'g'
)
)[1]
INTO usernames;
FOREACH username IN ARRAY usernames LOOP
SELECT (SELECT id FROM "public"."Profile" WHERE "username" = username) INTO profile_id
INSERT INTO "public"."CommentReference" (comment_id, profile_id) VALUES (NEW.id, profile_id);
END LOOP;
-- return nothing
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER create_comment_usernames_references_trigger
BEFORE INSERT OR UPDATE ON "public"."Comment"
FOR EACH ROW
EXECUTE PROCEDURE create_comment_usernames_references();
Solve it myself.
Final code:
CREATE OR REPLACE FUNCTION find_profile_ids_by_usernames_in_text(_value TEXT)
RETURNS TABLE (profile_id uuid) AS
$func$
BEGIN
RETURN QUERY
SELECT
id
FROM
"public"."Profile"
WHERE
username IN (
SELECT DISTINCT(
regexp_matches(
_value,
'#(([a-z0-9]*((?<=[a-z0-9])[-|_|\.](?=[a-z0-9]))[a-z0-9]*)*|[a-z0-9]*)',
'g')
)[1]
);
END
$func$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION create_comment_reference(_comment_id UUID, _content TEXT)
RETURNS integer AS $$
DECLARE
row RECORD;
BEGIN
FOR row IN
SELECT * FROM find_profile_ids_by_usernames_in_text(_content)
LOOP
INSERT INTO
"public"."CommentReference" (comment_id, profile_id)
VALUES
(_comment_id, row.profile_id)
ON CONFLICT DO NOTHING;
END LOOP;
RETURN 1;
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION create_comment_reference_trigger_func()
RETURNS trigger AS $$
DECLARE someval integer;
BEGIN
select * from create_comment_reference(NEW.id, NEW.content) into someval;
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER create_comment_usernames_references_trigger
AFTER INSERT OR UPDATE ON "public"."Comment"
FOR EACH ROW
EXECUTE PROCEDURE create_comment_reference_trigger_func();
How do i create a Procedure that returns a set of rows from a table?
or is it even possible to return a tabular result set with procedure.
I tried adding returns setof students like you do in a function and table(id int) but it doesn't work.
SAMPLE CODE:
CREATE OR REPLACE PROCEDURE getStudents()
LANGUAGE plpgsql
AS $$
BEGIN
SELECT * FROM STUDENTS
COMMIT;
RETURN;
END;
$$;
I can call it but it says query has no destination for result data
Procedures aren't meant to return data, that's what functions are for.
You can use a plain SQL function for this, no need for PL/pgSQL:
CREATE OR REPLACE funct get_students()
returns setof student
LANGUAGE sqö
AS $$
select *
from students;
$$;
Then use it like a table:
select *
from get_students();
There is also no need for a commit.
Try to use function instead of procedure. I usually use this.
You need to create a ctype for fetching the data.
Put whatever columns you have to fetch from STUDENTS table.
Syntax is as follows:
CREATE TYPE students_data_ctype AS
(
column_1 int4,
column_2 varchar(100),
column_3 varchar(500)
)
Then create a funcction :
CREATE
OR
REPLACE
FUNCTION PUBLIC.getStudents
()
RETURNS SETOF students_data_ctype AS $BODY$ DECLARE res
students_data_ctype;
BEGIN
FOR res IN
SELECT
column_1,
column_2,
column_3
FROM
STUDENTS
LOOP RETURN NEXT res;
END LOOP;
END
; $BODY$ LANGUAGE 'plpgsql'
GO
Function call :
Select * FROM getStudents()
Taddaaa! You will get your data.
I have to insert values [{name:'ravi',age;20},{name:'kumar',age;22}]. The stored procedure should take this array as input and store this as individual columns like below.
Table
name - age
ravi 20
age 22
Procedure to insert a json object:
CREATE OR REPLACE FUNCTION insertJson(jsonObjects ARRAY[])
RETURNS void AS $$
BEGIN
INSERT INTO MyTable
VALUES (jsonObjects)
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION insertJson(obj JSONB)
RETURNS void AS $$
BEGIN
INSERT INTO test2 (name, "age")
SELECT arr.row ->> 'name', (arr.row ->> 'age')::numeric FROM (SELECT jsonb_array_elements(obj) as row) as arr;
END;
$$ LANGUAGE plpgsql;