MYSQL stored procedure select statement select incorrect ID - mysql

I am not the most knowledge able person when it comes to stored procedures, and this problem is blowing my mind!
Basically I am just trying to run a simple update statement, but the user's ID that I am selecting to update the row is not correct when I run it in the procedure, but if I run the same select statement outside the procedure it returns the expected results.
DELIMITER $$
CREATE PROCEDURE catchUpBbs_Users()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE deleteUser, keepUser VARCHAR(255);
DECLARE id INT;
DECLARE cur1 CURSOR FOR SELECT u.username, b.username, b.id from users u RIGHT JOIN bbs_users b ON u.username = b.username WHERE u.username IS NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur1;
allusers: LOOP
FETCH cur1 INTO keepUser, deleteUser, id;
IF done THEN
LEAVE allusers;
END IF;
IF deleteUser != 'anonymous' THEN
-- This is where the problems start
-- Just for test purposes, returns correct id and username
SELECT id, username FROM bbs_users WHERE username = deleteUser;
-- Just for test purposes, return INCORRECT id, but the CORRECT username
SELECT id, username FROM bbs_users WHERE username = 'anonymous';
-- The update statement that does not work as I want it to, sets the user_id to be what it already it, so no updates occur
UPDATE bbs_posts SET user_id = (SELECT id FROM bbs_users WHERE username = 'anonymous') WHERE user_id = (SELECT id FROM bbs_users WHERE username = deleteUser);
-- delete the user from the bbs_users table
DELETE FROM bbs_users WHERE username = deleteUser;
END IF;
END LOOP allusers;
CLOSE cur1;
END;
$$
DELIMITER ;
When I call the procedure the two test select statements return:
1) These results are both correct
SELECT id, username FROM bbs_users WHERE username = deleteUser;
+------+----------+
| id | username |
+------+----------+
| 13 | deleteme |
+------+----------+
2) The ID is incorrect, but the username is incorrect, the ID should be 1
SELECT id, username FROM bbs_users WHERE username = 'anonymous';
+------+-----------+
| id | username |
+------+-----------+
| 13 | anonymous |
+------+-----------+
When I run the same, minus the variable, select statements outside the procedure both return the correct results
1) These results are both correct
SELECT id, username FROM bbs_users WHERE username = 'deleteme';
+----+----------+
| id | username |
+----+----------+
| 13 | deleteme |
+----+----------+
2) These results are both correct
SELECT id, username FROM bbs_users WHERE username = 'anonymous';
+----+-----------+
| id | username |
+----+-----------+
| 1 | anonymous |
+----+-----------+
What am I doing wrong? Is there something that I missed when it comes to selects and variables when using a stored procedure?
Any help or advice would be much appreciated

The issue is that you have a scalar variable called id defined in your cursor and you are selecting into it, so both of your select statements inside of the cursor are using that stored scalar value for all references to id.
In order to get the "correct" value, you'll need to remove all ambiguity:
-- Just for test purposes, returns correct id and username
SELECT b.id, b.username FROM bbs_users b WHERE b.username = deleteUser;
-- Just for test purposes, return INCORRECT id, but the CORRECT username
SELECT b.id, b.username FROM bbs_users b WHERE b.username = 'anonymous';

have you tryed repeat the sentence "FETCH cur1 INTO keepUser, deleteUser, id" after end if and before end loop...

