MYSQL loop in function not working - mysql

I have a mysql function which will change records accordingly . But loop only execute for one time and leaves loop with this condition. "IF v_finished =1 THEN
LEAVE get_stock;
END IF;"
However its is supposed to execute multiple time. Like in my test case 3 times
BEGIN
DECLARE P_stock int(11);
DECLARE P_product int(11);
DECLARE V_From_warehouse int(11);
DECLARE V_To_warehouse int(11);
DECLARE v_finished INTEGER DEFAULT 0;
DECLARE V_To_warehouse_stock int(11);
DECLARE V_From_warehouse_stock int(11);
declare cur1 cursor for
SELECT material_transfer_details.product_id , material_transfer_details.quantity FROM
material_transfers,
material_transfer_details
WHERE
material_transfers.id = material_transfer_details.mtm_id
AND
material_transfers.status = 'Y'
AND
material_transfers.id = V_MTM_id;
DECLARE CONTINUE HANDLER
FOR NOT FOUND SET v_finished = 1;
SELECT warehouse_from INTO V_From_warehouse FROM material_transfers WHERE id =V_MTM_id;
SELECT warehouse_to INTO V_To_warehouse FROM material_transfers WHERE id =V_MTM_id;
OPEN cur1;
get_stock: LOOP
IF v_finished =1 THEN
LEAVE get_stock;
END IF;
fetch cur1 into P_product , P_stock;
SELECT quantity INTO V_To_warehouse_stock from stocks where warehouse_id = V_To_warehouse and product_id = P_product;
SELECT quantity INTO V_From_warehouse_stock from stocks where warehouse_id = V_From_warehouse and product_id = P_product;
IF (V_To_warehouse_stock IS NOT NULL)
THEN
UPDATE
stocks SET quantity = quantity - P_stock
WHERE
warehouse_id = V_to_warehouse
AND
product_id = P_product;
ELSE
INSERT INTO stocks(product_id , warehouse_id , quantity ,status, created_datetime , updated_datetime) values
(P_product , V_to_warehouse , 0-P_stock , 'Y', sysdate() , sysdate());
END IF;
IF (V_From_warehouse_stock IS NOT NULL)
THEN
UPDATE
stocks SET quantity = quantity + P_stock
WHERE
warehouse_id = V_from_warehouse
AND
product_id = P_product;
ELSE
INSERT INTO stocks(product_id , warehouse_id , quantity ,status, created_datetime , updated_datetime) values
(P_product , V_from_warehouse , P_stock , 'Y', sysdate() , sysdate());
END IF;
SET P_stock = 0;
SET P_product = 0;
END LOOP get_stock;
CLOSE cur1;
UPDATE material_transfers SET Status = 'N' WHERE id= V_MTM_id;
UPDATE material_transfer_details SET Status = 'N' WHERE mtm_id = V_MTM_id;
return '00000';
END

Two things:
First, change your code.
get_stock: LOOP
SET v_finished = FALSE;
fetch cur1 into P_product , P_stock;
IF v_finished =1 THEN
LEAVE get_stock;
END IF;
Since you are doing other things that could trip the handler, reset v_finished, then fetch from the cursor, and only then test whether to leave the loop.
As written, if you hadn't tripped the handler prematurely, you would have been testing in entirely the wrong place and would have stayed in the loop too long.
Next... be sure you understand SELECT ... INTO. I don't think it does quite what you think it does.
A scalar subquery is a much safer solution:
SET V_To_warehouse_stock = (SELECT quantity from stocks where warehouse_id = V_To_warehouse and product_id = P_product);
If SELECT ... INTO returns no rows, the variable's value does not change. It retains its former value, if one exists, and that is rarely what you expect.
Spooky action at a distance like this is best avoided, and it's an easy trap to fall into, in a loop.
See examples of this effect at https://dba.stackexchange.com/a/35207/11651.

Related

Nested Cursors in MYSQL, cursor not working as expected

