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".
Related
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 ;
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
Simple version of the DDL:
create function rm00dv1.no_concurrent_schedules()
returns integer
LANGUAGE SQL
READS SQL DATA
NO EXTERNAL ACTION
NOT DETERMINISTIC
BEGIN
declare num_overlaps integer;
select count(*)
into num_overlaps
from
rm00dv1.schedules a
where
a.id != 0
and
exists (
select 1
from rm00dv1.schedules b
where
b.id = 0 -- matches the key of a given record
and rm00dv1.isConcurrent(b.schdl_eff_dt, b.schdl_trm_dt, a.schdl_eff_dt, a.schdl_trm_dt) != 0
);
return num_overlaps;
end;
Table:
create table rm00dv1.schedules (
id int not null,
schdl_eff_dt date not null,
schdl_trm_dt date not null,
info_chg_ts timestamp(6) not null with default
)
in RM00DV1.TSRMDV01 ;
alter table rm00dv1.schedules add constraint no_schedule_overlap
check ((schdl_trm_dt < '01/01/2015')
or
rm00dv1.no_concurrent_schedules() <= 0);
I am getting an SQL00551N - no execution privilege and that is odd because I can execute the function in a select statement.
Any idea to solve this problem?
Thanks.
Looks like you can't. I'm looking at the DB2 10 for z/OS reference for ALTER TABLE reference and it says the following under CHECK (check-condition): "A check-condition is a search condition, with the following restrictions: ... must not contain... Built-in or user-defined functions...".
Since your function looks like it won't convert to a check condition, defining triggers on the table might be the next best option.
I learned that AFTER triggers do not get a -746 like BEFORE triggers do. I had really wanted to use a CONSTRAINT because that best captures the intent for people who come after me, with a BEFORE trigger to terminate the active schedules. But, it looks like a sequence of triggers is going to be the way to go. It is a bit clunky because the triggers all have to be created separately and you have to look at them together to get the intent, and because correct behavior is dependent on their creation order. Yes, it is documented that they will be executed in the order of their creation.
Happy path termination of rows without a specified termination date:
CREATE TRIGGER terminate_no_trm
after
INSERT ON schedules
referencing new as new
FOR EACH ROW
MODE DB2SQL
BEGIN ATOMIC
update schedules
set
schdl_trm_dt = max(schdl_eff_dt, new.schdl_eff_dt - 1 days) -- prob not necessary, but don't set the trm before the eff
, info_chg_ts = new.info_chg_ts
where
new.keyCombo = keyCombo
and
schdl_trm_dt = '9999-12-31'
and schdl_eff_dt < new.schdl_eff_dt;
end
Prevent insert of rows if that insert causes an overlap:
CREATE TRIGGER no_overlapping_schedules_i
after
insert ON schedules
referencing new as n
FOR EACH ROW
MODE DB2SQL
when (num_concurrent_schedules(n.keyCombo) > 0)
begin atomic
SIGNAL SQLSTATE '75001' (
'Concurrent schedules detected: '
concat ' ' concat cast(n.keyCombo as varchar(32))
concat ': ' concat cast(n.schdl_eff_dt as varchar(32))
concat ' to ' concat cast(n.schdl_trm_dt as varchar(32))
);
end
and prevent UPDATE if that would result in an overlap
CREATE TRIGGER no_overlapping_schedules_u
after
update ON schedules
referencing new as n
FOR EACH ROW
MODE DB2SQL
when (num_concurrent_schedules(n.keyCombo) > 0)
begin atomic
SIGNAL SQLSTATE '75001' (
'Concurrent schedules detected: '
concat ' ' concat cast(n.keyCombo as varchar(32))
concat ': ' concat cast(n.schdl_eff_dt as varchar(32))
concat ' to ' concat cast(n.schdl_trm_dt as varchar(32))
);
end
Thanks for the ideas.
MySql dont supported CHECK Clause so i think i must be to use TRIGGER on a Table
in a simple Table we have two field that opinion field must be in ('normal','bad','good') :
CREATE TABLE `user`.`opinionTable` (
`uid` INT NOT NULL,
`opinion` VARCHAR(10) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
PRIMARY KEY (`uid`,`opinion`),
CHECK (opinion IN ('normal','bad','good'))
) ENGINE = InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci;
i want to check opinion data is a allowed value ('normal','bad','good') or no and when is not allowed i want to Transaction to Rollbacked
i tried this TRIGGER but not worked so what is the correct TRIGGER statement ?
CREATE TRIGGER check_values BEFORE INSERT ON `opinionTable`
FOR EACH ROW
BEGIN
IF (NEW.opinion IN ('normal','bad','good'))
THEN
END IF;
END
i catch this exception :
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: 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 'END IF;
mysql version is 5.1.34 community
jdbc version is 5.1.23
There are two ways to implement check constraint, if the inputs are not matched.
Force to use default values.
SIGNAL an error and abort the transaction.
Example 1: Force to use default values
You can define to use default value to silently use in case an invalid input is encountered.
IF ( LOWER( NEW.opinion ) NOT IN ('normal','bad','good')) THEN
SET default_opinion := 'normal'; -- declare this field first
NEW.opinion := default_opinion; -- change its value as desired
ELSE
NEW.opinion := LOWER( NEW.opinion ); -- change this as desired
END IF;
Example 2: SIGNAL an error and abort the transaction
Define an error state number for the case and relevant error messages.
Use the same to SIGNAL the error.
IF ( LOWER( NEW.opinion ) NOT IN ('normal','bad','good')) THEN
-- don't forget to declare variables first and then use
SET error_message := CONCAT( 'Invalid opinion option: ', NEW.opinion );
-- set proper error state number -- 302478 is just an example
SIGNAL SQLSTATE '302478' SET MESSAGE_TEXT := error_message;
END IF;
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