Accessing jsonb object in postgress throws error - postgresql

I have a table called "junaid", which has a column "connections" which is of type "jsonb".
create table junaid (
connection jsonb
}
The value in the "connections" column is array of objects.
conections = [{"name":"abc", "age":123},{"name":"xyz", "age":222}]
I have a stored procedure to access these values.
CREATE OR REPLACE FUNCTION test() RETURNS INTEGER AS $$
DECLARE
myconnection jsonb;
i jsonb;
BEGIN
select connections into myconnection from junaid;
FOR i IN SELECT * FROM jsonb_array_elements(myconnection)
LOOP
RAISE NOTICE 'output from space %', i->>’name’;
END LOOP;
return 0;
EXCEPTION WHEN others THEN
return 1;
END;
$$ LANGUAGE plpgsql;
When I run the stored proc, I get this error:
column "’name’" does not exist

You're using wrong quote characters. Instead of backticks or forward ticks or whatever, you should be using the single-quote character for the keyname too, as you seem to be using for the format string. I.e. it should be i->>'name'.
P.S. SO syntax highlighting shows that something fishy is going on...

Related

How to use FOREACH in a PostgreSQL LOOP

I am trying to write a very simple pgsql statement to loop through a simple array of state abbreviations.
CREATE OR REPLACE FUNCTION my_schema.showState()
RETURNS text AS
$$
DECLARE
my_array text[] := '["az","al", "ak", "ar"]'
BEGIN
FOREACH state IN my_array
LOOP
RETURN SELECT format('%s', state);
END LOOP;
END;
$$ LANGUAGE plpgsql;
SELECT * FROM showState();
I am using PostgresSQL version 11+. I keep getting an error ERROR: syntax error at or near "BEGIN" The output I want to see here is just seeing the state abbreviation printed in the results window for now. Like this:
What am I doing wrong here?
There's a ; missing after my_array text[] := '["az","al", "ak", "ar"]'.
'["az","al", "ak", "ar"]' isn't a valid array literal.
If you want a set returning function, you need to declare its return type as a SETOF.
The ARRAY keyword is missing in the FOREACH's head.
state must be declared.
You need to use RETURN NEXT ... to push a value into the set to be returned.
format() is pointless here, it doesn't effectively do anything.
With all that rectified one'd get something along the lines of:
CREATE
OR REPLACE FUNCTION showstate()
RETURNS SETOF text
AS
$$
DECLARE
my_array text[] := ARRAY['az',
'al',
'ak',
'ar'];
state text;
BEGIN
FOREACH state IN ARRAY my_array
LOOP
RETURN NEXT state;
END LOOP;
END;
$$
LANGUAGE plpgsql;
db<>fiddle

postgresql function and trigger execute select current_setting into integer variable causes error

