count number of hierarchical childrens in sql - mysql

I have a table that stores parent and left child and right child information. How do i count number of children belongs that parent?
for example my table structure is:
parent left right
--------------------
1 2 3
3 4 5
4 8 9
5 10 11
2 6 7
9 12 null
How do I count number of sub nodes for any parent. For example 4 contains following hierarchical child nodes - 8,9,12 so number of children are 3.
3 contains following sub nodes -> 4,5,10,11,8,9,12 so total number of children 7.
How do I achieve this using SQL query?

create table mytable
( parent int not null,
cleft int null,
cright int null
)
insert into mytable (parent,cleft,cright) values (1,2,3);
insert into mytable (parent,cleft,cright) values (2,6,7);
insert into mytable (parent,cleft,cright) values (3,4,5);
insert into mytable (parent,cleft,cright) values (4,8,9);
insert into mytable (parent,cleft,cright) values (5,10,11);
insert into mytable (parent,cleft,cright) values (6,null,null);
insert into mytable (parent,cleft,cright) values (7,null,null);
insert into mytable (parent,cleft,cright) values (8,13,null);
insert into mytable (parent,cleft,cright) values (9,12,null);
insert into mytable (parent,cleft,cright) values (10,null,null);
insert into mytable (parent,cleft,cright) values (12,null,null);
insert into mytable (parent,cleft,cright) values (13,null,17);
insert into mytable (parent,cleft,cright) values (17,null,null);
DELIMITER $$
CREATE procedure GetChildCount (IN parentID INT)
DETERMINISTIC
BEGIN
declare ch int;
declare this_left int;
declare this_right int;
declare bContinue boolean;
declare count_needs_scan int;
create temporary table asdf999 (node_id int,processed int);
-- insert into asdf999 (node_id,processed) values (1,0);
-- update asdf999 set processed=1;
SET ch = parentID;
set bContinue=true;
while bContinue DO
-- at this point you are sitting at a ch (anywhere in hierarchy)
-- as you are looping and getting/using children
-- save non-null children references: -----------------------------
select cleft into this_left from mytable where parent=ch;
if !isnull(this_left) then
insert asdf999 (node_id,processed) select this_left,0;
end if;
select cright into this_right from mytable where parent=ch;
if !isnull(this_right) then
insert asdf999 (node_id,processed) select this_right,0;
end if;
-- -----------------------------------------------------------------
select count(*) into count_needs_scan from asdf999 where processed=0;
if count_needs_scan=0 then
set bContinue=false;
else
select node_id into ch from asdf999 where processed=0 limit 1;
update asdf999 set processed=1 where node_id=ch;
-- well, it is about to be processed
end if;
END WHILE;
select count(*) as the_count from asdf999;
drop table asdf999;
END $$
DELIMITER ;
call GetChildCount(2); -- answer is 2
call GetChildCount(4); -- answer is 5
I could supply a version that creates a dynamically named table (or temp table) and clobbers it at end if you want . "dynamic sql / prepare statment" inside of a procedure. that way users won't step on each other with shared use of the work table asdf999. so this is not production ready. but the above gives you an idea of the concept

Related

MySQL - Insert Into 2 Tables on 1 Procedure

I need to create a procedure for inserting records into 2 tables, but on the second table, I want to insert the last ID that was inserted on the first table. Could anyone help me with this?
This is my query
DELIMITER //
DROP PROCEDURE IF EXISTS ROOM_FEATURE_INSERT;
CREATE PROCEDURE ROOM_FEATURE_INSERT (propID INT, featID INT, featNme VARCHAR(50))
BEGIN
-- BEGIN CHECK
IF NOT EXISTS
(
SELECT rFeatureName FROM COMPANY_T3s71.PROPERTY_RFEATURE PRFE
INNER JOIN COMPANY_T3s71.ROOM_FEATURE RFEA ON PRFE.rFeatureID=RFEA.rFeatureID
WHERE BINARY rFeatureName = featNme AND propertyID = propID
)
AND
(
SELECT rFeatureName FROM COMPANY_T3s71.ROOM_VIEW
WHERE BINARY rFeatureName = featNme
)
THEN
-- IF NOT EXISTS INSERT INTO 1st TABLE
INSERT INTO COMPANY_T3s71.ROOM_FEATURE (rFeatureName) VALUES (featNme);
END IF;
-- END CHECK
-- BEGIN CHECK 2nd TABLE
IF NOT EXISTS
(
SELECT propertyID, rFeatureID FROM COMPANY_T3s71.PROPERTY_RFEATURE
WHERE rFeatureID = featID AND propertyID = propID
)
THEN
-- IF NOT EXISTS INSERT INTO 2nd TABLE
INSERT INTO COMPANY_T3s71.PROPERTY_RFEATURE (propertyID, rFeatureID) VALUES (propID, featID);
END IF;
-- END CHECK 2nd TABLE
END
DELIMITER ;
How do we pass the featID param, when we just inserted it on the first INSERT query?
Thank you before hand.
Use SET featID = LAST_INSERT_ID(); after the first query and then use the variable
INSERT INTO COMPANY_T3s71.ROOM_FEATURE (rFeatureName) VALUES (featNme);
SET featID = LAST_INSERT_ID();
However, if the data is not insert at anytime then you have to make query in the if block to set the value for featID.

