MySQL: how to make an attribute "nullable" only if certain condition - mysql

I am creating a table for a DB, and I would like (if possible) to do something like this:
Attribute X can be NULL if, and only if, attribute Y is "value1".
Is there a way to do this? I want to do this because I could delete an entity, reducing the complexity of my project (or at least I think I would get some advantages).
Thanks :)

In very recent versions of MySQL, you can use a check constraint for this:
create table mytable (
x int,
y int,
check(x is not null or y = 1)
)

If MySQL version is not new enough for to use CHECK constraint (below 8.0.16) then use
DELIMITER ##;
CREATE TRIGGER tr_bi_check_my_constraint
BEFORE INSERT
ON my_table
FOR EACH ROW
BEGIN
IF NEW.attribute_X IS NULL AND NEW.attribute_Y != 'value1' THEN
SIGNAL SQLSTATE 45000
SET MESSAGE_TEXT = 'Attribute X can be NULL if, and only if, attribute Y is "value1".';
END IF;
END
##;
CREATE TRIGGER tr_bu_check_my_constraint
BEFORE UPDATE
ON my_table
FOR EACH ROW
BEGIN
IF NEW.attribute_X IS NULL AND NEW.attribute_Y != 'value1' THEN
SIGNAL SQLSTATE 45000
SET MESSAGE_TEXT = 'Attribute X can be NULL if, and only if, attribute Y is "value1".';
END IF;
END
##;
DELIMITER ;

Related

How to use Case-When and Update in Trigger?(Error in my code)

