I spent a lot of time on it, but I still can not resolve it.
For some reasons that I want prevent some same inserting to database. So I choose to use the trigger. That is,
CREATE TRIGGER trigger_card_usage_insert_block
BEFORE INSERT ON annual_card_usage_log
FOR EACH ROW
BEGIN
DECLARE lasttime INT; -- hold the last insert time
SELECT
usage_time INTO lasttime
FROM
annual_card_usage_log
WHERE
NEW.card_id = card_id
AND NEW.mch_id = mch_id
AND NEW.store_id = store_id
ORDER BY
usage_time DESC
LIMIT 1;
-- only insert when after 300 second
IF(
lasttime != NULL
AND(lasttime + 300 < new.usagetime)
) THEN
INSERT INTO annual_card_usage_log(
card_id ,
mch_id ,
store_id ,
usage_time
)
VALUES
(
NEW.card_id ,
NEW.mch_id ,
NEW.store_id ,
NEW.usage_time
);
END
IF;
END$
This is the creating code. And below is the table,
CREATE TABLE `annual_card_usage_log` (
`usage_id` int(10) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`card_id` int(10) unsigned NOT NULL COMMENT '',
`mch_id` int(10) unsigned NOT NULL COMMENT '',
`store_id` int(10) DEFAULT NULL,
`usage_time` int(10) NOT NULL COMMENT '',
PRIMARY KEY (`usage_id`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8;
Sadly I can not create the trigger, it seems wrong. Can you tell me why? Which code is wrong.
I use the mysql client called Sequel Pro, and when I run the sql I got a error message "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 '' at line 6 Execution stopped!" I don't now the reason.
Thanks to #anatol I solve it. I changed the way. I set the value to a invalid data. Then create a job to delete the invalid data.
Trigger can not insert into a same table because it will cause endless loop, like #anatol point out.
DELIMITER $$
CREATE TRIGGER triger_card_usage_insert_block BEFORE INSERT ON annual_card_usage_log FOR EACH ROW
BEGIN
DECLARE lasttime INT(10) ;
SET lasttime =(
SELECT
usage_time
FROM
annual_card_usage_log
WHERE
NEW.card_id = card_id
AND NEW.mch_id = mch_id
AND NEW.store_id = store_id
ORDER BY
usage_time DESC
LIMIT 1
) ;
IF (lasttime + 300 > new.usage_time) THEN
SET new.new_card_id = new.card_id;
SET new.card_id = 0 ;
END
IF ;
END$$
DELIMITER ;
Related
Challenge:
Create a method to set "auto_increment" values for tables in a non-sequential way.
The goal is to override the "auto_increment" mechanism and allow the function "LAST_INSERT_ID()" to continue working as expected (returning an INT), so that no changes are needed in software side.
My Solution
The method I found is based on an auxiliary table (unique_id), that stores values available to be assigned. Values are then selected randomly, and removed from the tables as used. When the table gets empty, a new set of ID's is created.
This example is working as expected, but with one problem.
Tables for the demo:
CREATE TABLE `unique_id` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=100;
CREATE TABLE `test_unique_id` (
`id` INT(10) UNSIGNED NOT NULL AUTO_INCREMENT,
`name` VARCHAR(50) NOT NULL DEFAULT '0',
PRIMARY KEY (`id`)
)
COLLATE='latin1_swedish_ci'
ENGINE=InnoDB
AUTO_INCREMENT=1;
Defined a stored procedure and a function:
DELIMITER $$
DROP PROCEDURE IF EXISTS `UNIQUE_ID_REFILL`$$
CREATE PROCEDURE UNIQUE_ID_REFILL()
BEGIN
DECLARE a INT Default 0 ;
simple_loop: LOOP
SET a=a+1;
INSERT INTO unique_id (id) values(null);
IF a=100 THEN
LEAVE simple_loop;
END IF;
END LOOP simple_loop;
END $$
DROP FUNCTION IF EXISTS `UNIQUE_ID_GET`$$
CREATE FUNCTION UNIQUE_ID_GET()
RETURNS INT(11)
MODIFIES SQL DATA
BEGIN
DECLARE new_id INT(11);
DECLARE unique_id_count INT(11);
SET new_id = 0;
SELECT COUNT(*) INTO unique_id_count FROM unique_id;
IF unique_id_count=0 THEN
CALL UNIQUE_ID_REFILL();
END IF;
SELECT id INTO new_id FROM unique_id ORDER BY RAND() LIMIT 1;
DELETE FROM unique_id WHERE id = new_id;
RETURN new_id;
END $$
Created a Trigger on the destination table (test_unique_id):
CREATE TRIGGER test_unique_id__unique_id BEFORE INSERT ON test_unique_id
FOR EACH ROW
SET NEW.id = UNIQUE_ID_GET();
The solution is getting the random ID's as expected:
INSERT INTO test_unique_id(name) VALUES ('A'),('B'),('C');
Creates the rows:
id name
154 'A'
129 'B'
173 'C'
The Problem
The main problem is that LAST_INSERT_ID() stops working... and the software side is broken:
SELECT LAST_INSERT_ID();
0
Any ideas on how to solve this problem? or any other different approach to the challenge?
Thank you very much.
I had a mysql trigger that has been working, I exported it and removed it and am trying to put it back, but I keep running into the following error:
#1064 - 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 '' at line 12
My trigger is:
CREATE TRIGGER `accounts_tracking` AFTER UPDATE ON `accounts`
FOR EACH ROW BEGIN
IF( NEW.`check_level` != OLD.`check_level` ) THEN
INSERT INTO `accounts_tracking` ( `change_type`, `account_id`, `field`, `old_int`, `new_int`, `old_time`, `new_time` )
VALUES
( "1",
OLD.id,
"check_level",
OLD.`check_level`,
NEW.`check_level`,
UNIX_TIMESTAMP(),
UNIX_TIMESTAMP());
END IF;
END
Line #12 is the 2nd UNIX_TIMESTAMP()
My table structure is as follows:
CREATE TABLE `accounts_tracking` (
`id` int(10) unsigned NOT NULL AUTO_INCREMENT,
`change_type` smallint(5) unsigned NOT NULL,
`account_id` int(10) unsigned NOT NULL,
`field` varchar(255) NOT NULL,
`old_int` int(11) NOT NULL,
`new_int` int(11) NOT NULL,
`new_time` int(10) unsigned NOT NULL,
`old_time` int(10) unsigned NOT NULL,
PRIMARY KEY (`id`),
KEY `account_id` (`account_id`)
) ENGINE=MyISAM DEFAULT CHARSET=latin1;
Server type: MySQL
Server version: 5.1.73-log
Thanks.
As barranka suggested in comments section, you need to enclose this trigger in a delimiter, like so:
DELIMITER $$
CREATE TRIGGER `accounts_tracking` AFTER UPDATE ON `accounts`
FOR EACH ROW BEGIN
IF( NEW.`check_level` != OLD.`check_level`) THEN
INSERT INTO `accounts_tracking` ( `change_type`, `account_id`, `field`, `old_int`, `new_int`, `old_time`, `new_time` )
VALUES
( "1",
OLD.id,
"check_level",
OLD.`check_level`,
NEW.`check_level`,
UNIX_TIMESTAMP(),
UNIX_TIMESTAMP());
END IF;
END$$
DELIMITER ;
The reason is that by adding a Begin and End to the statement you are essentially creating a stored routine/procedure with the trigger itself. In order to run multiple statements, like in stored routine/procedure, you need to add delimiters.
In other cases where you do not have the Begin and End within the trigger, you do not need the delimiters. For Example:
CREATE TABLE account (acct_num INT, amount DECIMAL(10,2));
CREATE TRIGGER ins_sum BEFORE INSERT ON account FOR EACH ROW SET #sum = #sum + NEW.amount;
I have a stored procedure, that checks if there is a user with the same e-mail address as the input, if not, then registeres one.
Here is the table:
CREATE TABLE IF NOT EXISTS `overkill`.`accounts` (
`accountID` INT NOT NULL AUTO_INCREMENT ,
`email` VARCHAR(64) NOT NULL ,
`firstName` VARCHAR(32) NOT NULL ,
`lastName` VARCHAR(32) NOT NULL ,
`passSaltedSHA` BINARY(20) NOT NULL ,
`salt` BINARY(20) NOT NULL ,
`gender` ENUM('m','f') NOT NULL ,
`birthDate` DATE NOT NULL ,
`regTime` TIMESTAMP NOT NULL ,
PRIMARY KEY (`accountID`) )
ENGINE = InnoDB;
Here is the stored procedure:
DELIMITER $$
CREATE PROCEDURE `overkill`.`registerUser` (
IN emailIN VARCHAR(64),
IN passwordIN VARCHAR(16),
IN firstNameIN VARCHAR(32),
IN lastNameIn VARCHAR(32),
IN birthIN DATE,
IN genderIN ENUM('f','m'))
BEGIN
DECLARE existingMailAccLOG INT DEFAULT NULL;
DECLARE saltLOC CHAR(40);
DECLARE regSuccessLOC BOOLEAN DEFAULT FALSE;
SELECT COUNT(*) INTO existingMailAccLOG FROM `overkill`.`accounts` WHERE `accounts`.`email` = emailIN;
IF existingMailAccLOG = 0 THEN
SET saltLOC = SHA1(rand());
SET regSuccessLOC = TRUE;
INSERT INTO `overkill`.`accounts` (`email`, `firstName`, `lastName`, `passSaltedSHA`, `salt`, `gender`, `birthDate`) VALUES(emailIN, firstNameIN, lastNameIn, UNHEX(SHA1(CONCAT(passwordIN, saltLOC))), UNHEX(saltLOC), genderIN, birthIN);
END IF;
SELECT regSuccessLOC AS `registered`, saltLOC AS `salt`;
END
If I call:
CALL registerUser("abc#def.com", "pass", "firstn", "lastn", "2012-01-01", "f");
It inserts a line into the accounts table, but forgets to return the proper values that I set inside the IF
SET saltLOC = SHA1(rand());
SET regSuccessLOC = TRUE;
How is it even possible? Why are theese lines skipped and INSERT still gets executed, without mistake?
Try to add "#" in front of your variable names after DECLARE keyword. It can cause some confusion, as it is described here: MySQL: #variable vs. variable. Whats the difference? (Part2) and here: MySQL: #variable vs. variable. Whats the difference?
Within a BPM web application, I have a field for an invoice # on a particular page but I need for it to be auto generated every time a user attaches an invoice and views that page. That number must be unique and preferably auto-incremented. A value for the invoice # field can be displayed by querying from a table from an external MYSQL database. So every time a user lands on that particular page, a SELECT query statement can be fired.
On MYSQL end, how would I set this up? So basically, I would like to setup a query for that invoice # field where it will for run a query for example,
SELECT invoice_num FROM invoice_generator
and every time this query runs, it would return the next incremented number.
You can use mysql trigger concept here....
I have added one example here...
It will be very usefull for u (see this link also :http://www.freemindsystems.com/mysql-triggers-a-practical-example/)
CREATE TABLE `products` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(50) NOT NULL DEFAULT '',
`price` int(20) NOT NULL DEFAULT '0',
`other` varchar(50) DEFAULT NULL,
PRIMARY KEY (`id`),
KEY `products_name_idx` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `freetags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`tag` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
CREATE TABLE `freetagged_objects` (
`tag_id` int(20) NOT NULL DEFAULT '0',
`object_id` int(20) NOT NULL DEFAULT '0',
`tagged_on` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
`module` varchar(50) NOT NULL DEFAULT '',
PRIMARY KEY (`tag_id`, `object_id`),
KEY `freetagged_objects_tag_id_object_id_idx` (`tag_id`, `object_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8
INSERT_PRODUCTS_TAGS
DELIMITER ||
DROP TRIGGER IF EXISTS insert_products_tags;
||
DELIMITER ##
CREATE TRIGGER insert_products_tags AFTER INSERT ON products
FOR EACH ROW
BEGIN
DECLARE current_id integer;
DECLARE tag_id integer;
DECLARE next integer;
DECLARE tag_field varchar(255);
DECLARE next_sep integer;
DECLARE current_tag varchar(255);
DECLARE right_tag varchar(255);
-- We use the field other as comma-separated tag_field
SET tag_field = NEW.other;
-- Check for empty tags
IF (CHAR_LENGTH(tag_field) <> 0) THEN
-- Loop until no more ocurrencies
set next = 1;
WHILE next = 1 DO
-- Find possition of the next ","
SELECT INSTR(tag_field, ',') INTO next_sep;
IF (next_sep > 0) THEN
SELECT SUBSTR(tag_field, 1, next_sep - 1) INTO current_tag;
SELECT SUBSTR(tag_field, next_sep + 1, CHAR_LENGTH(tag_field)) INTO right_tag;
set tag_field = right_tag;
ELSE
set next = 0;
set current_tag = tag_field;
END IF;
-- Drop spaces between comas
SELECT TRIM(current_tag) INTO current_tag;
-- Insert the tag if not already present
IF (NOT EXISTS (SELECT tag FROM freetags WHERE tag = current_tag)) THEN
-- Insert the tag
INSERT INTO freetags (tag) values (current_tag);
SELECT LAST_INSERT_ID() INTO tag_id;
ELSE
-- Or get the id
SELECT id FROM freetags WHERE tag = current_tag INTO tag_id;
END IF;
-- Link the object tagged with the tag
INSERT INTO freetagged_objects
(tag_id, object_id, module)
values
(tag_id, NEW.id, 'products');
END WHILE;
END IF;
END;
##
Now If you execute an insert on products table:
INSERT INTO PRODUCTS
(name, price, other)
values
("product1", 2, "tag1, tag2,tag3 , tag 4");
I'm trying to write a trigger to solve innodb auto_increment problem. I want to make orderID is auto_increment however innodb does not allow me. Here is ORDER table
CREATE TABLE IF NOT EXISTS `ORDER` (
`placeID` INT UNSIGNED NOT NULL,
`orderID` INT UNSIGNED NOT NULL,
`userID` INT UNSIGNED NOT NULL ,
`tableNum` SMALLINT NOT NULL,
`orderStatus` TINYINT NOT NULL,
`orderDate` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`placeID`,`orderID`),
FOREIGN KEY (`userID`) REFERENCES `USER` (`userID`),
FOREIGN KEY (`placeID`) REFERENCES `PLACE` (`placeID`))
ENGINE=InnoDB;
Here is the trigger
delimiter $$
DROP TRIGGER /*!50032 IF EXISTS */ `ORDER_TRIGGER` $$
CREATE TRIGGER `ORDER_TRIGGER` BEFORE INSERT ON `ORDER`
FOR EACH ROW
BEGIN
DECLARE orderID INT UNSIGNED;
SELECT MAX(`orderID`) INTO orderID FROM `ORDER` WHERE `placeID` = NEW.placeID;
IF orderID IS NULL THEN
orderID = 1;
END IF;
SET NEW.orderID = orderID+1;
END;
$$
delimiter;
When I execute this script I get this error.
#1064 - 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 '= 1;
END IF;
SET NEW.orderID = orderID+1;
END' at line 7
Can anybody help me? I looked at google but I can't find accurate solution.
I found my mistake. This is right code.
delimiter $$
DROP TRIGGER /*!50032 IF EXISTS */ `ORDER_TRIGGER` $$
CREATE TRIGGER `ORDER_TRIGGER` BEFORE INSERT ON `ORDER`
FOR EACH ROW
BEGIN
DECLARE orderID INT UNSIGNED;
SELECT MAX(`ORDER`.`orderID`) AS ID INTO orderID FROM `ORDER` WHERE `ORDER`.`placeID` = NEW.placeID;
IF orderID IS NULL THEN
SET orderID = 0;
END IF;
SET NEW.orderID = orderID+1;
END;
$$