MySQL signal duplicate key error - mysql

I have a table, which includes an ENUM field. I would like for a particular value of ENUM to require uniqueness with the reference ID column. So let's say I have this:
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
`type` ENUM('single','multi') NOT NULL,
`refid` INT UNSIGNED NOT NULL,
`extra` TEXT,
PRIMARY KEY (`id`)
I want (type,refid) to be a unique key, but only if type is single. This can't be done with conventional keys, so I figured I would use a Trigger to detect insertion of a row, check if type='single', look for a row with type='single' and refid=new.refid, and throw a duplicate entry error if one is found.
I'm using MySQL 5.5, so SIGNAL SQLSTATE is available to me. Can I use this to fire a Duplicate Key error in order to process the ON DUPLICATE KEY UPDATE part of the query, and if so, how?
As an alternative, I could update the row in the Trigger and return a generic error condition, but I think it would be nicer (or at least more intuitive) to have ON DUPLICATE KEY UPDATE work.

REPLACE INTO. Derp! Forgot all about that.

Try below trigger. I am not that good in writing triggers. I have not executed the code below. You can use this as a prototype.
CREATE TRIGGER checkDuplicateEntry
BEFORE INSERT OR UPDATE OF type, ref_id ON employees
DECLARE #duplicateCount INT;
FOR EACH ROW
IF ( new.type <> 'single' )
BEGIN
#duplicateCount = count(*) from yourTableName where type = 'single' AND refid = new.refid
END;
IF ( duplicateCount > 0 )
BEGIN
SIGNAL SQLSTATE VALUE '99999'
SET MESSAGE_TEXT = 'Duplicate Entry';
END

Related

MySQL Insert/Update Trigger with AUTO_INCREMENT

So, I've got a table roughly as follows:
CREATE TABLE CUSTOMER (
CUSTID INT NOT NULL AUTO_INCREMENT,
NAME CHAR (45),
CONSTRAINT CUSTOMER_PRIMARY_KEY PRIMARY KEY (CUSTID))
AUTO_INCREMENT = 100;
I'm auto incrementing the CUSTID so that it's possible to simply insert a name and have it created with the next available CUSTID. However, I also want to ensure that it isn't possible to set the CUSTID value to zero, either on creation of the row or on update so I've constructed the following trigger:
DELIMITER $$
CREATE TRIGGER `custid_before_insert` BEFORE INSERT ON `CUSTOMER`
FOR EACH ROW
BEGIN
IF (NEW.CUSTID) <= 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Check constraint on CUSTOMER.CUSTID failed';
END IF;
END$$
CREATE TRIGGER `custid_before_update` BEFORE UPDATE ON `CUSTOMER`
FOR EACH ROW
BEGIN
IF (NEW.CUSTID) <= 0 THEN
SIGNAL SQLSTATE '12345'
SET MESSAGE_TEXT = 'Check constraint on CUSTOMER.CUSTID failed';
END IF;
END$$
DELIMITER ;
Unfortunately in my blissful ignorance of how AUTO_INCREMENT worked, I've come to the conclusion that this is the wrong way to go about this. Trying to insert a customer with no CUSTID value is tripping the trigger causing the insert to fail which I presume is due to the value being a zero before insertion when AUTO_INCREMENT assigns it a value.
Would the best way to do this really be to change the trigger to occur after the insert and delete the row or is there a better way to do this to just throw an error?
The insert trigger is not needed.
From Auto_Increment
No value was specified for the AUTO_INCREMENT column, so MySQL
assigned sequence numbers automatically. You can also explicitly
assign 0 to the column to generate sequence numbers.
E.G.
create table t(id int auto_increment, primary key(id));
insert into t(id) values (0);
select id from t;
# 1
Update:
To allow the insert to complete when CUSTID is not specified,
INSERT INTO customer(name) VALUES('Chuck');
check for null in the trigger:
IF NEW.CUSTID IS NOT NULL AND NEW.CUSTID <= 0 THEN
Inserting '0' into an auto-increment column causes it to increment the same as inserting NULL, so you really neither need nor want the INSERT trigger. Try it with just the UPDATE trigger.

