Not Able to Trigger the Exception NO_DATA_FOUND in postgreSQL - postgresql

I am not able to trigger the exceptions NO_DATA_FOUND from functions in PostgreSql 8.2 even if the returned rows or result sets is zero.
Here is my code;
CREATE OR REPLACE FUNCTION func_ex() RETURNS trigger AS
$func_ex$
DECLARE
var_name name;
BEGIN
Select empname INTO var_name from emp_table1 WHERE empid = 161232;
EXCEPTION
WHEN NO_DATA_FOUND THEN
RAISE EXCEPTION 'No data found';
RETURN NEW;
END;
return new
$func_ex$ LANGUAGE plpgsql;
-- End of Function
-- Creation of Trigger
CREATE TRIGGER insert_trigger1 AFTER update of empname
ON emp_table1 EXECUTE PROCEDURE func_ex();
-- insertion enteries.
INSERT INTO emp_table1 (empid, empname, salary) values (124, ' Sapmle_CustormerName', '3000');

To trigger NO_DATA_FOUND exception use:
Select empname INTO STRICT var_name from emp_table1 WHERE empid = 161232;
Details here: http://www.postgresql.org/docs/current/static/plpgsql-statements.html
If STRICT is not specified in the INTO clause, then target will be set
to the first row returned by the query, or to nulls if the query
returned no rows.
If the STRICT option is specified, the query must return exactly one
row or a run-time error will be reported, either NO_DATA_FOUND (no
rows) or TOO_MANY_ROWS (more than one row).

Related

Function error – postgresql

I created this function that checks that total cost of a flight has been calculated correctly. It should multiply the price of seats e.g. 400 by the number of seats a customer has asked for e.g. 2. If the customer inputs the total correctly e.g.800 it should return the query successfully. If they don't input the total correctly e.g. 1000 then it should raise the exception 'price has been calculated incorrectly'.
CREATE FUNCTION check_totalcost() RETURNS trigger AS $check_totalcost$
DECLARE seatprice int;
DECLARE noofseats int;
BEGIN
SELECT priceperseat INTO seatprice
FROM flight
WHERE flightid = flightid;
SELECT numseats INTO noofseats
FROM flightbooking
WHERE flightid = flightid;
IF(seatprice*noofseats != new.totalcost) THEN
RAISE EXCEPTION 'price has been calculated incorrectly';
END IF;
RETURN NEW;
END;
$check_totalcost$ LANGUAGE plpgsql;
/* This is used to run the function above */
CREATE TRIGGER insert_totalcost
BEFORE INSERT OR UPDATE ON flightbooking
FOR EACH ROW EXECUTE PROCEDURE check_totalcost();
When I input the total incorrectly, it raises the exception which is what I want but when I input the total correctly, it also raises the exception when it should return the query successfully.
The queries
SELECT priceperseat INTO seatprice
FROM flight
WHERE flightid = flightid;
SELECT numseats INTO noofseats
FROM flightbooking
WHERE flightid = flightid;
make little sense as the conditions are always true.
I guess you want to get flightid from inserted/updated record. Use the special record NEW:
WHERE flightid = NEW.flightid;
You have one more logical error in your function. The number of seats also should be got from the new record.
CREATE OR REPLACE FUNCTION check_totalcost()
RETURNS trigger AS $check_totalcost$
DECLARE seatprice int;
BEGIN
SELECT priceperseat INTO seatprice
FROM flight
WHERE flightid = new.flightid;
IF seatprice * new.numseats != new.totalcost THEN
RAISE EXCEPTION 'price has been calculated incorrectly';
END IF;
RETURN NEW;
END;
$check_totalcost$ LANGUAGE plpgsql;

PSQL Add value from row to another value in the same row using triggers

