Recursive count of employees under a manager using mysql - php

I need the list of all employees, following the hierarchy of the manager using MYSQL from the following table. In oracle or mssql it is easy job, but could not find any solution in MySQL. Can anyone help me out how to sort it out.
id name manager
1 John 6
2 Gill 7
3 Ben 2
4 Roy 8
5 Lenin 6
6 Nancy 7
7 Sam 0
8 Dolly 3

If you still can limit the maximal number of levels, here is a solution with a recursive procedure. Since recursive functions are not allowed in MySQL, we have here a function (manager_count), which wraps the results from the recursive procedure. Recursion depth is controlled by the max_sp_recursion_depth variable, which takes 255 as its maximum. Use as follows: SELECT *,manager_count(id) FROM my_table. It's not the optimal solution, since it doesn't take into account already counted branches of the hierarchy (a temporary table can actually serve as a cache).
DELIMITER //
DROP FUNCTION IF EXISTS manager_count//
CREATE FUNCTION manager_count(_manager INT) RETURNS INT
BEGIN
DECLARE _count INT DEFAULT 0;
SET max_sp_recursion_depth = 255;
# manager_count_helper does the job
CALL manager_count_helper(_manager, _count);
# subtract 1, because manager_count_helper will count this manager as well
RETURN _count - 1;
END//
DROP PROCEDURE IF EXISTS manager_count_helper//
CREATE PROCEDURE manager_count_helper(IN _manager INT, INOUT _count INT)
BEGIN
IF EXISTS (SELECT 1 FROM my_table WHERE id = _manager) THEN
BEGIN
DECLARE _next_manager INT DEFAULT 0;
DECLARE done BOOLEAN DEFAULT FALSE;
# cursor to loop through the employees
DECLARE _cursor CURSOR FOR SELECT id FROM my_table WHERE manager = _manager;
# if done, the done variable gets TRUE and it's time too leave
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
# count 1, because this guy should be counted as well
SET _count = _count + 1;
OPEN _cursor;
read_loop: LOOP
FETCH _cursor INTO _next_manager;
IF done THEN LEAVE read_loop;
END IF;
CALL manager_count_helper(_next_manager, _count);
END LOOP;
CLOSE _cursor;
END;
END IF;
END

Related

Select row if at least one possible SUM of its children equal a given number?

I am trying to filter a set of appartments by computing every possible surface the building has got, and then checking if it matches my criterias or not.
Example : I have one building A that is composed of 3 appartments of 200m² each, let's name them 1, 2, and 3.
In my search, I should be able to retrieve that building if my criterias meet these given cases :
I'm looking for 200m² (We have three appartments that match this criteria)
I'm looking for 400m² (We have a few possible SUMS of surface in the building that would match, whether it's 1+2, 1+3, 2+3 doesn't matter)
I'm looking for 600m² (We have the SUM of all surfaces of the building, 1+2+3)
I am able to answer to the first case with a MIN(), so I get the smallest surface available. I am also able to answer to the last case because I get the max available surface possible with a SUM() of all appartments.
But the second case is troubling me, I don't know if i can compute these "Possible SUMS", as I'd call them, inside a query.
Here's the SQL I've got so far, knowing well that it doesn't answer the second case :
SELECT DISTINCT building.*
FROM building AS building
LEFT JOIN appartment a ON a.id_building = building.id
WHERE (
SELECT MIN(a2.surface)
FROM appartment a2
INNER JOIN building ON a2.id_building= building.id
) >= 399
AND (
SELECT SUM(a2.surface)
FROM appartment a2
INNER JOIN building ON lot.id_building= building.id
) <= 401
I tried to work with the whole set of results with PHP rather than in SQL but it's not my prefered option because it would mean the redoing of a lot of work that hasn't been done by me, so it quickly got harder. I also read about HAVING statements but I don't understand where I should put them and what the condition inside should be.
maybe something like this, but i'm not very sure about your table structure
SET #totalSurface= 0;
SELECT #totalSurface:=#totalSurface+`surface`, `id`, `building` FROM `apartment`
WHERE #totalSurface=400
GROUP BY `building` ;
I finally managed to find something using a stored function. I use this cursor with the building id as parameter to loop through every appartement of said building, sorted by surface. Then, for each loop, i add the surface to my total, and check if I am or not in my interval of given criterias. If yes, set return value to true, if not, return value stays false.
Here it goes, I hope it helps if someone is stuck as I was :
CREATE DEFINER=`homestead`#`%` FUNCTION `buildingHasSurface`(`surface_min` INT, `surface_max` INT, `building_id` INT) RETURNS TINYINT(1)
BEGIN
DECLARE curseurFini INT;
DECLARE valide INT;
DECLARE surface_totale INT;
DECLARE surface_row INT;
DECLARE curs CURSOR FOR SELECT surface_dispo FROM appartement WHERE id_building = building_id ORDER BY surface_dispo ASC;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET curseurFini = 1;
OPEN curs;
SET curseurFini = 0;
SET surface_totale = 0;
SET valide = 0;
REPEAT
FETCH curs INTO surface_row;
IF curseurFini = 0 THEN
SET surface_totale = surface_totale + surface_row;
IF surface_totale >= surface_min
AND surface_totale <= surface_max
THEN
SET valide = 1;
END IF;
END IF;
UNTIL curseurFini END REPEAT;
CLOSE curs;
RETURN valide;
END$$

