Mysql for every n-th update to do a special update - mysql

Is it possible to do something like this with mysql?
Imagine I've update query, that runs every time user gives successful answer. Now I'd like to count updates and give +1 bonus point every fourth time...
I could just count rows and divide them by 4, but that would give me non spendable bonus points, because for every update it will get recalculated...
Is there any mysql solution to my problem?

I think you may use trigger and calculate additional bonuses when user gives successful answer.
Here is working example:
DROP TABLE IF EXISTS answer;
CREATE TABLE answer
(
id int not null auto_increment,
bonus int not null,
primary key(id)
);
DELIMITER //
CREATE TRIGGER lucky_trigger BEFORE INSERT ON answer
FOR EACH ROW BEGIN
IF MOD((SELECT AUTO_INCREMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = 'answer'), 4) = 0 THEN
SET NEW.bonus = NEW.bonus + 1;
END IF;
END //
DELIMITER ;
INSERT INTO answer(bonus) VALUES(1);
INSERT INTO answer(bonus) VALUES(1);
INSERT INTO answer(bonus) VALUES(1);
INSERT INTO answer(bonus) VALUES(1);
SELECT id, bonus FROM answer;
Will give you next output:
+----+-------+
| id | bonus |
+----+-------+
| 1 | 1 |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
+----+-------+
4 rows in set (0.00 sec)

Related

Creating Primary key from 2 autonumber and constant letter when creating table