I have a test table with three columns (file, qty, qty_total). I will input multiple rows like this for example, insert into test_table (file,qty) VALUS (A,5);. What i want is for on commit is for a trigger to take the value from qty and add it to qty_total. As what will happen is that this value will get updated as this example demonstrates. Update test_table set qty = 10 where file = A; So the qty_total is now 15. Thanks
Managed to solve this myself. I created a trigger function `CREATE FUNCTION public.qty_total()
RETURNS trigger
LANGUAGE 'plpgsql'
COST 100.0
VOLATILE NOT LEAKPROOF
AS $BODY$
BEGIN
IF TG_OP = 'UPDATE' THEN
NEW."total" := (OLD.total + NEW.col2);
RETURN NEW;
ELSE
NEW."total" := NEW.col2;
RETURN NEW;
END IF;
END;
$BODY$;
ALTER FUNCTION public.qty_total()
OWNER TO postgres; This was called by a trigger CREATE TRIGGER qty_trigger
BEFORE INSERT OR UPDATE
ON public.test
FOR EACH ROW
EXECUTE PROCEDURE qty_total(); now when i insert a new code and value, the value is copied to the total, when it is updated, the value is added to the total and i have my new qty_total. This may not have the best error catching in it, but since i am passing the data from php, i am happy to make sure the errors are caught and removed.

How to clone a RECORD in PostgreSQL

I want to loop through a query, but also retain the actual record for the next loop, so I can compare two adjacent rows.
CREATE OR REPLACE FUNCTION public.test ()
RETURNS void AS
$body$
DECLARE
previous RECORD;
actual RECORD;
query TEXT;
isdistinct BOOLEAN;
tablename VARCHAR;
columnname VARCHAR;
firstrow BOOLEAN DEFAULT TRUE;
BEGIN
tablename = 'naplo.esemeny';
columnname = 'esemeny_id';
query = 'SELECT * FROM ' || tablename || ' LIMIT 2';
FOR actual IN EXECUTE query LOOP
--do stuff
--save previous record
IF NOT firstrow THEN
EXECUTE 'SELECT ($1).' || columnname || ' IS DISTINCT FROM ($2).' || columnname
INTO isdistinct USING previous, actual;
RAISE NOTICE 'previous: %', previous.esemeny_id;
RAISE NOTICE 'actual: %', actual.esemeny_id;
RAISE NOTICE 'isdistinct: %', isdistinct;
ELSE
firstrow = false;
END IF;
previous = actual;
END LOOP;
RETURN;
END;
$body$
LANGUAGE 'plpgsql'
VOLATILE
CALLED ON NULL INPUT
SECURITY INVOKER
COST 100;
The table:
CREATE TABLE naplo.esemeny (
esemeny_id SERIAL,
felhasznalo_id VARCHAR DEFAULT "current_user"() NOT NULL,
kotesszam VARCHAR(10),
idegen_azonosito INTEGER,
esemenytipus_id VARCHAR(10),
letrehozva TIMESTAMP WITHOUT TIME ZONE DEFAULT now() NOT NULL,
szoveg VARCHAR,
munkalap_id VARCHAR(13),
ajanlat_id INTEGER,
CONSTRAINT esemeny_pkey PRIMARY KEY(esemeny_id),
CONSTRAINT esemeny_fk_esemenytipus FOREIGN KEY (esemenytipus_id)
REFERENCES naplo.esemenytipus(esemenytipus_id)
ON DELETE RESTRICT
ON UPDATE RESTRICT
NOT DEFERRABLE
)
WITH (oids = true);
The code above doesn't work, the following error message is thrown:
ERROR: could not identify column "esemeny_id" in record data type
LINE 1: SELECT ($1).esemeny_id IS DISTINCT FROM ($2).esemeny_id
^
QUERY: SELECT ($1).esemeny_id IS DISTINCT FROM ($2).esemeny_id
CONTEXT: PL/pgSQL function "test" line 18 at EXECUTE statement
LOG: duration: 0.000 ms statement: SET DateStyle TO 'ISO'
What am I missing?
Disclaimer: I know the code doesn't make too much sense, I only created so I can demonstrate the problem.
This does not directly answer your question, and may be of no use at all, since you did not really describe your end goal.
If the end goal is to be able to compare the value of a column in the current row with the value of the same column in the previous row, then you might be much better off using a windowing query:
SELECT actual, previous
FROM (
SELECT mycolumn AS actual,
lag(mycolumn) OVER () AS previous
FROM mytable
ORDER BY somecriteria
) as q
WHERE previous IS NOT NULL
AND actual IS DISTINCT FROM previous
This example prints the rows where the current row is different from the previous row.
Note that I added an ORDER BY clause - it does not make sense to talk about "the previous row" without specifying ordering, otherwise you would get random results.
This is plain SQL, not PlPgSQL, but if you can wrap it in a function if you want to dynamically generate the query.
I am pretty sure, there is a better solution for your actual problem. But to answer the question asked, here is a solution with polymorphic types:
The main problem is that you need well known composite types to work with. the structure of anonymous records is undefined until assigned.
CREATE OR REPLACE FUNCTION public.test (actual anyelement, _col text
, OUT previous anyelement) AS
$func$
DECLARE
isdistinct bool;
BEGIN
FOR actual IN
EXECUTE format('SELECT * FROM %s LIMIT 3', pg_typeof(actual))
LOOP
EXECUTE format('SELECT ($1).%1$I IS DISTINCT FROM ($2).%1$I', _col)
INTO isdistinct
USING previous, actual;
RAISE NOTICE 'previous: %; actual: %; isdistinct: %'
, previous, actual, isdistinct;
previous := actual;
END LOOP;
previous := NULL; -- reset dummy output (optional)
END
$func$ LANGUAGE plpgsql;
Call:
SELECT public.test(NULL::naplo.esemeny, 'esemeny_id')
I am abusing an OUT parameter, since it's not possible to declare additional variables with a polymorphic composite type (at least I have failed repeatedly).
If your column name is stable you can replace the second EXECUTE with a simple expression.
I am running out of time, explanation in these related answers:
Declare variable of composite type in PostgreSQL using %TYPE
Refactor a PL/pgSQL function to return the output of various SELECT queries
Asides:
Don't quote the language name, it's an identifier, not a string.
Do you really need WITH (oids = true) in your table? This is still allowed, but largely deprecated in modern Postgres.

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.

a postgres update trigger performs everything else except the actual update

Let's use a test table :
CREATE TABLE labs.date_test
(
pkey int NOT NULL,
val integer,
date timestamp without time zone,
CONSTRAINT date_test_pkey PRIMARY KEY (pkey)
);
I have a trigger function defined as below. It is a function to insert a date into a specified column in the table. Its arguments are the primary key, the name of the date field, and the date to be inserted:
CREATE OR REPLACE FUNCTION tf_set_date()
RETURNS trigger AS
$BODY$
DECLARE
table_name text;
pkey_col text := TG_ARGV[0];
date_col text := TG_ARGV[1];
date_val text := TG_ARGV[2];
BEGIN
table_name := format('%I.%I', TG_TABLE_SCHEMA, TG_TABLE_NAME);
IF TG_NARGS != 3 THEN
RAISE 'Wrong number of args for tf_set_date()'
USING HINT='Check triggers for table ' || table_name;
END IF;
EXECUTE format('UPDATE %s SET %I = %s' ||
' WHERE %I = ($1::text::%s).%I',
table_name, date_col, date_val,
pkey_col, table_name, pkey_col )
USING NEW;
RAISE NOTICE '%', NEW;
RETURN NEW;
END;
$BODY$
LANGUAGE plpgsql VOLATILE;
The actual trigger definition is as follows:
CREATE TRIGGER t_set_ready_date
BEFORE UPDATE OF val
ON labs.date_test
FOR EACH ROW
EXECUTE PROCEDURE tf_set_date('pkey', 'date', 'localtimestamp(0)');
Now say I do: INSERT INTO TABLEdate_test(pkey) values(1);`
Then I perform an update as follows:
UPDATE labs.date_test SET val = 1 WHERE pkey = 1;
Now the date gets inserted as expected. But the val field is still NULL. It does not have 1 as one would expect (or rather as I expected).
What am I doing wrong? The RAISE NOTICE in the trigger shows that NEW is still what I expect it to be. Aren't UPDATEs allowed in BEFORE UPDATE triggers? One comment about postgres triggers seems to indicate that original the UPDATE gets overwritten if there is an UPDATE statement in a BEFORE UPDATE trigger. Can someone help me out?
EDIT
I am trying to update the same table that invoked the trigger, and that too the same row which is to be modified by the UPDATE statement that invoked the trigger. I am running Postgresql 9.2
Given all the dynamic table names it isn't entirely clear if this trigger issues an update on the same table that invoked the trigger.
If so: That won't work. You can't UPDATE some_table in a BEFORE trigger on some_table. Or, more strictly, you can, but if you update any row that is affected by the statement that's invoking the trigger results will be unpredictable so it isn't generally a good idea.
Instead, alter the values in NEW directly. You can't do this with dynamic column names, unfortunately; you'll just have to customise the trigger or use an AFTER trigger to do the update after the rows have already been changed.
I am not sure, but your triggers can do recursion calls - it does UPDATE same table from UPDATE trigger. This is usually bad practice, and usually is not good idea to write too generic triggers. But I don't know what you are doing, maybe you need it, but you have to be sure, so you are protected against recursion.
For debugging of triggers is good push to start and to end of function body debug messages. Probably you use GET DIAGNOSTICS statement after EXECUTE statement for information about impact of dynamic SQL
DECLARE
_updated_rows int;
_query text;
BEGIN
RAISE NOTICE 'Start trigger function xxx';
...
_query := format('UPDATE ....);
RAISE NOTICE 'dynamic sql %, %', _query, new;
EXECUTE _query USING new;
GET DIAGNOSICS _updated_rows = ROW_COUNT;
RAISE NOTICE 'Updated rows %', _updated_rows;
...