'Select' and 'insert into' multiple rows by a My Sql event - mysql

I need to write a MySql Event to select some values from a table under some conditions and put those values in a second table. By the select statement. I get multiple rows, so I need to store data in the second table as a batch. How can I achieve this? I wrote an event to select one row. But what I need to do is select multiple rows and store as a batch.
The event I wrote is as below.
DELIMITER $$
CREATE EVENT salary_add
ON SCHEDULE EVERY 24 HOUR
DO
BEGIN
DECLARE month_end DATETIME;
DECLARE today DATETIME;
DECLARE reg_id VARCHAR(6);
DECLARE sal INT(8);
SET month_end = LAST_DAY(DATE(NOW()));
SET today = DATE(NOW());
IF month_end=today THEN
SELECT register_id,salary INTO reg_id,sal FROM employees
WHERE status ='1' LIMIT 1;
INSERT INTO tbl_salary (register_id,amount,salary_date,status) VALUES (reg_id,sal,today,'0');
END IF;
END $$
DELIMITER ;

You can insert selected rows into the target table at once. For example:
DELIMITER $$
CREATE EVENT salary_add
ON SCHEDULE EVERY 24 HOUR
DO
BEGIN
DECLARE month_end DATETIME;
SET month_end = LAST_DAY(CURDATE());
IF month_end=CURDATE() THEN
INSERT INTO tbl_salary (register_id, amount, salary_date, status)
SELECT register_id,
salary,
CURDATE(),
'0'
FROM employees
WHERE status ='1'
END IF;
END $$
DELIMITER ;

Related

Return data in the single set from stored procedure using CURSOR/LOOP

I want to return data in the single set, however I'm getting 10 different outputs with single row. I want to have 10 rows in a single set:
DROP TABLE IF EXISTS calendar;
DROP PROCEDURE IF EXISTS p_generate_snapshot;
CREATE TABLE calendar(date date);
INSERT INTO calendar(date) VALUES
('2020-11-01'),
('2020-11-02'),
('2020-11-03'),
('2020-11-04'),
('2020-11-05'),
('2020-11-06'),
('2020-11-07'),
('2020-11-08'),
('2020-11-09'),
('2020-11-10');
DELIMITER $$
CREATE PROCEDURE p_generate_snapshot(start_date date, end_date date)
BEGIN
DECLARE d date;
DECLARE done INT DEFAULT FALSE;
DECLARE cursor_name CURSOR FOR SELECT * FROM calendar c WHERE date >= start_date AND date < end_date;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cursor_name;
fetch_loop: LOOP
FETCH cursor_name INTO d;
IF done THEN
LEAVE fetch_loop;
END IF;
SELECT d;
END LOOP;
CLOSE cursor_name;
END$$
DELIMITER ;
CALL p_generate_snapshot('20201101', '20201201');
It's not clear why you are using a cursor for this. Your procedure could be written as:
CREATE PROCEDURE p_generate_snapshot(start_date date, end_date date)
BEGIN
SELECT * FROM calendar c WHERE date >= start_date AND date < end_date;
END
That would return one result set as you said you wanted.
But it's possible that your example is simplified and you have other steps you need to do with the rows fetched by your cursor. That would be a legitimate reason to use a cursor.
But I don't think it's possible to return the rows handled by a cursor as if it's one result set. Certainly it is not possible to do it by SELECT d; in each iteration of your cursor loop. That is bound to return a separate result set per row, as you found out.
One workaround, though it is pretty awkward, is to insert the rows to a temporary table during the cursor loop. Then select from the temp table as a last step.
CREATE PROCEDURE p_generate_snapshot(start_date date, end_date date)
BEGIN
DECLARE _d date;
DECLARE done INT DEFAULT FALSE;
DECLARE cursor_name CURSOR FOR SELECT date FROM calendar c WHERE date >= start_date AND date < end_date;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
CREATE TEMPORARY TABLE t (d DATE);
OPEN cursor_name;
fetch_loop: LOOP
FETCH cursor_name INTO _d;
IF done THEN
LEAVE fetch_loop;
END IF;
INSERT INTO t SET d = _d;
END LOOP;
CLOSE cursor_name;
SELECT d FROM t;
DROP TABLE t;
END
For that matter, I don't know why you would use a procedure at all, instead of just running that SELECT statement in your client application. That would solve both problems -- handling rows one by one, but still treating it as a single query result.
I almost never use stored procedures in MySQL. I find they are really more trouble than they're worth. I posted reasons why here: https://www.quora.com/What-are-the-reasons-not-to-use-or-not-use-stored-procedures/answer/Bill-Karwin