I tried to create below Trigger but I have an Error.
Can I Use Update or use 'case when' in trigger?
Please help me to fix my issue here.
Code Explanation:
I want to update my existing row after insert or update.
Do not change the 'FUSDate1' or 'FUSDate2' If I didn't add anything to the 'FUSDate1' or 'FUSDate2'
Update the FUSDate to the New One or Inserted one If I Updated or inserted data to FUSDate
Code:
delimiter //
create trigger SafetyCertificationTRG
after insert on SafetyCertification
for each row
begin
case when (FUSDate1='' or FUZDate1 is NULL) then (FUZDate1=OLD.FUSDate1) else (update SafetyCertification set FUZDate1=NEW.FUSDate1) end;
case when (FUSDate2='' or FUZDate2 is NULL) then (FUZDate2=NEW.FUSDate2) else (update SafetyCertification set FUZDate2=NEW.FUSDate2) end;
end //
delimiter ;
Edit: I am going to add some information here to make the question more clear.
I have one column as FUS that can be get these 3 values only:('FUS1', 'FUS2' and 'FUS3')
I have 3 other columns: FUSDate1, FUSDate2, FUSDate3.
I want to save the Current Date to The FUSDate1 Or FUSDate2 Or FUSDate3 based on the user User Selection of FUS. (They are in same Table)
I used the provided answer and change it to this but I cannot do the above.
Code: This code is just for FUS1 and FUSDate1
delimiter //
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- detect a change made to a value in col
IF OLD.FUS <=> NEW.FUS THEN
-- value of col is not changed, so do nothing
DO 0;
ELSE
-- we detected a new value was assigned to col
IF OLD.FUS ='%FUS1%' THEN
-- we can override the new value, keep it the same
SET NEW.FUSDate1 = CURDATE();
END IF;
END IF;
END //
delimiter ;
Another code that I expected to do my work but still have problem, Does not update Like above Code:
delimiter //
CREATE TRIGGER SafetyCertification_bu
BEFORE INSERT ON SafetyCertification
FOR EACH ROW
BEGIN
IF NEW.FUS='%FUS1%' THEN
SET new.FUSDate1=MD5(CURDATE());
END IF;
END //
delimiter ;
Update 3:
The Third code that provided in answer does not add anything to the FUSdate1 and 2 and 3 when i update or insert any data.
Code
DELIMITER $$
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- set one of the `fusdateN` columns to current date
-- which column to set depends on the value assigned to `fus`
IF NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
ELSEIF NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
ELSEIF NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END IF;
END$$
DELIMITER ;
A couple of notes:
A trigger cannot issue an UPDATE against the table that fired the trigger, this is prohibited.
The unqualified references (e.g. FUSDate1 and FUZDate1) are not valid. Those references don't resolve to anything.
The reference to OLD.FUSDate1 is invalid in the context of an AFTER INSERT trigger. In an UPDATE trigger, the OLD.col refers to the value of col before the update.
The expression (FUZDate1=OLD.FUSDate1) is not an assignment, it's a comparison that's going to return 0, 1 or NULL.
If we want to trigger on an UPDATE and an INSERT, that will require two separate triggers.
If we want to modify the contents of the current row, we can use BEFORE trigger instead of AFTER.
I want to update my existing row after insert or update.
It would be much easier to apply changes before the row is inserted or updated. We can assign a value to a column col by referencing NEW.col in an assignment in a BEFORE INSERT or BEFORE UPDATE trigger. For example:
SET NEW.col = expr;
Do not change the FUSDate1 or FUSDate2 If I didn't add anything to the FUSDate1 or FUSDate2
This seems pretty straightforward. Just don't make any assignments to NEW.FUSDate1 or NEW.FUSDate2.
Update the FUSDate to the New One or Inserted one If I Updated or inserted data to FUSDate
The example proposed trigger contains references to FUZDate1 or FUZDate2, but there's no mention of these columns in the specification. The specification is confusing.
An UPDATE statement will assign a value to a column, no need for a trigger to do that. An INSERT statement can assign a value to a column, again, no need for a trigger for that.
The specification is not clear. Providing example starting state (rows in the table), and example INSERT or UPDATE statements, and the desired state after the statement is executed would go a long ways towards clarifying the requirements.
A demonstration of a BEFORE UPDATE trigger that prevents a new value being assigned to a particular column:
DELIMITER $$
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- detect a change made to a value in col
IF OLD.col <=> NEW.col THEN
-- value of col is not changed, so do nothing
DO 0;
ELSE
-- we detected a new value was assigned to col
IF OLD.col IS NOT NULL THEN
-- we can override the new value, keep it the same
SET NEW.col = OLD.col;
END IF;
END IF;
END$$
DELIMITER ;
For what this example trigger achieves, we don't necessarily need that many IF conditions; those are included as a demonstrate some of the checks we can perform, how we can make references to the existing value of col and the new value assigned to col.
FOLLOWUP
Based on the update information in the question, here's an example of a BEFORE UPDATE trigger that satisfies the specification.
This is for the UPDATE action. To get this same behavior with an INSERT statement, this trigger definition needs to be repeated, with BEFORE INSERT in place of BEFORE UPDATE.
(I would use the name _bu for the before update trigger, and _bi for before insert trigger.)
DELIMITER $$
CREATE TRIGGER SafetyCertification_bu
BEFORE UPDATE ON SafetyCertification
FOR EACH ROW
BEGIN
-- set one of the `fusdateN` columns to current date
-- which column to set depends on the value assigned to `fus`
IF NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
ELSEIF NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
ELSEIF NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END IF;
END$$
DELIMITER ;
second followup
To answer the question about using CASE WHEN statement in the context of a MySQL stored program...
We could implement the example trigger (directly above in this answer) replacing the IF-THEM with either of the two forms of the CASE statement.
either
CASE
WHEN NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
WHEN NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
WHEN NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END CASE;
-or-
CASE NEW.fus
WHEN 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
WHEN 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
WHEN 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END CASE;
note
The CASE statement is available in MySQL stored programs; outside of a stored program, it is not a valid SQL statement.
Also, we should not confuse this CASE statement with the CASE expression. The CASE expression is valid in the context of a SQL statement, such as a SELECT or UPDATE statement.
DEMONSTRATION
"I examined the Trigger, But unfortunately does not save anything in fusDates."
#Christiano: here is a simple demonstration
create table
CREATE TABLE `safety_certification`
( id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY
, fus VARCHAR(5)
, fusdate1 DATE
, fusdate2 DATE
, fusdate3 DATE
) ENGINE=INNODB
;
populate table with demonstration rows
INSERT INTO `safety_certification` (id, fus, fusdate1, fusdate2, fusdate3) VALUES
( 1, '', NULL, NULL, NULL)
,( 2, '', NULL, NULL, NULL)
,( 3, '', NULL, NULL, NULL)
,( 4, '', NULL, NULL, NULL)
,( 5, '', NULL, NULL, NULL)
;
create trigger
DELIMITER $$
CREATE TRIGGER `safety_certification_bu`
BEFORE UPDATE ON `safety_certification`
FOR EACH ROW
BEGIN
-- set one of the `fusdateN` columns to current date
-- which column to set depends on the value assigned to `fus`
IF NEW.fus = 'FUS1' THEN
SET NEW.fusdate1 = DATE(NOW());
ELSEIF NEW.fus = 'FUS2' THEN
SET NEW.fusdate2 = DATE(NOW());
ELSEIF NEW.fus = 'FUS3' THEN
SET NEW.fusdate3 = DATE(NOW());
END IF;
END$$
DELIMITER ;
updates will exercise BEFORE UPDATE trigger we just defined
UPDATE `safety_certification` sc SET sc.fus = 'FUS1' WHERE sc.id = 1 ;
UPDATE `safety_certification` sc SET sc.fus = 'FUS2' WHERE sc.id = 2 ;
UPDATE `safety_certification` sc SET sc.fus = 'FUS3' WHERE sc.id = 3 ;
UPDATE `safety_certification` sc SET sc.fus = 'FUS4' WHERE sc.id = 4 ;
display contents of table
SELECT * FROM `safety_certification`;
returns:
id fus fusdate1 fusdate2 fusdate3
------ ------ ---------- ---------- ------------
1 FUS1 2018-05-04 (NULL) (NULL)
2 FUS2 (NULL) 2018-05-04 (NULL)
3 FUS3 (NULL) (NULL) 2018-05-04
4 FUS4 (NULL) (NULL) (NULL)
5 (NULL) (NULL) (NULL)
seems like the trigger is populating the columns fusdate1, fusdate2 and fusdate3 per the specification, when some particular values are assigned to fus
I had a same problem and I used two trigger,
First trigger send data to the other table, so now you can create another trigger to send this data to the first table. I think it is low performance but it is the only way that I reached to it. I hope others have another solution for u.