I have created an SP in MYSQL to get values by date, by the sensor. My SP executes the inner cursor correctly but the outer cursor(1st cursor) is not executed. i.e. I only get 1 day of data, dateTable has a week's data.
CREATE PROCEDURE `Analysis`()
BEGIN
declare v_date datetime;
declare v_sensor varchar(50);
DECLARE datecursHandler,sensorCursHandler BOOLEAN DEFAULT FALSE;
Block1: BEGIN
declare datecursor CURSOR for
select distinct date from dateTable;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET datecursHandler = TRUE;
Open datecursor;
datecurs: loop
FETCH datecursor into date;
IF datecursHandler THEN
CLOSE datecursor;
LEAVE datecurs;
END IF;
Block2: BEGIN
declare sensorCursor CURSOR for
select distinct sensor from sensorTable ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET sensorcur = TRUE;
Open sensorCursor;
senscurs: loop
FETCH sensorCursor into sensor;
IF sensorcur THEN
SET sensorcur = False;
CLOSE sensorCursor;
LEAVE senscurs;
END IF;
Insert into temptable(
sensorValue,
DateID,
TimeID,
TotalCount,
TotalDistinctCount
)
SELECT
sensor AS sensorValue,
DATE_FORMAT(firstdate, '%Y%m%d') AS DateID,
HOUR(firstdate) + 1 AS TimeID,
COUNT(*) AS totalcount,
COUNT(DISTINCT sensor) AS sensordistinctcount
FROM
(SELECT
sensor AS sensor,
first_seen AS DeviceFirstSeen,
last_seen AS DeviceLastSeen,
DATE_FORMAT(FROM_UNIXTIME(first_seen), '%Y/%m/%d %k:%i:%s.%f') AS firstdate,
DATE_FORMAT(FROM_UNIXTIME(last_seen), '%Y/%m/%d %k:%i:%s.%f') AS lastdate,
FROM
sensorTable
INNER JOIN sensorTable2 ON sensorTable.ID = sensorTable2.ID
WHERE sensorTable.DeviceFirstSeen BETWEEN date_format(date_sub(date,interval 1 day),'%Y-%m-%d 15:00:00') AND date_format(date,'%Y-%m-%d 14:59:59')) a
GROUP BY DATE_FORMAT(firstdate, '%Y%m%d') , HOUR(firstdate) + 1;
end loop Maccurs;
END Block2;
END loop datecurs;
END Block1;
END
Can anyone please help me debug my code? I have researched but so far my code looks correct as per my research but doesn't work as expected.
Required Output:
Get counts of all the sensors for each day each hour that is selected from dateTable.
Try the below procedure, Since the issue might be incorrect closing of cursor.
CREATE PROCEDURE `Analysis`()
BEGIN
declare v_date datetime;
declare v_sensor varchar(50);
DECLARE datecursHandler,sensorCursHandler BOOLEAN DEFAULT FALSE;
Block1: BEGIN
declare datecursor CURSOR for
select distinct date from dateTable;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET datecursHandler = TRUE;
select distinct date from dateTable; #what is the result set you are getting?
Open datecursor;
datecurs: loop
FETCH datecursor into v_date;
IF datecursHandler THEN
LEAVE datecurs;
END IF;
Block2: BEGIN
declare sensorCursor CURSOR for
select distinct sensor from sensorTable ;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET sensorCursHandler = TRUE;
Open sensorCursor;
senscurs: loop
FETCH sensorCursor into v_sensor;
IF sensorCursHandler THEN
SET sensorCursHandler = false;
LEAVE senscurs;
END IF;
Insert into temptable(
sensorValue,
DateID,
TimeID,
TotalCount,
TotalDistinctCount
)
SELECT
sensor AS sensorValue,
DATE_FORMAT(firstdate, '%Y%m%d') AS DateID,
HOUR(firstdate) + 1 AS TimeID,
COUNT(*) AS totalcount,
COUNT(DISTINCT sensor) AS sensordistinctcount
FROM
(SELECT
sensor AS sensor,
first_seen AS DeviceFirstSeen,
last_seen AS DeviceLastSeen,
DATE_FORMAT(FROM_UNIXTIME(first_seen), '%Y/%m/%d %k:%i:%s.%f') AS firstdate,
DATE_FORMAT(FROM_UNIXTIME(last_seen), '%Y/%m/%d %k:%i:%s.%f') AS lastdate,
FROM
sensorTable
INNER JOIN sensorTable2 ON sensorTable.ID = sensorTable2.ID
WHERE sensorTable.DeviceFirstSeen BETWEEN date_format(date_sub(date,interval 1 day),'%Y-%m-%d 15:00:00') AND date_format(date,'%Y-%m-%d 14:59:59')) a
GROUP BY DATE_FORMAT(firstdate, '%Y%m%d') , HOUR(firstdate) + 1;
end loop senscurs;
close sensorCursor;
END Block2;
END loop datecurs;
close datecursor;
END Block1;
END

