Is updating with inline select atomic in mysql? - mysql

I have an app that calculates points for a user, based on certain rules, for every dollar they spent on my portal. I am using the following stored procedure to update the points of the user:
DROP PROCEDURE IF EXISTS `Update_Points`;
DELIMITER $$
CREATE PROCEDURE `Update_Points` (
pUserId BIGINT,
pPoints DECIMAL(13,2), // points accrued for the amount 'pAmount'
pAmount DECIMAL(13,2) // amount spent by the user
)
SPROC:BEGIN
DECLARE EXIT HANDLER FOR SQLEXCEPTION
BEGIN
ROLLBACK;
RESIGNAL;
END;
START TRANSACTION;
INSERT INTO AuditTable(UserId, Amount, Points)
VALUES (pUserId, pAmount, pPoints);
UPDATE PlayerPoints
SET Points = Points + pPoints
WHERE UserId = pUserId;
SELECT ROW_COUNT() INTO #RowsUpdated;
COMMIT;
SELECT #RowsUpdated;
END$$
DELIMITER ;
I am seeing discrepancies in total points accrued by some of the users. I know this because we have another backend database that stores user transactions.
We have multiple instances of our applications running to handle the load so one idea was that it could this stored procedure that could cause the accrued points discrepancy, based on the idea that the following snippet might not be atomic
UPDATE PlayerPoints
SET Points = Points + pPoints
WHERE UserId = pUserId;
So my question to you gurus of mysql is whether the above UPDATE statement is atomic or not?
The PlayerPoints table has following structure:
UserId
Amount
Points
1
30
50
1
10
20
2
35
30
3
10
20
2
35
30

Related

Select N roughly equidistant rows based on timestamp in MySQL/MariaDB

The question was asked earlier but it appears from the discussion that the question had insufficient input to determine output. I have a similar problem. I will try to come up with some spec/logic.
I have a table with timestamp data that I have converted to unix_timestamp.
id
p_value
ceil(unix_timestamp(updated_at))
3
300
1653549602
7
300
1653549902
11
300
1653550202
15
300
1653550502
19
300
1653550802
23
1200
1653551102
27
1300
1653551402
31
1300
1653551402
35
1300
1653551702
39
1300
1653551702
These are 10 rows with roughly equidistant times. And suppose I want N roughly equidistant rows. So I follow these steps for N = 3,
divide the set by N - 1 i.e. (max - min)/(N - 1). I get 2100/2 = 1050
pick first row (with timestamp 1653549602) save as last
then pick (the first row with updated_at > (last + 1050)) i.e. with timestamp 1653550802 and save as last.
repeat step 3 until it crosses max; use max as last sample. i.e. with timestamp 1653551702.
I have this rough algorithm but how to write this in SQL.
Sample output:
id
p_value
ceil(unix_timestamp(updated_at))
3
300
1653549602
19
300
1653550802
39
1300
1653551702
I just given a try. Check this can help you. Just try the function that i given.
Your '1653549602' is not the last. It is the first record that saved to table.
1653549602 = 2022-05-26 07:20:02 <-- first record 7:20
and 1653551702 = 2022-05-26 07:55:02. <-- last record at 7:55
Also i feel there is a logic issue in your described scenario while selecting the last record. Because 1653550802 + 1050 mean real time is --> "2022-05-26 07:57:32". So you cannot select "1653551702" as the record through this condition updated_at > (last + 1050)). 1653551702 = "2022-05-26 07:55:02". So your condition not valid with it.
1653550802 + 1050 = 1653551852 which is "2022-05-26 07:57:32"
So this condition is not working [ "2022-05-26 07:55:02" > "2022-05-26 07:57:32" ]
[Start from here]
Anyway i did a procedure for you. It give you a some idea to your requirement and also it will help you to go forward.
I used the same table structure as
create table `equidistants` (
`pid` int (11),
`id` int (11),
`p_value` int (11),
`unix_time` bigint (20)
);
pid is a column that i created as PK for me
Table name i used : equidistants
Created Below function
DROP PROCEDURE IF EXISTS my_proc_equidistant;
DELIMITER $$
CREATE PROCEDURE my_proc_equidistant(IN n_value INT)
BEGIN
DECLARE i_val INT; -- Variable for (max - min)/(N - 1)
DECLARE i_loop INT DEFAULT 0;
DECLARE i_Selected_unixTime INT;
SET n_value = n_value -1;
-- Handle the devided by 0 error
IF n_value = 0 THEN
SET n_value = 1 ;
END IF;
-- (max - min)/(N - 1) calculate here
SELECT (MAX(unix_time) - MIN(unix_time))/(n_value)
INTO i_val FROM `equidistants` ;
-- Get the first updated value. Not the last one
SELECT unix_time INTO i_Selected_unixTime
FROM `equidistants` ORDER BY unix_time ASC LIMIT 1;
-- Temporary table to keep your Data
DROP TABLE IF EXISTS temp_equidistants;
-- Inser the latest record from the data set.
CREATE TEMPORARY TABLE temp_equidistants
SELECT * FROM equidistants ORDER BY unix_time ASC LIMIT 1;
-- Start the loop based on the given N value
WHILE i_loop < n_value DO
-- Insert the next selected record into the temp table base on the [last selected unix time + i_val]
INSERT INTO temp_equidistants
SELECT * FROM equidistants WHERE unix_time > i_Selected_unixTime + i_val ORDER BY unix_time ASC LIMIT 1;
-- identify the next unix time
SELECT unix_time INTO i_Selected_unixTime FROM equidistants WHERE unix_time > i_Selected_unixTime + i_val ORDER BY unix_time ASC LIMIT 1;
SET i_loop=i_loop+1;
END WHILE;
-- Execute the result you need
SELECT * FROM temp_equidistants;
-- Drop the Temp table
DROP TABLE IF EXISTS temp_equidistants;
END$$
DELIMITER ;
Hope you can do something with this function by modifying some areas.
Result that i got
Note: 3rd record missing due to the condition miss match that i explain at the top
Here i used "ASC" for ther order by clause. You can change it to descending and you can run it other way-around.

