How do I use a recursive procedure in a loop in MySQL - mysql

I have an employees table with demographics data. This table has two fields I need: Employee ID and Manager ID. I need to build a join table that would look like this:
Employee IDManager ID
1 2
1 3
1 4
2 3
2 4
So I need the join table to show, for each employee, all the people above them. In case of employee 1 his direct manager is 2, his manager's manager is 3 and his manager's manager's manager is 4.
I have a MySQL procedure that seems to be working when called for an employee:
CREATE PROCEDURE chainReaction (IN employee_id int, IN orig_id int)
BEGIN
DECLARE manager_id int default NULL;
SET ##SESSION.max_sp_recursion_depth = 255;
SELECT ManagerID
INTO manager_id
FROM employees
WHERE EmployeeID = employee_id;
IF( manager_id is not null) THEN
INSERT INTO joinTable(EmpID, SupID) VALUES(
orig_id,manager_id
);
CALL chainReaction(manager_id, orig_id);
end if;
END;
Problem 1:
When calling chainReaction it looks like I need to provide the employee ID twice to the procedure because otherwise the original employee ID is 'lost' and the resulting inserts for employee 1 look like this:
Employee IDManager ID
1 2
2 3
4 4
So the Employee ID doesn't stay fixed to 1 as I needed it. Again, this procedure works when called as it is now: chainReaction(1,1);
But here's problem 2:
I have some code to loop through all rows in the employee table and call chainReaction on them:
CREATE PROCEDURE RowPerRow ()
BEGIN
DECLARE n int default 0;
DECLARE i int default 0;
DECLARE employee_id int default null;
SELECT COUNT(*) FROM employees INTO n;
SET i=0;
WHILE i<n DO
SELECT EmployeeID FROM employees WHERE LIMIT i,1 INTO employee_id;
CALL chainReaction(employee_id,employee_id);
END WHILE;
END;
The problem is that if I call this RowPerRow() procedure the employee column in the join table is stuck to the first employee ID no matter what row is being processed.

Looks like my procedures work just fine, what I was missing in the RowPerRow procedure is incrementing the counter (SET i = i + 1;). Here's the code fixed.
CREATE PROCEDURE RowPerRow ()
BEGIN
DECLARE n int default 0;
DECLARE i int default 0;
DECLARE employee_id int default null;
SELECT COUNT(*) FROM employees INTO n;
SET i=0;
WHILE i<n DO
SELECT EmployeeID FROM employees WHERE LIMIT i,1 INTO employee_id;
CALL chainReaction(employee_id,employee_id);
SET i = i + 1;
END WHILE;
END;

Related

MySQL Creating Stored Procedure to compare two values

