How can I read keys from a json variable in postgres - postgresql

I have json object that is structured like this
{
item_id:"f1e00bae-40b1-4bb6-ab21-fc8b5426ad0a"
item_amount:8978
}
now I have a function that looks something like this:
create or replace function update_item(
update_item JSON,
username text
)
RETURNS item
as $$
DECLARE
inserted item;
begin
UPDATE item
set amount=amount+update_item.item_amount
WHERE
item.owner=username
AND
item.id=update_item.item_id
RETURNING * INTO inserted;
RETURN inserted;
END $$
language plpgsql;
But it gives me this error:
missing FROM-clause entry for table "update_item"
So how can I get the required values from the JSON paramter?

You can access a key's value using the ->> operator. But as everything is "text" in JSON, you will have to cast the values when you use them:
create or replace function update_item(update_item JSON, username text)
RETURNS item
as $$
DECLARE
inserted item;
begin
UPDATE item
set amount = amount + (update_item ->> 'item_amount')::int
WHERE item.owner = username
AND item.id = (update_item ->> 'item_id')::uuid
RETURNING * INTO inserted;
RETURN inserted;
END $$
language plpgsql;

Related

how to iterate and append jsonb in postgres db function

I have a DB function say "myFunction(input integer)" which takes an input and returns a set of json as below
select * from myFunction(1)
>> [{"a":"b","c":"d"},{"e":"f"}]
I have a DB table "table1" with column 'column1'.
I need to pass the values from table1.column1 into myFunction as an argument and append its result to build one json array
I have created the below db function and it works fine with only one problem, as it appends an empty json at begining.
[{},{"a":"b","c":"d"},{"e":"f"},{"k":"l"}]
kindly help me to provide some optimal solutions or get rid of empty json object in array.
CREATE OR REPLACE FUNCTION myWrapperFunction()
RETURNS SETOF json
LANGUAGE 'plpgsql'
COST 100
STABLE STRICT
ROWS 1000
AS $BODY$
DECLARE
_elements INTEGER[];
_element INTEGER;
_results json;
_result json;
_all_result jsonb;
val json ='{}'::json;
BEGIN
SELECT ARRAY_AGG( DISTINCT column1) into _elements from table1;
FOREACH _element IN ARRAY _elements
LOOP
SELECT * FROM myFunction(_element) into _results;
IF _results IS NOT null THEN
val=val::jsonb||_results::jsonb;
END IF;
END LOOP;
RETURN QUERY select val;
RETURN ;
END; $BODY$;
ALTER FUNCTION myWrapperFunction()
OWNER TO postgres;

passing a list of varchars into plpgsql function

I would like to prepare a function which would be called
select gettvmlistic3('IC','AUTABB',array['565,568,569,570,572,573,574,575,576,577,578'])
the declaratin of my function is
create or replace function getTVMListIC3(operatorName varchar,groupid varchar, ids varchar[])
returns varchar as $$
declare
tvms varchar[];
res varchar;
begin
EXECUTE format('SELECT ARRAY (SELECT id from "%s".dictionary where groupid = ''%s'' and id = ANY(''%s'') order by id)', operatorName, groupid, ids) INTO tvms;
SELECT ARRAY_TO_STRING(tvms, ',') INTO res;
return res;
end;
$$
language plpgsql;
but the result I get is empty. In fact I should receive the same number of elements as in the array provided. What am I doing wrong?
You are passing an array with a single element.
If you want to pass multiple elements you need to use:
array['565','568','569','570','572','573','574','575','576','577','578']
But you shouldn't concatenate the parameter values like that.
Use placeholders ($1) in the dynamic SQL and pass the values with the USING clause. Identifiers should be injected with the %I in the format() function:
You also don't need to first select into an array and then convert that to a string again. You can aggregate everything into a single string right away.
create or replace function getTVMListIC3(operatorName varchar, groupid varchar, ids varchar[])
returns varchar
as $$
declare
res varchar;
begin
EXECUTE format($sql$
SELECT string_agg(id::text, ',' order by id)
from %I.dictionary
where groupid = $1
and id = ANY($2)
$sql$, operatorName)
INTO res
using groupid, ids;
return res;
end;
$$
language plpgsql
Those strings look like numbers. If that is the case it's better to define the parameter as int[] and then pass the list of number as:
array[565,568,569,570,572,573,574,575,576,577,578]

