Stored procedure inserting the same value over and over again - mysql

I'm facing an unlikely event handled by the Stored Procedure for MySQL.
I've managed to search for multiple rows in the stored procedure but I am testing it before I can go live.
My current issue is that the result from the below query returns 100 different result set.
But when the query to insert is where the problem begins. It's inserting the same #UID over and over again until it reaches 100.
Is there a way I can increment to the next row before I even insert it?
DECLARE pSpot INT(11) DEFAULT 0;
DECLARE con INT(11) DEFAULT 97;
DECLARE tempString VARCHAR(255) DEFAULT NULL;
DECLARE x INT(11) DEFAULT 0;
IF(pSpot<=97) -- condition 1
THEN
SELECT #uid:=uid,
nickname,
lastsync,
dob,
gender,
gender_preference,
Latest_LAT,
Latest_LON,
country,
imagetoken
FROM search_optimized_table
where country = #csid
and TIMESTAMPDIFF(YEAR,DOB,CURDATE()) BETWEEN minAge and maxAge
and gender=#tempString
and lastsync BETWEEN UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL -7 DAY))
and UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL 0 DAY))
LIMIT con;
SET con = con - pSpot;
SET pSpot = pSpot + (Select found_rows());
WHILE x <= pSpot DO
INSERT INTO temp_local_history (pUid) values (#uid);
END WHILE;

Well, i did a solution since the cursor method returns me 0 .
IF(pSpot<=97) -- condition 1
THEN
SET con = con - pSpot;
INSERT INTO temp_local_search(
SELECT #uid:=uid FROM search_optimized_table where country = #csid and TIMESTAMPDIFF(YEAR,DOB,CURDATE()) BETWEEN minAge and maxAge and gender=#tempString and lastsync BETWEEN UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL -7 DAY)) and UNIX_TIMESTAMP(DATE_ADD(NOW(),INTERVAL 0 DAY)) LIMIT con
);

Related

How can I add control flow into a mysql function?

I want to optimise a mysql 5.7 function that reads settings from a table. the function returns a 1 or 2 if the date parsed in is in 'semester 1' or 'semester 2'. the dates for semester 1 and 2 change each year.
we have confirmed that dateIn is a valid date.
the function is:
DELIMITER //
CREATE function getSemester (dateIN date)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE sem int;
select if( dateIN < a.mindate,1,2) into sem
from (SELECT min(date(value)) mindate FROM `settings` WHERE name = CONCAT(‘sem2_‘,year(dateIN),‘_start’) ) a;
return sem;
END//
DELIMITER ;
settings is defined as:
CREATE TABLE `settings` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(100) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
`value` varchar(255) CHARACTER SET utf8 COLLATE utf8_unicode_ci NOT NULL,
PRIMARY KEY (`id`),
KEY `name` (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
the settings data is:
INSERT INTO `mdl_sap_settings` (`id`, `name`, `value`)
VALUES
(4, 'sem2_2012_start', '2012/7/16'),
(15, 'sem2_2013_start', '2013/7/1'),
(25, 'sem2_2014_start', '2014/6/30'),
(29, 'sem2_2015_start', '2015/6/29'),
(37, 'sem2_2016_start', '2016/6/27'),
(42, 'sem2_2011_start', '2011/7/16'),
(50, 'sem2_2017_start', '2017/6/26'),
(56, 'sem2_2018_start', '2018/6/25'),
(63, 'sem2_2019_start', '2019/6/24');
the issue is the function is slow quite slow when called on 20,000 rows. I thought to optimise it by having some sort of flow control in the function something like:
if (year(dateIN) = 2012)
{
return dateIN < '2012-07-16' ? 1 : 2;
}
if (year(dateIN) = 2013)
{
return dateIN < '2013-07-01' ? 1 : 2;
}
... etc.
We need to keep the select as is query because if the code is not maintained we want it to return the correct values.
i was just wondering if this sort of control flow is possible in a mysql function, or is there an alternative way to optimise the function?
I don't have 20k records to test with but if I were you, I'd rewrite your function like this and see if that helps.
DELIMITER //
CREATE function getSemester (dateIN date)
RETURNS INT DETERMINISTIC
BEGIN
DECLARE sem int;
DECLARE nameByDate varchar(255);
SET nameByDate = (SELECT CONCAT('sem2_',year(dateIN),'_start') );
select if( dateIN < a.mindate,1,2) into sem
from (SELECT min(date(value)) mindate FROM `test`.`settings` WHERE name = nameByDate ) a;
return sem;
END//
DELIMITER ;
I'd avoid adding a concat in the where clause as there is a performance hit on large dataset. So I moved it out of where clause and assigned it to a variable once. If that doesn't help you can also try the query with explain and see if that provides any hints.
EXPLAIN select getSemester('2019/6/24');
Good luck.
With 20,000 rows in the results table, the my query run without the getSemester function in 157 msec.
with the version of getSemster in the question it was taking 1.1 seconds.
with this optimisation the query was running in 0.9 seconds.
DELIMITER //
CREATE function getSemester (dateIN date)
RETURNS tinyint DETERMINISTIC
BEGIN
DECLARE sem tinyint;
DECLARE sem2_start_label char(16);
set sem2_start_label = CONCAT('sem2_',year(dateIN),'_start');
select case year(dateIN)
when 2012 then
case when dateIN < date('2012-07-16') then 1 else 2 end
when 2013 then
case when dateIN < date('2013-07-01') then 1 else 2 end
when 2014 then
case when dateIN < date('2014-06-30') then 1 else 2 end
when 2015 then
case when dateIN < date('2015-06-29') then 1 else 2 end
when 2016 then
case when dateIN < date('2016-06-27') then 1 else 2 end
when 2017 then
case when dateIN < date('2017-06-26') then 1 else 2 end
when 2018 then
case when dateIN < date('2018-06-25') then 1 else 2 end
when 2019 then
case when dateIN < date('2019-06-24') then 1 else 2 end
else
case when dateIN < (SELECT min(date(value)) mindate FROM `settings` WHERE name = sem2_start_label ) then 1 else 2 end
end into sem;
return sem;
END//
DELIMITER ;
what i did was only select the date from the settings table when it was unknown -- seems to give a small optimisation.

VB.Net Daily Time Record MySQL

Good day, I have a problem regarding MySQL queries. I want to create a daily time record output but my problem is on making queries. What I want is that when a record have the same id and date it must be updated otherwise it will insert a new record. These are some of screenshots of table properties
CREATE PROCEDURE dbo.EditGrade
#Id int
,#TimeIn datetime
,#TimeOut datetime
,#Existing bit OUTPUT
AS
BEGIN
SET NOCOUNT ON;
DECLARE #CurrentTimeIn as datetime
DECLARE #CurrentId as int
SELECT #CurrentId = Id
FROM tblAttendance
WHERE TimeIn = #TimeIn
IF (#CurrentId <> #Id)
BEGIN
IF (SELECT COUNT(ISNULL(Id,0))
FROM tblAttendance
WHERE TimeIn = #TimeIn
SET #Existing = 0
ELSE
SET #Existing = 1
END
ELSE
BEGIN
SET #Existing = 0
END
IF #Name = ''
SET #Name = null
IF (#Existing = 0)
UPDATE tblAttendance
SET TimeIn = #TimeIn
--other column values here
WHERE Id = #Id
ELSE
--INSERT FROM tblAttendance query here
END
GO
this is from stored procedure of ms sql, you can just convert it into mysql version.
take note, datetime types also checks the seconds, so don't include the seconds as much as possible or it will render as NOT THE SAME (e.g time in = 10:00:01 and time out is 10:00:02 will be rendered as NOT THE SAME)

Column cannot be null - procedure

I am trying to create a procedure in MySQL that insert weeks (for current year) to my week table. But there is a problem because after first row is added for the next one I get an error: number column cannot be null. I am new to MySQL so I will appreciate any help.
CREATE PROCEDURE generateWeeks()
BEGIN
SET #currentYear = YEAR(CURDATE());
SET #nextYear = #currentYear + 1;
SET #startOfCurrentWeek = CURDATE();
WHILE(#currentYear < #nextYear) DO
SET #endOfCurrentWeek = DATE_ADD(#startOfCurrentWeek , INTERVAL 7 DAY);
SET #weekNumber = WEEK(#startOfCurrentWeek, 3) -
WEEK(#startOfCurrentWeek - INTERVAL DAY(#startOfCurrentWeek)-1 DAY, 3) + 1;
INSERT INTO `week` (`number`, `start_date`, `end_date`)
VALUES (#weekNumber, #startOfCurrentWeek, #endOfCurrentWeek);
SET #startOfCurrentWeek = #endOfCurrentWeek + 1;
SET #currentYear = YEAR(#endOfCurrentWeek);
END WHILE;
END //
DELIMITER ;
EDITED:
Table Creation:
CREATE TABLE `week` (
`id` INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,
`number` INT(11) NOT NULL,
`start_date` DATE NOT NULL,
`end_date` DATE NOT NULL
)
Why for first while iteration everything is ok (rows is added), but in the next one I get null value in #weekNumber variable ?
The line:
SET #startOfCurrentWeek = #endOfCurrentWeek + 1;
will convert the variable into a integer. Use date_add instead.
Also, instead of using user-defined variables (#endOfCurrentWeek) you better use local variabled (declare v_endOfCurrentWeek date).

Obtaining the amount of time spent above a threshold value from time series data

Given a table like this:
`sensor` int(11)
`reading` decimal(5,2)
`timestamp` datetime
that is representing temperature data and logging an entry whenever a value changes, how would I go about finding the amount of time recorded above a given value?
So there may be a bunch of readings from, say, 16 up to 30, the requirement would be to find the amount of time spent above 16.
Two solutions
I propose two solutions:
one query, but I don't know if it is very efficient with a lot of data, because of the subquery;
one function.
Schema
I tested the query and the function with this table:
CREATE TABLE `temperature` (
`sensor` int(11) NOT NULL,
`timestamp` datetime NOT NULL,
`reading` decimal(5, 2) NOT NULL,
PRIMARY KEY (`sensor`,`timestamp`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
And some data:
1;2014-09-18 17:00:00;15.0
1;2014-09-18 18:00:00;16.0
1;2014-09-18 19:00:00;15.0
Solution 1: a query
SELECT SUM(elapsed_time)
FROM (
SELECT
(UNIX_TIMESTAMP((
SELECT MIN(t2.timestamp)
FROM temperature t2
WHERE t1.sensor = t2.sensor AND t1.timestamp < t2.timestamp
)) - UNIX_TIMESTAMP(t1.timestamp))
AS elapsed_time
FROM temperature t1
WHERE t1.sensor = 1 AND t1.reading >= 16.0
) a;
Solution 2: a function
The function returns the amount of time above a value in seconds for a sensor.
It initializes the amount of time to 0. It then read all readings of the desired sensor. If the temperature is above your requirement, it adds the amount of time to go until the next reading to the sum. At last, it returns the amount of time above your requirement for the sensor.
DROP FUNCTION IF EXISTS getTotalTimeAbove;
DELIMITER $$
CREATE FUNCTION getTotalTimeAbove(sensor_id INTEGER, above DECIMAL(5, 2))
RETURNS INTEGER
BEGIN
DECLARE sum INTEGER DEFAULT 0;
DECLARE curr_time DATETIME;
DECLARE next_time DATETIME;
DECLARE curr_reading INTEGER;
DECLARE next_reading INTEGER;
DECLARE done INT DEFAULT FALSE;
DECLARE cur CURSOR FOR
SELECT `timestamp`, `reading`
FROM `temperature`
WHERE `sensor` = sensor_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
read_loop: LOOP
FETCH cur INTO next_time, next_reading;
IF done THEN
LEAVE read_loop;
END IF;
IF (curr_reading >= above) THEN
SET sum = sum + (UNIX_TIMESTAMP(next_time) - UNIX_TIMESTAMP(curr_time));
END IF;
SET curr_time = next_time;
SET curr_reading = next_reading;
END LOOP;
CLOSE cur;
RETURN sum;
END
$$
DELIMITER ;
The query and its result:
> SELECT getTotalTimeAbove(1, 16.0);
3600
Bonus
You can have the total amount of recorded time for a sensor with this query:
SELECT UNIX_TIMESTAMP(MAX(`timestamp`)) - UNIX_TIMESTAMP(MIN(`timestamp`))
FROM `temperature`
WHERE `sensor` = 1

MySQL insert trigger with multiple inserts at the same time

I'm trying to generate a primary key for my table, something like this
(simplified version) - the purpose is to have a daily incremented key:
DELIMITER ^
CREATE TABLE `ADDRESS` (
ID INTEGER NOT NULL DEFAULT -1,
NAME VARCHAR(25),
PRIMARY KEY(`ID`))^
CREATE FUNCTION `GETID`()
RETURNS INTEGER
deterministic
BEGIN
declare CURR_DATE DATE;
declare maxid, _year, _month, _day, newid INTEGER;
set CURR_DATE = CURRENT_DATE;
set _year = EXTRACT(YEAR FROM CURR_DATE);
set _mon = EXTRACT(MONTH FROM CURR_DATE);
set _day = EXTRACT(DAY FROM CURR_DATE);
set newid = (_year - (_year/100) * 100) * 10000 + _mon * 100 + _day;
select max(ID) into maxid From `ADDRESS`;
if (maxid is null) then
set maxid = 0;
end if;
if (MAXID / 1000 != newid) then
set MAXID = newid * 1000;
end if;
set MAXID = MAXID + 1;
return MAXID;
END^
CREATE TRIGGER `ADDRESS_ID_TRIGGER` BEFORE INSERT ON `ADDRESS`
FOR EACH ROW
BEGIN
if new.id=-1 then
set new.id = getid();
end if ;
END^
COMMIT^
DELIMITER ;
Generally it works fine, but when I test it with multiple inserts at the same time
it obviously fails (e.g. no dirty reads, the select max will fail for the 2nd insert,
thus it will generate the same id as fro the 1st insert).
Workaround:
Make primary key AUTO_INCREMENT.
Add TIMESTAMP field and use BEFORE INSERT/UPDATE trigget to set CURRENT_TIMESTAMP().
Also you can use ON UPDATE CURRENT_TIMESTAMP option for TIMESTAMP field, value will be updated automatically.
So, ID is ID, and TIMESTAMP field contains date and time.