stored procedure, accessing data that has just been inserted - postgresql

I am trying to insert values into a table based upon a person being inserted into another table. I have a trigger for this, when someone is assigned to employee, they are automatically assigned to employeepark with the first spot that is available. I cannot figure out how to access the id that is being input into the employee table. I would appreciate any tips or ideas, thank you !
This is the error I am receiving.
ERROR: record "new" is not assigned yet
create or replace function new_employeeAssign() returns trigger as $new_employeeAssign$
declare
open_spotID int := (select parkingspot.spotid
from employeepark e full outer join parkingspot on e.spotid = parkingspot.spotid
where e.spotid isNull limit 1);
begin
insert into employeepark(employeeid, spotid)
values(new.employeeid ,open_spotID);
End;
$new_employeeAssign$ language plpgsql;
create trigger new_employeeAssign after insert on employee
execute procedure new_employeeAssign();
insert into people(peopleid, fname, lname)
values(686, 'random', 'person');
insert into employee(employeeid)
values(686);
Patrick figured this out for me now I am running into THIS PROBLEM:
I want to select the first value out of all of these ranges that is null, I keep getting back one though and it is just bypassing the ranges and going straight to the isNull.
(select parkingspot.spotid
from employeepark e full outer join parkingspot on e.spotid = parkingspot.spotid
where (e.spotid = 301)
or (e.spotid = 1601)
or (e.spotid = 2001)
or (e.spotid = 2011)
or (e.spotid = 2121)
or (e.spotid = 2021)
or (e.spotid = 2771)
or (e.spotid = 2921)
or (e.spotid = 3021)
or (e.spotid = 3823) isNull
limit 1)

Your trigger definition is incorrect. By default, a trigger applies to FOR EACH STATEMENT and then the NEW parameter does not exist (the trigger does not apply to a row, after all). Instead you should have:
CREATE TRIGGER new_employeeAssign AFTER INSERT ON employee
FOR EACH ROW EXECUTE PROCEDURE new_employeeAssign();
There are also some issues with your trigger function, in particular the query that assigns to variable open_spotID. This query will always select NULL because e.spotid IS NULL and you join on e.spotid = parkingspot.spotid. The logic that you are looking for is probably that you want to assign a parking slot to a new employee by making a row in table employeepark with a spot_id that is not already assigned to some other employee. See code below.
You also have to RETURN NEW from the function.
Other than that, your trigger function could be much optimized like so:
CREATE FUNCTION new_employeeAssign() RETURNS trigger AS $new_employeeAssign$
BEGIN
INSERT INTO employeepark(employeeid, spotid)
SELECT NEW.employeeid, spotid
FROM parkingspot p
LEFT JOIN employeepark e USING (spotid)
WHERE e.employeeid IS NULL
LIMIT 1;
RETURN NEW;
END;
$new_employeeAssign$ LANGUAGE plpgsql;

Related

PostgreSQL update trigger: relation 'new' does not exist

I'm trying to get a better sense of triggers and not quite understanding why I'm unable to reference the new, incoming row in this trigger function definition:
CREATE OR REPLACE FUNCTION public.update_origin_country()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.origin_country = a.pk
from reference.admin_layers_0 a
inner join reference.world_port_index b on a.iso_a2 = b.country
inner join new c on b.id = c.origin_port;
RETURN NEW;
END;
$$;
CREATE TRIGGER "origin_country_update" BEFORE INSERT OR UPDATE OF "origin_port" ON "active"."events"
FOR EACH ROW
EXECUTE PROCEDURE "public"."update_origin_country"();
When I update the field origin_ports with the trigger applied, I get the error:
Relation "new" does not exist.
Not sure how to get around it. The goal is to evaluate the new row coming in, checking for the value in origin_ports and using that to update the value for origin_country from a query referencing a port table and a country name table. Any help appreciated.
I don't totally understand the desired logic but instead of joining to new (which is invalid since the new row is not a relation/table) you can just add the filter to a where clause, something like:
CREATE OR REPLACE FUNCTION public.update_origin_country()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
BEGIN
NEW.origin_country = a.pk
from reference.admin_layers_0 a
inner join reference.world_port_index b on a.iso_a2 = b.country
WHERE new.origin_port = b.id;
RETURN NEW;
END;
$$;
CREATE TRIGGER "origin_country_update" BEFORE INSERT OR UPDATE OF "origin_port" ON "active"."events"
FOR EACH ROW
EXECUTE PROCEDURE "public"."update_origin_country"();
Is that what you want it to do?

