MySQL Cursor not working with Date variable - mysql

I have a mysql cursor that is not returning any records when comparing a date variable to a date column in the table. All of this is taking place inside a stored procedure. Hence:
DECLARE StartDateInterval Date DEFAULT '1997-11-01';
DECLARE EndDateInterval Date DEFAULT '1997-12-31';
DECLARE End_Period Date DEFAULT '1997-12-31';
DECLARE prev_period_tournie_rating CURSOR FOR select PID, Rating, RD from rating where Rating_Date = End_Period;
SET End_Period = (StartDateInterval - interval 1 day);
OPEN prev_period_tournie_rating;
firstloop: LOOP
FETCH prev_period_tournie_rating INTO vPID, PrevRat, PrevRD;
IF done = 1 THEN LEAVE firstloop; END IF;
SET decayedRD = ROUND(`newRD`(PrevRD));
INSERT into rating (PID, Rating, RD, Rating_Date, Rating_Type) VALUES (vPID, PrevRat, decayedRD, EndDateInterval, 2);
END LOOP;
CLOSE prev_period_tournie_rating;
There is more code in the Stored Procedure that I have deleted. After the DECLARE statements, all of the above takes place inside a WHILE . . .END WHILE that iterates through 2 month intervals from 1997 to the present, hence the need for End_Period to be a variable and not coded to a single date.
When I remove the where condition the Cursor retrieves records from the rating table.
I do not pass End_Period as a paramater in the cursor DECLARE statement. It appears that some mysql dialects allow/require this and some don't. Even though I have tried to set my dialect to MariaDB (which does allow parameters, I get an error if I add End_Period as a parameter.
Rating_Date is a date column in the table rating, using the standard mysql format (YYYY-MM-DD).
Any help on solving this would be appreciated. This is my first attempt at using a Stored Procedure and it is proving to be a challenge!

Related

Issue while calling a stored procedure inside another stored procedure in while loop

