Postgresql trigger set empty string to NULL - postgresql

I have a table with field "mac" of type MACADDR. Now I would like to treat a situation (probably with trigger?) when somebody inserts empty string instead of mac address. I would like to turn this empty string to NULL, so postgresql will not complain: invalid input syntax for type macaddr: ""
What I have now in trigger function is this:
IF CHAR_LENGTH(NEW.mac) = 0 THEN
NEW.mac := NULL;
END IF;
But it does not seem to work. What would you do, if you want to treat this on DB level?
Thank you very much. -Jan
PS: I am a postgresql newbie. But a fascinated one :)

You can't do what you want with a trigger. Your incoming empty string will be parsed and converted to a macaddr (or at least the parsing will be attempted) before the trigger is executed. However, you could write a simple function to convert empty strings to NULL and use that in your INSERT:
create function macaddr_ify(m varchar) returns macaddr as $$
begin
if m is null or length(m) = 0 then
return null;
end if;
return cast(m as macaddr);
end;
$$ language plpgsql;
And then:
insert into t (addr) values (macaddr_ify(str));
I'd recommend that your client application properly convert empty MAC address strings to NULLs itself though.

Related

Accessing jsonb object in postgress throws error

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...

Postgres function - can it return a type AND/OR a setof type?

I would like to write a PLPGSQL function for postres that returns a complex type on success, and a SETOF some other complex type on failure. Is that possible?
I have considered returning an array of the type on failure, but the array could potentially contain tens of thousands of entries, so I am assuming it would be unwise to return it as an array. Is that a correct assumption?
You can do it by returning a record of a cursor and a complex type like this:
CREATE FUNCTION example(IN integer, INOUT refcursor, OUT boolean, OUT point) AS $$
BEGIN
IF 0 = $1 THEN -- Dummy condition
OPEN $2 FOR SELECT * FROM pg_class; -- Open the given cursor for some query.
$3 := true; -- Indicate that the operation was successful.
ELSE
$4 := point(1, 2); -- Return a composite type.
$3 := false;
END IF;
END;
$$ STABLE LANGUAGE plpgsql;
Use like this:
BEGIN;
SELECT example(0, 'my_cursor');
-- Check the value of the first output parameter.
FETCH ALL IN 'my_cursor';
COMMIT;
I don't think it will be very convenient, though. If the contents of the cursor can't be fetched from a view or a table, you need to have another function that assumes that the operation succeeds. Also having a branch that handles either a set of rows or a single value won't be easy to write in SQL.

2201B ERROR: invalid regular expression when using replace() function

Why Postgres gives me this error
[2014-10-30 13:43:36] [2201B] ERROR: invalid regular expression:
invalid escape \ sequence Where: PL/pgSQL function
normalize_username() line 5 at assignment
when I use the SQL statement:
UPDATE users."user" SET username = username
with the following trigger active:
CREATE OR REPLACE FUNCTION normalize_username()
RETURNS TRIGGER AS $normalize_username$
BEGIN
IF NOT (NEW.username IS NULL)
THEN
NEW.username := replace(NEW.username, 'ё', 'е');
END IF;
RETURN NEW;
END;
$normalize_username$ LANGUAGE plpgsql;
Table was created like this:
CREATE TABLE USERS.user (
user_id SERIAL PRIMARY KEY,
username CITEXT,
)
replace() is a simple text function, right? It shouldn't have any relation to regular expressions I suppose.
The problem was solved by casting username to TEXT in assignment like this:
CREATE OR REPLACE FUNCTION normalize_username()
RETURNS TRIGGER AS $normalize_username$
BEGIN
IF NOT (NEW.username IS NULL)
THEN
NEW.username := replace(NEW.username::TEXT, 'ё', 'е');
END IF;
RETURN NEW;
END;
$normalize_username$ LANGUAGE plpgsql;
It looks like CITEXT column operations are silently converted to regular expression operations under the hood. Can someone confirm this?
I faced same issue with postgres v13 function REPLACE(source, old_text, new_text ). Casting old_text to TEXT resolved the issue.

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.

