Postgres JSON Array data processing - json

Have a table "json_test" and inserted the following record:
create table json_test ( v json);
insert into json_test values ('{"facilityId": ["20","30","40","50","51"]}')
SELECT trim(json_array_elements_text(v->'facilityId') ) from json_test
The above select lists the facility ID as individual rows.
I need the same rows in a Postgres function to insert the record into another table. I wrote the following code to return i. The output of the v_status when checked is (20,,,,,,,,,,,,). I need to get just 20, but I am unable to get that.
for i in SELECT json_array_elements_text(v->'facilityId') from json_test
loop
v_status:= i;
end loop;

You have not specified entire function definition in your question.
Assuming you have DDL:
CREATE TABLE json_test(
id SERIAL PRIMARY KEY,
v JSON
);
INSERT INTO json_test(v) VALUES
('{"facilityId": ["20","30","40","50","51"]}'::JSON);
You can check full PL/pgSQL guide as a reference, but your function may be defined as the following:
CREATE OR REPLACE FUNCTION get_facility_ids(rid INTEGER)
RETURNS SETOF INTEGER AS $$
DECLARE
t TEXT;
BEGIN
FOR t IN SELECT json_array_elements_text(v->'facilityId')
FROM json_test WHERE id = rid
LOOP
RETURN NEXT t;
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT get_facility_ids(1) AS facultyId;
Just for your information, you can INSERT records from SELECT statements. Check the documentation.

Related

How to make some jsonb field's value to a json array in PostgreSQL?

Here is a table:
create table test(
fjson jsonb
);
then insert some value:
insert into test values
('{"name":"A"}'::jsonb),
('{"name":"B"}'::jsonb),
('{"name":"C"}'::jsonb);
I want to get a json array like below:
[
{"name":"A"},
{"name":"B"},
{"name":"C"}
]
How to write the SQL?
Thanks.
Problem solved. thanks
do $$ declare
jdata jsonb;
begin
create temp table test(
fjson jsonb
) on commit drop;
insert into test values
('{"name":"A"}'::jsonb),
('{"name":"B"}'::jsonb),
('{"name":"C"}'::jsonb);
select (select jsonb_agg(fjson) from test) into jdata;
raise notice '%',jdata;
end;
$$ language plpgsql;
You can use
SELECT jsonb_agg(fjson) from test
to get a jsonb array, or
SELECT array_agg(fjson) from test
to get a native pg array.

PostgreSQL: a valid variable assignation sample?