MySql event which updates daily and alters some items in the database based on their contents

Basically, I am creating a simple auction website. I want the code to check every night at 9 PM for items which are set to expire today. If it finds any, it should insert the highest bidder into the database as the buyer. Here is what I have so far, which is not working:
DELIMITER $$
CREATE
EVENT `sold`
ON SCHEDULE EVERY 1 DAY STARTS '2018-05-03 21:00:00'
DO BEGIN
declare amt int;
declare counter int;
set counter=0;
set amt = SELECT COUNT(*) FROM ITEMS;
declare test;
while (counter < amt)
begin
set test = select Visibility FROM ITEMS where Keyy = counter;
if (test == 1)
set test = select sold from ITEMS where Keyy = counter;
if (!test)
set test = select Exp from ITEMS where Keyy = counter;
if (test == CURDATE())
UPDATE ITEMS SET `sold`= `bidder` WHERE Keyy =counter;
set counter = counter + 1;
end
END */$$
DELIMITER ;
Identifiers for DECLAREd variables in MySQL do not begin with #; that is only for "session" variables (that are global to the connection to the database).
Also:
"test" was not declared with a type
== is not the equality operator in MySQL, just =
set variable = select ... can have problems if the select cannot guarantee a single result; some of those may need LIMIT 1 clauses.... though it looks like you're just checking for existence of data, so EXISTS queries might be more helpful for those.
your count increment at the end is missing a SET
*/ does not belong there, it is to close block comments.
Also
DECLARE is permitted only inside a BEGIN ... END compound statement
and must be at its start, before any other statements.
from https://dev.mysql.com/doc/refman/8.0/en/declare.html
This means you cannot interleave DECLAREs and SETs.

Error Code: 1172. Result consisted of more than one row [MySQL]