MySQL 1:10 fixed relationship. How to implement it at database level?

Is it possible to implement a 1:N relationship that has 10 rows of the many referencing 1 row from the one table at most?
// ID int
INSERT INTO one VALUES (1);
// ...
INSERT INTO one VALUES (25);
//ID int, one_id int
INSERT INTO many VALUES (1,1);
// ...
INSERT INTO many VALUES (1,10);
INSERT INTO many VALUES (1,11); // ERROR!
INSERT INTO many VALUES (2,11); // working
// ...
INSERT INTO many VALUES (2,20); // working
INSERT INTO many VALUES (2,21); // ERROR!
You can do this with a trigger:
create trigger trg_mytable_max10 before insert on mytable
for each row
begin
declare cnt int;
set cnt = (select count(*) from mytable where col1 = new.col1);
if cnt = 10 then
signal sqlstate '45000' set message_text = 'only 10 records per col1 allowed';
end if;
end;
http://rextester.com/EORH56497

WHILE Loop with insert not working as expected

I am try to find all child and sub-child of a given parent, but not getting desired output if I use WHILE loop. Surprisingly, if I execute the scripts manually, it works as expected. Please have a look at below script.
DROP TABLE IF EXISTS test;
CREATE TABLE test (id INT, parent_id INT);
INSERT INTO test VALUES (1,NULL);
INSERT INTO test VALUES (2,1);
INSERT INTO test VALUES (3,2);
INSERT INTO test VALUES (4,2);
INSERT INTO test VALUES (5,3);
INSERT INTO test VALUES (6,4);
INSERT INTO test VALUES (7,2);
INSERT INTO test VALUES (8,4);
INSERT INTO test VALUES (9,1);
INSERT INTO test VALUES (10,1);
COMMIT;
DELIMITER $$
USE `test`$$
DROP PROCEDURE IF EXISTS `get_parent_child_hierarchy`$$
CREATE PROCEDURE `get_parent_child_hierarchy`(Parent_ID INT)
BEGIN
DECLARE `rowcount` INT DEFAULT 1;
DROP TABLE IF EXISTS Temp;
CREATE TABLE Temp(ParentID INT, ChildID INT, Stage INT);
INSERT INTO Temp(ParentID, ChildID, Stage)
SELECT Parent_ID, Parent_ID, 2;
WHILE rowcount > 0 DO
UPDATE Temp
SET Stage = 1
WHERE Stage = 2;
COMMIT;
INSERT INTO Temp(ParentID, ChildID, Stage)
SELECT Parent_ID, id, 2
FROM Test T
WHERE Parent_id IN (SELECT ChildID
FROM Temp
WHERE Stage = 1
AND Temp.ChildID <> T.id);
UPDATE Temp
SET Stage = 0
WHERE Stage = 1;
COMMIT;
SET rowcount = (SELECT COUNT(ChildID)
FROM Temp
WHERE Stage = 2);
END WHILE;
SELECT *
FROM Temp
WHERE Parent_ID <> ChildID;
DROP TABLE IF EXISTS Temp;
END$$
DELIMITER ;
Input: 2
Expected Output:
ParentID | ChildID | Stage
---------|---------|------
2 | 3 | 0
2 | 4 | 0
2 | 5 | 0
2 | 6 | 0
2 | 7 | 0
2 | 8 | 0
But unfortunately, when the run the script using WHILE loop, I get all the rows in the input.
Any help is appreciated.
I agree this is moreover specific problem than issue with technical stuff. But I wonder if there is any issue with MySQL, when you use WHILE loop and INSERT.
You defined input parameter's name as Parent_ID which is a column in table test, this is reason that you did not get what you expected.
Try following;)
CREATE PROCEDURE `get_parent_child_hierarchy`(pid INT)
BEGIN
DECLARE `rowcount` INT DEFAULT 1;
DROP TABLE IF EXISTS Temp;
CREATE TABLE Temp(ParentID INT, ChildID INT, Stage INT);
INSERT INTO Temp(ParentID, ChildID, Stage)
SELECT pid, pid, 2;
WHILE rowcount > 0 DO
UPDATE Temp
SET Stage = 1
WHERE Stage = 2;
COMMIT;
INSERT INTO Temp(ParentID, ChildID, Stage)
SELECT pid, id, 2
FROM test T
WHERE parent_id IN (SELECT ChildID
FROM Temp
WHERE Stage = 1
AND Temp.ChildID <> T.id);
UPDATE Temp
SET Stage = 0
WHERE Stage = 1;
COMMIT;
SET rowcount = (SELECT COUNT(ChildID)
FROM Temp
WHERE Stage = 2);
END WHILE;
SELECT *
FROM Temp
WHERE pid <> ChildID;
DROP TABLE IF EXISTS Temp;
END