Delete function storing the deleted record as json

I'm using a function in my Postgres and with this function, I want to delete a record using table and id and store the record as a JSON.
As you can imagine, I'm getting the table name as a parameter, but at that point, I don't know the name of the fields inside of my table. And I have:
create or replace function deletetable(_table text, _id int)
returns json as
$func$
declare
res json;
begin
execute format('SELECT json_to_recordset(::json) FROM ' || _table || ' WHERE id ' || id) into res;
-- HERE ONCE I GET THE RECORD AS JSON I HAVE TO INSERT IT IN MY TABLE
return res;
end
$func$ language plpgsql;
Well, my specific question is, how can I retrieve that information?
With my current function I get:
SELECT json_to_recordset(::json) FROM table
First create a log table:
create table logtable (
id serial primary key,
del_tname text not null,
del_ts timestamptz not null default now(),
del_rec jsonb
);
Your function should then look like this:
create or replace function deletetable(_table text, _id int)
returns jsonb as
$$
declare
res jsonb;
begin
execute format('with d as (delete from %I where id = %s returning *) '
'insert into logtable (del_tname, del_rec) '
'select %L, to_jsonb(d) from d returning del_rec',
_table, _id, _table) into res;
return res;
end
$$ language plpgsql;
Executing a select * from deletetable('tablename', 14); will delete your row, log it to logtable, and return the jsonb of the row.

Query to insert array of json object into postgres

I have a table with field fields json[]. I have created a function to insert the record into that table and here is the call
select * from add('[{"name":"Yes","value":"yes"},{"name":"No","value":"no"},{"name":"Neutral","value":"neutral"}]') as result;
and here is the function itself
CREATE OR REPLACE FUNCTION public.add(
_fields json[]) RETURNS SETOF json
LANGUAGE 'plpgsql'
AS $BODY$
DECLARE
query text;
BEGIN
insert into my_table(fields)
values(_fields);
query = 'SELECT json_build_object(''message'', ''Added.'')';
RETURN QUERY EXECUTE query;
END;
$BODY$;
but this is showing
malformed array literal: "[{"name":"Yes","value":"yes"},{"name":"No","value":"no"},{"name":"Neutra
then I tried this
select * from add('{"name":"Yes","value":"yes"},{"name":"No","value":"no"},{"name":"Neutral","value":"neutral"}') as result;
and this insert statement in function like
insert into polls(fields) values(array([''||_fields||'']::json[]));
and this is showing
syntax error at or near "["
I was missing " for each individual object. This is how to create json object
{"{\"name\":\"Yes\",\"value\":\"yes\"}","{\"name\":\"No\",\"value\":\"no\"}","{\"name\":\"Neutral\",\"value\":\"neutral\"}"}
so the final query is
select * from add('{"{\"name\":\"Yes\",\"value\":\"yes\"}","{\"name\":\"No\",\"value\":\"no\"}","{\"name\":\"Neutral\",\"value\":\"neutral\"}"}') as result;

How to write a function that returns text or integer values?