MySQL Trigger not entirely working

my trigger is coming back with no errors. But when i test it, no error message is returned and the test data is stored in the table.. Which isn't what I want.
Basically (this is for coursework), I want a trigger that displays an error message when a location other than Barcelona is inserted into the table.
This is my trigger. As I said, no errors come back, but it doesn't work?
DELIMITER $$
CREATE TRIGGER after_location_insert_finance
AFTER INSERT ON Finance
FOR EACH ROW
BEGIN
DECLARE LocationTrigger varchar(255);
DECLARE msg varchar(255);
SELECT Location INTO LocationTrigger
FROM Finance
WHERE Location != "Barcelona";
IF(LocationTrigger != "Barcelona") THEN
SIGNAL SQLSTATE '45000'
SET MESSAGE_TEXT = 'Not a valid location';
END IF;
END$$
The expression in the IF won't evaluate to TRUE if LocationTrigger is null.
And LocationTrigger will be NULL if there are not rows in Boss that satisfy the condition in the WHERE clause.
I'm not understanding why there's no check of the value being inserted into the location column; or why are we running a query of the Boss table, when we're comparing to literal value of 'Barcelona'.
Seems like we'd want this kind of check in a BEFORE trigger rather than an AFTER trigger.
Are we going to allow NULL values for location?
If the only value we allow to be inserted for location is 'Barcelona', and if we aren't allowing NULL values, then we don't need to mess with any query of the Boss table. Just do a comparison...
BEGIN
IF NOT ( NEW.location <=> 'Barcelona' ) THEN
SIGNAL ...
END IF;
END
Maybe the goal of the trigger is to enforce a referential integrity constraint (which we'd normally implement by declaring a FOREIGN KEY constraint, rather than implementing a procedure.)
Assuming that a "valid" location is defined by the domain of location values in Boss table,
and assuming we aren't going to allow NULL values, then:
BEGIN
DECLARE ls_location VARCHAR(255) DEFAULT NULL;
-- check if the value being inserted exists in a lookup table
SELECT b.location INTO ls_location
FROM Boss b
WHERE b.location = NEW.location
LIMIT 1
;
-- if we didn't find a match, ls_location will be NULL
IF ls_location IS NULL THEN
SIGNAL ...
END IF;
END
If no matching row is found in Boss, then ls_location will be NULL.
A "not equals" comparison to NULL is going to evaluate to NULL, not TRUE or FALSE.
Boolean logic is SQL is tri-valued... TRUE, FALSE and NULL
SELECT 0 = 1 AS c0
, 0 <> 1 AS c1
, 0 = NULL AS c2
, 0 <> NULL AS c3
, NULL = NULL AS c4
, NULL <> NULL AS c5

Set a limit occurrence for a value in a mysql table

So let's say i have a table that has a column "id_author" , i'd like to set a limit for the occurrence of the same value ; example : can't have more than 3 same "id_author" values so when i insert the 4th one it's refused.
Is there a way to implement this? Thanks
You can use a trigger before insert, that will throw a signal in case it violates your condition:
CREATE TRIGGER tooManyRecords
BEFORE INSERT ON yourTable
FOR EACH ROW
BEGIN
DECLARE counter INTEGER;
SELECT COUNT(*) INTO counter FROM yourTable
WHERE id_author = NEW.id_author;
IF counter >= 3 THEN
SIGNAL SQLSTATE '45000' SET message_text = 'there are already 3 records for the provided id';
END

Not null if other entry is not null

I am writing a table and I need A to not be null if B is not null (see below). I am trying to write a CHECK however I am not sure how to write it. Any advise is much appreciated.
CREATE TABLE `Test`
`A` varchar(4) CHECK(?),
`B` varchar(4)
);
Don't bother with a CHECK constraint. According to the manual:
The CHECK clause is parsed but ignored by all storage engines.
You can instead use a trigger, as suggested in this thread. The test itself would be
(B is null) or (A is not null)
EDIT: Assuming you are using MySQL version 5.5 or later, here's how you can trigger an error signal when the condition is violated:
mysql> DELIMITER //
mysql> DROP TRIGGER IF EXISTS before_insert_trigger//
mysql> CREATE TRIGGER before_insert_trigger
-> BEFORE INSERT ON Customer FOR EACH ROW
-> BEGIN
-> DECLARE msg VARCHAR(255);
-> IF (NEW.B is not null) and (NEW.A is null) THEN
-> SET msg = concat('Error: Trying to insert a NULL A when B is not null: ',
-> cast(NEW.B as char));
-> SIGNAL sqlstate '45000' SET message_text = msg;
-> END IF;
-> END//
mysql> delimiter ;
(Note that I inverted the test because I want to test when it is violated, not when it is satisfied.) If you are using an earlier version of MySQL, you can use the suggestion provided in this answer or here.
MySQL does not enforce check constraints. So, the only way you can do this in MySQL (using one table) is to use triggers.
I think the check constraint syntax you want is:
check ((a is null and b is null) or (a is not null and b is not null))
although it might be:
check ((b is null) or (a is not null))
depending on whether you really mean "if" or "if and only if".