Trigger to validate room capacity

I'm trying to do a room capacity validation for a specific date using a before trigger.
This is the current trigger I'm using:
delimiter $$
create trigger my_insert_trigger before insert on my_table
for each row
begin
if (select count(*) from my_table where room_type = new.room_type) > 3 then
signal sqlstate '45000';
end if;
end;
$$
delimiter ;
I currently have two columns in the same table which are date and room, this is my desired output
However this would not be the case because the trigger will still limit 'Single' three times regardless of date.
do a room capacity validation for a specific date
Just add the date to the control query:
delimiter $$
create trigger my_insert_trigger before insert on my_table
for each row
begin
if (
select count(*)
from my_table t
where t.room_type = new.room_type and t.date = new.date
) >= 3 then
signal sqlstate '45000';
end if;
end;
$$
delimiter ;
Note: because it has condition > 3, your existing code would allow 4 records per room. I changed that to >= 3 so only 3 records are allowed per room and date, which seems to be what you are looking for.

How to use cursors to update some columns according to another column?

I have a table called USER. This table has 2 columns as end_date, access_date. access_date is empty right now but i want to populate it like:
if end_date exists : access_date = end_date + 1 year (anway i can make that operation) but my problem i could not construct the cursor i have not use cursor logic before.
i need something like:
DELIMITER $$
CREATE PROCEDURE user_procedure ()
BEGIN
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE v_user varchar(100) DEFAULT "";
-- declare cursor for user
DEClARE user_cursor CURSOR FOR
SELECT * FROM USER;
-- declare NOT FOUND handler
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
OPEN user_cursor;
get_user: LOOP
FETCH user_cursor INTO v_user;
IF v_finished = 1 THEN
LEAVE get_user;
END IF;
-- operation
-- something like:
set #end_date = select from cursor
update expiry... etcs
END LOOP get_user;
CLOSE user_cursor;
END$$
DELIMITER ;
CALL user_procedure();
but the problem i do not know how to define the cursor because as you see in example:
DECLARE v_user varchar(100) DEFAULT "";
i am pretty sure it is wrong and i try to fetch it into
FETCH user_cursor INTO v_user;
So how can i properly define the cursor and fetch as a whole row and make change ?
Edit: some people did not understand and claimed i have asked same question again, ok i will edit the real code, now as below according to people's comment. This update must be applied to each individual row.
set #key = 'bla bla';
delimiter $$
create procedure select_or_insert()
begin
IF EXISTS (select USER_EXPIRY_DATE from USER) THEN
update USER set ACCESS_EXPIRY_DATE = DATE_ADD(USER_EXPIRY_DATE, INTERVAL 1 YEAR);
ELSE IF EXISTS (select USER_START_DATE from USER) THEN
SET #start_date = (select USER_START_DATE from USER);
SET #start_date_to_be_added = aes_decrypt(#start_date,#key)
update USER set ACCESS_EXPIRY_DATE = DATE_ADD(USER_EXPIRY_DATE, INTERVAL 1 YEAR);
END IF;
end $$
delimiter ;
but in here for example:
ELSE IF EXISTS (select USER_START_DATE from USER)
is returning more than 1 row.
You don't need a cursor for that, you can simply use an update statement:
UPDATE User
SET access_date = DATE_ADD(end_date, INTERVAL 1 YEAR)
WHERE end_date IS NOT NULL