Insert into change value if duplicate entry

I have this sql query:
INSERT INTO my_table
SELECT id, name, type
FROM other_table
I have this row: 1,bob,male
And I try insert: 1,bob,male
So, I have a duplicate entry error and I want change my insert value with an increment by one so after I would have two rows:
bob,male <=NOT UPDATED
bob,male
I don't want update the existing row, if I have a duplicate entry error. The insert increments the id value. So, I think ON DUPLICATE KEY isn't the solution.
UPDATE:
If I use a trigger like this:
DELIMITER |
CREATE TRIGGER incremente_primary BEFORE INSERT ON my_table FOR EACH ROW
BEGIN
IF( EXISTS( SELECT * FROM my_table )) THEN
SET NEW.id = NEW.id + 1;
END IF;
END |
DELIMITER ;
It doesn't work because a trigger can read only one line.
As per your requirement, you need to set auto_increment property for your id and then just insert other columns except id, so that it can be auto_increment like below-
INSERT INTO my_table (name,type)
SELECT name, type
FROM other_table;
If you just want to ignore if there is duplicate then you can use-
INSERT IGNORE INTO my_table
SELECT id,name, type
FROM other_table;
make sure that the primary key isn't the name or the type. Because you can input duplicate rows as long as you do not duplicate primary keys
I find a solution, i use a cursor:
DROP PROCEDURE proc_incremente;
DELIMITER |
CREATE PROCEDURE proc_incremente()
BEGIN
DECLARE var_name, var_type VARCHAR(100);
DECLARE var_id INT;
DECLARE end TINYINT(1) DEFAULT 0;
DECLARE cursor_incremente CURSOR
FOR SELECT * FROM other_table;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET end = 1;
OPEN cursor_incremente;
loop_cursor: LOOP
FETCH cursor_incremente INTO var_id, var_name, var_type;
IF end = 1 THEN
LEAVE loop_cursor;
END IF;
WHILE ( EXISTS( SELECT * my_table WHERE id = var_id) IS TRUE) DO
SET var_id = var_id + 1;
END WHILE;
INSERT INTO my_table(id,name,type) VALUES(var_id,var_name,var_type);
END LOOP;
CLOSE cursor_incremente;
END |
DELIMITER ;
CALL proc_incremente();

Loop over SQL stored procedure

I have a stored procedure In which insert query fire on two table.
masters_table,child_table as you see here #Value1,#Value2,#Value3,#Value4 having parameter. I have written insert statement for each block, can I make use loop here to avoid multiple insert write for each block, suppose I have nth value then i have to write nth time block wise insert query, any better way to achieve this task
Create proc sp_insert_master_child
(
#Details_Data custom_tb READONLY,
#Value1 nvarchar(500),
#Value2 nvarchar(500),
#Value3 nvarchar(500),
#Value4 nvarchar(500),
#Value5 nvarchar(500),
#Value6 nvarchar(500),
#Value7 nvarchar(500)
)
as begin
declare #id bigint
------- block 1
if #Value1 is not null
begin
insert into masters_table (col1,col2,col3))
select Substring(#Value1, 1,Charindex(',', #Value1)-1) as col_value_1,
Substring(#Value1, Charindex(',', #Value1)+1, LEN(#Value1)) as col_value_2
select #id = ##IDENTITY
insert into child_table (col1,col2,col3)
SELECT #id,col_val_2,col_val_3 FROM #Details_Data WHERE blockType='blk_1';
end
------ block 2
if #Value2 is not null
begin
insert into masters_table (col1,col2,col3))
select Substring(#Value2, 1,Charindex(',', #Value2)-1) as col_value_1,
Substring(#Value2, Charindex(',', #Value2)+1, LEN(#Value2)) as col_value_2
select #id = ##IDENTITY
insert into child_table (col1,col2,col3)
SELECT #id,col_val_2,col_val_3 FROM #Details_Data WHERE blockType='blk_2';
end
------ block 3
if #Value3 is not null
begin
insert into masters_table (col1,col2,col3))
select Substring(#Value3, 1,Charindex(',', #Value3)-1) as col_value_1,
Substring(#Value3, Charindex(',', #Value2)+1, LEN(#Value3)) as col_value_2
select #id = ##IDENTITY
insert into child_table (col1,col2,col3)
SELECT #id,col_val_2,col_val_3 FROM #Details_Data WHERE blockType='blk_3';
end
------ block 4
.......
------ block 5
.......
You can insert all parameters into a table .Use while loop and read one by one row (parameter) from table .
Ex:
declare #x int
set #x= 1
declare #tmp table ( val int )
while (#x < noofrows)
begin
***********
Use Top #x value to get row(parameter in your case) from table
perform your operation
*************
increment x(set #X=#x+1)
end