it is my first time to create a stored procedure in mysql.
What it does is,
first- get count of all records
second- loop through that table 1 by 1
third- compare each entry if it is a duplicate
fourth- insert duplicate in a temporary table
last- display duplicates
It is working properly on 100-200 entries BUT on bigger records up to 500+ (sometimes 25k) it throws a message
Error Code: 1172. Result consisted of more than one row
I have googled this issue but none of them (answers) help me to solve my problem.
Please take a look on my script
BEGIN
DECLARE n INT DEFAULT 0;
DECLARE i INT DEFAULT 0;
DECLARE i_sku VARCHAR(255);
DECLARE i_concatenated_attributes MEDIUMTEXT;
DECLARE f_sku VARCHAR(255);
DECLARE f_offer_type VARCHAR(255);
DECLARE f_name VARCHAR(255);
DECLARE f_product_owner VARCHAR(255);
DECLARE f_listing_city VARCHAR(255);
DECLARE f_listing_area VARCHAR(255);
DECLARE f_price DOUBLE;
DECLARE f_bedrooms INT;
DECLARE f_building_size INT;
DECLARE f_land_size INT;
DECLARE f_concatenated_attributes MEDIUMTEXT;
DECLARE f_duplicate_percentage INT;
SELECT COUNT(*) FROM unit_temp_listing INTO n;
CREATE TEMPORARY TABLE IF NOT EXISTS temp_temp (dup_sku VARCHAR(255), dup_percentage INT, attribs MEDIUMTEXT);
SET i=0;
WHILE i<n DO
-- Get all unit listings (one by one)
SELECT
sku, concat_ws(',',offer_type,name,product_owner,listing_city,listing_area,price,ifnull(bedrooms,0),ifnull(building_size,0),ifnull(land_size,0)) as concatenated_attributes
INTO i_sku, i_concatenated_attributes
FROM unit_temp_listing
limit 1 offset i;
-- Compare one by one (sadla)
SELECT
f.sku, f.offer_type, f.name, f.product_owner, f.listing_city, f.listing_area, f.price, f.bedrooms, f.building_size, f.land_size,
levenshtein_ratio(concat_ws(',',f.offer_type,f.name,f.product_owner,f.listing_city,f.listing_area,f.price,ifnull(f.bedrooms,0),ifnull(f.building_size,0),ifnull(f.land_size,0)),i_concatenated_attributes) as f_duplicate_percentage,
concat_ws(',',f.offer_type,f.name,f.product_owner,f.listing_city,f.listing_area,f.price,ifnull(f.bedrooms,0),ifnull(f.building_size,0),ifnull(f.land_size,0)) as fconcatenated_attributes
INTO f_sku, f_offer_type, f_name, f_product_owner, f_listing_city, f_listing_area, f_price, f_bedrooms, f_building_size, f_land_size, f_duplicate_percentage, f_concatenated_attributes
FROM unit_temp_listing f
WHERE substring(soundex(concat_ws(',',offer_type,name,product_owner,listing_city,listing_area,price,ifnull(bedrooms,0),ifnull(building_size,0),ifnull(land_size,0))),1,10) = substring(soundex(i_concatenated_attributes),1,10)
AND levenshtein_ratio(concat_ws(',',offer_type,name,product_owner,listing_city,listing_area,price,ifnull(bedrooms,0),ifnull(building_size,0),ifnull(land_size,0)),i_concatenated_attributes) > 90
AND f.sku != i_sku;
-- INSERT duplicates
IF(f_sku IS NOT NULL) THEN
INSERT INTO temp_temp (dup_sku, dup_percentage, attribs) VALUES (f_sku, f_duplicate_percentage, f_concatenated_attributes);
SET f_sku = null;
SET f_duplicate_percentage = null;
SET f_concatenated_attributes = null;
END IF;
SET i = i + 1;
END WHILE;
SELECT * FROM temp_temp;
DROP TABLE temp_temp;
End
What is the problem?
Hello to my fellow Developers out there I already solved my issue and I want to share it here.
The issue was, my SECOND SELECT statement inside the WHILE loop was returning multiple rows. I tried using CURSOR but I still got the error message. So I tried to put that second SELECT statement of mine inside the INSERT statement like this
INSERT INTO table_name (columns, ...) SELECT_STATEMENT
and then, problem was solved! But if anyone here have an idea to optimize my query please do help me. I have to process 20k+ records but because of time of execution took too long, I only settle for 500 for 15-20 mins.
Thank you for the 18 views (at the time of writing).
Happy coding!

Mysql stored procedure creation gets 2014 error