I am calling a stored procedure in a loop in another stored procedure, But Inner SP runs only the first time. No error is shown there, I can't seem to find the reason, Any help would be highly appreciated
DELIMITER $$
USE `xxx_db`$$
DROP PROCEDURE IF EXISTS `wrapper`$$
CREATE DEFINER=`root`#`%` PROCEDURE `wrapper`(p_StartPeriod DATE, p_EndPeriod DATE)
BEGIN
DECLARE v_startperiod DATE;
DECLARE v_endperiod DATE;
DECLARE v_nextdate DATE;
SET v_startperiod = p_StartPeriod;
SET v_endperiod = p_EndPeriod;
SET #TempStart = v_startperiod;
WHILE (#TempStart < v_endperiod) DO
SET v_nextdate = DATE_ADD(#TempStart, INTERVAL 1 DAY);
call innersp(#TempStart,v_nextdate);
SET #TempStart = DATE_ADD(#TempStart, INTERVAL 1 DAY);
END WHILE;
END$$
DELIMITER ;
In this code, The date arguments are passed to the inner SP, if a date range is given, Inner SP runs only for the first date coming in loop. Please help me to find the issue. The inner SP works fine when it is called alone, It inserts data into a table, but since the number of rows are huge I want to insert on a per day basis, instead of doing the complete date range. It's arguments are dates. That is why I wrote a wrapper SP to call this inner SP giving arguments per day for the complete date range.

options for creating sproc variable in mysql?

I have a sproc with multiple selects and result sets. The last query in the sproc needs to select data where a table created date >= the first day of the current month. I have SQL which successfully returns the first day of the month as expected. I need to select this value into a sproc variable FirstDayOfTheMonth and then reference this variable in the WHERE clause of the subsequent SELECT statement in the sproc. I included the following SQL before the final result set in the sproc but it seems that MySQL doesn't like something about it - something about its structure, positioning or syntax:
DECLARE FirstDayOfMonth INT DEFAULT 0;
SET FirstDayOfMonth = (SELECT DATE_SUB(LAST_DAY(NOW()),INTERVAL DAY(LAST_DAY(NOW()))- 1 DAY)
How can I update my existing attempt at a MySQL sproc variable so that my sproc compiles successfully with this variable declaration?
UPDATE
I tried to put the following 2 lines immediately after the BEGIN keyword in my sproc:
DECLARE FirstDayOfMonth INT DEFAULT 0;
SET FirstDayOfMonth = (SELECT DATE_SUB(LAST_DAY(NOW()),INTERVAL DAY(LAST_DAY(NOW()))- 1 DAY)
MySQL Workbench displays an error on the SET statement:
FirstDayOfMonth is not valid at this position, expecting an identifier
Any idea what I need to do differently here?
The DECLARE-statements need to be in the beginning of the procedure, before anything else, just after the BEGIN.

MySQL: Run a stored procedure getting parameter from a query inside another stored procedure

I am new to MySQL.
I am developing a system where many users are assigned to specific tasks. When they are inactive for a certain period of time (lets say more than 10 minutes) I would like the system automatically clear their assignments so that others can work on them.
To achieve that I have created a table called tblactivitytracker for activity tracking. Assignments are in a table called tblinquiries. I have created a stored procedure to get the inactive users.
Here is an sqlfiddle example: Get Inactive Users
In the above example I get 3 inactive users: auditor1, auditor2 and auditor3.
I have created a stored procedure to clear assignment of a single user which does the job perfectly.
CREATE PROCEDURE `spClearAssignedInquiry`(IN `pAssignedTo` VARCHAR(50))
UPDATE
tblinquiries
SET
AuditStatus='Check', AssignedTo=NULL, Result=NULL,
ResultCategories=NULL, AuditBy=NULL,
Remarks=NULL, StartTime=NULL, EndTime=NULL
WHERE
AssignedTo=pAssignedTo AND
AuditStatus='Assigned' AND EndTime IS NULL
If I pass auditor1 as a parameter in the above procedure it will clear the user's assignment.
To pass all inactive users and clear the assignments in a single go I tried the below procedure following this stackoverflow solution:
CREATE PROCEDURE `spInactiveUsers`()
BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE AssignedTo VARCHAR(50);
DECLARE cur CURSOR FOR
SELECT
q1.AssignedTo AS AssignedTo
FROM
(SELECT
InquiryId, AssignedTo
FROM
tblinquiries
WHERE
AuditStatus='Assigned' AND StartTime IS NOT NULL AND EndTime IS NULL
ORDER BY
AssignedTo ASC
) q1
RIGHT JOIN
(SELECT
UserId, MAX(LastActivity) AS LastActivity, ROUND(TIME_TO_SEC(TIMEDIFF(MAX(LastActivity),CURRENT_TIMESTAMP()))/60,0) AS InactiveMinutes
FROM
tblactivitytracker
GROUP BY
UserId
ORDER BY
LastActivity ASC
) q2
ON
q2.UserId=q1.AssignedTo
WHERE
q2.InactiveMinutes>10;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done := TRUE;
OPEN cur;
testLoop: LOOP
FETCH cur INTO AssignedTo;
IF done THEN
LEAVE testLoop;
END IF;
CALL spClearAssignedInquiry(AssignedTo);
END LOOP testLoop;
CLOSE cur;
END
But it does not clear any of the assignments.
I am banging my head to the wall for the last couple of days. Any help would be much appreciated. Thanks in advance.
You are using a variable name that is also the name of a column. The value of the variable will take precedence over the column value, see the documentation:
A local variable should not have the same name as a table column. If an SQL statement, such as a SELECT ... INTO statement, contains a reference to a column and a declared local variable with the same name, MySQL currently interprets the reference as the name of a variable.
So in
...
FROM
(SELECT
InquiryId, AssignedTo
...
you are selecting the variable AssignedTo (which is null), not the column from your table.
Just rename it (in declare and the loop), or, less advised, explicitly state the tablename to set the scope, e.g. SELECT InquiryId, tblinquiries.AssignedTo .... order by tblinquiries.AssignedTo.
There is another (minor) problem is your use of TIMEDIFF in TIMEDIFF(MAX(LastActivity), CURRENT_TIMESTAMP()). It requires the later time in the first argument if you want to get a positive number (as in q2.InactiveMinutes>10).

SQL - Looping through ever row of table in MySQL?

So I have 2 tables, communication,and movement.
communication has columns fromID,timestamp that has ID of caller, and time the call was made. Then I have another table movement that has ID,timestamp,x,y, that has the ID of a person, their location (x,y), and the time that they are at that location.
I want to write a query that looks something like this:
For every single row of communication(R)
SELECT * FROM movement m
WHERE m.ID = R.fromID && m.timestamp <= R.timestamp
ORDER BY timestamp
Basically, what this is doing is finding the closest movement timestamp for a given communication timestamp. After that, eventually, I want to find the location (x,y) of a call, based on the movement data.
How would I do this? I know there's a set based approach, but I don't want to do it that way. I looked into cursors, but I get the feeling that the performance is terrible on that.
So is there anyway to do this with a loop? I essentially want to loop through every single row of the communication, and get the result.
I tried something like this:
DELMITER $$
CREATE PROCEDURE findClosestTimestamp()
BEGIN
DECLARE commRowCount DEFAULT 0;
DECLARE i DEFAULT 0;
DECLARE ctimestamp DEFAULT 0;
SELECT COUNT(*) FROM communication INTO commRowCount;
SET i = 0;
WHILE i < commRowCount DO
SELECT timestamp INTO ctimestamp FROM communication c
SELECT * FROM movement m
WHERE m.vID = c.fromID && m.timestamp <= R.timestamp
END$$
DELIMITER ;
But I know that's completely wrong.
Is the only way to do this cursors? I just can't find an example of this anywhere on the internet, and I'm completely new to procedures in SQL.
Any guidance would be greatly appreciated, thank you!!
Let's see if I can point you in the right direction using cursors:
delimiter $$
create procedure findClosestTimeStamp()
begin
-- Variables to hold values from the communications table
declare cFromId int;
declare cTimeStamp datetime;
-- Variables related to cursor:
-- 1. 'done' will be used to check if all the rows in the cursor
-- have been read
-- 2. 'curComm' will be the cursor: it will fetch each row
-- 3. The 'continue' handler will update the 'done' variable
declare done int default false;
declare curComm cursor for
select fromId, timestamp from communication; -- This is the query used by the cursor.
declare continue handler for not found -- This handler will be executed if no row is found in the cursor (for example, if all rows have been read).
set done = true;
-- Open the cursor: This will put the cursor on the first row of its
-- rowset.
open curComm;
-- Begin the loop (that 'loop_comm' is a label for the loop)
loop_comm: loop
-- When you fetch a row from the cursor, the data from the current
-- row is read into the variables, and the cursor advances to the
-- next row. If there's no next row, the 'continue handler for not found'
-- will set the 'done' variable to 'TRUE'
fetch curComm into cFromId, cTimeStamp;
-- Exit the loop if you're done
if done then
leave loop_comm;
end if;
-- Execute your desired query.
-- As an example, I'm putting a SELECT statement, but it may be
-- anything.
select *
from movement as m
where m.vID = cFromId and m.timeStamp <= cTimeStamp
order by timestampdiff(SECOND, cTimeStamp, m.timeStamp)
limit 1;
end loop;
-- Don't forget to close the cursor when you finish
close curComm;
end $$
delimiter ;
References:
MySQL Reference: Cursors
MySQL Reference: Date and time functions - timestampdiff()

Method of finding gaps in time series data in MySQL?

Lets say we have a database table with two columns, entry_time and value. entry_time is timestamp while value can be any other datatype. The records are relatively consistent, entered in roughly x minute intervals. For many x's of time, however, an entry may not be made, thus producing a 'gap' in the data.
In terms of efficiency, what is the best way to go about finding these gaps of at least time Y (both new and old) with a query?
To start with, let us summarize the number of entries by hour in your table.
SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
COUNT(*) samplecount
FROM table
GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)
Now, if you log something every six minutes (ten times an hour) all your samplecount values should be ten. This expression: CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) looks hairy but it simply truncates your timestamps to the hour in which they occur by zeroing out the minute and second.
This is reasonably efficient, and will get you started. It's very efficient if you can put an index on your entry_time column and restrict your query to, let's say, yesterday's samples as shown here.
SELECT CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME) hour,
COUNT(*) samplecount
FROM table
WHERE entry_time >= CURRENT_DATE - INTERVAL 1 DAY
AND entry_time < CURRENT_DATE
GROUP BY CAST(DATE_FORMAT(entry_time,'%Y-%m-%d %k:00:00') AS DATETIME)
But it isn't much good at detecting whole hours that go by with missing samples. It's also a little sensitive to jitter in your sampling. That is, if your top-of-the-hour sample is sometimes a half-second early (10:59:30) and sometimes a half-second late (11:00:30) your hourly summary counts will be off. So, this hour summary thing (or day summary, or minute summary, etc) is not bulletproof.
You need a self-join query to get stuff perfectly right; it's a bit more of a hairball and not nearly as efficient.
Let's start by creating ourselves a virtual table (subquery) like this with numbered samples. (This is a pain in MySQL; some other expensive DBMSs make it easier. No matter.)
SELECT #sample:=#sample+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
) C,
(SELECT #sample:=0) s
This little virtual table gives entry_num, entry_time, value.
Next step, we join it to itself.
SELECT one.entry_num, one.entry_time, one.value,
TIMEDIFF(two.value, one.value) interval
FROM (
/* virtual table */
) ONE
JOIN (
/* same virtual table */
) TWO ON (TWO.entry_num - 1 = ONE.entry_num)
This lines up the tables next two each other offset by a single entry, governed by the ON clause of the JOIN.
Finally we choose the values from this table with an interval larger than your threshold, and there are the times of the samples right before the missing ones.
The over all self join query is this. I told you it was a hairball.
SELECT one.entry_num, one.entry_time, one.value,
TIMEDIFF(two.value, one.value) interval
FROM (
SELECT #sample:=#sample+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
) C,
(SELECT #sample:=0) s
) ONE
JOIN (
SELECT #sample2:=#sample2+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
) C,
(SELECT #sample2:=0) s
) TWO ON (TWO.entry_num - 1 = ONE.entry_num)
If you have to do this in production on a large table you may want to do it for a subset of your data. For example, you could do it each day for the previous two days' samples. This would be decently efficient, and would also make sure you didn't overlook any missing samples right at midnight. To do this your little rownumbered virtual tables would look like this.
SELECT #sample:=#sample+1 AS entry_num, c.entry_time, c.value
FROM (
SELECT entry_time, value
FROM table
ORDER BY entry_time
WHERE entry_time >= CURRENT_DATE - INTERVAL 2 DAY
AND entry_time < CURRENT_DATE /*yesterday but not today*/
) C,
(SELECT #sample:=0) s
A very efficient way to do this is with a stored procedure using cursors. I think this is simpler and more efficient than the other answers.
This procedure creates a cursor and iterates it through the datetime records that you are checking. If there is ever a gap of more than what you specify, it will write the gap's begin and end to a table.
CREATE PROCEDURE findgaps()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a,b DATETIME;
DECLARE cur CURSOR FOR SELECT dateTimeCol FROM targetTable
ORDER BY dateTimeCol ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
FETCH cur INTO a;
read_loop: LOOP
SET b = a;
FETCH cur INTO a;
IF done THEN
LEAVE read_loop;
END IF;
IF DATEDIFF(a,b) > [range you specify] THEN
INSERT INTO tmp_table (gap_begin, gap_end)
VALUES (a,b);
END IF;
END LOOP;
CLOSE cur;
END;
In this case it is assumed that 'tmp_table' exists. You could easily define this as a TEMPORARY table in the procedure, but I left it out of this example.
I'm trying this on MariaDB 10.3.27 so this procedure may not work, but I'm getting an error creating the procedure and I can't figure out why! I have a table called electric_use with a field Intervaldatetime DATETIME that I want to find gaps in. I created a target table electric_use_gaps with fields of gap_begin datetime and gap_end datetime
The data are taken every hour and I want to know if I'm missing even an hour's worth of data across 5 years.
DELIMITER $$
CREATE PROCEDURE findgaps()
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a,b DATETIME;
DECLARE cur CURSOR FOR SELECT Intervaldatetime FROM electric_use
ORDER BY Intervaldatetime ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur;
FETCH cur INTO a;
read_loop: LOOP
SET b = a;
FETCH cur INTO a;
IF done THEN
LEAVE read_loop;
END IF;
IF TIMESTAMPDIFF(MINUTE,a,b) > [60] THEN
INSERT INTO electric_use_gaps(gap_begin, gap_end)
VALUES (a,b);
END IF;
END LOOP;
CLOSE cur;
END&&
DELIMITER ;
This is the error:
Query: CREATE PROCEDURE findgaps() BEGIN DECLARE done INT DEFAULT FALSE; DECLARE a,b DATETIME; DECLARE cur CURSOR FOR SELECT Intervalda...
Error Code: 1064
You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near '[60] THEN
INSERT INTO electric_use_gaps(gap_begin, gap_end)
...' at line 16