Update Whole Table adding value from previous row

Table acc
If I Edit "A" Amount to 100 then Total amount change
whole table need to update...
So What Will be the mysql query for updating whole table by adding (credit) or subtracting (debit) from previous total amount...
As commented by #Gordon Linoff, you can archive your goal using AFTER UPDATE trigger and a loop. I wrote up the idea as below for example.
DELIMITER //
CREATE TRIGGER acc_after_update
AFTER UPDATE
ON acc FOR EACH ROW
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE type varchar(10);
DECLARE total_amount DEFAULT 0;
DECLARE name varchar(10)
DECLARE amount INT;
DECLARE cur1 CURSOR FOR SELECT name, type, amount FROM acc;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
OPEN cur1;
REPEAT
FETCH cur1 INTO name, type, amount;
IF NOT done THEN
CASE type
WHEN 'credit' THEN = total_amount + amount;
WHEN 'debit' THEN total_amount = total_amount - amount
END CASE
UPDATE acc
SET amount = total_amount
WHERE name = #name --not sure about this syntax
END IF;
UNTIL done END REPEAT;
CLOSE cur1;
END; //
DELIMITER ;
Hope this helps.
CREATE VIEW [dbo].[vSales]
AS
SELECT ROW_NUMBER() OVER(ORDER BY s.[Name] ) AS 'RowNo',
s.*
, CASE WHEN Type = 'Credit' THEN Amount ELSE - 1 * Amount END As NewAmount
FROM dbo.Sales AS s
GO
SELECT a.[RowNo],
a.[Name],
SUM(b.[NewAmount])
FROM dbo.vSales AS a INNER JOIN dbo.vSales AS b ON a.[RowNo] >= b.[RowNo]
GROUP BY a.[RowNo], a.[Name]
dbo.Sales is the table that holds all the values

All Tables cant' perform INSERT OR UPDATE

I used a stored procedure that uses a cursor to loop through and process an attendance data table on Mariadb 10.1 database after calling the procedure the first time all the tables on the database lost the ability to perform INSERT INTO or UPDATE statements unless the targeted table is truncated first, can any one tell me what went wrong and how to fix it
the procedure that caused the problem:
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `settle_attendance`()
MODIFIES SQL DATA
BEGIN
DECLARE trans_done BOOLEAN DEFAULT FALSE;
DECLARE punchid BIGINT(20);
DECLARE timein DATETIME;
DECLARE utctimein DATETIME;
DECLARE timeout DATETIME;
DECLARE utctimeout DATETIME;
DECLARE inday DATE;
DECLARE outday DATE;
DECLARE todaysdate DATE;
DECLARE attendcur CURSOR FOR
SELECT id, punch_in_utc_time, punch_in_user_time,
punch_out_utc_time, punch_out_user_time
FROM ohrm_attendance_record
ORDER BY id ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET trans_done = TRUE;
OPEN attendcur;
edit_loop: LOOP
SET todaysdate = CURRENT_DATE();
FETCH attendcur INTO punchid, utctimein, timein, utctimeout, timeout;
IF trans_done THEN
CLOSE attendcur;
LEAVE edit_loop;
END IF;
SET inday = DATE(timein);
SET outday = DATE(timeout);
SET todaysdate = CURRENT_DATE();
IF (inday < todaysdate) OR (outday < todaysdate) THEN
CASE
WHEN (timein IS NULL OR timein = '')
OR (utctimein IS NULL OR utctimein = '') THEN
UPDATE ohrm_attendance_record
SET punch_in_utc_time = utctimeout,
punch_in_user_time = timeout,
state = 'PUNCHED OUT'
WHERE punchid = id;
ELSE BEGIN END;
END CASE;
CASE
WHEN (timeout IS NULL OR timeout = '')
OR (utctimeout IS NULL OR utctimeout = '') THEN
UPDATE ohrm_attendance_record
SET punch_out_utc_time = utctimein,
punch_out_user_time = timein,
state = 'PUNCHED OUT'
WHERE punchid = id;
ELSE BEGIN END;
END CASE;
END IF;
END LOOP edit_loop;
END $$
DELIMITER ;
I choose to avoid the question you asked. Instead, let's try to do the query 10 times as fast by getting rid of the pesky CURSOR. The entire Stored Procedure can be done in 2 UPDATEs, no loop:
UPDATE ohrm_attendance_record
SET punch_in_utc_time = utctimeout,
punch_in_user_time = timeout,
state = 'PUNCHED OUT'
WHERE ( timein < CURDATE() OR timeout < CURDATE() )
AND ( ( timein IS NULL OR timein = '' )
OR ( utctimein IS NULL OR utctimein = '' )
);
UPDATE ohrm_attendance_record
SET punch_out_utc_time = utctimein,
punch_out_user_time = timein,
state = 'PUNCHED OUT'
WHERE ( timein < CURDATE() OR timeout < CURDATE() )
AND ( ( timeout IS NULL OR timeout = '' )
OR ( utctimeout IS NULL OR utctimeout = '' )
);
I am, however, suspicious of your tests against timein and timeout.
The queries would be easier to read if you settled on either NULL or '' for missing times.
If you store only UTC values in a TIMESTAMP, you can let the user's timezone take care of coverting to local time -- this would eliminate quite a few columns and simplify the UPDATEs.
I'll make a stab at the question... Do SHOW CREATE PROCEDURE settle_attendance;, you may find that the CHARACTER SET or COLLATION is inconsistent with what you think it should be.