Trying to create my first stored procedure, I have researched extensively and could not find a way to input the code via phpMyAdmin so downloaded MYSQL Workbench 6.0 and put my script in that way- but it throws a 2014 error (Commands out of sync, you cannot run this command now). However I notice that Workbench seems to create two lines - the first says running and the second the error.
The routine is to update three tables and insert into another when a contract is issued. Contracts are made up of bundles and they in turn consist of items which relate to specific problems in another table.
Is it my code or should I include some clearing command at the end and if so what should that be? The procedure input is a contract id, user id and their IP address (for logging).
In PHP when, eventually, using a CALL how would I clear results, as I keep seeing Stackoverflow 2014 answers where second calls fail? Also how can I get this onto an ubuntu server either via phpMyAdmin or if with putty where should I ftp the sql script to?
DELIMITER //
DROP PROCEDURE IF EXISTS ContrctAwardStatusLog;
CREATE PROCEDURE ContrctAwardStatusLog( IN c_Id INT(8), IN u_Id INT(11), IN u_Ip varchar(20) )
BEGIN
Block1: BEGIN
DECLARE done INT DEFAULT 0;
DECLARE b_id INT DEFAULT 0;
DECLARE citm_id INT DEFAULT 0;
DECLARE r_id INT DEFAULT 0;
DECLARE c_1 CURSOR FOR SELECT bundle_id FROM bundles WHERE contract_id = c_Id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN c_1;
REPEAT
FETCH c_1 INTO b_id ;
Block2: BEGIN
DECLARE done2 INT DEFAULT 0;
DECLARE c_2 CURSOR FOR SELECT contitem_id, issue_id FROM c_items WHERE bundle_id= b_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = 1;
OPEN c_2;
REPEAT
FETCH c_2 INTO citm_id r_id;
INSERT INTO track_status (rowid, WStatusBy, WStatus, WStatusWhen) VALUES(r_id, u_Id, 13,NOW());
UPDATE h_issues SET WStatus='13' WHERE RowID = r_id;
UPDATE c_items SET act_state='13' WHERE contitem_id=citm_id;
UNTIL done2
END REPEAT;
CLOSE c_2;
END Block2;
UPDATE bundles SET bundle_stat = '13' WHERE bundle_id = b_id;
UNTIL done
END REPEAT;
CLOSE c_1;
END Block1;
END //
DELIMITER ;
I finally sorted this myself.
I needed to put the DROP ..if exists BEFORE the DELIMITER Setting and there was a syntax error in the FETCH after OPEN c_2. It was missing a comma separator.
DROP PROCEDURE IF EXISTS ContrctAwardStatusLog;
DELIMITER //
CREATE PROCEDURE ContrctAwardStatusLog( IN c_Id INT(8), IN u_Id INT(11), IN u_Ip varchar(20) )
BEGIN
Block1: BEGIN
DECLARE done INT DEFAULT 0;
DECLARE b_id INT DEFAULT 0;
DECLARE citm_id INT DEFAULT 0;
DECLARE r_id INT DEFAULT 0;
DECLARE c_1 CURSOR FOR SELECT bundle_id FROM bundles WHERE contract_id = c_Id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN c_1;
REPEAT
FETCH c_1 INTO b_id ;
Block2: BEGIN
DECLARE done2 INT DEFAULT 0;
DECLARE c_2 CURSOR FOR SELECT contitem_id, issue_id FROM c_items WHERE bundle_id= b_id;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done2 = 1;
OPEN c_2;
REPEAT
FETCH c_2 INTO citm_id, r_id;
INSERT INTO track_status (rowid, WStatusBy, WStatus, WStatusWhen) VALUES(r_id, u_Id, 13,NOW());
UPDATE h_issues SET WStatus='13' WHERE RowID = r_id;
UPDATE c_items SET act_state='13' WHERE contitem_id=citm_id;
UNTIL done2
END REPEAT;
CLOSE c_2;
END Block2;
UPDATE bundles SET bundle_stat = '13' WHERE bundle_id = b_id;
UNTIL done
END REPEAT;
CLOSE c_1;
END Block1;
END //
DELIMITER ;

Complicated ORDER BY needed to sort irregular values