Does the following work any better. Have cleaned up the locations where you're effectively doing the same query multiple times. Sorry if I've misunderstood the problem - but if I've understood correctly then the query below should operate a bit more efficiently. Also removes all ambiguous field names.
DELIMITER $$
CREATE PROCEDURE catchUpBbs_Users()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE oldID INT;
DECLARE newID INT;
DECLARE cur1 CURSOR FOR
SELECT b.id
FROM users u
RIGHT JOIN bbs_users b
ON u.username = b.username
WHERE u.username IS NULL;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
// Get the new id
SELECT id INTO newID FROM bbs_users WHERE username = 'anonymous';
OPEN cur1;
allusers: LOOP
FETCH cur1 INTO oldID; // b.id
IF done THEN
LEAVE allusers;
END IF;
IF deleteUser != 'anonymous' THEN
-- Just for test purposes, returns correct id and username
SELECT id, username FROM bbs_users WHERE id = oldID;
-- Just for test purposes, return INCORRECT id, but the CORRECT username
SELECT id, username FROM bbs_users WHERE id = newID;
UPDATE bbs_posts SET user_id = newID WHERE user_id = oldID;
-- delete the user from the bbs_users table
DELETE FROM bbs_users WHERE id = oldID;
END IF;
END LOOP allusers;
CLOSE cur1;
END;
$$
DELIMITER ;

Related

How stored procedure output instead of rows count?