MYSQL: Partitioning Table keeping id unique

We are using a table which has schema like following:-
CREATE TABLE `user_subscription` (
`ID` varchar(40) NOT NULL,
`COL1` varchar(40) NOT NULL,
`COL2` varchar(30) NOT NULL,
`COL3` datetime NOT NULL,
`COL4` datetime NOT NULL,
`ARCHIVE` tinyint(1) NOT NULL DEFAULT '0',
PRIMARY KEY (`ID`)
)
Now we wanted to do partition on column ARCHIVE. ARCHIVE can have only 2 values 0 or 1 and so 2 partitions.
Actually in our case, we are using partitioning as a Archival process. To do partition, we need to make ARCHIVE column as a part of primary key. But the problem here is that 2 rows can have same ID with different ARCHIVE column value. Actually thats not the main problem for us as 2 rows will be in different partitions. Problem is when we will update the archive column value of one of them to other to move one of the row to archive partition, then it will not allow us to update the entry giving "Duplicate Error".
Can somebody help in this regard?
Unfortunately,
A UNIQUE INDEX (or a PRIMARY KEY) must include all columns in the table's partitioning function
and since MySQL does not support check constraints either, the only ugly workaround I can think of is enforcing the uniqueness manually though triggers:
CREATE TABLE t (
id INT NOT NULL,
archived TINYINT(1) NOT NULL DEFAULT 0,
PRIMARY KEY (id, archived), -- required by MySQL limitation on partitioning
)
PARTITION BY LIST(archived) (
PARTITION pActive VALUES IN (0),
PARTITION pArchived VALUES IN (1)
);
CREATE TRIGGER tInsert
BEFORE INSERT ON t FOR EACH ROW
CALL checkUnique(NEW.id);
CREATE TRIGGER tUpdate
BEFORE UPDATE ON t FOR EACH ROW
CALL checkUnique(NEW.id);
DELIMITER //
CREATE PROCEDURE checkUnique(pId INT)
BEGIN
DECLARE flag INT;
DECLARE message VARCHAR(50);
SELECT id INTO flag FROM t WHERE id = pId;
IF flag IS NOT NULL THEN
-- the below tries to mimic the error raised
-- by a regular UNIQUE constraint violation
SET message = CONCAT("Duplicate entry '", pId, "'");
SIGNAL SQLSTATE "23000" SET
MYSQL_ERRNO = 1062,
MESSAGE_TEXT = message,
COLUMN_NAME = "id";
END IF;
END //
(fiddle)
MySQL's limitations on partitioning being such a downer (in particular its lack of support for foreign keys), I would advise against using it altogether until the table grows so large that it becomes an actual concern.

Mysql Column constraint as "not empty" / "required"

Can we specify a column in mysql as "not empty" / "required". The requirement is to ensure that the field never remains blank on any record insertion.
I assume you don't want blank (empty string, as opposed to NULL) values to be allowed in the table either.
Normally, that's what a CHECK constraint for. You do something like
CREATE TABLE
mytable
(
myfield NOT NULL VARCHAR(200),
CHECK(myfield > '')
)
However, MySQL parses the constraint but does not enforce it. You are still allowed to insert empty values.
To work around that, create a BEFORE INSERT trigger and raise a signal on an attempt to insert a blank value:
CREATE TRIGGER
tr_mytable_bi
BEFORE INSERT
ON mytable
FOR EACH ROW
BEGIN
IF NEW.myfield = '' THEN
SIGNAL SQLSTATE '45001' SET MESSAGE_TEXT = 'Blank value on mytable.myfield';
END IF;
END;
Do the same on BEFORE UPDATE if you want to forbid updates to a blank value as well.
MySQL8 now has value constraints, which allows you to implement value restrictions without using triggers
For example:
CREATE TABLE my_table (
myfield VARCHAR(255) NOT NULL
CONSTRAINT myfield_not_empty CHECK(
LENGTH(myfield) > 0
)
);
Will ensure the values in myfield will never be empty
Use NOT NULL.
It causes input to be required.
But a field to be not empty you need validation script explicitly.
Example:
create table example(
login_name varchar(16) not null
pass_word varchar(32) not null
);
Refer to: Wiki on Null (SQL)
You can define the column as NOT NULL as answered by #Ravinder
In addition to it you can set a default value to a column. If we take the example of previous answer, we can do it as below:
create table example(
login_name varchar(16) = null
pass_word varchar(32) = null
);
Here if no value is received for these columns then NULL will be inserted by default.

