For example I have a table which is used for logging. So very old data is useless and there are no reasons to leave it in the table. I want create a trigger which will delete old rows if number of existing rows more than 10 for example. What I already have:
CREATE TABLE log (
logId INT UNSIGNED NOT NULL AUTO_INCREMENT,
firstLogin DATETIME NOT NULL,
lastLogin DATETIME NOT NULL,
fingerprint VARCHAR(64) CHARACTER SET BINARY,
ip VARCHAR(24) NOT NULL,
accountId INT UNSIGNED NOT NULL,
FOREIGN KEY (accountId)
REFERENCES accounts (accountId)
ON UPDATE CASCADE ON DELETE CASCADE,
PRIMARY KEY (logId)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
DELIMITER |
CREATE TRIGGER logbeforeinsert BEFORE INSERT ON log
FOR EACH ROW
BEGIN
SET #rowcount = (SELECT COUNT(*) FROM log WHERE accountId = NEW.accountId);
IF #rowcount > 9 THEN
DELETE FROM log WHERE accountId = NEW.accountId LIMIT 1;
END IF;
END;
|
DELIMITER ;
But with this trigger inserting stopped at all after number of rows had reached 10.
Your trigger tries to write to the same table (DELETE is write access), that it is inserting into - this is not supported.
As you have a BEFORE INSERT trigger, failure of the trigger means failure of the INSERT.
You need either to trigger the delete from an operation, that does not write to log, or rethink your model.
Related
I try to use View to create a constraint on a Mysql Table.
I have one table with cinemas sessions and one other for tickets, the goal is we can't sell tickets when the session is full.
The session has a number of places, for example, 300 places. When one ticket is sold, the number of places decreases. The rest is 299. (this is what I do with a view)
I will check before the data is written if the amount of places is enough.
The condition is place_left >= number (of tickets wanted).
For example, the rest of places is 5 and a user wants 6 places, the request is rejected because the condition is not true.
The condition is place_left >= number (of tickets wanted).
Actually I use two views to do that (not tested, just created), I think I can do this in one shot but I failed.
I have seen this possibility here:
https://dev.mysql.com/doc/refman/8.0/en/view-check-option.html
I first tried something like that:
CREATE VIEW view_place_left AS
SELECT sessions.id,
(SELECT places FROM rooms WHERE id=sessions.room_id)-(SELECT SUM(number) AS count FROM tickets WHERE session_id=sessions.id)
AS place_left FROM sessions WHERE place_left>=0
WITH CHECK OPTION;
, but the part WHERE place_left>=0 is not working.
So I do the job in two steps
The first view:
CREATE VIEW view_place_left
AS SELECT sessions.id,
(SELECT places FROM rooms WHERE id=sessions.room_id)-(SELECT SUM(number) AS count FROM tickets WHERE session_id=sessions.id)
AS place_left FROM sessions;
And the second view:
CREATE VIEW check_view_place_left AS
SELECT id, place_left FROM view_place_left WHERE place_left>=0
WITH CHECK OPTION;
That seems ok, but I think it's possible to do the job in a single view, someone can help?
I work with MySql 8.0
First Add :
create sessions table :
CREATE TABLE IF NOT EXISTS sessions
(
id VARCHAR(36) NOT NULL PRIMARY KEY DEFAULT (UUID()),
movie_id VARCHAR(36),
room_id VARCHAR(36),
date DATETIME,
FOREIGN KEY (movie_id) REFERENCES movies(id) ON DELETE CASCADE,
FOREIGN KEY (room_id) REFERENCES rooms(id) ON DELETE CASCADE
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
Create tickets table :
CREATE TABLE IF NOT EXISTS tickets
(
id VARCHAR(36) NOT NULL PRIMARY KEY DEFAULT (UUID()),
session_id VARCHAR(36) NOT NULL,
user_id VARCHAR(36) NOT NULL,
price_id VARCHAR(36) NOT NULL,
number INT NOT NULL,
date DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
paid INT,
FOREIGN KEY (session_id) REFERENCES sessions(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE ON UPDATE CASCADE,
FOREIGN KEY (price_id) REFERENCES prices(id) ON DELETE CASCADE ON UPDATE CASCADE
) ENGINE = InnoDB CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci;
SOLUTION
So i found my solution.
To Check and validate the place left, i just need a trigger before insert.
Here is my code :
\! echo "\033[33m*** Create trigger on 'tickets' ***\033[m";
DELIMITER $$
CREATE TRIGGER check_places BEFORE INSERT ON tickets
FOR EACH ROW
BEGIN
IF PlaceLeft(NEW.session_id, NEW.number) != 1 THEN SIGNAL sqlstate '45000' set message_text = "No enough places";
END IF;
END$$
DELIMITER ;
The function PlaceLeft
DELIMITER $$
CREATE FUNCTION PlaceLeft(session_id VARCHAR(36), number INT)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE places INT;
SELECT place_left INTO places FROM view_place_left;
IF places>=number THEN RETURN 1;
ELSE RETURN 0;
END IF;
END$$
Maybe that can help another lost soul :)
I have a large table named 'roomlogs' which has nearly 1 million entries.
The structure of the table:
id --> PK
roomId --> varchar FK to rooms table
userId --> varchar FK to users table
enterTime --> Date and Time
exitTime --> Date and Time
status --> bool
I have the previous indexing on roomID, I recently added an index on the userId column.
So, When I run a stored procedure with following code it is taking more time like on average 50 seconds. WHich it should not take.
DELIMITER ;;
CREATE DEFINER=`root`#`%` PROCEDURE `enter_room`(IN pRoomId varchar(200), IN puserId varchar(50), IN ptime datetime, IN phidden int, pcheckid int, pexit datetime)
begin
update roomlogs set
roomlogs.exitTime = ptime,
roomlogs.`status` = 1
where
roomlogs.userId = puserId
and roomlogs.`status` = 0
and DATEDIFF(ptime,roomlogs.enterTime) = 0;
INSERT into roomlogs
( roomlogs.roomId,
roomlogs.userId,
roomlogs.enterTime,
roomlogs.exitTime,
roomlogs.hidden,
roomlogs.checkinId )
value
( pRoomId,
userId,
ptime,
pexit,
phidden,
pcheckid);
select *
from
roomlogs
where
roomlogs.id= LAST_INSERT_ID();
end ;;
DELIMITER ;
What Can be the reason for it to take this much time:
I added an index recently so previous rows are not indexed.
There is no selection on storage type for any indexes right now. Should I change it to B-tree?
On my website, I get 20-30 simultaneous call on other procedures also while this procedure has 10-20 simultaneous calls, does the update query in the procedure make a lock? But in MySQL.slow_logs table for each query the lock _time shows 0.
Is there any other reason for this behaviour?
Edit: Here is the SHOW TABLE:
CREATE TABLE `roomlogs` (
`roomId` varchar(200) CHARACTER SET latin1 DEFAULT NULL,
`userID` varchar(50) CHARACTER SET latin1 DEFAULT NULL,
`enterTime` datetime DEFAULT NULL,
`exitTime` datetime DEFAULT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
`status` int(11) DEFAULT '0',
`hidden` int(11) DEFAULT '0',
`checkinId` int(11) DEFAULT '-1',
PRIMARY KEY (`id`),
KEY `RoomLogIndex` (`roomId`),
KEY `RoomLogIDIndex` (`id`),
KEY `USERID` (`userID`)
) ENGINE=InnoDB AUTO_INCREMENT=1064216 DEFAULT CHARSET=utf8
I can also see that this query is running more number of times like 100000 times per day (nearly continuously).
SELECT count(*) from roomlogs where roomId=proomId and status='0';
Because of this query reads from the same table, does InnoDB block or create a lock on update query because I can see that when the above-stored procedure is running more number of times then this query is taking more time.
Here is the link for MySQL variables: https://docs.google.com/document/d/17_MVaU4yvpQfVDT83yhSjkLHsgYd-z2mg6X7GwvYZGE/edit?usp=sharing
roomlogs needs this 'composite' index:
INDEX(userId, `status`, enterTime)
I added an index recently so previous rows are not indexed.
Not true. Adding an INDEX indexes the entire table.
The default index type is BTree; no need to explicitly specify it.
does the update query in the procedure make a lock?
It does some form of locking. What is the value of autocommit? Do you explicitly use BEGIN and COMMIT? Is the table ENGINE=InnoDB? Please provide SHOW CREATE TABLE.
MySQL.slow_logs table for each query the lock _time shows 0.
The INSERT you show seems to be inserting the same row as the UPDATE. Maybe you need INSERT ... ON DUPLICATE KEY UPDATE ...?
Don't "hide an index column in a function"; instead of DATEDIFF(roomlogs.enterTime,NOW()) = 0, do
AND enterTime >= CURDATE()
AND enterTime < CURDATE() + INTERVAL 1 DAY
This allows the index to be used more fully.
KEY `RoomLogIndex` (`roomId`), Change to (roomId, status)
KEY `RoomLogIDIndex` (`id`), Remove, redundant with the PK
Buffer pool in only 97,517,568 -- make it more like 9G.
So, I have this one column in my table that gets filled by a trigger when a new entry is inserted.
CREATE TABLE `users` (
`idusers` int(11) NOT NULL AUTO_INCREMENT,
`uid` char(64) DEFAULT NULL,
`uname` varchar(80) NOT NULL,
`password` char(128) NOT NULL,
`mail` varchar(120) NOT NULL,
PRIMARY KEY (`idusers`),
UNIQUE KEY `uname_UNIQUE` (`uname`),
UNIQUE KEY `mail_UNIQUE` (`mail`),
UNIQUE KEY `uid_UNIQUE` (`uid`)
);
DELIMITER $$
TRIGGER `flask`.`users_BEFORE_INSERT` BEFORE INSERT ON `users` FOR EACH ROW
BEGIN
set new.uid = sha2(new.idusers, 256);
END$$
DELIMITER ;
The problem now is that when I try to add a new row (only have a test one yet because of the error), in the trigger the value of new.idusers is somehow always 0 instead of the current auto_increment value.
What do I need to change in my trigger code so that the value used for generating the uid is the actual id and not always 0?
Since idusers is an AUTO_INCREMENT field, its value is known only after the record is inserted in the table.
Use an AFTER INSERT trigger instead of a BEFORE INSERT trigger, and update the newly inserted record:
DELIMITER $$
CREATE TRIGGER `flask`.`users_AFTER_INSERT` AFTER INSERT ON `users` FOR EACH ROW
BEGIN
UPDATE users SET uid = sha2(NEW.idusers, 256) WHERE idusers = NEW.idusers;
END $$
DELIMITER ;
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.
I have multiple tables in this database; two of which are involved with this trigger
create table shipment_item(
shipmentID int not null,
shipmentItemID int not null,
purchaseID int not null,
insuredValue decimal(5,2) not null,
constraint shipment_ItemPK primary key(shipmentID, shipmentItemID),
constraint shipmentFK foreign key(shipmentID)
references shipment(shipmentID)
on delete cascade,
constraint purchaseFK foreign key(purchaseID)
references purchase(purchaseID)
);
create table purchase(
purchaseID int not null auto_increment,
storeID int not null,
purchaseDate date not null,
description char(30) not null,
category char(30) not null,
price decimal(5,2) not null,
constraint purchasePK primary key(purchaseID),
constraint storeFK foreign key(storeID)
references store(storeID)
);
I'm trying to implement a trigger in my MySQL database. That trigger looks like this
DELIMITER //
CREATE TRIGGER checkInsuranceTrigger
BEFORE INSERT ON shipment_item
FOR EACH ROW BEGIN
IF(shipment_item.insuredValue <= purchase.price) THEN
SET NEW.insuredValue = purchase.price;
END IF;
END
//
DELIMITER ;
When I implement this trigger and then try to insert data into the shipment_item table I get the following error
Error Code 1109: Unknown Table 'shipment_item' in field list
Reference the column in the row being inserted with the NEW keyword, like you did on the SET statement.
To reference values from rows in other tables, you need a SQL statement, in your case, looks like you want a SELECT.
For example (following the outline of the logic in your trigger), something like this:
BEGIN
-- local variable
DECLARE ln_purchase_price DECIMAL(5,2);
-- populate local variable (this is just an example of one way to do this)
SELECT p.price
INTO ln_purchase_price
FROM purchase p
WHERE p.purchaseID = NEW.purchaseID
LIMIT 1;
-- compare value from row to local variable
IF (NEW.insuredValue <= ln_purchase_price) THEN
SET NEW.insuredValue = ln_purchase_price;
END IF;
May I suggest verifying that the table really exists in the same database as the trigger itself?