1172 - Result consisted of more than one row in mysql

How can I solve this problem (Result consisted of more than one row in mysql)
DROP PROCEDURE IF EXISTS `doMarksApplication`;
CREATE PROCEDURE `doMarksApplication`(
in kuser varchar(20),
out idpro int(11))
SP:BEGIN
declare no_more_rows int default FALSE;
declare total_marks decimal(10,2) default 0;
declare idfor int(11) default 0;
declare sskod int(5) default getCurSession();
declare bdata int(5) default 0;
declare nopmh varchar(20);
# Data PB [Permohonan Baru] DM [Proses Pemarkahan]
declare cur1 cursor for
select ind_nopmh from pinduk
left join pprses on pro_nopmh = ind_nopmh
where ind_sskod = sskod and
concat(pro_stats,pro_statp) in ('PB','DM') and
not exists (select mar_idnum from pmrkah where mar_nopmh = ind_nopmh)
order by ind_nopmh;
declare continue handler for not found set no_more_rows = TRUE;
begin
select count(ind_nopmh) into bdata
from pinduk
left join pprses on pro_nopmh = ind_nopmh
where ind_sskod = sskod and
concat(pro_stats,pro_statp) in ('PB','DM') and
not exists (select mar_idnum from pmrkah where mar_nopmh = ind_nopmh);
end;
begin
select count(for_idnum) into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
end;
if idfor = 1 and sskod <> 0 then
begin
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
end;
begin
insert into pprmar
(pma_tkmla,pma_msmla,pma_puser,pma_sskod,pma_idfor,pma_bdata)
values
(curdate(),curtime(),kuser,sskod,idfor,bdata);
end;
begin
select last_insert_id() into idpro;
end;
open cur1;
LOOP1:loop
fetch cur1 into nopmh;
if no_more_rows then
close cur1;
leave LOOP1;
end if;
begin
call getMarksAnakPerak(nopmh,#total_perak);
call getMarksAkademik(nopmh,#total_akdmk);
call getMarksSosioekonomi(nopmh,#total_sosio);
end;
set total_marks = #total_perak + #total_akdmk + #total_sosio;
begin
insert into pmrkah
(mar_idpro,mar_nopmh,mar_idfor,mar_perak,mar_akdmk,mar_sosio,mar_total)
values
(idpro,nopmh,idfor,#total_perak,#total_akdmk,#total_sosio,total_marks);
end;
begin
update pprses
set pro_stats = 'D',
pro_statp = 'M',
pro_tkmsk = curdate(),
pro_msmsk = curtime(),
pro_kuser = kuser
where pro_nopmh = nopmh;
end;
end loop;
begin
update pprmar
set pma_tktmt = curdate(),
pma_mstmt = curtime()
where pma_idnum = idpro;
end;
end if;
END;
i have been programming in mysql for 15 years and this is easily the most confusing stored procedure i have ever seen.
None the less, one possible place for your issue is here
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg;
I know it does not seem to be the reason but without knowing the content of the other three stored procedures you are calling this is the only candidate. You should add a limit 1 to it, and to every select into statement that reads from a table (i.e. not a sum() or a count() etc...) as that would always have the potential to cause the error you are seeing.
select for_idnum into idfor from xkod_markah_00_formula
where for_stats = 'A' and
curdate() between for_tkhdr and for_tkhhg limit 1;
In addition, you should comment out the three stored procedure calls and see if the error goes away. My guess is that the issue is one of those stored procedures due to a select into similar to above has more than one row in the result set but does not use limit 1 and does not filter properly.

Mysql Function - not sure where to start

I would like to write a function in Mysql that from a given product code return a formatted string
Here is an example of the calls I need to manually make now to get the result I want.
SELECT p.productcategoryid from products p where (isnull(p.endeffdt) or (p.endeffdt = '0000-00-00') or (p.endeffdt > now())) and p.code='T29R66N1';
T29R66NQ is the product code I need the full path for - the above call returns '38' as the category ID.
I then perform the following select based on the result from above
SELECT name,parentid,productcategorypath FROM productcategory WHERE recid = '38';
This returns
name->Built-In Hobs
parentid->7
productcategorypath=222,7,38
Using that result I then
SELECT name,parentid,productcategorypath FROM productcategory WHERE recid = '7';
giving me
name->Built-In
parentid->222
productcategorypath=222,7
and again, I then do
SELECT name,parentid,productcategorypath FROM productcategory WHERE recid = '222';
which in turn gives me
name->Kitchen & Home Appliances
parentid->0
productcategorypath=222
I stop there because parentid = 0 (it may go on for more iterations but will always end with parent id of 0) but i need the results from the last 3 selects to give me the following string
Kitchen & Home Appliances > Built-In > Built-In Hobs
I would like a mysql function whereby I can use it like
select getpath(code) from products where code='T29R66N1'
Any help would be appreciated.
EDIT:
I managed to figure it myself - here is my function
DROP FUNCTION IF EXISTS mydb.getpath;
CREATE FUNCTION mydb.getpath (itemid VARCHAR(20))
RETURNS varchar(255)
BEGIN
DECLARE path_name varchar(255);
DECLARE tmp_name varchar(255);
DECLARE tmp_parentid INT;
DECLARE tmp_parentid1 INT;
SELECT p.productcategoryid INTO tmp_parentid from products p where (isnull(p.endeffdt) or (p.endeffdt = '0000-00-00') or (p.endeffdt > now())) and p.code=itemid;
myloop:LOOP
SELECT name,parentid INTO tmp_name,tmp_parentid1 FROM productcategory WHERE recid = tmp_parentid;
SET path_name = concat_ws(' > ', tmp_name,path_name);
IF tmp_parentid1!=0 THEN
SET tmp_parentid = tmp_parentid1;
ITERATE myloop;
ELSE
LEAVE myloop;
END IF;
END LOOP;
RETURN path_name;
END;
DROP FUNCTION IF EXISTS mydb.getpath;
CREATE FUNCTION mydb.getpath (itemid VARCHAR(20))
RETURNS varchar(255)
BEGIN
DECLARE path_name varchar(255);
DECLARE tmp_name varchar(255);
DECLARE tmp_parentid INT;
DECLARE tmp_parentid1 INT;
SELECT p.productcategoryid INTO tmp_parentid from products p where (isnull(p.endeffdt) or (p.endeffdt = '0000-00-00') or (p.endeffdt > now())) and p.code=itemid;
myloop:LOOP
SELECT name,parentid INTO tmp_name,tmp_parentid1 FROM productcategory WHERE recid = tmp_parentid;
SET path_name = concat_ws(' > ', tmp_name,path_name);
IF tmp_parentid1!=0 THEN
SET tmp_parentid = tmp_parentid1;
ITERATE myloop;
ELSE
LEAVE myloop;
END IF;
END LOOP;
RETURN path_name;
END;