I want to add an attribute to an while condition on MySQL

i'm doing my first DB on mySQL and I want to make an underground like system. To start the system I want to make them go from one station to another on a fixed time of 2 seconds so I use the following code
delimiter |
CREATE EVENT runBlue
ON SCHEDULE EVERY 2 SECOND
DO
BEGIN
WHILE (SELECT Actual_Station FROM route WHERE Actual_Station) < 311 DO
UPDATE mydb.route SET Actual_Station = (Actual_Station+1);
END WHILE;
WHILE (SELECT Actual_Station FROM route WHERE Actual_Station) >= 300 DO
UPDATE mydb.route SET Actual_Station = (Actual_Station-1);
END WHILE;
END |
delimiter ;
What concerns me is that there is no increment to the Actual_Station that is represented by an INT, the reason why I am doing this is because the underground is divided in 3 routes, this is the example for the first one that goes form station 300 to station 311.

Calculation of a moving average using mysql leads to problems if there are gaps in the datasets

My problem is that I try to calculate a moving average over some values from my table (one avg value for each row). It actually works but if it comes to gaps such as id[20,18,17] or date[2018-05-11,2018-05-9,2018-05-8] the calculation becomes wrong. I´m looking for a way to use a specific number of next rows to prevent this to happen.
The table contains id (auto_increment), date and close (Float).
This is my code:
CREATE DEFINER=`root`#`localhost` PROCEDURE `moving_avg`(IN periode INT)
NO SQL
BEGIN
select hist_ask.id, hist_ask.date, hist_ask.close, round(avg(past.close),2) as mavg
from hist_ask
join hist_ask as past
on past.id between hist_ask.id - (periode-1) and hist_ask.id
group by hist_ask.id, hist_ask.close
ORDER BY hist_ask.id DESC
LIMIT 10;
END
The table I use looks like this
id , date , close
20 , 2018-10-13 , 12086.5
19 , 2018-10-12 , 12002.2
17 , 2018-10-11 , 12007.0
and so on
The output looks like this:
The output I get from the query
Thanks in advance!
I finaly make it work using a temporary table.
I can now give two parameters to the procedure:
periode: the periode the moving average is calculated with
_limit: limits the result set
Important for performance is the
ALTER TABLE temp
ENGINE=MyISAM;
statement because it reduces the execution time significantly. For example when proccessing 2000 rows it needs about 0.5 seconds, before adding it it needed about 6 seconds
Thats the code:
CREATE DEFINER=`root`#`localhost` PROCEDURE `moving_avg`(IN periode INT, IN _limit INT)
NO SQL
BEGIN
DECLARE a FLOAT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE count_limit INT DEFAULT 0;
SET #rn=0;
CREATE TEMPORARY TABLE IF NOT EXISTS temp (
SELECT
#rn:=#rn+1 AS pri_id,
date,
close , a AS
mavg
FROM hist_ask);
ALTER TABLE temp
ENGINE=MyISAM;
SET i=(SELECT pri_id FROM temp ORDER by pri_id DESC LIMIT 1);
SET count_limit= (i-_limit)-periode;
WHILE i>count_limit DO
SET a= (SELECT avg(close) FROM temp WHERE pri_id BETWEEN i-(periode-1) AND i);
UPDATE temp SET mavg=a WHERE pri_id=i;
SET i=i-1;
END WHILE;
SELECT pri_id,date,close,round(mavg,2) AS mavg FROM temp ORDER BY pri_id DESC LIMIT _limit;
DROP TABLE temp;
END
The result looks like that:
CALL `moving_avg`(3,5)
pri_id, date, close, mavg
1999 2018-09-13 12086.6 12032.03
1998 2018-09-11 12002.2 11983.47
1997 2018-09-10 12007.3 11976.53
1996 2018-09-07 11940.9 11993.80
1995 2018-09-06 11981.4 12089.23
5 row(s) returned 0.047 sec / 0.000 sec

