sql query inside if stage with exists - sql

I want to check if the id I want to insert into tableA exists in tableB into an if statement
Can I do something like this
if new.id exists (select id from tableB where stat = '0' ) then
some code here
end if;
When I try this I get an error message, any thoughts?

Why not do it like this? I'm not very knowledgeable about PostgreSQL but this would work in T-SQL.
INSERT INTO TargetTable(ID)
SELECT ID
FROM TableB
WHERE ID NOT IN (SELECT DISTINCT ID FROM TargetTable)

This is usually done with a trigger. A trigger function does the trick:
CREATE FUNCTION "trf_insert_tableA"() RETURNS trigger AS $$
BEGIN
PERFORM * FROM "tableB" WHERE id = NEW.id AND stat = '0';
IF FOUND THEN
-- Any additional code to go here, optional
RETURN NEW;
ELSE
RETURN NULL;
END IF;
END; $$ LANGUAGE plpgsql;
CREATE TRIGGER "tr_insert_tableA"
BEFORE INSERT ON "tableA"
FOR EACH ROW EXECUTE PROCEDURE "trf_insert_tableA"();
A few notes:
Identifiers in PostgreSQL are case-insensitive. PostgreSQL by default makes them lower-case. To maintain the case, use double-quotes. To make your life easy, use lower-case only.
A trigger needs a trigger function, this is always a two-step affair.
In an INSERT trigger, you can use the NEW implicit parameter to access the column values that are attempted to be inserted. In the trigger function you can modify these values and those values are then inserted. This only works in a BEFORE INSERT trigger, obviously; AFTER INSERT triggers are used for side effects such as logging, auditing or cascading inserts to other tables.
The PERFORM statement is a special form of a SELECT statement to test for the presence of data; it does not return any data, but it does set the FOUND implicit parameter that you can use in a conditional statement.
Depending on your logic, you may want the insert to succeed or to fail. RETURN NEW to make the insert succeed, RETURN NULL to make it fail.
After you defined the trigger, you can simply issue an INSERT statement: the trigger function is invoked automatically.

Presumably, you want something like this:
if exists (select 1 from tableB b where stat = '0' and b.id = new.id) then
some code here
end if;

Related

How to select all inserted rows to execute an insert trigger with a stored procedure in postgresql?

I'm trying to set an "after insert" trigger that executes a procedure. The procedure would take all inserted rows in table A, group them by a column and insert the result in a table B. I know about "new" variable but it gets inserted rows one by one. Is it possible to get all of them?
I think I can't use a for each row statement as I need to group rows depending on the "trackCode" variable, shared by different rows in tableA.
CREATE OR REPLACE PROCEDURE Public.my_procedure(**inserted rows in tableA?**)
LANGUAGE 'plpgsql'
AS $$
BEGIN
INSERT INTO Public."tableB" ("TrackCode", "count")
SELECT "TrackCode", count(*) as "count" FROM Public."tableA" --new inserted rows in this table
GROUP BY "vmsint"."TrackCode" ;
COMMIT;
END;
$$;
create trigger Public.my_trigger
after insert ON Public.tableA
execute procedure Public.my_procedure(**inserted rows in tableA?**)
Thank you!
You create a statement lever trigger, but do not attempt to pass parameters. Instead use the clause referencing new table as reference_table_name. In the trigger function you use the reference_table_name in place of the actual table name. Something like: (see demo)
create or replace function group_a_ais()
returns trigger
language 'plpgsql'
as $$
begin
insert into table_b(track_code, items)
select track_code, count(*)
from rows_inserted_to_a
group by track_code ;
return null;
end;
$$;
create trigger table_a_ais
after insert on table_a
referencing new table as rows_inserted_to_a
for each statement
execute function group_a_ais();
Do not attempt to commit in a trigger, it is a very bad id even if allowed. Suppose the insert to the main table is part of a larger transaction, which fails later in its process.
Be sure to refer to links provided by Adrian.

Merge statement issue in oracle