After reading this question, I'm trying to convert some SQL from MySQL to PostgreSQL. Thus I need variable assignation:
INSERT INTO main_categorie (description) VALUES ('Verbe normal');
SET #PRONOMINAL := SELECT LAST_INSERT_ID();
INSERT INTO main_mot (txt,im,date_c,date_v_d,date_l)
VALUES ('je m''abaisse',1,NOW(),NOW(),NOW());
SET #verbe_149 = SELECT LAST_INSERT_ID();
INSERT INTO main_motcategorie (mot_id,categorie_id) VALUES (#verbe_149,#PRONOMINAL);
How would you do this with PostgreSQL? No useful sample in the documentation of v9 and v8 (almost the same).
NB: I dont want to use a stored procedure like here, I just want "raw sql" so I can inject it through CLI interface.
There are no variables in Postgres SQL (you can use variables only in procedural languages).
Use RETURNING in WITH query:
WITH insert_cat AS (
INSERT INTO main_categorie (description)
VALUES ('Verbe normal')
RETURNING id
),
insert_mot AS (
INSERT INTO main_mot (txt,im,date_c,date_v_d,date_l)
VALUES ('je m''abaisse',1,NOW(),NOW(),NOW())
RETURNING id
)
INSERT INTO main_motcategorie (mot_id,categorie_id)
SELECT m.id, c.id
FROM insert_mot m, insert_cat c;
As an alternative, you can use custom configuration parameters in the way described in this post.
Create two functions:
create or replace function set_var (name text, value text)
returns void language plpgsql as $$
begin
execute format('set mysql.%s to %s', name, value);
end $$;
create or replace function get_var (name text)
returns text language plpgsql as $$
declare
rslt text;
begin
execute format('select current_setting(''mysql.%s'')', name) into rslt;
return rslt;
end $$;
With the functions you can simulate variables, like in the example:
INSERT INTO main_categorie (description)
VALUES ('Verbe normal');
SELECT set_var('PRONOMINAL', (SELECT currval('main_categorie_id_seq')::text));
INSERT INTO main_mot (txt,im,date_c,date_v_d,date_l)
VALUES ('je m''abaisse',1,NOW(),NOW(),NOW());
SELECT set_var('verbe_149', (SELECT currval('main_mot_id_seq')::text));
INSERT INTO main_motcategorie (mot_id,categorie_id)
SELECT get_var('verbe_149')::int, get_var('PRONOMINAL')::int;
This is certainly not an example of good code.
Particularly the necessity of casting is troublesome.
However, the conversion can be done semi-automatically.
You can run PostgreSQL scripts outside of a function using the do construct. Here's an example with Donald Ducks' nephews. First the nephew will be added to the nephew table, and then we'll add a baseball cap using the newly inserted nephew's id.
First, create two tables for nephews and baseball caps:
drop table if exists nephew;
drop table if exists cap;
create table nephew (id serial primary key, name text);
create table cap (id serial, nephewid bigint, color text);
Now add the first nephew:
do $$declare
newid bigint;
begin
insert into nephew (name) values ('Huey') returning id into newid;
insert into cap (nephewid, color) values (newid, 'Red');
end$$;
The returning ... into ... does in Postgres what currval does in MySQL. Huey's new id is assigned to the newid variable, and then used to insert a new row into the cap table. You can run this script just like any other SQL statement. Continue with Dewey and Louie:
do $$declare
newid bigint;
begin
insert into nephew (name) values ('Dewey') returning id into newid;
insert into nephew (name) values ('Louie') returning id into newid;
insert into cap (nephewid, color) values (newid, 'Green');
end$$;
And you end up with:
# select * from nephew;
id | name
----+-------
1 | Huey
2 | Dewey
3 | Louie
(3 rows)
# select * from cap;
id | nephewid | color
----+----------+-------
1 | 1 | Red
2 | 3 | Green
(2 rows)
See it working at SQL Fiddle.

How to select into a variable in SQL when the result might be null?

I have the following stored procedure:
DELIMITER //
CREATE PROCEDURE NewSeqType(IN mySubschemaID INT, IN hashVal bigint(20))
BEGIN
DECLARE newSeqTypeID INT;
SELECT MAX(ID)+1 INTO newSeqTypeID FROM sequenceType WHERE subschemaID=mySubschemaID;
INSERT INTO SequenceType(ID, HashValue, subschemaID) VALUES(newSeqTypeID, hashVal, mySubschemaID);
SELECT LAST_INSERT_ID() as ID; -- return prim key
END//
This works when there is already data in the table where subschemaID=mySubschemaID, but if that SELECT statement returns null, then the MAX(ID)+1 part gives the error column ID cannot be null.
How can I give ID a default value, say 0, in that case?
For this, you can use coalesce():
SELECT coalesce(MAX(ID)+1, 1) INTO newSeqTypeID FROM sequenceType WHERE subschemaID=mySubschemaID;
INSERT INTO SequenceType(ID, HashValue, subschemaID) VALUES(newSeqTypeID, hashVal, mySubschemaID);
Often, this type of work is done in a before insert trigger, to keep the values aligned.

Function to return a table of all children of a node

Let's say I've got the following table structure:
| ID | ParentID | Name |
I'd like to write a recursive PostgreSQL function for getting all child nodes of a node ID passed to it as a parameter.
Here's my code so far (I only have one part of the function which gets all the children of the passed ID, and now I need the recursive part):
CREATE OR REPLACE FUNCTION GetAllChildren(IN NodeID INTEGER) RETURNS INTEGER AS $$
DECLARE
Crs CURSOR FOR SELECT ID, ParentID, Name FROM Tree WHERE ParentID=NodeID;
VarRow Tree%ROWTYPE;
BEGIN
OPEN Crs;
CREATE TEMPORARY TABLE TBL(
ID SERIAL,
ParentID INTEGER,
Name CHARACTER(100)
);
LOOP
FETCH Crs INTO VarRow;
IF VarRow IS NULL THEN
EXIT;
END IF;
INSERT INTO TBL(ID, ParentID, Name) VALUES(VarRow.ID, VarRow.ParentID, VarRow.Name);
END LOOP;
CLOSE Crs;
RETURN 0;
END;
$$ LANGUAGE plpgsql;
Perhaps the biggest problem is that I don't know where to save the output between the calls of the recursion.
If you haven't figured out so far, it's about the adjacency list, getting all the children of a node and printing them out to a table.
Does anyone have a solution?
For information, there are common table expressions in Postgres, which will probably help here:
http://www.postgresql.org/docs/current/static/queries-with.html
Adapting the example from the docs:
WITH RECURSIVE rec_tree(parent_id, node_id, data, depth) AS (
SELECT t.parent_id, t.node_id, t.data, 1
FROM tree t
UNION ALL
SELECT t.parent_id, t.node_id, t.data, rt.depth + 1
FROM tree t, rec_tree rt
WHERE t.parent_id = rt.node_id
)
SELECT * FROM rec_tree;
(See the docs for an example that prevents cycles for a graph.)
PostgreSQL doesn't know local (procedure) limited temporary tables - your temp table is visible in all instances of called function, and it will be visible outside your function too - it has session visibility.
But PostgreSQL functions (PostgreSQL has no procedures) can returns table directly - so you don't need use auxiliary table for storing data
CREATE OR REPLACE FUNCTION children_by_parent(_parent_id int)
RETURNS SETOF children AS $$ -- children is table name
DECLARE r children;
BEGIN
FOR r IN
SELECT * FROM children
WHERE parent_id = _parent_id
LOOP
RETURN NEXT r; -- return node
RETURN QUERY SELECT * FROM children_by_parent(r.id); -- return children
END LOOP;
RETURN;
END;
$$ LANGUAGE plpgsql STRICT;
This form is faster, because you don't fill any table (although temp table is usually in RAM only).
You don't need use a explicit cursor in PostgreSQL - statement FOR does all, it is shorter and more user friendly.
the best solution is Denis idea - use CTE - recursive SQL.

How to return a table, rows or record from a function in PostgreSQL 9?

I have a table called person which has id,name,status and I want to return rows as a result of a function with 1 parameter (name).
Can anyone help me? Please make it easy, because im very noob in PostgreSQL.
This is my code from a normal function
create or replace function fn_list(vname varchar) returns void as $$
begin
SELECT id,name,status from usuario WHERE name= vname;
end;
$$ language plpgsql;
I know I'm returning a void function but how can I do if I want a list of rows?
I know that pipelined returns in Oracle does this, so I used that to find 'RETURN NEXT' from plpgsql:
http://www.postgresql.org/message-id/007b01c6dc31$ae395920$0a00a8c0#trivadis.com
Also on grokbase:
http://grokbase.com/t/postgresql/pgsql-performance/069kcttrfr/pipelined-functions-in-postgres
(Edit to add official documentation): http://www.postgresql.org/docs/9.2/static/plpgsql-control-structures.html
Killer, I will have to make use of this myself.
Editing one more time to add in some demo code (directly from postgresql.org documentation):
CREATE TABLE foo (fooid INT, foosubid INT, fooname TEXT);
INSERT INTO foo VALUES (1, 2, 'three');
INSERT INTO foo VALUES (4, 5, 'six');
CREATE OR REPLACE FUNCTION getAllFoo() RETURNS SETOF foo AS
$BODY$
DECLARE
r foo%rowtype;
BEGIN
FOR r IN SELECT * FROM foo
WHERE fooid > 0
LOOP
-- can do some processing here
RETURN NEXT r; -- return current row of SELECT
END LOOP;
RETURN;
END
$BODY$
LANGUAGE 'plpgsql' ;
SELECT * FROM getallfoo();
Using a loop to return the result of a query is slow and inefficient. The overhead of PL/pgSQL is not even required for this.
The best solution is:
create or replace function fn_list(vname varchar)
returns table(id integer, name text, status text)
as $$
SELECT id,name,status
from usuario
WHERE name= vname;
$$ language sql;
If PL/pgSQL is needed because some other procedural code needs to run before the query, then return query should be used instead of a loop:
create or replace function fn_list(vname varchar)
returns table(id integer, name text, status text)
as $$
begin
-- do some work....
return query
SELECT id,name,status
from usuario
WHERE name= vname;
end;
$$ language plpgsql;
Then call it using:
select *
from fn_list('Arthur');
Many answers here omit important parts of using functions. Here's an updated way of using functions in postgres (including declaration, variables, args, return values, and running). Below is an over-baked example of updating the tweet on the bottom right "blurb" with "hello world".
id (serial)
pub_id (text)
tweet (text)
1
abc
hello world
2
def
blurb
-- Optional drop if replace fails below.
drop function if exists sync_tweets(text, text);
create or replace function sync_tweets(
src_pub_id text, -- function arguments
dst_pub_id text
) returns setof tweets as -- i.e. rows. int, text work too
$$
declare
src_id int; -- temp function variables (not args)
dest_id int;
src_tweet text;
begin
-- query result into a temp variable
src_id := (select id from tweets where pub_id = src_pub_id);
-- query result into a temp variable (another way)
select tweet into src_tweet from tweets where id = src_id;
dest_id := (select id from tweets where pub_id = dst_pub_id);
update tweets set tweet=src_tweet where id = dest_id;
return query -- i.e. rows, return 0 with return int above works too
select * from tweets where pub_id in (src_pub_id, dst_pub_id);
end
$$ language plpgsql; -- need the language to avoid ERROR 42P13
-- Run it!
select * from sync_tweets('abc', 'def');
/*
Outputs
__________________________________________________
| id (serial) | pub_id (text) | tweet (text) |
|---------------|-----------------|----------------|
| 1 | abc | hello world |
| 2 | def | blurb |
--------------------------------------------------
*/