Postgresql 9.6.x
I am getting an error with a postgresql function where I am recording a log on every table modification. This was all working great until I added this functionality where I am recording the current user id using current_setting functionality of postgresql. I set the current user on transactions in the background like so:
select set_config('myvars.active_user_id', '2123', true)
All this functionality works perfectly fine, except when the user is not set. This occurs when the tables are being updated by back end system queries and in that case the setting 'myvars.active_user_id' is null.
I want it to be null when it is not set. The user id field in the log is nullable.
It seems to be trying to convert null to an empty string and put that in the integer variable which it doesn't like.
This appears to be some kind of weird problem specific to functions with triggers. I must be doing something wrong because as far as I know assigning a null value through a select...into"is no issue.
The error I get in that case is so:
PSQLException: ERROR: invalid input syntax for integer: ""
I have added tracing statements and it is on this line:
EXECUTE 'select current_setting(''myvars.active_user_id'', true) ' into log_user_id;
I don't understand why in this setting it gets upset about the null value. But it seems limited to this type of trigger function. Below is essentially the function I am using
CREATE OR REPLACE FUNCTION update_log() RETURNS TRIGGER AS $update_log$
DECLARE
logid int;
log_user_id int;
BEGIN
EXECUTE 'select current_setting(''myvars.active_user_id'', true) ' into log_user_id;
IF (TG_OP='DELETE') THEN
EXECUTE 'select nextval(''seq_log'') ' into logid;
-- INSERT INTO log ....
RETURN NULL;
ELSIF (TG_OP='INSERT') THEN
EXECUTE 'select nextval(''seq_log'') ' into logid;
-- INSERT INTO log ....
RETURN NEW;
ELSIF (TG_OP='UPDATE') THEN
-- INSERT INTO log ....
END IF;
END IF;
RETURN NULL;
END;
$log$ LANGUAGE plpgsql;
Any thoughts?
GUC (Global User Setting) variables like your myvars.active_user_id are not nullable internally. It holds text or empty text. These variables cannot to store NULL. So when you store NULL, then empty string is stored, and this empty string is returned from function current_setting.
In Postgres (and any database without Oracle) NULL is not empty string and empty string is not NULL.
So this error is expected:
postgres=# do $$
declare x int;
begin
perform set_config('x.xx', null, false);
execute $_$ select current_setting('x.xx', true) $_$ into x;
end;
$$;
ERROR: invalid input syntax for integer: ""
CONTEXT: PL/pgSQL function inline_code_block line 5 at EXECUTE
You need to check result first, and replace empty string by NULL:
create or replace function nullable(anyelement)
returns anyelement as $$
select case when $1 = '' then NULL else $1 end;
$$ language sql;
do $$
declare x int;
begin
perform set_config('x.xx', null, false);
execute $_$ select nullable(current_setting('x.xx', true)) $_$ into x;
end;
$$;
DO
#Laurenz Albe has big true in your comment. Use dynamic SQL (execute command) only when it is necessary. It is not this case. So your code should looks like:
do $$
declare x int;
begin
perform set_config('x.xx', null, false);
x := nullable(current_setting('x.xx', true));
end;
$$;
DO
Note: There is buildin function nullif, so your code can looks like (and sure, buildin functionality should be preferred):
do $$
declare x int;
begin
perform set_config('x.xx', null, false);
x := nullif(current_setting('x.xx', true), '');
end;
$$;
DO

How to get the value of a dynamically generated field name in PL/pgSQL

Sample code trimmed down the the bare essentials to demonstrate question:
CREATE OR REPLACE FUNCTION mytest4() RETURNS TEXT AS $$
DECLARE
wc_row wc_files%ROWTYPE;
fieldName TEXT;
BEGIN
SELECT * INTO wc_row FROM wc_files WHERE "fileNumber" = 17117;
-- RETURN wc_row."fileTitle"; -- This works. I get the contents of the field.
fieldName := 'fileTitle';
-- RETURN format('wc_row.%I',fieldName); -- This returns 'wc_row."fileTitle"'
-- but I need the value of it instead.
RETURN EXECUTE format('wc_row.%I',fieldName); -- This gives a syntax error.
END;
$$ LANGUAGE plpgsql;
How can I get the value of a dynamically generated field name in this situation?
Use a trick with the function to_json(), which for a composite type returns a json object with column names as keys:
create or replace function mytest4()
returns text as $$
declare
wc_row wc_files;
fieldname text;
begin
select * into wc_row from wc_files where "filenumber" = 17117;
fieldname := 'filetitle';
return to_json(wc_row)->>fieldname;
end;
$$ language plpgsql;
You don't need tricks. EXECUTE does what you need, you were on the right track already. But RETURN EXECUTE ... is not legal syntax.
CREATE OR REPLACE FUNCTION mytest4(OUT my_col text) AS
$func$
DECLARE
field_name text := 'fileTitle';
BEGIN
EXECUTE format('SELECT %I FROM wc_files WHERE "fileNumber" = 17117', field_name)
INTO my_col; -- data type coerced to text automatically.
END
$func$ LANGUAGE plpgsql;
Since you only want to return a scalar value use EXECUTE .. INTO ... - optionally you can assign to the OUT parameter directly.
RETURN QUERY EXECUTE .. is for returning a set of values.
Use format() to conveniently escape identifiers and avoid SQL injection. Provide identifiers names case sensitive! filetitle is not the same as fileTitle in this context.
Are PostgreSQL column names case-sensitive?
Use an OUT parameter to simplify your code.