I came from Microsoft SQL environment.I have two tables tak_ne and tak_beb and my requirement was to insert values from tak_beb to tak_ne if value is not present,if it is present just update.So i made a merge statement as shown below.But the problem now i am facing is veryday 50000 count is getting increment for sequence number.Oracle is stable database, and i don't know why they made it like that.So i create a Function and prevented incrementing sequence number.My question is ,is it a right approach by creating function.Following is what i did
merge into tak_ne a using tak_beb b ON (a.NAME=b.NAME)
When matched then
Update
Set a.AC_NO = b.AC_NO
a.LOCATION = b.LOCATION
a.MODEL = b.MODEL
When not matched then
insert
(
sl_no,
AC_NO,
LOCATION
MODEL
)
Values
(
s_slno_nextval
b.AC_NO
b.LOCATION
b.MODEL
)
and then i created a function
CREATE OR REPLACE FUNCTION s_slno_nextval
RETURN NUMBER
AS
v_nextval NUMBER;
BEGIN
SELECT s_emp.nextval
INTO v_nextval
FROM dual;
RETURN v_nextval;
END;
Oracle uses this approach to generate unique id for each row inserted by a statement. Your TAK_BEB table has probably 50000 rows, so the sequence is incremented 50000 times.
To hide increment into a function does not help. Function is called AND EXECUTED for every row, it increments sequence for 50000 times again. And it adds overhead with 50000 selects from dual table.
If you really need to use ONE value from sequence for ALL rows inserted by statement, use package variable:
create package single_id_pkg is
id Number;
function get_id return number;
end;
/
create or replace package body single_id_pkg is
function get_id return number is
begin
return id;
end;
end;
/
Now use for example before statement trigger on table to set the variable:
create trigger tak_ne_BSI_trg
before insert
on tak_ne
begin
select s_emp.nextval
into single_id_pkg.id
from dual;
end;
Insert trigger has one disadvantage - with MERGE clause it fires even if the statement does only updates rows (see https://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:25733900083512). If it is a problem, you have to initialize the variable in other way.
Next modify your statement to use a package variable:
merge into tak_ne a
using tak_beb b
on (a.NAME=b.NAME)
when matched then
update
set a.AC_NO = b.AC_NO
a.LOCATION = b.LOCATION
a.MODEL = b.MODEL
when not matched then
insert (sl_no,
AC_NO,
LOCATION,
MODEL)
values (single_id_pkg.get_id
b.AC_NO,
b.LOCATION,
b.MODEL)
In Oracle standard way to use autoincrement field is by using sequences. And of course it will increment sequence number each time you want to use it.
But you can omit calling sequence_name.nextval, hiding it in trigger it is considered the standard approach also.
CREATE OR REPLACE EDITIONABLE TRIGGER TAK_NE_ID_TR"
BEFORE INSERT ON tak_ne
FOR EACH ROW
BEGIN
IF :old.sl_no IS NULL THEN
:new.sl_no := s_emp.nextval;
END IF;
END;
If you want to add same id for a batch of your inserts you can use global temporary table for saving it. For example, like this:
create global temporary table tak_ne_id ("id" number) on commit delete rows
create or replace trigger tak_ne_BSI_trg
before insert
on tak_ne
begin
insert into tak_ne_id("id")
values(s_emp.nextval);
end
create or replace TRIGGER TAK_NE_ID_TR
BEFORE INSERT ON tak_ne
FOR EACH ROW
BEGIN
if :old.sl_no is null then
SELECT "id"
INTO :new.sl_no
FROM tak_ne_id;
end if;
END;
Then you can use you merge as before, and without calling nextval:
merge into tak_ne a using tak_beb b ON (a.NAME=b.NAME)
When matched then
update
set a.AC_NO = b.AC_NO,
a.LOCATION = b.LOCATION,
a.MODEL = b.MODEL
When not matched then
insert
(
AC_NO,
LOCATION,
MODEL
)
Values
(
b.AC_NO,
b.LOCATION,
b.MODEL
);

Trigger function to delete certain rows from the same table

I'm trying to create a Trigger/Function in Postgres that will check, upon an insert to a table, whether or not there is already another post by a different member with the same content. If there is a post, this function will not insert the new one and leave the table unchanged. Otherwise, it will be added.
So far, the trigger and function look like:
Trigger:
CREATE TRIGGER isPostUnique
AFTER INSERT ON posts
FOR EACH ROW
EXECUTE PROCEDURE deletePost();
Function:
CREATE FUNCTION deletePost() RETURNS TRIGGER AS $isPostUnique$
BEGIN
IF (EXISTS (SELECT * FROM posts p1, posts p2
WHERE (p1.userID <> p2.userID)
AND (p1.content LIKE p2.content)))
THEN
DELETE FROM NEW WHERE (posts.postID = NEW.postID);
RETURN NEW;
END IF;
END;
$isPostUnique$ LANGUAGE plpgsql;
Adding the function and trigger works without any errors, but when I try to run the following query to test it: INSERT INTO posts VALUES (7, 3, 'test redundant post', 10, 1); I get this error
ERROR: relation "new" does not exist
LINE 1: DELETE FROM NEW WHERE (posts.postID = NEW.postID)
^
QUERY: DELETE FROM NEW WHERE (posts.postID = NEW.postID)
CONTEXT: PL/pgSQL function dp() line 7 at SQL statement
I am aware that you can't use 'NEW' in FOR EACH ROW inserts, but I have no other idea of how to accomplish this.
Updated answer for updated question
Of course you can use NEW in FOR EACH ROW trigger function. You just can't direct a DELETE statement at it. It's a row type (data type HeapTuple to be precise), not a table.
To abort the INSERT silently (no exception raised) if the same content is already there ...
CREATE FUNCTION deletePost()
RETURNS TRIGGER AS
$func$
BEGIN
IF EXISTS (
SELECT 1
FROM posts p
WHERE p.content = NEW.content
-- AND p.userID <> NEW.userID -- I doubt you need this, too?
) THEN
RETURN NULL; -- cancel INSERT
ELSE
RETURN NEW; -- go ahead
END IF;
END
$func$ LANGUAGE plpgsql;
Of course this only works for a trigger ...
...
BEFORE INSERT ON posts
...
Unique index
A UNIQUE constraint or a unique index (almost the same effect) might be a superior solution:
CREATE UNIQUE INDEX posts_content_uni_idx (content);
Would raise an exception at the attempt to insert a duplicate value. No trigger necessary.
It also provides the very well needed index to speed up things.

Disable Trigger for a particular DELETE Query

I have a ruby app. The app is doing the insert,update and delete on a particular table.
It does 2 kinds of INSERT, one insert should insert a record in the table and also into trigger_logs table. Another insert is just to insert the record into the table and do nothing. Another way to put it is, one kind of insert should log that the 'insert' happened into another table and another kind of insert should just be a normal insert. Similarly, there are 2 kinds of UPDATE and DELETE also.
I have achieved the 2 types of INSERT and UPDATE using a trigger_disable. Please refer to the trigger code below.
So, when I do a INSERT, I will set the trigger_disable boolean to true if I don't want to log the trigger. Similarly I am doing for an UPDATE too.
But I am not able to differentiate between the 2 kinds of DELETE as I do for an INSERT or UPDATE. The DELETE action is logged for both kinds of DELETE.
NOTE: I am logging all the changes that are made under a certain condition, which will be determined by the ruby app. If the condition is not satisfied, I just need to do a normal INSERT, UPDATE or DELETE accordingly.
CREATE OR REPLACE FUNCTION notify_#{#table_name}()
RETURNS TRIGGER
LANGUAGE plpgsql
AS $$
DECLARE
changed_row_id varchar(100);
BEGIN
IF TG_OP = 'DELETE' THEN
-- When the trigger is due to a delete
IF (OLD.trigger_disable IS NULL)
OR (OLD.trigger_disable = false) THEN
-- Prevent the trigger if trigger_disable is 'true'
-- The Problem is here: This insertion into the
-- trigger_logs table happens
-- for all the delete statements.
-- But during certain deletes I should not
-- insert into trigger_logs
INSERT INTO trigger_logs (table_name, action, row_id, dirty)
VALUES (
'#{#table_name}',
CAST(TG_OP AS Text),
OLD.id,
true
) RETURNING id into changed_row_id;
END IF;
RETURN OLD;
ELSE
-- The trigger is due to a Insert or Update
IF (NEW.trigger_disable IS NULL)
OR (NEW.trigger_disable = false) THEN
-- Prevent the trigger if trigger_disable is 'true'
INSERT INTO trigger_logs (table_name, action, row_id, dirty)
VALUES (
'#{#table_name}',
CAST(TG_OP AS Text),
NEW.id,
true
) RETURNING id into changed_row_id;
ELSE
NEW.trigger_disable := false;
END IF;
RETURN NEW;
END IF;
END
I'm going to take a stab in the dark here and guess that you're trying to contextually control whether triggers get fired.
If so, perhaps you can use a session variable?
BEGIN;
SET LOCAL myapp.fire_trigger = 'false';
DELETE FROM ...;
COMMIT;
and in your trigger, test it:
IF current_setting('myapp.fire_trigger') = 'true' THEN
Note, however, that if the setting is missing from a session you won't get NULL, you'll get an error:
regress=> SELECT current_setting('myapp.xx');
ERROR: unrecognized configuration parameter "myapp.xx"
so you'll want to:
ALTER DATABASE mydb SET myapp.fire_trigger = 'true';
Also note that the parameter is text not boolean.
Finally, there's no security on session variables. So it's not useful for security audit, since anybody can come along and just SET myapp.fire_trigger = 'false'.
(If this doesn't meet your needs, you might want to re-think whether you should be doing this with triggers at all, rather than at the application level).

How to check in Trigger what has been given in the where clause?

I am writing an INSTEAD OF UPDATE trigger and I want to identify what columns has been given to the WHERE clause of the UPDATE statement that triggers the trigger.
For example,
Let's say that we have the table below
table_name
--COL1
--COL2
--COL3
--COL4
I want, when an update is performed
e.g.UPDATE table_name SET COL1=VAL1,COL2=VAL2 WHERE COL3=VAL3
to be able to say in my trigger
CREATE or replace TRIGGER DEVICES_VIEW_TR
INSTEAD OF UPDATE ON DEVICES_VW
BEGIN
IF (COL3 has been given in the where clause) THEN
variable=getValueOf(COL3);
ELSEIF (COL4 has been given in the where clause) THEN
variable=getValueOf(COL4);
END IF;
END;
/
Can this be done?
Thanks
You can use the UPDATING('column name') in your trigger:
-- in INSTEAD OF trigger body:
IF updating('COL1') THEN
-- some operation
END IF;
Check this for an example: Example of using UPDATING
You could use the NEW and OLD pseudorecords and run a comparison of the values
if :NEW.COL3 <> :OLD.COL3 THEN ...
Triggers don't know anything about the statement that invoked them, so you'll have to use some kind of out-of-band signalling, e.g. change your application to set some globals in a database package, or use an application context.