How to make MySQL produce an error if value not specified on NOT NULL Columns?

Let's assume that I have a NOT NULL column in a table,
How can I make MySQL to produce an error if such statement is used?
INSERT INTO tableName () VALUES ();
Thank you.
To set a column to not null use this syntax :
ALTER TABLE table_name
MODIFY column_name [data type] NOT NULL;
If you column is declared not null an error will be produced !!
If you want a customized error msg then you need to create trigger action !
Here is a trigger that can help you :
DELIMITER $$
CREATE TRIGGER trgBEFORE UPDATE ON `tbl`
FOR EACH ROW BEGIN
declare msg varchar(255);
IF (NEW.col1IS NULL ) THEN
set msg = concat('MyTriggerError: Trying to insert a null value );
signal sqlstate '45000' set message_text = msg;
ELSE
SET NEW.col1= NEW.col1);
END IF;
END$$
DELIMITER ;
I know this is late but it might help someone.
You can set the SQL mode, either in the configuration file or at runtime for current session.
To produce the error you need to enable strict sql mode with:
SET GLOBAL sql_mode = 'STRICT_ALL_TABLES'; or SET GLOBAL sql_mode = 'STRICT_TRANS_TABLES';
Here is a link for more information on sql modes.
How to make MySQL produce an error, when inserting a row to a table containing NOT NULL column, when not specifying a value to that column like in "INSERT INTO tableName () VALUES()".
Is there a way without a trigger?
This is possible without a trigger only when you define no default when defining a column. Also the same is applicable for alter ... column ...
Example 1:
create table ck_nn( i int not null );
insert into ck_nn values();
The above insert throws an error as no default is defined on the column.
Message can be something like Field 'i' doesn't have a default value.
Example 2:
create table ck_nn2( i2 int not null default 999 );
insert into ck_nn2 values();
The above insert won't throw any error as default value is defined on the column.
select * from ck_nn2;
+-----+
| i2 |
+-----+
| 999 |
+-----+
Example # SQL Fiddle