Can you help me with this problem? I have two tables in a MySQL database:
ServiceProvider(SPID, Name, ... etc.)
hasTag(SPID, TagID)
Each service provider is supposed to have at least one tag, and a maximum of five tags. The max-constraint is not a problem, but the min-constraint refuses to work properly. I first tried to implement this via assertions, but then I found out, that MySQL does not support assertions. Thus, I wrote the following trigger:
delimiter |
CREATE TRIGGER MinTags BEFORE INSERT
ON ServiceProvider
FOR EACH ROW BEGIN
IF EXISTS (SELECT SPID FROM ServiceProvider
WHERE NOT EXISTS (SELECT DISTINCT SPID FROM hasTag))
THEN INSERT INTO stop_action VALUES(1, 'Assert Failure');
END IF;
END;
|
delimiter ;
The insert of 'Assert Failure' into the stop_action table is only to create a constraint violation, so that the DB would abort the action.
Now, normally, when I insert any value into the ServiceProvider table, without inserting anything in to the hasTag table, I should get an error, right? But, somehow it doesn't work ... I can insert anything I want into the ServiceProvider table, without receiving any kind of error. Do you know, what is wrong with my code?
How about denormalising a tad:
ALTER TABLE ServiceProvider
ADD COLUMN TagID1 BIGINT UNSIGNED NOT NULL,
ADD COLUMN TagID2 BIGINT UNSIGNED NULL,
ADD COLUMN TagID3 BIGINT UNSIGNED NULL,
ADD COLUMN TagID4 BIGINT UNSIGNED NULL,
ADD COLUMN TagID5 BIGINT UNSIGNED NULL;
Include foreign key constraints, if appropriate.
As written, this trigger does not even use the values from the row to be inserted.
The syntax to get the value of the SPID column is:
NEW.SPID
Also, consider using the SIGNAL statement to raise an error.
If you want to use ASSERT in SQL, this post may help:
SQL Scripts - Does the equivalent of a #define exist?
Related
I recently encountered an error in my application with concurrent transactions. Previously, auto-incrementing for compound key was implemented using the application itself using PHP. However, as I mentioned, the id got duplicated, and all sorts of issues happened which I painstakingly fixed manually afterward.
Now I have read about related issues and found suggestions to use trigger.
So I am planning on implementing a trigger somewhat like this.
DELIMITER $$
CREATE TRIGGER auto_increment_my_table
BEFORE INSERT ON my_table FOR EACH ROW
BEGIN
SET NEW.id = SELECT MAX(id) + 1 FROM my_table WHERE type = NEW.type;
END $$
DELIMITER ;
But my doubt regarding concurrency still remains. Like what if this trigger was executed concurrently and both got the same MAX(id) when querying?
Is this the correct way to handle my issue or is there any better way?
An example - how to solve autoincrementing in compound index.
CREATE TABLE test ( id INT,
type VARCHAR(192),
value INT,
PRIMARY KEY (id, type) );
-- create additional service table which will help
CREATE TABLE test_sevice ( type VARCHAR(192),
id INT AUTO_INCREMENT,
PRIMARY KEY (type, id) ) ENGINE = MyISAM;
-- create trigger which wil generate id value for new row
CREATE TRIGGER tr_bi_test_autoincrement
BEFORE INSERT
ON test
FOR EACH ROW
BEGIN
INSERT INTO test_sevice (type) VALUES (NEW.type);
SET NEW.id = LAST_INSERT_ID();
END
db<>fiddle here
creating a service table just to auto increment a value seems less than ideal for me. – Mohamed Mufeed
This table is extremely tiny - you may delete all records except one per group with largest autoincremented value in this group anytime. – Akina
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=61f0dc36db25dd5f0cf4647d8970cdee
You may schedule excess rows removing (for example, daily) in service event procedure.
I have managed to solve this issue.
The answer was somewhat in the direction of Akina's Answer. But not quite exactly.
The way I solved it did indeed involved an additional table but not like the way He suggested.
I created an additional table to store meta data about transactions.
Eg: I had table_key like this
CREATE TABLE `journals` (
`id` bigint NOT NULL AUTO_INCREMENT,
`type` smallint NOT NULL DEFAULT '0',
`trans_no` bigint NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),
KEY `transaction` (`type`,`trans_no`)
)
So I created a meta_journals table like this
CREATE TABLE `meta_journals` (
`type` smallint NOT NULL,
`next_trans_no` bigint NOT NULL,
PRIMARY KEY (`type`),
)
and seeded it with all the different types of journals and the next sequence number.
And whenever I insert a new transaction to the journals I made sure to increment the next_trans_no of the corresponding type in the meta_transactions table. This increment operation is issued inside the same database TRANSACTION, i.e. inside the BEGIN AND COMMIT
This allowed me to use the exclusive lock acquired by the UPDATE statement on the row of meta_journals table. So when two insert statement is issued for the journal concurrently, One had to wait until the lock acquired by the other transaction is released by COMMITing.
i have a DB for managing an airport. I want to try to calculate the time difference between columns ArrivingDate and DepartingDate (both are DATETIME type) into a third column called Flight_time (TIME type) after any INSERT in the table FLIGHT_SCHEDULES.
I tried to create a trigger for doing that, but with no success. I have already read some stuff on the internet about my error but couldn't find something which solve my dilemma.
This is the table:
CREATE TABLE FLIGHT_SCHEDULES(
id INT(10) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
Flight INT(10) UNSIGNED NOT NULL,
Airplane INT(10) UNSIGNED NOT NULL,
DepartingDate DATETIME NOT NULL,
ArrivingDate DATETIME NOT NULL,
Flight_time TIME DEFAULT '00:00:00',
CONSTRAINT flight_unique UNIQUE (Flight),
CONSTRAINT fk_scheduled_flight_id FOREIGN KEY (Flight) REFERENCES FLIGHTS(id) ON UPDATE CASCADE ON DELETE CASCADE,
CONSTRAINT fk_scheduled_airplane_id FOREIGN KEY (Airplane) REFERENCES AIRPLANES(id) ON UPDATE CASCADE ON DELETE CASCADE
);
This is the trigger I created:
delimiter //
CREATE TRIGGER calculate_flightTime2 BEFORE INSERT ON FLIGHT_SCHEDULES FOR EACH ROW
BEGIN
UPDATE FLIGHT_SCHEDULES
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
END //
I can create the trigger with no problems, but the I try to insert a new row in the table I receive the following error message:
Error Code: 1442. Can't update table 'flight_schedules' in stored function/trigger because it is already used by statement which invoked this stored function/trigger. 0.000 sec
Note that I tried to create an AFTER trigger too, but the result was the same.
It is a restriction of mysql so you don t end up in an endless loop.
Try something like this.
CREATE TRIGGER calculate_flightTime2
BEFORE INSERT
ON FLIGHT_SCHEDULES
FOR EACH ROW
BEGIN
IF NEW.Flight_time = '00:00:00' THEN
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
END IF;
END
Usually it's not a good idea to materialize values that can be calculated from others. This bears the risk of inconsistencies. What happens if you change the arriving time for example?
So it's probably best if you drop the column Flight_time.
ALTER TABLE FLIGHT_SCHEDULES
DROP Flight_time;
For convenience you can then create a view, that includes the calculated value.
CREATE VIEW FLIGHT_SCHEDULES_WITH_FLIGHT_TIME
AS
SELECT *,
timediff(ArrivingDate, DepartingDate) Flight_time
FROM FLIGHT_SCHEDULES;
In MySQL 8+ you could alternatively use a generated column. That's a safe way as the DBMS guarantees consistency like that.
ALTER TABLE FLIGHT_SCHEDULES
ADD Flight_time time AS timediff(ArrivingDate, DepartingDate);
But if you insist on using a trigger, you don't need an UPDATE to set a value in the new pseudo record. Just an assignment is enough.
...
SET NEW.Flight_time = TIMEDIFF(new.ArrivingDate, new.DepartingDate);
...
I have the same question as described here and also I think the answer https://stackoverflow.com/a/22343265/297487 is a good solution but I have another question about this answer.
Is the following trigger (copied from answer) thread-safe? I mean if two concurrent record inserted to table, does "priority" column (as describe in question) have consistent value (The same value as id)?
delimiter //
drop trigger if exists bi_table_name //
create trigger bi_table_name before insert on table_name
for each row begin
set #auto_id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='table_name'
AND TABLE_SCHEMA=DATABASE() );
set new.priority= #auto_id;
end;
//
delimiter ;
How do we interpret "for each row" clause?
Suppose that MySQL wants insert two concurrent rows. How does this trigger works?
One interpretation is as follows:
MySQL locks table and before insert one of the rows (one of concurrent record) MySQL trigger starts and gets current AUTO_INCREMENT value and sets it to "priority" column and then inserts a record. After that MySQL starts inserting another record and then the same situation applies for new record.
Another interpretation might be as follows:
When two concurrent records are inserted to MySQL, MySQL locks the table and then before inserting two concurrent records a trigger starts and "for each row" clause iterate between two record and set "priority" column value to the same value and then insert two concurrent record in database. In this situation the trigger does not work as expected.
Which one of the above interpretation is correct?
Update :
I have the following table :
CREATE TABLE `t_file` (
`id` bigint(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) COLLATE utf8_persian_ci NOT NULL,
`p_name` bigint(20) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `p_name_2` (`p_name`)
) ENGINE=InnoDB AUTO_INCREMENT=206284 DEFAULT CHARSET=utf8 COLLATE=utf8_persian_ci;
I want to insert value of p_name the same as id when row inserted.
The trigger that sets value of p_name is as follows(copied from your code)
delimiter $$
drop trigger if exists file_p_name $$
create trigger file_p_name before insert on t_file
for each row begin
set #id := ( SELECT AUTO_INCREMENT
FROM INFORMATION_SCHEMA.TABLES
WHERE TABLE_NAME='t_file'
AND TABLE_SCHEMA=DATABASE() );
set new.p_name= #id;
end;
$$
delimiter ;
In our application, i surround the code that inserts int_file table with try catch,in almost always everything is OK,but sometimes(in concurrent insert to t_file table), i see the following exception in our application's log:
SQLSTATE[23000]: Integrity constraint violation: 1062 Duplicate entry \'387456\' for key \'p_name_2\''
It seems the trigger does not work as expected or maybe i am wrong!!!
The answer referred in your question was posted by me some time back.
While in "For Each Row", The NEW is the corresponding new row, being inserted, in context. And the set new.xxx is only applicable for that row and but not 'for all rows' in batch. So there won't be any collision and the question of failure should not arise.
I have also answered a similar question
How does “for each row” work in triggers in mysql?. Please go through the examples given in the answer.
Refer to Documentation:
MySQL: Triggers
I have a table:
CREATE TABLE `student` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) NOT NULL,
`score` int(11) NOT NULL,
PRIMARY KEY (`id`)
Now i want to create a stored procedure that will insert new column into this table with the next id (which is automaticly created), name (value which I will write), and score (which i will write).
I have tryed
DELIMITER ;;
CREATE PROCEDURE insertStudent (IN ime varchar(100),IN score int(11))
BEGIN
insert into student (name,score) values (in_score,in_score);
END
;;
But it doesnt seem to work.
I know this is basic but i need help.
Are you inserting a column or a row? It looks like you're trying to insert a row, which is much more conventional.
As for "doesn't seem to work", I imagine you're getting an error from the database. What is that error? I would guess it has something to do with unknown identifiers in the query or syntax errors. Take a look at your procedure declaration:
insertStudent (IN ime varchar(100),IN score int(11))
And at your query:
insert into student (name,score) values (in_score,in_score);
What is in_score in the query? Where do you get that? You have a couple of problems here:
in_score is never defined, instead you have values called ime and score
You're trying to insert the same value into columns of two different types, that's not going to work.
There may be additional problems with your procedure declaration, I'm not familiar enough with SQL to say with certainty. But IN and int(11) look suspicious to me.
Maybe your query should be this?:
insert into student (name,score) values (ime,score);
At the very least, even if it doesn't work that may change the error message you're seeing. Which would be a step in the right direction. At that point continue to examine the error message to help debug what's wrong.
CREATE PROCEDURE insertStudent #ime varchar(100),score int
as
BEGIN
insert into student
values (#a1, #a2);
END
You forgot to put # before the parameter and you don't need to use in
and forgot to put as
I am a user of a some host company which serves my MySql database. Due to their replication problem, the autoincrement values increses by 10, which seems to be a common problem.
My question is how can I simulate (safely) autoincrement feature so that the column have an consecutive ID?
My idea was to implement some sequence mechanism to solve my problem, but I do not know if it is a best option. I had found such a code snipset over the web:
DELIMITER ;;
DROP TABLE IF EXISTS `sequence`;;
CREATE TABLE `sequence` (
`name` CHAR(16) NOT NULL,
`value` BIGINT UNSIGNED NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;;
DROP FUNCTION IF EXISTS `nextval`;
CREATE FUNCTION `nextval`(thename CHAR(16) CHARSET latin1)
RETURNS BIGINT UNSIGNED
MODIFIES SQL DATA
SQL SECURITY DEFINER
BEGIN
INSERT INTO `sequence`
SET `name`=thename,
`value`=(#val:=##auto_increment_offset)+##auto_increment_increment
ON DUPLICATE KEY
UPDATE `value`=(#val:=`value`)+##auto_increment_increment;
RETURN #val;
END ;;
DELIMITER ;
which seems quite all correct. My second question is if this solution is concurrent-safe? Of course INSERT statement is, but what about ON DUPLICATE KEY update?
Thanks!
Why do you need to have it in the first place?
Even with auto_increment_increment == 1 you are not guaranteed, that the autoincrement field in the table will have consecutive values (what if the rows are deleted, hmm?).
With autoincrement you are simply guaranteed by the db engine, that the field will be unique, nothing else, really.
EDIT: I want to reiterate: In my opinion, it is not a good idea to assume things like concurrent values of an autoincrement column, because it is going to bite you later.
EDIT2: Anyway, this can be "solved" by an "on insert" trigger
create trigger "sequence_b_ins" before insert on `sequence`
for each row
begin
NEW.id = select max(id)+1 from `sequence`;
end
Or something along these lines (sorry, not tested)
Another option would be to use a stored proc to do the insert and have it either select max id from your table or keep another table with the current id being used and update as id's are used.