Avoid firing trigger by another trigger in MYSQL

my problem is as follows:
Table A contains list of "Tasks"
Table B contains "TaskProgress" - just an information about time spent on task, user ID, task ID and note
On table A (Tasks), I have trigger, which updates datetime of last change - column DateChanged (intended to capture datetime of edits made by user)
On table B (TaskProgress) I have trigger, which updates total time spent on a task (sum all times for given Task_ID and update column TotalTime in table A)
I wish to update DateChanged in Table A only when user mades the update (which is every time except when trigger on table B updates TotalTime)
So I wonder, whether there is a way how to tell the database not to fire TRIGGER when updating the values in another trigger.
/* set date of last change and date od closing of task */
DELIMITER $$
CREATE TRIGGER before_tasks_update
BEFORE UPDATE ON tasks
FOR EACH ROW BEGIN
SET new.DateChanged = NOW();
IF new.Status_ID = 3 THEN
SET new.DateClosed = NOW();
END IF;
END$$
DELIMITER ;
/* set total time spent on task */
DELIMITER $$
CREATE TRIGGER after_taskprogress_insert
AFTER INSERT ON taskprogress
FOR EACH ROW BEGIN
DECLARE sumtime TIME;
SET #sumtime := (SELECT SEC_TO_TIME( SUM( TIME_TO_SEC( `timeSpent` ) ) )
FROM taskprogress WHERE Task_ID = new.Task_ID);
UPDATE `tasks` SET TimeReal = #sumtime WHERE ID = new.Task_ID;
END $$
DELIMITER ;
I use MySQL 5.5.40
Thanks, zbynek
Add a check to verify that the update implies a new TimeReal value,since only the second trigger updates that column.
DELIMITER $$
CREATE TRIGGER before_tasks_update
BEFORE UPDATE ON tasks
FOR EACH ROW BEGIN
IF new.TimeReal=old.TimeReal THEN SET new.DateChanged = NOW();
END IF;
IF new.TimeReal=old.TimeReal AND new.Status_ID = 3 THEN
SET new.DateClosed = NOW();
END IF;
END$$
DELIMITER ;

Why do I get duplicate record inserted only at the last record?

I get a duplicate record from my procedure which inserts 330+ records.
But ONLY on the very last record. So in other words the last 2 records are not distinct, they are the same. What is it about this procedure that allows the last record to get duplicated.
DELIMITER $$
DROP PROCEDURE IF EXISTS `zzExclude_Products` $$
CREATE DEFINER=`root`#`%` PROCEDURE `zzExclude_Products`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE VAR_ENTITY_ID VARCHAR(50);
DECLARE CUR_NO CURSOR FOR
SELECT DISTINCT NO
FROM stage_product_data.ITEMMAST AS IM
JOIN stage_product_data.zzLive_Products AS LIVE ON IM.NO = LIVE.SKU
WHERE DIVISION = '30' AND STATUS NOT IN ('XX','YY','ZZ');
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN CUR_NO;
REPEAT
FETCH CUR_NO INTO VAR_ENTITY_ID;
INSERT INTO zz_CATALOG (TYPE, ENTITY_ID, ESTRICTION_TYPE, RESTRICTION_VALUE)
VALUES ('Product', VAR_ENTITY_ID, 'Country', 'ALL');
UNTIL done END REPEAT;
CLOSE CUR_NO;
END $$
DELIMITER ;
There's really no need for a cursor here. This can be done in a single INSERT statement.
INSERT INTO zz_CATALOG
(TYPE, ENTITY_ID, ESTRICTION_TYPE, RESTRICTION_VALUE)
SELECT DISTINCT 'Product', EDPNO, 'Country', 'ALL'
FROM stage_product_data.ITEMMAST AS IM
JOIN stage_product_data.zzLive_Products AS LIVE
ON IM.EDPNO = LIVE.SKU
WHERE DIVISION = '30'
AND STATUS NOT IN ('XX','YY','ZZ');
Are you sure it is the procedure and not duplicates in the data you are inserting?