I am trying to develop a system to display products from a database on a webpage. Normally this is no problem, except that one manufacturer has several abnormal part numbers I need to sort by properly.
Normally, I could just use an invisible column to sort things out, but then that makes inserting new items in-between two older ones much more difficult.
For example, here are some of the part numbers:
1211
1225
14-302
14-303
2015
23157
3507
35280UP
42-3309
42-3312
4241
Now, the normal order by mfgr produces the above order.
What it SHOULD be is something more like this:
14-302
14-303
42-3309
42-3312
1211
1225
2015
3507
4241
23157
35280UP
What is going to be my best bet on sorting this properly? If they were just being made in a csv file and uploaded afterwards this wouldn't be a problem. But because of automatic database changes, the server will be modifying values in real time. So manually updating it is out of the question. This means that a back end will be required to insert new items, but by what method could I insert an item between another? I would prefer to not resort to something like decimals to give me X in between values (Like I have 1.00 and 2.00 and I want to put another between them so I make it 1.50).
Any help would be appreciated.
EDIT:
The way I would like to sort it is this:
if it has a hyphen, ie 14-302, it is sorted by the 14, and then any 14-xxx is sorted by the numbers after the hyphen.
then, just numbers would be sorted by their actual numner, 802 comes before 45768.
Then any number that has a letter(s) after it will be sorted by the number, the the letter so 123a comes before 123b but after 122. And 123b comes before 124c.
and lastly anything that begins with an M- will be sorted last, and by the numbers after the hyphen.
With your adjusted question the principle is still the same.
My first instinct was to go with extra field but you could use stored function for those.
You still need to divide your partnumber in 3 parts (I take it it's not more than that).
Part1 is an INTEGER and has the first number if existing else fill it with maxint (largest for part1).
Part2 is a CHAR and contains letters if existing.
Part3 is an INTEGER containing the second number if existing.
You can sort by calling a function for these values.
Here is the complete source:
For your convenience a link to a working SQL Fiddle.
It's really sloppy/fast programming but it works. Put together in little time and i'm sure there are points for improvement. You can tweak it yourself. Later you can delete the part1, part2 and part3 from the view. (but leave it in the order by) It's only to show how the sort is done.
DROP PROCEDURE IF EXISTS `uGetParts`//
DROP FUNCTION IF EXISTS `uExtractPart1`//
DROP FUNCTION IF EXISTS `uExtractPart2`//
DROP FUNCTION IF EXISTS `uExtractPart3`//
CREATE PROCEDURE `uGetParts`(
IN ins varchar(50),
OUT num1 int unsigned,
OUT num2 int unsigned,
OUT num3 int unsigned)
NO SQL
BEGIN
SET num1=0;
SET num2=0;
SET num3=0;
WHILE (num1<length(ins)) AND
(SUBSTRING(ins,num1+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num1=num1+1;
END WHILE;
SET num2=num1;
WHILE (num2<length(ins)) AND
(SUBSTRING(ins,num2+1,1) REGEXP('(^[0-9]+$)')=0) DO
SET num2=num2+1;
END WHILE;
SET num3=num2;
WHILE (num3<length(ins)) AND
(SUBSTRING(ins,num3+1,1) REGEXP('(^[0-9]+$)')=1) DO
SET num3=num3+1;
END WHILE;
END//
CREATE FUNCTION `uExtractPart1`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num1>0 THEN
RETURN CAST(SUBSTRING(ins,1,num1) AS UNSIGNED);
ELSE
RETURN ~0 >> 32;
END IF;
END//
CREATE FUNCTION `uExtractPart2`(ins varchar(50))
RETURNS varchar(50) NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num2>num1 THEN
RETURN SUBSTRING(ins,num1+1,num2-num1);
ELSE
RETURN '';
END IF;
END//
CREATE FUNCTION `uExtractPart3`(ins varchar(50))
RETURNS int unsigned NO SQL
BEGIN
DECLARE num1 INT default 0;
DECLARE num2 INT default 0;
DECLARE num3 INT default 0;
call uGetParts(ins,num1,num2,num3);
IF num3>num2 THEN
RETURN CAST(SUBSTRING(ins,num2+1,num3-num2) AS UNSIGNED);
ELSE
RETURN 0;
END IF;
END//
You can call it like this:
SELECT
id,
TYPE,
uExtractPart1(TYPE) as part1,
uExtractPart2(TYPE) as part2,
uExtractPart3(TYPE) as part3
FROM Article
ORDER BY
uExtractPart1(TYPE),
uExtractPart2(TYPE),
uExtractPart3(TYPE)

Categories