My stored procedure always returns 0. I tried unique data and duplicated but the insert is done with success but the return value is always the same #new_identity = 0
CREATE PROCEDURE [dbo].[spAddAuthor]
#Author tyAuthor READONLY,
#new_identity INT = NULL OUTPUT
AS
BEGIN
SET NOCOUNT ON;
-- check if the author exists
IF NOT EXISTS (SELECT Id_Author FROM dbo.Authors
WHERE (dbo.Authors.Username = (SELECT Username FROM #Author)
OR dbo.Authors.phone = (SELECT phone FROM #Author)
OR dbo.Authors.email = (SELECT email FROM #Author)))
BEGIN
INSERT INTO dbo.Authors (Username, sexe, email, phone, address)
SELECT [Username], [sexe], [email], [phone], [address]
FROM #Author
-- output the new row
SELECT #new_identity = ##IDENTITY;
END
ELSE
BEGIN
-- get the author Id if already exists
SELECT #new_identity = (SELECT TOP 1 Id_Author
FROM dbo.Authors
WHERE (dbo.Authors.Username = (SELECT Username FROM #Author)
OR dbo.Authors.phone = (SELECT phone FROM #Author)
OR dbo.Authors.email = (SELECT email FROM #Author)))
END
END
I found that in the declaration of the parameters I put null beside the output and that what caused the problem.
#new_identity INT = NULL OUTPUT
but I don't understand why, I thought the 'null' was like the default value, or when you try to make the parameter optional you add null as default value.
can someone explain, please?

MySQL cursor avg() fetch null

I have a computed column in cursor select query.
drop procedure if exists update_avg;
delimiter $$
create procedure update_avg()
BEGIN
declare score decimal(9,4);
declare id varchar(5);
declare done bool default false;
declare c_update cursor for
select stu_id, avg(score) from chooses group by stu_id;
declare continue HANDLER for not found set done = true;
open c_update;
fetch c_update into id, score;
select id, score; -- test purpose
while(not done) do
update student set average_score = score where student_id = id;
fetch c_update into id, score;
end while;
close c_update;
END
delimiter ;
call update_avg();
When I execute this query it works fine:
select stu_id, avg(score) from chooses group by stu_id;
|stu_id|avg(score)|
|------|----------|
|1 | 73.5000|
|10 | 93.0000|
|11 | 53.0000|
...
And when I call update_avg(); output:
|id|score|
|--|-----|
|1 |NULL |
My question is why this cursor cannot fetch avg(score) from select query and how to fix this.
This operation does not require cursor and could be done using correlated subquery:
update student
set average_score = (SELECT AVG(score)
FROM chooses
WHERE chooses.stu_id = student.student_id)
-- WHERE EXISTS (SELECT 1 FROM chooses WHERE chooses.stu_id = student.student_id)
-- this condition is to avoid updating rows
-- that do not have corresponding rows in chooses table

SQL query for insertion multiple data using for loop

My data set
Tabel Name Users
unique_id uid
123487.1 1000
123488.1
123489.1
123490.1
As shown above this is my existing data and i want to add uid, so my data should be displayed as shown below.
unique_id uid
123487.1 1000
123488.1 1001
123489.1 1002
123490.1 1003
You don't need cursors for this. Just do an update:
select #u := max(user_id)
from users;
update users
set user_id = (#u := #u + 1)
where user_id is null
order by unique_id;
Providing that uid value is the only a single value in your data set, you can use that simple query:
select unique_id, first_value(uid) over(order by unique_id) + row_number() over(order by unique_id) - 1 fv from users;
https://dbfiddle.uk/?rdbms=mysql_8.0&fiddle=d8102c3ef394d304eefa9d42b5a479ba
Best regards.
You can create a procedure like this:
CREATE PROCEDURE uid_update()
BEGIN
DECLARE Done_c INT;
DECLARE v_min_id INT;
declare number_plus int;
declare v_cur int;
DECLARE curs CURSOR FOR
select ROW_NUMBER() OVER (order by unique_id) rn
from testTable
where uid is null;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET Done_c = 1;
SELECT max(uid) INTO number_plus FROM testTable;
OPEN curs;
SET Done_c = 0;
REPEAT
FETCH curs INTO v_cur;
select min(unique_id) into v_min_id
from testTable
where uid is null;
update testTable
set uid = number_plus + v_cur
where uid is null
and unique_id = v_min_id ;
commit;
UNTIL Done_c END REPEAT;
CLOSE curs;
END
And then call that procedure like this:
call uid_update;
The values will then be updated as you asked for.
Here is the DEMO.

Select Inside Insert Statement - MySQL Cursors

I tried some, but I couldn't find the solution, somehow I managed to get this result.
Here is the query:
DELIMITER ##
CREATE PROCEDURE test1(start_date DATE,end_date DATE)
BEGIN
DECLARE done INT DEFAULT FALSE;
DECLARE a INT;
DECLARE present INT;
DECLARE total INT;
-- Declare the cursor
DECLARE id CURSOR
FOR
SELECT staff_id FROM ost_staff;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
-- Open the cursor
DROP TEMPORARY TABLE IF EXISTS reports;
CREATE TEMPORARY TABLE IF NOT EXISTS reports
(
staff_id INT(10),
present INT(10),
total INT(10)
);
OPEN id;
read_loop: LOOP
FETCH id INTO a;
IF done THEN
LEAVE read_loop;
END IF;
INSERT INTO reports(staff_id,present,total)
SELECT (COUNT(I.interval_start)) AS present, DATEDIFF(DATE_ADD(end_date,INTERVAL 1 DAY),start_date) AS total
FROM effort_frequency E
RIGHT OUTER JOIN time_intervals I ON I.interval_start = E.log_date
AND E.staffid=a AND E.log_date BETWEEN start_date AND end_date
LEFT OUTER JOIN ost_holidays H ON H.holiday_date = I.interval_start
WHERE DATE_FORMAT(I.interval_start,'%a') = 'Sun' OR H.holiday_date = I.interval_start OR E.total_effortspent IS NOT NULL;
-- Close the cursor
END LOOP;
CLOSE id;
END ##
I got the below result:
+----------+-----------------+
| staff_id | present | total |
+----------+---------+-------+
| (NULL) | 23 | 24 |
| (NULL) | 22 | 24 |
+----------+---------+-------+
I'm getting (NULL) for staff_id, How can I get the staff_id's there ?
I tried using declared variable 'a' in insert statement, but at that time I got only staff_id, I didn't get the other 2 fields, I can't get the staff_id from the select inside insert statement coz there is some problem.
Now what i need is I need to insert the staff_id from the variable 'a' into that temporary table.
note: I'm really new to this stored procedure, but somehow managed till here, Its good if I get some detail on how to use the Select inside Insert including the solution for this.
Try this -
SELECT a, (COUNT(I.interval_start)) AS present, DATEDIFF(DATE_ADD(end_date,INTERVAL 1 DAY),start_date) AS total
Your INSERT requires three fields, but your SELECT statement only selects two: present and total .
Try:
SELECT E.staffid, (COUNT(I.interval_start))
AS present, DATEDIFF(DATE_ADD(end_date,INTERVAL 1 DAY),start_date) AS total

How to query a MySql table to display the root and its subchild.

UserID UserName ParentID TopID
1 abc Null Null
2 edf 1 1
3 gef 1 1
4 huj 3 1
5 jdi 4 1
6 das 2 1
7 new Null Null
8 gka 7 7
TopID and ParentID is from the userID
I Want to get a user record and its child and subchild record. Here userid1 is the root and its child are userid2 and userid 3. So If the user id is 1 I have to display all the records from userid 1 to userid 6 since all are child and SUbchild of the root. Similarly for userid3 I have to display userid3 and its child Userid 4 and Child of Userid 4 Userid5
if the userid is 3
output should be
Userid Username
3 gef
4 huj
5 jdi
I will know the userid and the topID so how can I do the query to acheive the above result.
SELECT UserID, UserName FROM tbl_User WHERE ParentID=3 OR UserID=3 And TopID=1;
By the above query I am able to display userid 3 and userid 4 I am not able to display userid 5, Kind of struck in it. Need help. Thanks
It is technically possible to do recursive hierarchical queries in MySQL using stored procedures.
Here is one adapted to your scenario:
CREATE TABLE `user` (
`UserID` int(16) unsigned NOT NULL,
`UserName` varchar(32),
`ParentID` int(16) DEFAULT NULL,
`TopID` int(16) DEFAULT NULL,
PRIMARY KEY (`UserID`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO user VALUES (1, 'abc', NULL, NULL), (2, 'edf', 1, 1), (3, 'gef', 1, 1),
(4, 'huj', 3, 1), (5, 'jdi', 4, 1), (6, 'das', 2, 1), (7, 'new', NULL, NULL),
(8, 'gka', 7, 7);
DELIMITER $$
DROP PROCEDURE IF EXISTS `Hierarchy` $$
CREATE PROCEDURE `Hierarchy` (IN GivenID INT, IN initial INT)
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE next_id INT;
-- CURSOR TO LOOP THROUGH RESULTS --
DECLARE cur1 CURSOR FOR SELECT UserID FROM user WHERE ParentID = GivenID;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
-- CREATE A TEMPORARY TABLE TO HOLD RESULTS --
IF initial=1 THEN
-- MAKE SURE TABLE DOESN'T CONTAIN OUTDATED INFO IF IT EXISTS (USUALLY ON ERROR) --
DROP TABLE IF EXISTS OUT_TEMP;
CREATE TEMPORARY TABLE OUT_TEMP (userID int, UserName varchar(32));
END IF;
-- ADD OURSELF TO THE TEMPORARY TABLE --
INSERT INTO OUT_TEMP SELECT UserID, UserName FROM user WHERE UserID = GivenID;
-- AND LOOP THROUGH THE CURSOR --
OPEN cur1;
read_loop: LOOP
FETCH cur1 INTO next_id;
-- NO ROWS FOUND, LEAVE LOOP --
IF done THEN
LEAVE read_loop;
END IF;
-- NEXT ROUND --
CALL Hierarchy(next_id, 0);
END LOOP;
CLOSE cur1;
-- THIS IS THE INITIAL CALL, LET'S GET THE RESULTS --
IF initial=1 THEN
SELECT * FROM OUT_TEMP;
-- CLEAN UP AFTER OURSELVES --
DROP TABLE OUT_TEMP;
END IF;
END $$
DELIMITER ;
CALL Hierarchy(3,1);
+--------+----------+
| userID | UserName |
+--------+----------+
| 3 | gef |
| 4 | huj |
| 5 | jdi |
+--------+----------+
3 rows in set (0.07 sec)
Query OK, 0 rows affected (0.07 sec)
CALL Hierarchy(1,1);
+--------+----------+
| userID | UserName |
+--------+----------+
| 1 | abc |
| 2 | edf |
| 6 | das |
| 3 | gef |
| 4 | huj |
| 5 | jdi |
+--------+----------+
6 rows in set (0.10 sec)
Query OK, 0 rows affected (0.10 sec)
Time to point out some caveats:
Since this is recursively calling a stored procedure, you need to increase the size of max_sp_recursion_depth, which has a max value of 255 (defaults to 0).
My results on a non-busy server with the limited test data (10 tuples of the user table) took 0.07-0.10 seconds to complete. The performance is such that it might be best to put the recursion in your application layer.
I didn't take advantage of your TopID column, so there might be a logic flaw. But the two test-cases gave me the expected results.
Disclaimer: This example was just to show that it can be done in MySQL, not that I endorse it in anyway. Stored Procedures, temporary tables and cursors are perhaps not the best way to do this problem.
Well not a pretty clean implementation but since you need only the children and sub-children, either of these might work:
Query1:
SELECT UserID, UserName
FROM tbl_user
WHERE ParentID = 3 OR UserID = 3
UNION
SELECT UserID, UserName
FROM tbl_user
WHERE ParentID IN (SELECT UserID
FROM tbl_user
WHERE ParentID = 3);
Query 2:
SELECT UserID, UserName
FROM tbl_user
WHERE UserID = 3
OR ParentID = 3
OR ParentID IN (SELECT UserID
FROM tbl_user
WHERE ParentID = 3);
EDIT 1: Alternatively, you may modify your table structure to make it more convenient to query all children of a particular category. Please follow this link to read more on storing hierarchical data in MySQL.
Also, you may think on storing your data hierarchically in a tree-like fashion that is very well explained in this article.
Please note that each method has its trade-offs with respect to retrieving desired results vs adding/removing categories but I'm sure you'll enjoy the reading.
This is one of the best articles I've seen for explaining the "Modified Preorder Tree Traversal" method of storing tree-like data in a SQL-style database.
http://www.sitepoint.com/hierarchical-data-database/
The MPTT stuff starts on page 2.
Essentially, you store a "Left" and a "Right" value for each node in the tree, in such a manner that to get all children of ParentA, you get the Left and Right for ParentA, then
SELECT *
FROM TableName
WHERE Left > ParentLeft
AND Right < ParentRight
To get all of the parents of the selected child (user_id = 3 in this example):
SELECT
#parent_id AS _user_id,
user_name,
(
SELECT #parent_id := parent_id
FROM users
WHERE user_id = _user_id
) AS parent
FROM (
-- initialize variables
SELECT
#parent_id := 3
) vars,
users u
WHERE #parent_id <> 0;
To get all of the children of a selected user_id
SELECT ui.user_id AS 'user_id', ui.user_name AS 'user_name', parent_id,
FROM
(
SELECT connect_by_parent(user_id) AS user_id
FROM (
SELECT
#start_user := 3,
#user_id := #start_user
) vars, users
WHERE #user_id IS NOT NULL
) uo
JOIN users ui ON ui.user_id = uo.user_id
This requires the following function
CREATE FUNCTION connect_by_parent(value INT) RETURNS INT
NOT DETERMINISTIC
READS SQL DATA
BEGIN
DECLARE _user_id INT;
DECLARE _parent_id INT;
DECLARE _next INT;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET #user_id = NULL;
SET _parent_id = #user_id;
SET _user_id = -1;
IF #user_id IS NULL THEN
RETURN NULL;
END IF;
LOOP
SELECT MIN(user_id)
INTO #user_id
FROM users
WHERE parent_id = _parent_id
AND user_id > _user_id;
IF #user_id IS NOT NULL OR _parent_id = #start_with THEN
RETURN #user_id;
END IF;
SELECT user_id, parent_id
INTO _user_id, _parent_id
FROM users
WHERE user_id = _parent_id;
END LOOP;
END
This example heavily uses session variables which many sql users may be unfamiliar with, so here's a link that may provide some insight: session variables