I tried creating a stored procedure from the question below but when I test it using the values from my tables in my database, it is not giving me the correct output. Meaning if it should output 1, it outputs 2 instead.
Please help me figure what I did wrong.
Question and my sql statement is below.
Question: Create a stored procedure to compare the actual incomes of two employees. If the former is higher than the latter, output 1; otherwise, output 2.
Delimiter $$
CREATE PROCEDURE real_income(in employeeID_1 varchar(6), in employeeID_2 varchar(6),
out c int)
BEGIN
IF (SELECT income-outcome FROM salary WHERE employeeID=employeeID_1)>
(SELECT income-outcome FROM salary WHERE employeeID=employeeID_2) THEN SET c = 1;
ELSE SET c = 2;
END IF;
END $$
Sample data from salary table
You selection for the column is bad as mysql interprets it a subtraction of two columns.
So use backticks, or rename it
CREATE TABLE salary(employeeID INT,`income-outcome` DECIMAL(10,2))
INSERT INTO salary VALUEs (1,10.2),(2,20.2)
CREATE PROCEDURE real_income(in employeeID_1 varchar(6), in employeeID_2 varchar(6),
out c int)
BEGIN
IF (SELECT `income-outcome` FROM salary WHERE employeeID=employeeID_1)>
(SELECT `income-outcome` FROM salary WHERE employeeID=employeeID_2) THEN SET c = 1;
ELSE SET c = 2;
END IF;
END
CALL real_income(1,2,#i)
SELECT #i
| #i |
| -: |
| 2 |
db<>fiddle here
A better solution is this, so you can also check for equality
CREATE PROCEDURE real_income(in employeeID_1 varchar(6), in employeeID_2 varchar(6),
out c int)
BEGIN
SET c = (CASE WHEN (SELECT `income-outcome` FROM salary WHERE employeeID=employeeID_1)>
(SELECT `income-outcome` FROM salary WHERE employeeID=employeeID_2) THEN 1
WHEN (SELECT `income-outcome` FROM salary WHERE employeeID=employeeID_1) =
(SELECT `income-outcome` FROM salary WHERE employeeID=employeeID_2) THEN 0
ELSE 2 END);
END

Debugging stored procedure in SQL group function

[
For the cname='Liam', there are 3 card_id
i.e. 87260101, 87260153, 87501026
We want to find out how many extra card_id this person has, means we want output=2.
For the cname='Elizabeth', there are 2 card_id.
i.e.87501022, 87501000 and we want an output of 1.
For the others who only has one card_id which correspond to an empty loss_report_date,
count(loss_report_date)=0, the output=0
For the code below, we get the error code=1111, invalid use of group function, but the store procedure does not report any error when we run procedure alone.
The DBMS we are using is Innodb.
DELIMITER $$
CREATE PROCEDURE Gettransaction(IN cname varchar(50), OUT cnt int)
BEGIN
#select card_id if there is no old_id, if t1.card_id=t2.old_id then use card_id
DECLARE maxcardid char(8);
DECLARE cnt int default 1;
DECLARE nextMax int;
set maxcardid = (select max(card_id) from card
group by cname);
WHILE cname in (select cname from card where count(loss_report_date)>=1)
DO
set nextMax = (select max(card_id)
from card
where card_id < maxcardid);
set cnt = cnt + 1;
set maxcardid = nextMax;
END WHILE;
END$$
DELIMITER ;
call Gettransaction('Liam', #output);
select #output

Loop items in list of user defined table types

I have simple table with 2 columns CarId int, primary key and CarName, varchar.
I need to create a stored procedure which accepts a list of cars. If car with CarId doesn't exist, I want to insert that car, and if already exists, I want to update it.
I created a user-defined table type CarType:
CREATE TYPE dbo.CarType
AS TABLE
(
CARID int null,
CARNAME varchar(800) not null,
);
and stored procedure InsertCars:
CREATE PROCEDURE dbo.InsertCars
#Cars AS CarType READONLY
AS
DECLARE #CarCount INT = 0;
DECLARE #Counter INT = 0;
BEGIN
SET #CarsCount = (SELECT COUNT(CarId) FROM #Cars);
WHILE(#Counter < #CarsCount)
BEGIN TRY
--how get item from list Cars?
#CurrentCar = Cars(#Counter)
IF EXISTS(SELECT 1 FROM Cars WHERE CarsId = CurrentCar.CarId)
--if doesn’t exist insert
BEGIN
INSERT INTO CARS(CARID, CARNAME)
SELECT * FROM #CurrentCar;
END
ELSE
BEGIN
--if exist update
END
END
SET #Counter= #Counter + 1;
END TRY
BEGIN CATCH
Print (ERROR_MESSAGE());
END CATCH
END
I don't know how get current car in loop from list of cars (parameter Cars in the stored procedure).
Or some elegant solution for this problem.
It seems you may get rid of loop here:
CREATE PROCEDURE dbo.InsertCars
#Cars AS CarType READONLY
AS
BEGIN
SET NOCOUNT ON;
UPDATE c
SET c.CARNAME = c2.CARNAME
FROM Cars c
JOIN #Cars c2 on c2.CARID = c.CARID;
INSERT INTO Cars(CARID, CARNAME)
SELECT c.CARID, c.CARNAME
FROM #Cars c
WHERE NOT EXISTS (SELECT 1 FROM Cars WHERE CARID = c.CARID);
END
or (using merge construct):
CREATE PROCEDURE dbo.InsertCars
#Cars AS CarType READONLY
AS
BEGIN
SET NOCOUNT ON;
MERGE Cars AS target
USING (SELECT CARID, CARNAME FROM #Cars) AS source (CARID, CARNAME)
ON (target.CARID = source.CARID)
WHEN MATCHED THEN
UPDATE SET CARNAME = source.CARNAME
WHEN NOT MATCHED THEN
INSERT (CARID, CARNAME)
VALUES (source.CARID, source.CARNAME);
END

Stored procedure using if statement

I have here stored procedure:
delimiter //
create procedure insert(p int, n varchar(10), d date, q int)
begin
select pname if(pname!=n)
then
insert into prod(p,n,d,q)values(p,n,d,q)
else quant=quant + q;
from prod;
end;
//
This stored procedure will add records to the table prod if pname and n are not equal ELSE if they are equal, the quant will be updated and added by the value of q. This idea is not working can you help me? How to achieve this one?
....
BEGIN
DECLARE p CHAR DEFAULT NULL;
SELECT pname FROM prod WHERE pname = n LIMIT 1 INTO p FOR UPDATE;
IF(p) THEN
UPDATE prod SET quant = quant + q WHERE pname = n;
ELSE
INSERT INTO prod(p,n,d,q) values(p,n,d,q);
END IF;
END;
There are simpler ways to do it if you have a UNIQUE key on pname in prod.

SQL finding ancestors/decendants in a self-referencing table

I have a table which references itself, like this:
CREATE TABLE Foo (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
parent INT NULL,
name VARCHAR (30) NOT NULL,
FOREIGN KEY (parent) REFERENCES Foo(id) ON DELETE CASCADE);
Sample data:
id parent name
1 NULL a
2 NULL b
3 1 a1
4 1 a2
5 3 a1x
6 3 a2x
I want to write queries which will list the ancestors and decenders of a given row, e.g.
CALL find_ancestors('a1x')
Will return
id name
3 a1
1 a
and
CALL find_descendants('a')
Will return
id name
3 a1
5 a1x
How can I write these stored procedures for MySQL 5? Thanks
Bonus question for bounty: also select the distance of the returned row from the source and pass a maximum-distance parameter to the procedure, e.g.
CALL find_ancestors('a1x')
Will return
id name distance
3 a1 1
1 a 2
and
CALL find_ancestors_bounded('a1x',1)
Will return
id name distance
3 a1 1
Lets say we have a table with four elements, id, item, class and parent_id. We want to have the complete Ancestors of any given Item, what we need to do is a custom mysql function that will actually loop through every record looking for a match for our item parent_id, once it founds a match, if the matched item has a parent_id, it will start looping again, and so forth. Every time our function finds a match, it will store it in a comma separated string that will be returned in the end (ex: 1,2,3,4)
Our function would look something like this:
DELIMITER $$
DROP FUNCTION IF EXISTS `junk`.`GetAncestry` $$
CREATE FUNCTION `junk`.`GetAncestry` (GivenID INT) RETURNS VARCHAR(1024)
DETERMINISTIC
BEGIN
DECLARE rv VARCHAR(1024);
DECLARE cm CHAR(1);
DECLARE ch INT;
SET rv = '';
SET cm = '';
SET ch = GivenID;
WHILE ch > 0 DO
SELECT IFNULL(parent_id,-1) INTO ch FROM
(SELECT parent_id FROM pctable WHERE id = ch) A;
IF ch > 0 THEN
SET rv = CONCAT(rv,cm,ch);
SET cm = ',';
END IF;
END WHILE;
RETURN rv;
END $$
DELIMITER ;
This code is authored by RolandoMySQLDBA