Trigger to silently ignore/delete duplicate entries on INSERT - mysql

I have the following table:
T(ID primary key, A, B)
I want to have pair (A, B) unique but I don't want to have constraint unique(A,B) on them because it will give error on insert.
Instead I want MySQL to silently ignore such inserts.
I can't use "insert on duplicate keys ignore" because I can't control client's queries.
So, can I build such trigger? Or maybe there is some constraint that allows silent ignore?
Edit: I dug around and I think I want something like SQLite's "Raise Ignore" statement.

Before mysql 5.5. it wasn't possible to stop an insert inside a trigger. There where some ugly work arounds but nothing I would recommend. Since 5.5 you can use SIGNAL to do it.
delimiter //
drop trigger if exists aborting_trigger //
create trigger aborting_trigger before insert on t
for each row
begin
set #found := false;
select true into #found from t where a=new.a and b=new.b;
if #found then
signal sqlstate '45000' set message_text = 'duplicate insert';
end if;
end //
delimiter ;

Add a unique key (A,B) and use INSERT statement with an IGNORE keyword.
From the reference - If you use the IGNORE keyword, errors that occur while executing the INSERT statement are treated as warnings instead.
INSERT Syntax.

Related

How do i write a trigger before insert to check for duplicates checking 6000+ records

what i came up with so far
CREATE TRIGGER no_duplicate BEFORE INSERT ON street_master
FOR EACH ROW
BEGIN
IF(EXISTS(SELECT id from street_master WHERE name=NEW.name))
THEN ;
END IF;
END
what should i write in 'then' part i get syntax error everytime i write select statement or any other......
As documented under IF Syntax:
Each statement_list consists of one or more SQL statements; an empty statement_list is not permitted.
You could consider using SIGNAL to raise an error, or else deliberately perform an erroneous operation (like calling a non-existent procedure). But that's the wrong way to solve this problem.
What you really want is to define a UNIQUE index. As documented under CREATE INDEX Syntax:
A UNIQUE index creates a constraint such that all values in the index must be distinct. An error occurs if you try to add a new row with a key value that matches an existing row.
Thus:
CREATE UNIQUE INDEX uniqueNames ON street_master (name);

How to create a CHECK constraint in MySQL that requires negative values?

I want to alter an integer column on a table to add a check constraint preventing the value from being zero or a positive number. For example:
CREATE TABLE example (id INTEGER)
ALTER TABLE example ADD CONSTRAINT chk_negID CHECK (id<0)
MySQL happily complies with these yet then allows the following:
INSERT INTO example VALUES (-1);
INSERT INTO example VALUES (1);
Are my constraints not actually being added? Is there a way to list constraints that have been added to a table after it was created?
It's not supported by mysql (even though it accepts it as a valid syntax)
The CHECK clause is parsed but ignored by all storage engines
http://dev.mysql.com/doc/refman/5.5/en/create-table.html
Here is something that would work, but may be hard to debug. This is a trigger:
DELIMITER $$;
CREATE TRIGGER my_trigger AFTER INSERT ON my_table
FOR EACH ROW
BEGIN
IF(OLD.id <= 0)
THEN
DELETE FROM my_table where id = OLD.id;
END IF;
END

Mysql dropping inserts with triggers

Using mysql 5.6. I have two tables. One has a whitelist of hashes. When I insert a new row into the other table, I want to first compare the hash in the insert statement to the whitelist. If it's in the whitelist, I don't want to do the insert (less data to plow through later). The inserts are generated from another program and are text files with sql statements.
I've been playing with triggers, and almost have it working:
CREATE TRIGGER `Filelist` BEFORE INSERT ON `filelist`
FOR EACH ROW BEGIN IF(
SELECT count( md5hash ) FROM whitelist WHERE md5hash = new.hash ) >0
THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Can not have duplicates';
END IF ;
END
But there's a problem. The Signal throwing up the error stops the import. I want to skip that line, not stop the whole import.
Some searching didn't find any way to silently skip the import.
My next idea was to create a duplicate table definition, and redirect the insert to that dup table. But the old and new don't seem to apply to table names.
Other then adding an ignore column to my table then doing a mass drop based on that column after the import, is there any way to achieve my goal? I'm having problems with this too [Ignore is a tinyint(1)]:
DELIMITER $$
CREATE TRIGGER whitelisted
BEFORE INSERT ON filelist
FOR EACH ROW BEGIN
IF (select count(md5hash) from whitelist where md5hash=new.hash) > 0 THEN
SET Ignore = true;
END IF;
END$$
/* This is now "END$$" not "END;" */
/* Reset the delimiter back to ";" */
DELIMITER ;
#1064 - You have an error in your SQL syntax; check the manual that corresponds to
your MySQL server version for the right syntax to use near ') THEN SET Ignore = true;
END IF; END' at line 4
Any suggestions? I've also tried
SET Ignore = 1;
SET Ignore = '1';
SET new.Ignore = {all of the above};
I'm not sure if I follow this specification:
I have two tables. One has a whitelist of hashes. When I insert a new row into the other table, I want to first compare the hash in the insert statement to the whitelist. If it's in the whitelist, I don't want to do the insert
My first attempt would be like this:
INSERT INTO filelist (filename, hash)
SELECT "myfile", "ABCD" FROM DUAL
WHERE NOT EXISTS(
SELECT md5hash FROM whitelist where md5hash = "ABCD"
);
I don't think you need triggers for this at all unless there are missing details in your requirements.
I take it you're doing some kind of ON INSERT-trigger.
You need to add the following statement to your trigger to make it work as wanted:
FOR EACH ROW
This will make the trigger execute once on every row.