Postgresql trigger syntax error at or near "NEW"

Here is what i'm trying to do:
ALTER TABLE publishroomcontacts ADD COLUMN IF NOT EXISTS contactorder integer NOT NULL default 1;
CREATE OR REPLACE FUNCTION publishroomcontactorder() RETURNS trigger AS $publishroomcontacts$
BEGIN
IF (TG_OP = 'INSERT') THEN
with newcontactorder as (SELECT contactorder FROM publishroomcontacts WHERE publishroomid = NEW.publishroomid ORDER BY contactorder limit 1)
NEW.contactorder = (newcontactorder + 1);
END IF;
RETURN NEW;
END;
$publishroomcontacts$ LANGUAGE plpgsql;
CREATE TRIGGER publishroomcontacts BEFORE INSERT OR UPDATE ON publishroomcontacts
FOR EACH ROW EXECUTE PROCEDURE publishroomcontactorder();
I've been looking into a lot of examples and they all look like this. Most of them a couple of years old tho. Has this changed or why doesn't NEW work? And do i have to do the insert in the function or does postgres do the insert with the returned NEW object after the function is done?
I'm not sure what you're trying to do, but your syntax is wrong here:
with newcontactorder as (SELECT contactorder FROM publishroomcontacts WHERE publishroomid = NEW.publishroomid ORDER BY contactorder limit 1)
NEW.contactorder = (newcontactorder + 1);
Do not use CTE query if there is no select that comes afterwards. If you want to increment contactorder column for particular publishroomid whenever new one is being added and this is your sequence (auto increment) mechanism then you should replace it with:
NEW.contactorder = COALESCE((
SELECT max(contactorder)
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
), 1);
Note the changes:
there's no CTE, just variable assignment with SELECT query
use MAX() aggregate function instead of ORDER BY + LIMIT
wrapped up with COALESCE(x,1) function to properly insert first contacts for rooms, it will return 1 if your query does return NULL
Your trigger should look like this
CREATE OR REPLACE FUNCTION publishroomcontactorder() RETURNS trigger AS $publishroomcontacts$
BEGIN
IF (TG_OP = 'INSERT') THEN
NEW.contactorder = COALESCE((
SELECT max(contactorder) + 1
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
), 1);
END IF;
RETURN NEW;
END;
$publishroomcontacts$ LANGUAGE plpgsql;
Postgres will insert the row itself, you don't have to do anything, because RETURN NEW does that.
This solution does not take care of concurrent inserts which makes it unsafe for multi-user environment! You can work around this by performing an UPSERT !
WITH is not an assignment in PL/pgSQL.
PL/pgSQL interprets the line as SQL statement, but that is bad SQL because the WITH clause is followed by NEW.contactorder rather than SELECT or another CTE.
Hence the error; it has nothing to do with NEW as such.
You probably want something like
SELECT contactorder INTO newcontactorder
FROM publishroomcontacts
WHERE publishroomid = NEW.publishroomid
ORDER BY contactorder DESC -- you want the biggest one, right?
LIMIT 1;
You'll have to declare newcontactorder in the DECLARE section.
Warning: If there are two concurrent inserts, they might end up with the same newcontactorder.

Recursive with cursor on psql, nothing data found