Table entry restriction

Let's say I have two tables:
info(id PRIMARY KEY, opendate NOT NULL, closedate)
fileinfo(fileno PRIMARY KEY, id FOREIGN KEY REFERENCES info.id)
I would like to allow new entries into fileinfo only if the corresponding value of info.closedate is null. Is there a way to do that?
You can attach a trigger to the fileinfo table, to run on INSERT, which looks up the closedate on info, and throws an error if it's not null.
EDIT: Added example of a SQLServer trigger which would do the same thing. Edit as MySQL syntax requires.
CREATE TRIGGER InsertFileInfo
ON fileinfo
AFTER INSERT
AS
BEGIN
declare #closedate DateTime ;
select #closedate = closedate from info where id in (select id from new);
if #closedate is not null
begin
raiserror ('CloseDate is not null', 16, 1);
end
END

MySQL: Constraining a set of columns so at least one is not NULL

I want to have a SQL table with two columns. One is a key into another table, the other holds a string literal. The idea is phone numbers can be entered either exactly (in which case the ID into the phonebook table is used) or as a wildcard (in which the string literal is used).
This means that one column in the table will hold a value, and the other will hold a NULL.
Is it possible to constrain a table in such a way that one column must have a value, and the other must be NULL? If both columns are NULL or both have a value, then the row is invalid.
I have a feeling that MySQL can't do this (as it doesn't seem to have a comprehensive toolbox when it comes to constraints), but it couldn't hurt to ask.
I am not aware of a way to enforce such a constraint.
As a workaround, you may consider to have two different columns: If you have one column for the data - containing the phonebook id or the string literal, and another column for the data type - either 'exact' or 'wildcard' -, you can set a NOT NULL constraint to both columns. One obvious drawback is that you cannot have a FK constraint to the phonebooks table any more.
Ever since GENERATED columns are a thing, this is possible.
CREATE TABLE `test_multiple_not_null` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`idOne` int(11) DEFAULT NULL,
`idTwo` int(11) DEFAULT NULL,
`not_null_constrain` int(11) GENERATED ALWAYS AS (coalesce(`idOne`,`idTwo`)) VIRTUAL NOT NULL,
PRIMARY KEY (`id`)
);
Since every time a row is inserted, the generated column must run to see it if satisfies NOT NULL constrain it will reply with 1048: Column 'not_null_constrain' cannot be null, if it would violate this restriction.
You can make triggers to run on before the insert, to check the values and determine if the insert or update should happen or not. A good example for how to create triggers like this can be found here: https://dba.stackexchange.com/questions/43284/two-nullable-columns-one-required-to-have-value
The following triggers worked for me:
CREATE TRIGGER tgr_OrgFeeOwnerInsert
BEFORE INSERT
ON OrganisationFee
FOR EACH ROW
BEGIN
IF (SELECT ((new.fieldA IS NULL) + (new.fieldB IS NULL) + (new.fieldC IS NULL)) <> 2)
THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'my error message';
END IF;
END;
CREATE TRIGGER tgr_OrgFeeOwnerUpdate
BEFORE UPDATE
ON OrganisationFee
FOR EACH ROW
BEGIN
IF (SELECT ((new.fieldA IS NULL) + (new.fieldB IS NULL) + (new.fieldC IS NULL)) <> 2)
THEN
SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'my error message';
END IF;
END;