postgres check if null then cast to numeric

I am trying check if a value is null if so the select null else cast to numeric, but it throws an error. This is actually part of an insert statement
INSERT into someTable(name,created,power)
SELECT 'xyz',now(),
case when :power ='null' then NULL else cast(:power as numeric) end from abc
error that I get is
Error: ERROR: invalid input syntax for type numeric: "null"
:power is a variable that can be given any value using java code. If I give a value of null it give an error.
In code I get the following error from the java stack trace
org.postgresql.util.PSQLException: ERROR: cannot cast type bytea to numeric
Error:
SELECT CASE WHEN 'null' = 'null' THEN NULL ELSE cast('null' AS numeric) END
No error:
DO $$
DECLARE
power text := 'null';
BEGIN
PERFORM CASE WHEN power = 'null' THEN NULL ELSE cast(power AS numeric) END;
END;
$$
Explanation:
If you build a query string, the expression cast('null' AS numeric) or simply 'null'::numeric always raises an exception, even in an ELSE block that is never executed, because it is invalid input syntax and the exception is raised during the syntax check (like the error message implies), not during execution.
A CASE statement like you display only makes sense with a parameter or variable not with literals. The second instance of the literal has no connection to the first instance whatsoever after the query string has been assembled.
For dynamic SQL like that, you need to check the value before you build the query string. Or you use a function or prepared statement and pass the value as parameter. That would work, too.
More advice after comment:
In your particular case you could check the value in the app and build a query string like this:
INSERT INTO tbl(name, abc_id, created, power)
SELECT 'xyz'
, abc_id
, now()
, <insert_value_of_power_or_NULL_here> -- automatically converted to numeric
FROM abc
You may be interested in a different approach to INSERT data from a file conditionally.
Use COPY for files local to the server or psql's meta-command \copy for files local to the client.
if the field value is null, and you want in this case to map it to some value you can use coalesce(field_name, 'Some value') or coalesce(field_name, 123).
For full documentation see here.
You have to check with the IS operator, and not with the equal when you dealing with NULL :
INSERT into someTable(name,created,power)
SELECT 'xyz',now(),
case when :power IS null then NULL else cast(:power as numeric) end from abc
INSERT into someTable(name,created,power) SELECT 'xyz',now(),
case :power when 'null' then NULL else :power end::numeric from abc
I was trying to do something similar in order to update/insert some records where a numeric value can be null or not.
You can validate a variable before you send it to the function or inside the function depending the value passed
(For me using a variable is better than use CASE WHEN THEN ELSE END CASE every time you need to validate the value)
So to work with the NULL values using a regular comparison operand in order to find a record to update can be done by turning transform_null_equals to ON
I hope this help someone
CREATE OR REPLACE FUNCTION update_insert_transaction(vcodaccount integer, vcodaccountaux text,
vdescription text, vcodgroup integer)
RETURNS integer AS $$
DECLARE
n integer = 0;
vsql text = 'NULL';
BEGIN
IF vcodaccountaux <> '' THEN
vsql = vcodaccountaux;
END IF;
SET LOCAL transform_null_equals TO ON;
EXECUTE 'UPDATE account_import_conf SET (codaccount, codaccountaux, description, codgroup) =
('||vcodaccount||','||vsql||',trim('||quote_literal(vdescription)||'),'||vcodgroup||')
WHERE codaccount='||vcodaccount||' AND codaccountaux = '||vsql||' RETURNING * ';
GET DIAGNOSTICS n = ROW_COUNT;
IF n = 0 THEN
EXECUTE 'INSERT INTO account_import_conf (codaccount, codaccountaux, description, codgroup)
SELECT '||vcodaccount||','||vsql||' ,trim('||quote_literal(vdescription)||'),'||vcodgroup||';';
END IF;
RETURN n;
END;$$
LANGUAGE plpgsql;