How to use a recursive query and then using cursor to update multiple rows in postgresql. I try to return data but no data is found. Any alternative to using recursive query and cursor, or maybe better code please help me.
drop function proses_stock_invoice(varchar, varchar, character varying);
create or replace function proses_stock_invoice
(p_medical_cd varchar,p_post_cd varchar, p_pstruserid character varying)
returns void
language plpgsql
as $function$
declare
cursor_data refcursor;
cursor_proses refcursor;
v_medicalCd varchar(20);
v_itemCd varchar(20);
v_quantity numeric(10);
begin
open cursor_data for
with recursive hasil(idnya, level, pasien_cd, id_root) as (
select medical_cd, 1, pasien_cd, medical_root_cd
from trx_medical
where medical_cd = p_pstruserid
union all
select A.medical_cd, level + 1, A.pasien_cd, A.medical_root_cd
from trx_medical A, hasil B
where A.medical_root_cd = B.idnya
)
select idnya from hasil where level >=1;
fetch next from cursor_data into v_medicalCd;
return v_medicalCd;
while (found)
loop
open cursor_proses for
select B.item_cd, B.quantity from trx_medical_resep A
join trx_resep_data B on A.medical_resep_seqno = B.medical_resep_seqno
where A.medical_cd = v_medicalCd and B.resep_tp = 'RESEP_TP_1';
fetch next from cursor_proses into v_itemCd, v_quantity;
while (found)
loop
update inv_pos_item
set quantity = quantity - v_quantity, modi_id = p_pstruserid, modi_id = now()
where item_cd = v_itemCd and pos_cd = p_post_cd;
end loop;
close cursor_proses;
end loop;
close cursor_data;
end
$function$;
but nothing data found?
You have a function with return void so it will never return any data to you. Still you have the statement return v_medicalCd after fetching the first record from the first cursor, so the function will return from that point and never reach the lines below.
When analyzing your function you have (1) a cursor that yields a number of idnya values from table trx_medical, which is input for (2) a cursor that yields a number of v_itemCd, v_quantity from tables trx_medical_resep, trx_resep_data for each idnya, which is then used to (3) update some rows in table inv_pos_item. You do not need cursors to do that and it is, in fact, extremely inefficient. Instead, turn the whole thing into a single update statement.
I am assuming here that you want to update an inventory of medicines by subtracting the medicines prescribed to patients from the stock in the inventory. This means that you will have to sum up prescribed amounts by type of medicine. That should look like this (note the comments):
CREATE FUNCTION proses_stock_invoice
-- VVV parameter not used
(p_medical_cd varchar, p_post_cd varchar, p_pstruserid varchar)
RETURNS void AS $function$
UPDATE inv_pos_item -- VVV column repeated VVV
SET quantity = quantity - prescribed.quantity, modi_id = p_pstruserid, modi_id = now()
FROM (
WITH RECURSIVE hasil(idnya, level, pasien_cd, id_root) AS (
SELECT medical_cd, 1, pasien_cd, medical_root_cd
FROM trx_medical
WHERE medical_cd = p_pstruserid
UNION ALL
SELECT A.medical_cd, level + 1, A.pasien_cd, A.medical_root_cd
FROM trx_medical A, hasil B
WHERE A.medical_root_cd = B.idnya
)
SELECT B.item_cd, sum(B.quantity) AS quantity
FROM trx_medical_resep A
JOIN trx_resep_data B USING (medical_resep_seqno)
JOIN hasil ON A.medical_cd = hasil.idnya
WHERE B.resep_tp = 'RESEP_TP_1'
--AND hacil.level >= 1 Useless because level is always >= 1
GROUP BY 1
) prescribed
WHERE item_cd = prescribed.item_cd
AND pos_cd = p_post_cd;
$function$ LANGUAGE sql STRICT;
Important
As with all UPDATE statements, test this code before you run the function. You can do that by running the prescribed sub-query separately as a stand-alone query to ensure that it does the right thing.

Postgres - Trigger with matched key

I have several tables. A table, cexp, is a table that has attributes cid and total. Cid is grouped and total is the sum of quantity * price for that cid (matched on cid)
The cexp table was populated with the results of the following code:
SELECT c.cid, sum(ol.quantity*b.price) as total
FROM customers c join orders o on c.cid=o.cid
join orderlist ol on o.ordernum=ol.ordernum
join books b on b.isbn=ol.isbn
GROUP BY C.CID
My task is to create a trigger that, when inserting rows for order and orderderlist, finds the matching name, in cexp and increments the existing total by the product of new quantity (from orderlist) and the price (from books). If no match, insert a row in cexp.
Tables are as follows:
Customers-cid,name pk-cid
Books - isbn,title,price pk-isbn
Orders - ordernum,cid pk-ordernum
Orderlist - ordernum,isbn, quantity - pk-(ordernum,isbn)
cexp - cid,total - pk-cid
I am getting syntax errors. Can anyone correct this code?
CREATE OR REPLACE FUNCTION cexpupd()
RETURNS trigger as
$cexpupd$
BEGIN
UPDATE cexp
SET new.total=total+Select (b.price*new.quantity) FROM customers c
join orders o on c.cid=o.cid
join orderlist ol on o.ordernum=ol.ordernum
join books b on b.isbn=ol.isbn
where b.isbn=new.isbn;
--INSERT CODE WHEN ABOVE LINE DOES NOT OCCUR -INSERTS NEW ROW INTO CEXP
END;
$cexpupd$
LANGUAGE plpgsql
I would say that your UPDATE statement is an unsalvageable birds-nest. Fortunately, there's an easier way to achieve the same result.
Keep in mind that a trigger function is procedural code, so there's no need to concurrently load the gun, pull the trigger, scream, and shoot yourself in the foot in a single statement. You have access to all the procedural goodies like local variables and flow control statements.
The following should give you a good headstart on what you want:
CREATE OR REPLACE FUNCTION cexpupd()
RETURNS trigger as
$cexpupd$
DECLARE
bookprice books.price%TYPE;
ext_total cexp.total%TYPE;
custid orders.cid%TYPE;
BEGIN
SELECT cid
INTO custid
FROM orders
WHERE orders.ordernum = NEW.ordernum;
SELECT price
INTO bookprice
FROM books
WHERE books.isbn = NEW.isbn;
ext_total = bookprice * NEW.quantity;
UPDATE cexp
SET total = ext_total
WHERE cid = custid;
IF NOT FOUND THEN
--INSERT new CID record here
INSERT INTO cexp
(cid, total)
VALUES
(custid, ext_total);
END IF;
RETURN NEW;
END;
$cexpupd$
LANGUAGE plpgsql;
Note the statement near the end, RETURN NEW; This line is crucial, as it is how a PostgreSQL trigger function tells the database to go ahead and finish executing the statement that fired the trigger.
If you need any clarification, please don't hesitate to ask. Please note I have not tried executing this function, but I did create the necessary tables to compile it successfully.