Passing table names in an array

I need to do the same deletion or purge operation (based on several conditions) on a set of tables. For that I am trying to pass the table names in an array to a function. I am not sure if I am doing it right. Or is there a better way?
I am pasting just a sample example this is not the real function I have written but the basic is same as below:
CREATE OR REPLACE FUNCTION test (tablename text[]) RETURNS int AS
$func$
BEGIN
execute 'delete * from '||tablename;
RETURN 1;
END
$func$ LANGUAGE plpgsql;
But when I call the function I get an error:
select test( {'rajeev1'} );
ERROR: syntax error at or near "{"
LINE 10: select test( {'rajeev1'} );
^
********** Error **********
ERROR: syntax error at or near "{"
SQL state: 42601
Character: 179
Array syntax
'{rajeev1, rajeev2}' or ARRAY['rajeev1', 'rajeev2']. Read the manual.
TRUNCATE
Since you are deleting all rows from the tables, consider TRUNCATE instead. Per documentation:
Tip: TRUNCATE is a PostgreSQL extension that provides a faster
mechanism to remove all rows from a table.
Be sure to study the details. If TRUNCATE works for you, the whole operation becomes very simple, since the command accepts multiple tables:
TRUNCATE rajeev1, rajeev2, rajeev3, ..
Dynamic DELETE
Else you need dynamic SQL like you already tried. The scary missing detail: you are completely open to SQL injection and catastrophic syntax errors. Use format() with %I (not %s to sanitize identifiers like table names. Or, better yet in this particular case, use an array of regclass as parameter instead:
CREATE OR REPLACE FUNCTION f_del_all(_tbls regclass)
RETURNS void AS
$func$
DECLARE
_tbl regclass;
BEGIN
FOREACH _tbl IN ARRAY _tbls LOOP
EXECUTE format('DELETE * FROM %s', _tbl);
END LOOP;
END
$func$ LANGUAGE plpgsql;
Call:
SELECT f_del_all('{rajeev1,rajeev2,rajeev3}');
Explanation here:
Table name as a PostgreSQL function parameter
You used wrong syntax for text array constant in the function call. But even if it was right, your function is not correct.
If your function has text array as argument you should loop over the array to execute query for each element.
CREATE OR REPLACE FUNCTION test (tablenames text[]) RETURNS int AS
$func$
DECLARE
tablename text;
BEGIN
FOREACH tablename IN ARRAY tablenames LOOP
EXECUTE FORMAT('delete * from %s', tablename);
END LOOP;
RETURN 1;
END
$func$ LANGUAGE plpgsql;
You can then call the function for several tables at once, not only for one.
SELECT test( '{rajeev1, rajeev2}' );
If you do not need this feature, simply change the argument type to text.
CREATE OR REPLACE FUNCTION test (tablename text) RETURNS int AS
$func$
BEGIN
EXECUTE format('delete * from %s', tablename);
RETURN 1;
END
$func$ LANGUAGE plpgsql;
SELECT test('rajeev1');
I recommend using the format function.
If you want to execute a function (say purge_this_one_table(tablename)) on a group of tables identified by similar names you can use this construction:
create or replace function purge_all_these_tables(mask text)
returns void language plpgsql
as $$
declare
tabname text;
begin
for tabname in
select relname
from pg_class
where relkind = 'r' and relname like mask
loop
execute format(
'purge_this_one_table(%s)',
tabname);
end loop;
end $$;
select purge_all_these_tables('agg_weekly_%');
It should be:
select test('{rajeev1}');

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.