How to move back after FETCH NEXT in stored procedure while using CURSOR in MySQL

Issue

Following is my table:

CREATE TABLE IF NOT EXISTS `clockintest` (
  `Id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `PartnerId` bigint(20) unsigned DEFAULT NULL,
  `TimeStamp` datetime DEFAULT NULL,
  `City` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Street` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Postalcode` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Country` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `CommissionId` bigint(20) unsigned DEFAULT NULL,
  `WorkDayId` bigint(20) unsigned DEFAULT NULL,
  `UserId` bigint(20) unsigned DEFAULT NULL,
  `WorkDescription` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `EmployeeId` bigint(20) unsigned DEFAULT NULL,
  `Type` enum('Start','End') COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Kostenstelle` int(11) DEFAULT NULL,
  `Sachkonto` int(11) DEFAULT NULL,
  `Bearbeitungsschluessel` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `CreatedAt` datetime DEFAULT current_timestamp(),
  `UpdatedAt` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `DeletedAt` datetime DEFAULT NULL,
  `ExportedAt` datetime DEFAULT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE KEY `Id` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=712 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Dumping data for table basedb.clockintest: ~6 rows (approximately)
DELETE FROM `clockintest`;
/*!40000 ALTER TABLE `clockintest` DISABLE KEYS */;
INSERT INTO `clockintest` (`Id`, `PartnerId`, `TimeStamp`, `City`, `Street`, `Postalcode`, `Country`, `CommissionId`, `WorkDayId`, `UserId`, `WorkDescription`, `EmployeeId`, `Type`, `Kostenstelle`, `Sachkonto`, `Bearbeitungsschluessel`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, `ExportedAt`) VALUES
    (645, 1, '2021-10-26 08:00:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'Start', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-27 11:43:20', NULL, NULL),
    (649, 1, '2021-10-26 08:00:00', NULL, NULL, NULL, NULL, NULL, 150, 1, NULL, 1, 'Start', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-29 13:02:50', NULL, NULL),
    (680, 1, '2021-10-26 10:00:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'End', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-28 08:46:05', NULL, NULL),
    (684, 1, '2021-10-26 12:00:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'Start', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-29 07:37:51', NULL, NULL),
    (709, 1, '2021-10-26 23:59:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'End', NULL, NULL, NULL, '2021-10-29 09:52:07', '2021-10-29 09:52:07', NULL, NULL),
    (710, 1, '2021-10-26 23:59:00', NULL, NULL, NULL, NULL, NULL, 150, 1, NULL, 1, 'End', NULL, NULL, NULL, '2021-10-29 09:52:07', '2021-10-29 13:02:44', NULL, NULL);

Depending on this table I have wrote down a Stored Procedure in MySQL. I used cursor for retrieving the rows and for the calculation. But, I need to calculate between two rows and for that I need to move next and move backwards. I achieved the next purpose using FETCH NEXT.

But, the problem is that after moving FETCH NEXT, I am iterating two rows in a single loop execution. That is why when I have 4 rows (I should iterate 4 times the loop), but I am iterating only 2 times.

How can I solve this? Any help? Following in my Stored Procedure.

DELIMITER //
DROP PROCEDURE IF EXISTS calculation //

CREATE PROCEDURE calculation (IN workDayId INT)
BEGIN 
    DECLARE TimeSpan INT DEFAULT 0;
    DECLARE BreakTime INT DEFAULT 0;
    DECLARE Id INT DEFAULT 0;
        DECLARE c_Id INT DEFAULT 0;
            DECLARE c_TimeStamp DateTime;
                DECLARE c_WorkDayId INT DEFAULT 0;
                    DECLARE c_EmployeeId INT DEFAULT 0;
                        DECLARE c_Type VARCHAR(10);
                            DECLARE n_TimeStamp DateTime;
                                DECLARE n_Id INT DEFAULT 0;
                                    DECLARE n_WorkDayId INT DEFAULT 0;
                                        DECLARE n_EmployeeId INT DEFAULT 0;
                                            DECLARE n_Type VARCHAR(10);
                                                    DECLARE cur_Type VARCHAR(100) DEFAULT "";
    DECLARE done INT DEFAULT FALSE;
    DECLARE cursor_clockIn CURSOR FOR SELECT c.Id, c.TimeStamp, c.WorkDayId, c.EmployeeId, c.`Type` FROM clockintest c WHERE c.WorkDayId = Id ORDER BY c.TimeStamp;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    SET Id = workDayId;
    OPEN cursor_clockIn;
    SET TimeSpan = 0;
    SET BreakTime = 0;
    loop_through_rows: LOOP
        FETCH cursor_clockIn INTO c_Id, c_TimeStamp, c_WorkDayId, c_EmployeeId, c_Type;
        BEGIN
        FETCH NEXT FROM cursor_clockIn INTO n_Id, n_TimeStamp, n_WorkDayId, n_EmployeeId, n_Type;
    
        IF done THEN 
            LEAVE loop_through_rows;
        END IF;
        
        IF c_Type = 'Start' THEN
            SET TimeSpan = TimeSpan + TIMESTAMPDIFF(MICROSECOND, c_TimeStamp, n_TimeStamp) / 1000;
        END IF;
        
        IF c_Type = 'End' THEN
            SET BreakTime = BreakTime + TIMESTAMPDIFF(MICROSECOND, c_TimeStamp, n_TimeStamp) / 1000;
        END IF;

        
            SET cur_Type = CONCAT(cur_Type,';', c_Id );
            END;
    END LOOP;
    SELECT TimeSpan, BreakTime, cur_Type;
        UPDATE workday w SET w.TimeSpan = TimeSpan, w.BreakTime = BreakTime WHERE w.Id = workDayId;

    CLOSE cursor_clockIn;

END //

DELIMITER ;

CALL calculation (149);

Solution

Ok that was tricky

i changed your procedure.

The cursor now uses the variable directly, your construch doesn’t give any errors but also doesn’t produce the correct result

The first fetch is now outside the loop so the whole thing gets intialised.

The add the end of the summarizing, i added the the pat where i set all c_… variables to the value of n_… so that the routine can go to the next loop

Also as you can see a good indentation helps to grasp the concept better as you can see where something starts and ends. This is also a good advise for the future

CREATE TABLE IF NOT EXISTS `clockintest` (
  `Id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
  `PartnerId` bigint(20) unsigned DEFAULT NULL,
  `TimeStamp` datetime DEFAULT NULL,
  `City` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Street` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Postalcode` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Country` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `CommissionId` bigint(20) unsigned DEFAULT NULL,
  `WorkDayId` bigint(20) unsigned DEFAULT NULL,
  `UserId` bigint(20) unsigned DEFAULT NULL,
  `WorkDescription` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `EmployeeId` bigint(20) unsigned DEFAULT NULL,
  `Type` enum('Start','End') COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `Kostenstelle` int(11) DEFAULT NULL,
  `Sachkonto` int(11) DEFAULT NULL,
  `Bearbeitungsschluessel` varchar(512) COLLATE utf8mb4_unicode_ci DEFAULT NULL,
  `CreatedAt` datetime DEFAULT current_timestamp(),
  `UpdatedAt` datetime DEFAULT current_timestamp() ON UPDATE current_timestamp(),
  `DeletedAt` datetime DEFAULT NULL,
  `ExportedAt` datetime DEFAULT NULL,
  PRIMARY KEY (`Id`),
  UNIQUE KEY `Id` (`Id`)
) ENGINE=InnoDB AUTO_INCREMENT=712 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

-- Dumping data for table basedb.clockintest: ~6 rows (approximately)
DELETE FROM `clockintest`;
/*!40000 ALTER TABLE `clockintest` DISABLE KEYS */;
INSERT INTO `clockintest` (`Id`, `PartnerId`, `TimeStamp`, `City`, `Street`, `Postalcode`, `Country`, `CommissionId`, `WorkDayId`, `UserId`, `WorkDescription`, `EmployeeId`, `Type`, `Kostenstelle`, `Sachkonto`, `Bearbeitungsschluessel`, `CreatedAt`, `UpdatedAt`, `DeletedAt`, `ExportedAt`) VALUES
    (645, 1, '2021-10-26 08:00:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'Start', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-27 11:43:20', NULL, NULL),
    (649, 1, '2021-10-26 08:00:00', NULL, NULL, NULL, NULL, NULL, 150, 1, NULL, 1, 'Start', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-29 13:02:50', NULL, NULL),
    (680, 1, '2021-10-26 10:00:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'End', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-28 08:46:05', NULL, NULL),
    (684, 1, '2021-10-26 12:00:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'Start', NULL, NULL, NULL, '2021-10-27 11:41:29', '2021-10-29 07:37:51', NULL, NULL),
    (709, 1, '2021-10-26 23:59:00', NULL, NULL, NULL, NULL, NULL, 149, 1, NULL, 1, 'End', NULL, NULL, NULL, '2021-10-29 09:52:07', '2021-10-29 09:52:07', NULL, NULL),
    (710, 1, '2021-10-26 23:59:00', NULL, NULL, NULL, NULL, NULL, 150, 1, NULL, 1, 'End', NULL, NULL, NULL, '2021-10-29 09:52:07', '2021-10-29 13:02:44', NULL, NULL);
CREATE TABLE workday (id int, TimeSpan bigint, BreakTime BIGINT )
INSERT INTO workday VALUES (149,0,0)
SELECT c.Id, c.TimeStamp, c.WorkDayId, c.EmployeeId, c.`Type` 
                       FROM clockintest c WHERE c.WorkDayId = 149 ORDER BY c.TimeStamp;
 Id | TimeStamp           | WorkDayId | EmployeeId | Type 
--: | :------------------ | --------: | ---------: | :----
645 | 2021-10-26 08:00:00 |       149 |          1 | Start
680 | 2021-10-26 10:00:00 |       149 |          1 | End  
684 | 2021-10-26 12:00:00 |       149 |          1 | Start
709 | 2021-10-26 23:59:00 |       149 |          1 | End  
DROP PROCEDURE IF EXISTS calculation ;

CREATE PROCEDURE calculation (IN workDayId INT)
BEGIN 
    DECLARE TimeSpan INT DEFAULT 0;
    DECLARE BreakTime INT DEFAULT 0;
    DECLARE c_Id INT DEFAULT 0;
    DECLARE c_TimeStamp DateTime;
    DECLARE c_WorkDayId INT DEFAULT 0;
    DECLARE c_EmployeeId INT DEFAULT 0;
    DECLARE c_Type VARCHAR(10);
    DECLARE n_TimeStamp DateTime;
    DECLARE n_Id INT DEFAULT 0;
    DECLARE n_WorkDayId INT DEFAULT 0;
    DECLARE n_EmployeeId INT DEFAULT 0;
    DECLARE n_Type VARCHAR(10);
    DECLARE cur_Type VARCHAR(100) DEFAULT "";
    DECLARE done INT DEFAULT FALSE;
    DECLARE cursor_clockIn CURSOR FOR SELECT c.Id, c.TimeStamp, c.WorkDayId, c.EmployeeId, c.`Type` 
                       FROM clockintest c WHERE c.WorkDayId = workDayId ORDER BY c.TimeStamp;
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    OPEN cursor_clockIn;
    SET TimeSpan = 0;
    SET BreakTime = 0;
        FETCH cursor_clockIn INTO c_Id, c_TimeStamp, c_WorkDayId, c_EmployeeId, c_Type;
        SET @a = c_Id;  
    loop_through_rows: LOOP

        BEGIN
          
        FETCH NEXT FROM cursor_clockIn INTO n_Id, n_TimeStamp, n_WorkDayId, n_EmployeeId, n_Type;

        IF done THEN 
            LEAVE loop_through_rows;
        END IF;
      SET @a = CONCAT(@a,';', n_id);      
        IF c_Type = 'Start' THEN
            SET TimeSpan = TimeSpan + TIMESTAMPDIFF(MICROSECOND, c_TimeStamp, n_TimeStamp) / 1000;
        END IF;
        
        IF c_Type = 'End' THEN
            SET BreakTime = BreakTime + TIMESTAMPDIFF(MICROSECOND, c_TimeStamp, n_TimeStamp) / 1000;
        END IF;

        
            SET cur_Type = CONCAT(cur_Type,';', c_Id );
            SET c_Id = n_id;
            SET c_TimeStamp = n_TimeStamp;
            SET c_WorkDayId = n_WorkDayId;
            SET c_EmployeeId = n_EmployeeId;
            SET c_Type = n_Type;
            END;
    END LOOP;
    SELECT TimeSpan, BreakTime, cur_Type;
        UPDATE workday w SET w.TimeSpan = TimeSpan, w.BreakTime = BreakTime WHERE w.Id = workDayId;

    CLOSE cursor_clockIn;

END 
CALL calculation (149);
TimeSpan | BreakTime | cur_Type    
-------: | --------: | :-----------
50340000 |   7200000 | ;645;680;684

✓
SELECT @a
| @a              |
| :-------------- |
| 645;680;684;709 |
SELECT * FROM workday
 id | TimeSpan | BreakTime
--: | -------: | --------:
149 | 50340000 |   7200000

db<>fiddle here

Answered By – nbk

This Answer collected from stackoverflow, is licensed under cc by-sa 2.5 , cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply

(*) Required, Your email will not be published