How can I check if an element in a table exists?

I'm writing a Postgres function which should delete from 3 tables successively.
The relation is delete from mobgroupdata -> mobilenums -> terminals and when I don't have an element in mobgroupdata, I want to delete from mobilenums and then from terminals. But what should be the condition. I've tried with
IF mRec.id != 0, but it didn't work, than I've tried with exists, it also didn't work. Also when I made my select statement from the DB and mobgroupdata's id doesn't exist, the code is breaking, but when I select element which consist in all tables it works. Does anybody know what should be the if statement to make it works?
CREATE OR REPLACE FUNCTION "Delete_From_Terminals_Casc_final12"(
"Id_list" bigint,
"Curuser_id" bigint)
RETURNS SETOF term_mgd_mobnums AS
$BODY$
declare
mRec "term_mgd_mobnums"%ROWTYPE;
BEGIN
for mRec in select mn."id_terminals", t.sn , t.imei ,t.les ,t.category ,t.model ,t.tswv ,t.status ,t.activation_date ,t.deactivation_date ,t.paytype ,t.ip_address ,t.pin1 ,t.pin2 ,t.puk1 ,t.puk2 ,t.notes ,t.units ,t.validtill, t.responsible_user,t.id_clients,t.currentuser, t.isn,
md.id_mobilenums, mn.current_status, mn.start_date ,mn.streason ,mn.unit ,mn.mobnumber ,mn.service ,mn.status as mn_status,mn.activator ,mn.responsible_department,mn.date_changed ,mn.reason ,mn.installed_on ,mn.usedby ,mn.regnumber ,mn.responsible_user as mn_responsible_user ,mn.description,
md.id,md.les1 ,md.les2,md.les3,md.les4,md.les5,md.member1 ,md.member2,md.member3,md.member4,md.member5,md.user1 ,md.user2,md.user3,md.user4,md.user5,md.pass1 ,md.pass2,md.pass3,md.pass4,md.pass5 from terminals t
inner join mobilenums mn on t."id" = mn."id_terminals"
inner join mobgroupdata md on md."id_mobilenums" = mn."id"
where mn."id_terminals" = $1
loop
IF exists THEN
PERFORM "Delete_From_Mobgroupdata2"(mRec.id,$2);
PERFORM "Delete_From_Mobilenums"(mRec.id_mobilenums::text,$2);
PERFORM "Delete_From_Terminals"(mRec.id_terminals::text,$2);
ELSE
PERFORM "Delete_From_Mobilenums"(mRec.id_mobilenums::text,$2);
PERFORM "Delete_From_Terminals"(mRec.id_terminals::text,$2);
END IF;
RETURN NEXT mRec;
end loop;
return;
end;$BODY$
LANGUAGE plpgsql VOLATILE
COST 100
ROWS 1000;
ALTER FUNCTION "Delete_From_Terminals_Casc_final12"(bigint, bigint)
OWNER TO postgres;
Two problems with your code, if I am reading your question correctly:
You are using INNER JOIN to join to mobgroupdata. This will only retrieve results for rows which do exist in all of your tables. Use LEFT OUTER JOIN instead.
You tried mRec.id != 0, but you are looking for NULL, not 0. 0 and NULL are not the same thing in SQL. The condition you want is mRec.id IS NOT NULL.