I want to check during a request if the constraint are respected, if they are not instead of sending an ERROR message, simply return FALSE. How would I do that?
Example of TABLE I'm using:
CREATE TABLE tree (
name VARCHAR(64) UNIQUE PRIMARY KEY,
leaf INT CHECK (leaf > 0)
);
Example of Functions I'd use:
CREATE FUNCTION add_tree(name, nb_leaf) RETURNS BOOLEAN;
CREATE FUNCTION remove_leaf(tree_name, leaf_to_remove) RETURNS BOOLEAN;
In my function it would be too repetitive to check for the name
IF EXISTS (SELECT name FROM tree WHERE name=tree_name) THEN...
Since I already have a UNIQUE constraint, but if I don't check I get the error message of course, how do I not use the check (IF..) and not get the error message but a return false instead when the input is wrong?
PS: I'm using postgresql if that changes anything
When exception is raised, return false from your function:
EXCEPTION
WHEN OTHERS THEN
return false;
END;
http://www.postgresql.org/docs/9.3/static/plpgsql-control-structures.html
At the end of the page you will see an example:
DECLARE
text_var1 text;
text_var2 text;
text_var3 text;
BEGIN
-- some processing which might cause an exception
...
EXCEPTION WHEN OTHERS THEN
GET STACKED DIAGNOSTICS text_var1 = MESSAGE_TEXT,
text_var2 = PG_EXCEPTION_DETAIL,
text_var3 = PG_EXCEPTION_HINT;
END;
Related
I have a problem where my SQL sequence has to be above a certain number to fix a unique constraint error. Now I started to write an if-statement that checks for a certain number and if it's below it should be increased to a certain number. The statement is for Postgres.
I got the separate parts running but the connection over if is throwing an error and I don't know why.
First for selecting the current number:
SELECT nextval('mySequence')
Then to update the number:
SELECT setval('mySequence', targetNumber, true)
The full statement looks something like this in my tries:
IF (SELECT nextval('mySequence') < targetNumber)
THEN (SELECT setval('mySequence', targetNumber, true))
END IF;
and the error is
ERROR: syntax error at »IF«
Can someone explain to me what I did wrong there because the error message isn't giving me much to work with? I would appreciate your help.
Try this:
SELECT setval('mySequence', targetNumber, true)
WHERE (SELECT nextval('mySequence') < targetNumber) is true;
You can use postgres functions if you want to use IF statement.
You can try something like this:
CREATE SEQUENCE seq_test_id_seq;
CREATE TABLE seq_test(
id integer NOT NULL DEFAULT nextval('seq_test_id_seq'),
name VARCHAR
);
CREATE OR REPLACE FUNCTION seq_test_function(target_number bigint)
RETURNS void
LANGUAGE 'plpgsql'
VOLATILE
PARALLEL UNSAFE
COST 100
AS $BODY$
DECLARE
seq_val INTEGER;
BEGIN
SELECT nextval('seq_test_id_seq') INTO seq_val;
RAISE NOTICE 'NEXT SEQUENCE [%]', seq_val;
IF (seq_val < target_number) THEN
SELECT setval('seq_test_id_seq', target_number, true) INTO seq_val;
RAISE NOTICE 'SEQUENCE VALUE MODIFIED [%]', seq_val;
END IF;
END;
$BODY$;
Then call the procedure:
select seq_test_function(10);
I am trying to automate a set of sentences that I execute several times a day. For this I want to put them in a postgres function and just call the function to execute the sentences consecutively. If everything runs OK then in the end return the SUCCESS value. The following function replicates my idea and the error I am getting when executing the function:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * from MY_TABLE;
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Invocation:
select * from createTable();
With my ignorance of postgresql I would expect to obtain the SUCCESS value as a return (If everything runs without errors). But the returned message causes me confusion, isn't it the same as a function in any other programming language? When executing the function I get the following message:
query has no destination for result data Hint: If you want to
discard the results of a SELECT, use PERFORM instead.
query has no destination for result data Hint: If you want to discard the results of a SELECT, use PERFORM instead.
You are getting this error because you do not assign the results to any variable in the function. In a function, you would typically do something like this instead:
select * into var1 from MY_TABLE;
Therefore, your function would look something like this:
CREATE OR REPLACE FUNCTION createTable() RETURNS int AS $$
DECLARE
var1 my_table%ROWTYPE;
BEGIN
DROP TABLE IF EXISTS MY_TABLE;
CREATE TABLE MY_TABLE
(
ID integer
)
WITH (
OIDS=FALSE
);
insert into MY_TABLE values(1);
select * into var1 from MY_TABLE;
<do something with var1>
RETURN 'SUCCESS';
END;
$$ LANGUAGE plpgsql;
Otherwise, if you don't put the results into a variable, then you're likely hoping to achieve some side effect (like advancing a sequence or firing a trigger somehow). In that case, plpgsql expects you to use PERFORM instead of SELECT
Also, BTW your function RETURNS int but at the bottom of your definition you RETURN 'SUCCESS'. SUCCESS is a text type, not an int, so you will eventually get this error once you get past that first error message -- be sure to change it as necessary.
Say we have a PostgreSQL table like so:
CREATE TABLE master (
id INT PRIMARY KEY,
...
);
and many other tables referencing it with foreign keys:
CREATE TABLE other (
id INT PRIMARY KEY,
id_master INT NOT NULL,
...
CONSTRAINT other_id_master_fkey FOREIGN KEY (id_master)
REFERENCES master (id) ON DELETE RESTRICT
);
Is there a way to check (from within trigger function) if a master row is deletable without actually trying to delete it? The obvious way is to do a SELECT on all referencing tables one by one, but I would like to know if there is an easier way.
The reason I need this is that I have a table with hierarchical data in which any row can have child rows, and only child rows that are lowest in hierarchy can be referenced by other tables. So when a row is about to become a parent row, I need to check whether it is already referenced anywhere. If it is, it cannot become a parent row, and insertion of new child row is denied.
You can try to delete the row and roll back the effects. You wouldn't want to do that in a trigger function because any exception cancels all persisted changes to the database. The manual:
When an error is caught by an EXCEPTION clause, the local variables of
the PL/pgSQL function remain as they were when the error occurred, but
all changes to persistent database state within the block are rolled back.
Bold emphasis mine.
But you can wrap this into a separate block or a separate plpgsql function and catch the exception there to prevent the effect on the main (trigger) function.
CREATE OR REPLACE FUNCTION f_can_del(_id int)
RETURNS boolean AS
$func$
BEGIN
DELETE FROM master WHERE master_id = _id; -- DELETE is always rolled back
IF NOT FOUND THEN
RETURN NULL; -- ID not found, return NULL
END IF;
RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception
EXCEPTION
WHEN FOREIGN_KEY_VIOLATION THEN
RETURN FALSE;
WHEN SQLSTATE 'MYERR' THEN
RETURN TRUE;
-- other exceptions are propagated as usual
END
$func$ LANGUAGE plpgsql;
This returns TRUE / FALSE / NULL indicating that the row can be deleted / not be deleted / does not exist.
db<>fiddle here
Old sqlfiddle
One could easily make this function dynamic to test any table / column / value.
Since PostgreSQL 9.2 you can also report back which table was blocking.
PostgreSQL 9.3 or later offer more detailed information, yet.
Generic function for arbitrary table, column and type
Why did the attempt on a dynamic function that you posted in the comments fail? This quote from the manual should give a clue:
Note in particular that EXECUTE changes the output of GET DIAGNOSTICS, but does not change FOUND.
It works with GET DIAGNOSTICS:
CREATE OR REPLACE FUNCTION f_can_del(_tbl regclass, _col text, _id int)
RETURNS boolean AS
$func$
DECLARE
_ct int; -- to receive count of deleted rows
BEGIN
EXECUTE format('DELETE FROM %s WHERE %I = $1', _tbl, _col)
USING _id; -- exception if other rows depend
GET DIAGNOSTICS _ct = ROW_COUNT;
IF _ct > 0 THEN
RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception
ELSE
RETURN NULL; -- ID not found, return NULL
END IF;
EXCEPTION
WHEN FOREIGN_KEY_VIOLATION THEN
RETURN FALSE;
WHEN SQLSTATE 'MYERR' THEN
RETURN TRUE;
-- other exceptions are propagated as usual
END
$func$ LANGUAGE plpgsql;
db<>fiddle here
Old sqlfiddle
While being at it, I made it completely dynamic, including the data type of the column (it has to match the given column, of course). I am using the polymorphic type anyelement for that purpose. See:
How to write a function that returns text or integer values?
I also use format() and a parameter of type regclass to safeguard against SQLi. See:
SQL injection in Postgres functions vs prepared queries
You can do that also with Procedure.
CREATE OR REPLACE procedure p_delable(_tbl text, _col text, _id int)
AS $$
DECLARE
_ct bigint;
_exists boolean; -- to receive count of deleted rows
BEGIN
_exists := (SELECT EXISTS ( SELECT FROM information_schema.tables
WHERE table_schema = 'public' AND table_name = $1 ));
IF _exists THEN
EXECUTE format('DELETE FROM %s WHERE %I = $1', _tbl, _col)
USING _id; -- exception if other rows depend
GET DIAGNOSTICS _ct = ROW_COUNT;
IF _ct > 0 THEN
RAISE SQLSTATE 'MYERR'; -- If DELETE, raise custom exception
ELSE
RAISE NOTICE 'no records found. no records will be deleted';
END IF;
ELSE
raise notice 'Input text is invalid table name.';
END IF;
EXCEPTION
WHEN undefined_column then
raise notice 'Input text is invalid column name.';
WHEN undefined_table then
raise notice 'Input text is invalid table name.';
WHEN FOREIGN_KEY_VIOLATION THEN
RAISE NOTICE 'foreign key violation, cannot be deleted.';
WHEN SQLSTATE 'MYERR' THEN
RAISE NOTICE 'rows % found and can be deleted.', _ct;
END
$$ LANGUAGE plpgsql;
You can call it, also can validate your input.
call p_delable('parent_tree', 'parent_id',30);
Will get:
NOTICE: no records found. no records will be deleted
Lets try an actual exist row.
call p_delable('parent_tree', 'parent_id',3);
It will return
NOTICE: rows 1 found and can be deleted.
It can also check your input table name exists in public schema or not.
call p_delable('parent_tre', 'parent_id',3);
It will give you notice:
NOTICE: Input text is invalid table name.
Trying to create a pl/sql cursor based function to return details from an oracle database.
The relevant table, MEETING, has the columns
MEETING_ID: number(10), TIME: timestamp(4), TITLE: varchar(20)
CREATE OR REPLACE FUNCTION GetMeeting
(meetingnumber MEETING.MEETING_ID%TYPE)
RETURN VARCHAR
IS
CURSOR current_meeting(meetingnumber MEETING.MEETING_ID%TYPE)
IS
SELECT TITLE, TIME
FROM MEETING
WHERE MEETING_ID = meetingnumber;
r_meeting current_meeting%ROWTYPE;
BEGIN
OPEN current_meeting(meetingnumber);
FETCH current_meeting INTO r_meeting;
IF current_meeting%NOTFOUND THEN
r_meeting.TITLE := 'UNKNOWN APPOINTMENT';
END IF;
CLOSE current_meeting;
RETURN r_meeting.TITLE;
END;
SELECT GetMeeting (27) name
FROM MEETING;
The function seems to compile okay - but when called throws
ORA-06575: Package or function GETMEETING is in an invalid state
Perhaps this will work better for you:
create or replace function
getmeeting(
meeting_id number)
return
varchar
is
meeting_title meeting.title%Type;
begin
select title
into meeting_title
from meeting
where meeting_id = getmeeting.meeting_id;
return meeting_title;
exception
when NO_DATA_FOUND then
return 'UNKNOWN APPOINTMENT';
end;
/
Not syntax checked.
Error being generated by column identifier 'TIME' which is an SQL keyword; triggering a runtime error when executed.
Code unfortunately returns NULL when 'TIME' is removed
There aren't enough facts to know but I would look into some form of circular dependency.
select *
from user_dependencies
where referenced_name = 'GETMEETING'
and referenced_type = 'FUNCTION';
The best way to avoid circular dependencies is to use packages where references to other packages are made in the body only. Avoid standalone function and procedure objects.
I have a trigger statement like this:
CREATE TRIGGER update_customer_address UPDATE OF address ON customers
BEGIN
UPDATE orders SET address = new.address WHERE customer_name = old.name;
-- RAISE ...
END;
END;
EDIT:
I want to do a step:
int result = sqlite3_step(statement);
I know result returns the result code like SQLITE_DONE, SQLITE_ROW, and SQLITE_CONSTRAINT. I also discovered that RAISE ABORT within TRIGGER returns SQLITE_CONSTRAINT to result.
Is there a way for me to create a custom error code? I want to do something like
int result = sqlite3_step(statement);
// get custom error code here
Is it possible to raise with a custom error code in sqlite? Or are there any workarounds?
You can throw an error using the RAISE() function.
You have to use pre-defined error codes (IGNORE, ABORT, FAIL or ROLLBACK) but you can add a custom message:
CREATE TRIGGER update_customer_address UPDATE OF address ON customers
BEGIN
UPDATE orders SET address = new.address WHERE customer_name = old.name;
SELECT CASE
WHEN ( (SELECT * from orders WHERE address = 'old address!!') IS NOT NULL)
THEN RAISE (FAIL, 'Can still find old address, not everything was updated!')
END;
END;
Note that RAISE() is meant to be used only when something was wrong, see the doc for more information about raise() behavior.
Yes ofcourse you can define custom error codes or messages. All messages sent by
raise(ERR, "message")
can be retreived in C/C++ through
const char *sqlite3_errmsg(sqlite3*);