MySQL - Trigger to delete previous record

I'm trying to create a trigger that will allow only one record in a database, so it would delete any previous records.
But currently, it doesn't allow me to insert anything it, because it's instantly deleted.
DELIMITER $$
CREATE TRIGGER test_insert
BEFORE INSERT ON test
FOR EACH ROW BEGIN
DELETE FROM test WHERE id = NEW.id - 1;
END$$
How would I delete a previously (or all previous) inserted record?
"But currently, it doesn't allow me to insert anything it, because it's instantly deleted."
Acutally, when you do an INSERT, the execution of your trigger should be throwing exception:
Error Code: 1442
Can't update table 'test' in stored function/trigger because it is
already used by statement which invoked this stored function/trigger.
(Unless something is radically different in a newer version of MySQL.)
The operation you want to perform (i.e. deleting rows from the same table you are inserting into) cannot be done in a MySQL trigger.
You could use a combination of a UNIQUE KEY and a BEFORE INSERT trigger to prevent more than one row from being inserted. The BEFORE INSERT trigger could set the value of the column that has a unique
key on it to be a static value, then the INSERT statement would throw a duplicate key ("Duplicate entry") exception.
Then, you could use an INSERT ... ON DUPLICATE KEY UPDATE ... statement to update the values of columns other than the unique id, e.g.
CREATE TRIGGER `test_insert` BEFORE INSERT ON test
FOR EACH ROW BEGIN
SET NEW.id := 1;
END
ALTER TABLE test ADD UNIQUE KEY (id);
INSERT INTO test (somecol) VALUES ('someval')
ON DUPLICATE KEY UPDATE somecol = VALUES(somecol) ;
To do that you first need to find some value at which you want to delete all and insert for. This way you don't need a trigger, you simply delete all previous cases and then add a new row. Unless for some reason unexplained from your code you would need a trigger a simple solution in your loop could work Like:
$query = mysql_query("DELETE FROM test WHERE id = NEW.id -1");
$new_id = $new.id -1;
$query2 = mysql_query("INSERT INTO test VALUES('$new_id','$var1','$var2'));

mysql and trigger usage question

I have a situation in which I don't want inserts to take place (the transaction should rollback) if a certain condition is met. I could write this logic in the application code, but say for some reason, it has to be written in MySQL itself (say clients written in different languages will be inserting into this MySQL InnoDB table) [that's a separate discussion].
Table definition:
CREATE TABLE table1(x int NOT NULL);
The trigger looks something like this:
CREATE TRIGGER t1 BEFORE INSERT ON table1
FOR EACH ROW
IF (condition) THEN
NEW.x = NULL;
END IF;
END;
I am guessing it could also be written as(untested):
CREATE TRIGGER t1 BEFORE INSERT ON table1
FOR EACH ROW
IF (condition) THEN
ROLLBACK;
END IF;
END;
But, this doesn't work:
CREATE TRIGGER t1 BEFORE INSERT ON table1 ROLLBACK;
You are guaranteed that:
Your DB will always be MySQL
Table type will always be InnoDB
That NOT NULL column will always stay the way it is
Question: Do you see anything objectionable in the 1st method?
From the trigger documentation:
The trigger cannot use statements that explicitly or implicitly begin or end a transaction such as START TRANSACTION, COMMIT, or ROLLBACK.
Your second option couldn't be created. However:
Failure of a trigger causes the statement to fail, so trigger failure also causes rollback.
So Eric's suggestion to use a query that is guaranteed to result in an error is the next option. However, MySQL doesn't have the ability to raise custom errors -- you'll have false positives to deal with. Encapsulating inside a stored procedure won't be any better, due to the lack of custom error handling...
If we knew more detail about what your condition is, it's possible it could be dealt with via a constraint.
Update
I've confirmed that though MySQL has CHECK constraint syntax, it's not enforced by any engine. If you lock down access to a table, you could handle limitation logic in a stored procedure. The following trigger won't work, because it is referencing the table being inserted to:
CREATE TRIGGER t1 BEFORE INSERT ON table1
FOR EACH ROW
DECLARE num INT;
SET num = (SELECT COUNT(t.col)
FROM your_table t
WHERE t.col = NEW.col);
IF (num > 100) THEN
SET NEW.col = 1/0;
END IF;
END;
..results in MySQL error 1235.
Have you tried raising an error to force a rollback? For example:
CREATE TRIGGER t1 BEFORE INSERT ON table1
FOR EACH ROW
IF (condition) THEN
SELECT 1/0 FROM table1 LIMIT 1
END IF;
END;