insert data into one table from another table on specific condition

I have "table1" in which master data store like -
id name create_date validity expire_date
1 A 2015-08-01 3 2015-11-01
2 B 2015-09-01 12 2016-08-01
3 C 2015-09-15 1 2015-10-15
But now want to insert data in "table2" for expire_date according to validity period like without changing in front end. using trigger or procedure want to achieve this task.
id parent_id expire_date
1 1 2015-09-01
2 1 2015-10-01
3 1 2015-11-01
How can I achieve this using procedure or trigger.
It's hard to be specific because your question is not specific.
In general, here's the procedure to follow to design a query to insert stuff from one table into another.
First, write a SELECT query yielding a resultset containing the rows and columns you want inserted into your table. Use a list of columns to get the right columns, and appropriate WHERE clauses to get the right rows. Eyeball that query and that resultset to make sure it contains the correct information.
Second, prepend that debugged SELECT query with
INSERT INTO target_tablename (col, col, col)
Test this to make sure the correct rows are being inserted into your target table.
Third, create yourself an EVENT in your MySQL server to run the query you have just written. The event will, at the appropriate times of day, run your query.
If you take these steps out of order, you'll probably be very confused.
Can achieve the task using Store Procedure -
CREATE DEFINER=`root`#`localhost` PROCEDURE `addexpire`(IN uname varchar(50), IN cdate date, IN vm int)
BEGIN
insert into table1 (name,create_date,validity) values (uname,cdate,vm);
BEGIN
declare uparent_id INT;
declare v_val int default 0;
SET uparent_id = LAST_INSERT_ID();
while v_val < vm do
BEGIN
declare expire_date date;
SET expire_date = DATE_ADD(cdate,INTERVAL v_val+1 MONTH);
insert into table2 (parent_id,expire_date) values (uparent_id,expire_date);
set v_val=v_val+1;
END;
end while;
END;
END

MySQL trigger to create timer

There is a special data type 'time' in MySQL.
How would a trigger look if I want my 'time' value to start counting when some state_id changes from 1 to 2? For example:
CREATE TRIGGER log_time AFTER UPDATE ON usr
FOR EACH ROW
BEGIN
IF usr.st_id = 2 THEN
#.... - thats what i dont know
END IF;
END;
It would stop counting when the state_id changes back from 2 to 1.
Instead of starting/stopping the counter (I don't know if that's even possible), you should store the value in 2 different columns (and then substract to get the time when needed)
DELIMITER $$
CREATE TRIGGER log_time AFTER UPDATE ON usr
FOR EACH ROW
BEGIN
IF new.st_id = 2 THEN
UPDATE <table> set <log_start_time> = CURTIME() <where_clause>;
elseif new.st_id = 1 THEN
UPDATE <table> set <log_end_time> = CURTIME() <where_clause>;
END IF;
END;
$$
DELIMITER;
OR in 1 column by storing initial value and then updating it in the trigger
DELIMITER $$
CREATE TRIGGER log_time AFTER UPDATE ON usr
FOR EACH ROW
BEGIN
IF new.st_id = 2 THEN
UPDATE <table> set <logtime> = CURTIME() <where_clause>;
else if new.st_id = 1 THEN
UPDATE <table> set <logtime> = subtime(CURTIME(), select statement to get original value) <where_clause>;
END IF;
END;
$$
DELIMITER;
Okay I'm new to coding and was wondering if this would even be possible and if so what would be the best way to get around this problem
Okay in my database I have a row for current time and a row for duration and I am wanting to find a way so that when the time is the value of T + D it would change a colour (green) ? Or even better if it could be done so if equal to or under 2 mins amber colour and over 2 mins red colour ( kind of like a traffic light idea)
Hope this makes sense
T | D
---------------
22:50 | 6 (mins)
At 22:56 this would change colour on website
Thank You