I'm using PostgreSQL 9.2.4.
postgres=# select version();
version
-------------------------------------------------------------
PostgreSQL 9.2.4, compiled by Visual C++ build 1600, 64-bit
(1 row)
sqlfiddle link
My Query executes the insertion safely. What i need is that my function should return something except the void datatype. Something like text("inserted into table") or integer(0-false,1-true) , it will be useful for me to validate whether it is inserted or not?
I need a syntax for a function that returns an integer or a text when an insertion is done. For validation purpose. Is there any way to solve this?
What you probably need
Most likely you need one function to return text and another one to return integer or a function that returns boolean to indicate success. All of this is trivial and I'll refer you to the excellent manual on CREATE FUNCTION or code examples in similar questions on SO.
What you actually asked
How to write a function that returns text or integer values?
... in the sense that we have one return type being either text or integer. Not as trivial, but also not impossible as has been suggested. The key word is: polymorphic types.
Building on this simple table:
CREATE TABLE tbl(
tbl_id int,
txt text,
nr int
);
This function returns either integer or text (or any other type if you allow it), depending on the input type.
CREATE FUNCTION f_insert_data(_id int, _data anyelement, OUT _result anyelement)
RETURNS anyelement AS
$func$
BEGIN
CASE pg_typeof(_data)
WHEN 'text'::regtype THEN
INSERT INTO tbl(tbl_id, txt) VALUES(_id, _data)
RETURNING txt
INTO _result;
WHEN 'integer'::regtype THEN
INSERT INTO tbl(tbl_id, nr) VALUES(_id, _data)
RETURNING nr
INTO _result;
ELSE
RAISE EXCEPTION 'Unexpected data type: %', pg_typeof(_data)::text;
END CASE;
END
$func$
LANGUAGE plpgsql;
Call:
SELECT f_insert_data(1, 'foo'::text); -- explicit cast needed.
SELECT f_insert_data(1, 7);
Simple case
One function that returns TRUE / FALSE to indicate whether a row has been inserted, only one input parameter of varying type:
CREATE FUNCTION f_insert_data2(_id int, _data anyelement)
RETURNS boolean AS
$func$
BEGIN
CASE pg_typeof(_data)
WHEN 'text'::regtype THEN
INSERT INTO tbl(tbl_id, txt) VALUES(_id, _data);
WHEN 'integer'::regtype THEN
INSERT INTO tbl(tbl_id, nr) VALUES(_id, _data);
ELSE
RAISE EXCEPTION 'Unexpected data type: >>%<<', pg_typeof(_data)::text;
END CASE;
IF FOUND THEN RETURN TRUE;
ELSE RETURN FALSE;
END IF;
END
$func$
LANGUAGE plpgsql;
The input type can be replaced with a text parameter for most purposes, which can be cast to and from any other type.
It sounds like you're solving a problem by creating a bigger problem.
You don't need a function for this at all. Do it on the client side by checking the affected rows count that's returned by every DML query, or use INSERT ... RETURNING.
You didn't mention your client language, so here's how to do it in Python with psycopg2. The same approach applies in other languages with syntax variations.
#!/usr/bin/env python
import psycopg2
# Connect to the db
conn = psycopg2.connect("dbname=regress")
curs = conn.cursor()
# Set up the table to use
curs.execute("""
DROP TABLE IF EXISTS so17587735;
CREATE TABLE so17587735 (
id serial primary key,
blah text not null
);
""");
# Approach 1: Do the insert and check the rowcount:
curs.execute("""
INSERT INTO so17587735(blah) VALUES ('whatever');
""");
if curs.rowcount != 1:
raise Exception("Argh, insert affected zero rows, wtf?")
print("Inserted {0} rows as expected".format(curs.rowcount))
# Approach 2: Use RETURNING
curs.execute("""
INSERT INTO so17587735(blah) VALUES ('bored') RETURNING id;
""");
returned_rows = curs.fetchall();
if len(returned_rows) != 1:
raise Exception("Got unexpected row count {0} from INSERT".format(len(returned_rows)))
print("Inserted row id is {0}".format(returned_rows[0][0]))
In the case of PL/PgSQL calling INSERT you can use the GET DIAGNOSTICS command, the FOUND variable, or RETURN QUERY EXECUTE INSERT ... RETURNING .... Using GET DIAGNOSTICS:
CREATE OR REPLACE FUNCTION blah() RETURNS void AS $$
DECLARE
inserted_rows integer;
BEGIN
INSERT INTO some_table VALUES ('whatever');
GET DIAGNOSTICS inserted_rows = ROW_COUNT;
IF inserted_rows <> 1 THEN
RAISE EXCEPTION 'Failed to insert rows; expected 1 row, got %', inserted_rows;
END IF;
END;
$$ LANGUAGE plpgsql VOLATILE;
or if you must return values and must for some reason use PL/PgSQL:
CREATE OR REPLACE FUNCTION blah() RETURNS SETOF integer AS $$
BEGIN
RETURN QUERY EXECUTE INSERT INTO some_table VALUES ('whatever') RETURNING id;
END;
$$ LANGUAGE plpgsql VOLATILE;
(assuming the key is id)
which would be the same as:
CREATE OR REPLACE FUNCTION blah() RETURNS SETOF integer AS $$
INSERT INTO some_table VALUES ('whatever') RETURNING id;
$$ LANGUAGE sql;
or just
INSERT INTO some_table VALUES ('whatever') RETURNING id;
In other words: Why wrap this in a function? It doesn't make sense. Just check the row-count client side, either with RETURNING or by using the client driver's affected-rows count for INSERT.
A function can only return one type. In your case, you could create a composite type with two fields, one integer and one text, and return that.