I am new to MYSQL and would like to create a table where a constant Letter depicting the department is added to an auto increment number. This way I would be able to identify the category of the worker upon viewing the ID.
Ex. Dept A and employee 135. The ID I am imaging should read A135 or something similar. I have created the table, the auto increment works fine, the constant letter has been declared and is featuring. However I would like to concatenate them in order to use the A135 as a primary key.
Any Help Please?
This quite tricky, and you would be probably better off doing manual concatenation in a select query.
But since you asked for it...
In normal usage you would have used a computed column for this, but they do not support using autoincremented columns in their declaration. So you would need to use triggers:
on insert, query information_schema.tables to retrieve the autoincremented id that is about to be assigned and use it to generate the custom id
on update, reset the custom id
Consider the following table structure:
create table workers (
id int auto_increment primary key,
name varchar(50) not null,
dept varchar(1) not null,
custom_id varchar(12)
);
Here is the trigger for insert:
delimiter //
create trigger trg_workers_insert before insert ON workers
for each row
begin
if new.custom_id is null then
select auto_increment into #nextid
from information_schema.tables
where table_name = 'workers' and table_schema = database();
set new.custom_id = CONCAT(new.dept, lpad(#nextid, 11, 0));
end if;
end
//
delimiter ;
And the trigger for update:
delimiter //
create trigger trg_workers_update before update ON workers
for each row
begin
if new.dept is not null then
set new.custom_id = CONCAT(new.dept, lpad(old.id, 11, 0));
end if;
end
//
delimiter ;
Let's run a couple of inserts for testing:
insert into workers (dept, name) values ('A', 'John');
insert into workers (dept, name) values ('B', 'Jim');
select * from workers;
| id | name | dept | custom_id |
| --- | ---- | ---- | ------------ |
| 1 | John | A | A00000000001 |
| 2 | Jim | B | B00000000002 |
And let's test the update trigger
update workers set dept = 'C' where name = 'Jim';
select * from workers;
| id | name | dept | custom_id |
| --- | ---- | ---- | ------------ |
| 1 | John | A | A00000000001 |
| 2 | Jim | C | C00000000002 |
Demo on DB Fiddle
Sorry, my answer does not fit in a comment.
I agree with #GMB.
This is a tricky situation and in some cases (selects mainly) will lead in a performance risk due you'll have to split PK in where statements, which is not recommended.
Having a column for department and another for auto_increment is more logical. And the only gap you have is to know the number of employees per department you'll have to make a count grouping by dept. Instead of a max() splitting your concatenated PK, which is is at high performance cost.
Let atomic and logic data remain in separate columns. I would suggest to create a third column with the concatenated value.
If, for some company reason, you need B1 and A1 values for employees of different departments, I'd suggest to have 3 columns
Col1 - letter(not null)
Col2 - ID(Not auto-increment, but calculated as #GMB's solution) (Not NULL)
Col3 - Concatenation of Col1 and Col2 (not null)
PK( Col1, col2)

Trigger needs to fire only when column value is today's date

I want to apply the trigger in database when column_value match the particular scenario like
In goal table there are fields like goal, status, start_Date, end_Date
Now I want to change the status of goal. When user enter his/her goal, he/she filled end_Date. Now I want to change the status of goal when end_Date matched to current Date
Example:-
+------+--------+--------------+-------------+
| GOAL | STATUS | START_DATE | END_DATE |
+------+--------+--------------+-------------+
| 1 | Active | 2017-07-03 | 2017-07-09 |
+------+--------+------------+---------------+
When END_DATE equals to current Date, then I want to change status 'Active' to 'Finished'
I hope I am able to understand my question.
Thanks in advance!
Body of an oracle table level trigger would look like this...
BEGIN
IF INSERTING and (:new.end_date = sysdate) THEN
:NEW.goal_status := desired_value;
ELSIF UPDATING AND (:new.end_date = sysdate) then
:NEW.goal_status := desired_value;
END IF;
End;
The logic for this is
DROP TRIGGER IF EXISTS TB;
DELIMITER //
CREATE TRIGGER TB BEFORE INSERT ON T
FOR EACH ROW
BEGIN
IF NEW.START_DT = DATE(NOW()) THEN
SET NEW.STATUS = 'YES' ;
end if;
END //
DELIMITER ;
use sandbox;
DROP TABLE IF EXISTS T;
CREATE TABLE T(GOAL INT, STATUS VARCHAR(3), START_DT DATE,END_DATE DATE);
mysql> TRUNCATE TABLE T;INSERT INTO T VALUES(1,NULL,'2017-07-06','2017-07-06');SELECT * FROM T;
Query OK, 0 rows affected (0.73 sec)
Query OK, 1 row affected (0.06 sec)
+------+--------+------------+------------+
| GOAL | STATUS | START_DT | END_DATE |
+------+--------+------------+------------+
| 1 | YES | 2017-07-06 | 2017-07-06 |
+------+--------+------------+------------+
1 row in set (0.00 sec)

MySQL: Reference current row in delete trigger

I'm having trouble referencing the current row in an AFTER DELETE trigger in MySQL. Pretend I have the following books table:
+----+------+----------+
| id | name | ordering |
+----+------+----------+
| 1 | It | 3 |
| 2 | Cujo | 1 |
| 3 | Rage | 2 |
+----+------+----------+
I want to create a trigger that will decrement all rows whose ordering value is greater than the ordering value in a row that is deleted. For example, if I do DELETE FROM books WHERE id = 2, I want the resulting table to look like:
+----+------+----------+
| id | name | ordering |
+----+------+----------+
| 1 | It | 2 |
| 3 | Rage | 1 |
+----+------+----------+
I've tried:
DROP TRIGGER IF EXISTS reorder_books_on_delete;
DELIMITER $$
CREATE TRIGGER reorder_books_on_delete
AFTER DELETE ON books
FOR EACH ROW
BEGIN
IF ordering > OLD.ordering
THEN
UPDATE books SET ordering = ordering - 1
WHERE id = id;
END IF;
END$$
DELIMITER ;
But this results in an error when I execute a DELETE on the table:
ERROR 1054 (42S22): Unknown column 'ordering' in 'where clause'
This refers to the if statement, so how do I reference the current row in an ON DELETE trigger? The column definitely does exist.
The reason why it fails is because there is no current row and hence, ordering column doesn't exist, it should be used in WHERE clause, like this:
DROP TRIGGER IF EXISTS reorder_books_on_delete;
DELIMITER $$
CREATE TRIGGER reorder_books_on_delete
AFTER DELETE ON books
FOR EACH ROW
BEGIN
UPDATE books SET ordering = ordering - 1
WHERE ordering > OLD.ordering;
END$$
DELIMITER ;
However, as per MySQL's documentation, you can't do this, it will return the following error:
SQL Error (1442): Can't update table 'books' in stored
function/trigger because it is already used by statement which invoked
this stored function/trigger.
So, you will have to run another UPDATE query after DELETE query in a single transaction to achieve this functionality.
As said by Darshan, you can't do this with a Trigger.... But, Procedure can make if for you !
DROP PROCEDURE IF EXISTS delete_book;
DELIMITER //
CREATE PROCEDURE delete_book(IN pId INT)
BEGIN
set #a = (
SELECT ordering
FROM books
WHERE id = pId
);
UPDATE books SET ordering = ordering - 1
WHERE ordering > #a;
delete from books
WHERE id = pId;
END
//
DELIMITER ;
And you just have, instead of your DELETE FROM books WHERE id = 4 to make a CALL delete_book(4);

Reset auto_increment based on datetime column

I'm working with MySQL through phpMyAdmin. I need to reset the ID fields in one of the tables in my database, but I need to do it based on the publication date of each row. I've been looking everywhere and I can't seem to find a solution :(
The following lines of code work fine, but do not do exactly what I require based on the datetime column:
SET #count = 0;
UPDATE `table_name` SET `table_name`.`ID` = #count:= #count + 1;
So this is what I have:
+----+---------------------+
| ID | post_date |
+----+---------------------+
| 1 | 2013-11-04 20:06:28 |
| 2 | 2012-03-30 11:20:22 |
| 3 | 2014-06-26 22:59:51 |
+----+---------------------+
And this is what I need:
+----+---------------------+
| ID | post_date |
+----+---------------------+
| 1 | 2005-08-02 16:51:48 |
| 2 | 2005-08-02 16:59:36 |
| 3 | 2005-08-02 17:01:54 |
+----+---------------------+
Thanks in advance, guys :)
Try this, a simple approach though.
But you will lose all the relations to other tables since you are
resetting the PRIMARY ID Keys.
# Copy entire table to a temporary one based on the publication date
CREATE TEMPORARY TABLE IF NOT EXISTS `#temp_table` AS SELECT * FROM `wp_posts` ORDER BY `post_date`;
# Drop `ID` column from the temporary table
ALTER TABLE `#temp_table` DROP COLUMN `ID`;
# Reset the table `wp_posts` as well as its `ID`
TRUNCATE TABLE `wp_posts`;
# Exclude `ID` column in the INSERT statement below
INSERT INTO `wp_posts`(`post_author`, `post_date`, ..., `comment_count`) SELECT * FROM `#temp_table`;
# Remove the temporary table
DROP TABLE `#temp_table`;
Also see the ERD for WP3.0 below,
Ref: https://codex.wordpress.org/Database_Description/3.3
Try doing it with the following script. It selects every row of your table and orders the rows by its date ascending. Then your Update-Command will be executed within a loop.
Add the type of the ID of your table to the DECLARE-Statement and change the
field-Name in the UPDATE-Statement to your ID-Column name.
BEGIN
DECLARE col_id BIGINT;
DECLARE stepLoopDone BOOLEAN DEFAULT FALSE;
DECLARE counter INT DEFAULT 1;
DECLARE ORDER_CURSOR CURSOR FOR
SELECT id
FROM wp_posts
ORDER BY post_date ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET stepLoopDone = TRUE;
OPEN ORDER_CURSOR;
myLoop: LOOP
FETCH ORDER_CURSOR INTO col_id;
IF stepLoopDone THEN
LEAVE myLoop;
END IF;
/*YOUR UPDATE COMMAND*/
UPDATE wp_posts
SET id = counter
WHERE id = col_id;
/*YOUR UPDATE COMMAND*/
SET counter = counter + 1;
END LOOP;
CLOSE ORDER_CURSOR;
END

MySQL Trigger after update only if row has changed

Is there any possibility to use an "after update" trigger only in the case the data has been REALLY changed.
I know of "NEW and OLD". But when using them I'm only able to compare columns.
For example "NEW.count <> OLD.count".
But I want something like: run trigger if "NEW <> OLD"
An Example:
create table foo (a INT, b INT);
create table bar (a INT, b INT);
INSERT INTO foo VALUES(1,1);
INSERT INTO foo VALUES(2,2);
INSERT INTO foo VALUES(3,3);
CREATE TRIGGER ins_sum
AFTER UPDATE ON foo
FOR EACH ROW
INSERT INTO bar VALUES(NEW.a, NEW.b);
UPDATE foo SET b = 3 WHERE a=3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
select * from bar;
+------+------+
| a | b |
+------+------+
| 3 | 3 |
+------+------+
The point is, there was an update, but nothing has changed.
But the trigger ran anyway. IMHO there should be a way it doesn't.
I know that I could have used
IF NOW.b <> OLD.b
for this example.
BUT imagine a large table with changing columns.
You have to compare every column and if the database changes you have to adjust the trigger.
AND it doesn't "feel" good to compare every column of the row hardcoded :)
Addition
As you can see on the line
Rows matched: 1 Changed: 0 Warnings: 0
MySQL knows that the line didn't change. But it doesn't share this knowledge with the trigger.
A trigger like "AFTER REAL UPDATE" or something like this would be cool.
As a workaround, you could use the timestamp (old and new) for checking though, that one is not updated when there are no changes to the row. (Possibly that is the source for confusion? Because that one is also called 'on update' but is not executed when no change occurs)
Changes within one second will then not execute that part of the trigger, but in some cases that could be fine (like when you have an application that rejects fast changes anyway.)
For example, rather than
IF NEW.a <> OLD.a or NEW.b <> OLD.b /* etc, all the way to NEW.z <> OLD.z */
THEN
INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF
you could use
IF NEW.ts <> OLD.ts
THEN
INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b) ;
END IF
Then you don't have to change your trigger every time you update the scheme (the issue you mentioned in the question.)
EDIT: Added full example
create table foo (a INT, b INT, ts TIMESTAMP);
create table bar (a INT, b INT);
INSERT INTO foo (a,b) VALUES(1,1);
INSERT INTO foo (a,b) VALUES(2,2);
INSERT INTO foo (a,b) VALUES(3,3);
DELIMITER ///
CREATE TRIGGER ins_sum AFTER UPDATE ON foo
FOR EACH ROW
BEGIN
IF NEW.ts <> OLD.ts THEN
INSERT INTO bar (a, b) VALUES(NEW.a, NEW.b);
END IF;
END;
///
DELIMITER ;
select * from foo;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 1 | 1 | 2011-06-14 09:29:46 |
| 2 | 2 | 2011-06-14 09:29:46 |
| 3 | 3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
3 rows in set (0.00 sec)
-- UPDATE without change
UPDATE foo SET b = 3 WHERE a = 3;
Query OK, 0 rows affected (0.00 sec)
Rows matched: 1 Changed: 0 Warnings: 0
-- the timestamo didnt change
select * from foo WHERE a = 3;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 3 | 3 | 2011-06-14 09:29:46 |
+------+------+---------------------+
1 rows in set (0.00 sec)
-- the trigger didn't run
select * from bar;
Empty set (0.00 sec)
-- UPDATE with change
UPDATE foo SET b = 4 WHERE a=3;
Query OK, 1 row affected (0.00 sec)
Rows matched: 1 Changed: 1 Warnings: 0
-- the timestamp changed
select * from foo;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 1 | 1 | 2011-06-14 09:29:46 |
| 2 | 2 | 2011-06-14 09:29:46 |
| 3 | 4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
3 rows in set (0.00 sec)
-- and the trigger ran
select * from bar;
+------+------+---------------------+
| a | b | ts |
+------+------+---------------------+
| 3 | 4 | 2011-06-14 09:34:59 |
+------+------+---------------------+
1 row in set (0.00 sec)
It is working because of mysql's behavior on handling timestamps.
The time stamp is only updated if a change occured in the updates.
Documentation is here:
https://dev.mysql.com/doc/refman/5.7/en/timestamp-initialization.html
desc foo;
+-------+-----------+------+-----+-------------------+-----------------------------+
| Field | Type | Null | Key | Default | Extra |
+-------+-----------+------+-----+-------------------+-----------------------------+
| a | int(11) | YES | | NULL | |
| b | int(11) | YES | | NULL | |
| ts | timestamp | NO | | CURRENT_TIMESTAMP | on update CURRENT_TIMESTAMP |
+-------+-----------+------+-----+-------------------+-----------------------------+
BUT imagine a large table with changing columns. You have to compare every column and if the database changes you have to adjust the trigger. AND it doesn't "feel" good to compare every row hardcoded :)
Yeah, but that's the way to proceed.
As a side note, it's also good practice to pre-emptively check before updating:
UPDATE foo SET b = 3 WHERE a=3 and b <> 3;
In your example this would make it update (and thus overwrite) two rows instead of three.
I cant comment, so just beware, that if your column supports NULL values, OLD.x<>NEW.x isnt enough, because
SELECT IF(1<>NULL,1,0)
returns 0 as same as
NULL<>NULL 1<>NULL 0<>NULL 'AAA'<>NULL
So it will not track changes FROM and TO NULL
The correct way in this scenario is
((OLD.x IS NULL AND NEW.x IS NOT NULL) OR (OLD.x IS NOT NULL AND NEW.x IS NULL) OR (OLD.x<>NEW.x))
You can do this by comparing each field using the NULL-safe equals operator <=> and then negating the result using NOT.
The complete trigger would become:
DROP TRIGGER IF EXISTS `my_trigger_name`;
DELIMITER $$
CREATE TRIGGER `my_trigger_name` AFTER UPDATE ON `my_table_name` FOR EACH ROW
BEGIN
/*Add any fields you want to compare here*/
IF !(OLD.a <=> NEW.a AND OLD.b <=> NEW.b) THEN
INSERT INTO `my_other_table` (
`a`,
`b`
) VALUES (
NEW.`a`,
NEW.`b`
);
END IF;
END;$$
DELIMITER ;
(Based on a different answer of mine.)
In here if there any row affect with new insertion Then it will update on different table in the database.
DELIMITER $$
CREATE TRIGGER "give trigger name" AFTER INSERT ON "table name"
FOR EACH ROW
BEGIN
INSERT INTO "give table name you want to add the new insertion on previously given table" (id,name,age) VALUES (10,"sumith",24);
END;
$$
DELIMITER ;
Use the following query to see which rows have changes:
(select * from inserted) except (select * from deleted)
The results of this query should consist of all the new records that are different from the old ones.
MYSQL TRIGGER BEFORE UPDATE IF OLD.a<>NEW.b
USE `pdvsa_ent_aycg`;
DELIMITER $$
CREATE TRIGGER `cisterna_BUPD` BEFORE UPDATE ON `cisterna` FOR EACH ROW
BEGIN
IF OLD.id_cisterna_estado<>NEW.id_cisterna_estado OR OLD.observacion_cisterna_estado<>NEW.observacion_cisterna_estado OR OLD.fecha_cisterna_estado<>NEW.fecha_cisterna_estado
THEN
INSERT INTO cisterna_estado_modificaciones(nro_cisterna_estado, id_cisterna_estado, observacion_cisterna_estado, fecha_cisterna_estado) values (NULL, OLD.id_cisterna_estado, OLD.observacion_cisterna_estado, OLD.fecha_cisterna_estado);
END IF;
END
Here are two interesting dead ends (as of MySQL 5.7)-
The new.* and old.* constructs are invalid, MySQL complains about Unknown table 'new' or syntax to use near '*, which precludes tricks like
select ... from (select (select new.* union select old.*)a having count(*)=2) has_change
The documentation for "ROW_COUNT()" has a useful clue-
For UPDATE statements, the affected-rows value by default is the number of rows actually changed
And indeed, after an update statement, ROW_COUNT() correctly shows the count of rows that had changes from the update. However, during the update, inside the trigger, ROW_COUNT() = 0 always. That function has no useful value in a row-level trigger, and there's no statement-level trigger in MySQL as of this answer.
Hope this "null result" prevents future frustration.