MySQL SELECT pattern replace with results from another table - mysql

How do I substitute values in a SELECT with text matches to rows in another table?
**products**
> +-------+--------------------------------------------------+
> | id | description |
> +-------+--------------------------------------------------+
> | 10001 | This product is %block1% and %block4% |
> | 10002 | This product is %block2%, %block3%, and is %block4% |
> +-------+--------------------------------------------------+
**descriptions**
> +-----------+-------------------+
> | blockname | blockcontent |
> +-----------+-------------------+
> | %block1% | 5 feet tall |
> | %block2% | matte white |
> | %block3% | makes music |
> | %block4% | made of real wood |
> +-----------+-------------------+
Ideally, I'd like to run a single query that returns
> +-------+--------------------------------------------------+
> | id | newdescription |
> +-------+--------------------------------------------------+
> | 10001 | This product is 5 feet tall and made of real wood |
> | 10002 | This product is matte white, makes music, and is made of real wood |
> +-------+--------------------------------------------------+
I've investigated REPLACE() and SUBSTITUTE() but they don't appear to be what I am looking for.

You can create function:
DROP FUNCTION my_subst;
DELIMITER $$
CREATE FUNCTION my_subst(str VARCHAR(255))
RETURNS VARCHAR(255)
BEGIN
DECLARE pos1 INT DEFAULT 0;
DECLARE pos2 INT DEFAULT 0;
DECLARE token VARCHAR(255);
DECLARE new_token VARCHAR(255);
label1: LOOP
SET pos1 = LOCATE('%%',str);
IF pos1 = 0 THEN
LEAVE label1;
END IF;
SET pos2 = LOCATE('%%',str,pos1+1);
IF pos2 = 0 THEN
LEAVE label1;
END IF;
SET token = SUBSTR(str,pos1,pos2-pos1+2);
SELECT blockcontent INTO new_token FROM descriptions WHERE blockname = token;
SET str = REPLACE(str,token,new_token);
END LOOP label1;
RETURN str;
END;
$$
DELIMITER ;
and then just use this function to do substitutions.

Related

Calculation in Nested Stored Procedure

I'm using a data sample looking like that :
| Parent | Child | Order | Quantity |
|:-----------|:--------:| -----:|---------:|
| Meal | Element 1| 10 |1 |
| Meal | Element 2| 20 |1 |
| Element 1 | Recipe 1 | 10 |0.2 |
| Element 1 | Recipe 2 | 20 |0.5 |
| Recipe 1 | Recipe 3 | 10 |0.1 |
| Recipe 3 | Raw Mat1 | 10 |1 |
| Element 2 | Recipe 4 | 10 |0.6 |
| Element 2 | Recipe 5 | 20 |0.3 |
| Recipe 4 | Recipe 6 | 10 |1.2 |
| Recipe 6 | Raw Mat2 | 10 |1.5 |
I know the unit weight of each Recipe and Raw material but I need to calculate the weight of Element1 and 2 as well as Meal parent, which depends from the calculated weight of both elements.
My result should look like that :
| Material | Order full | Quantity | U.Weight |
|:-----------|:------------:| --------:|---------:|
| Meal | / | 1 | TOBECALC | --0.2*1+0.5*1 + 0.6*1+0.3*1
| Element 1 | /10 | 1 | TOBECALC | --0.2*1+0.5*1
| Recipe 1 | /10/10 | 0.2 | 1 |
| Recipe 3 | /10/10/10 | 0.02 | 1 |
| Raw Mat1 | /10/10/10/10 | 0.02 | 1 |
| Recipe 2 | /10/20 | 0.5 | 1 |
| Element 2 | /20 | 1 | TOBECALC | --0.6*1+0.3*1
| Recipe 4 | /20/10 | 0.6 | 1 |
| Recipe 6 | /20/10/10 | 0.72 | 1 |
| Raw Mat2 | /20/10/10/10 | 1.08 | 1 |
| Recipe 5 | /20/20 | 0.3 | 1 |
Here is what I did so far :
A procedure PROC_INCO calling PROC_DETAILED_INCO calling PROC_UNIT_INCO that inserts lines in the final table. PROC_DETAILED_INCO is recursively called for each child and this is where I implemented the weight calculation.
DELIMITER $$
CREATE PROCEDURE PROC_UNIT_INCO
(IN ID_ARTICLE INTEGER,
IN ID_ROOT_EXE INTEGER,
IN T_ORDER_FULL VARCHAR(100),
IN QUANTITY FLOAT,
OUT U_WEIGHT FLOAT)
BEGIN
DECLARE WEIGHT FLOAT;
SELECT F_WEIGHT INTO WEIGHT FROM MATERIAL WHERE I_ID = ID_ARTICLE;
IF T_ORDER_FULL IS NULL THEN
SET T_ORDER_FULL = '/';
END IF;
IF TYPEART IN ('RawMat','Recipe') THEN
SET WEIGHT = WEIGHT;
SET U_WEIGHT = WEIGHT * QUANTITY;
ELSE
SET WEIGHT = 0;
SET U_WEIGHT = 0;
END IF;
INSERT INTO FINAL_RESULT (T_CODE, TT_ORDER_FULL, F_WEIGHT, F_QUANTITY, I_ID_ROOT_EXE)
VALUES(CODEART, T_ORDER_FULL, WEIGHT, QUANTITY, ID_ROOT_EXE);
END$$
CREATE PROCEDURE PROC_DETAILED_INCO
(IN ID_ARTICLE INTEGER,
IN ID_ROOT_EXE INTEGER,
IN T_ORDER_FULL VARCHAR(100),
IN QUANTITY FLOAT,
OUT CHILD_WEIGHT FLOAT)
BEGIN
DECLARE IS_DONE INT DEFAULT FALSE;
DECLARE ID_CHILD INTEGER;
DECLARE TEMP_ORDER INTEGER;
DECLARE NEW_ORDER_FULL VARCHAR(100);
DECLARE TYPE INTEGER;
DECLARE CHILD_CURSOR CURSOR FOR
SELECT A.CHILD, A.ORDER, QUANTITY * A.QUANTITY AS QUANTITY
FROM BOM A
INNER JOIN MATERIAL B ON A.CHILD = B.CODE
WHERE A.PARENT = ID_ARTICLE
ORDER BY A.ORDER;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET IS_DONE = TRUE;
CALL PROC_UNIT_INCO (ID_ARTICLE, ID_ROOT_EXE, T_ORDER_FULL, QUANTITY, #U_WEIGHT);
SET #FIN_WEIGHT:= 0;
SELECT #U_WEIGHT INTO CHILD_WEIGHT;
OPEN FILS_CURSOR;
get_list: LOOP
FETCH FILS_CURSOR INTO ID_CHILD, TEMP_ORDER, QUANTITY, PERTE;
IF IS_DONE THEN
LEAVE get_list;
END IF;
SET NEW_ORDER_FULL = CONCAT(COALESCE(T_ORDER_FULL,''),'/',lpad(cast(TEMP_ORDER AS char(200)),5,'0'));
CALL PROC_DETAILED_INCO(ID_CHILD, ID_ROOT_EXE, NEW_ORDER_FULL, QUANTITY, #CHILD_WEIGHT);
--this is where i want to calculate my weight by summing my children's weight
SET #FIN_WEIGHT := #FIN_WEIGHT + #CHILD_WEIGHT;
END LOOP get_list;
IF TYPE NOT IN ('RawMat','Recipe') THEN
UPDATE FINAL_RESULT SET F_WEIGHT = #FIN_WEIGHT WHERE TT_ORDER_FULL = T_ORDER_FULL;
END IF;
CLOSE FILS_CURSOR;
END$$
CREATE PROCEDURE PROC_INCO
(IN CODE_ART VARCHAR(50))
BEGIN
DECLARE ID_ARTICLE INTEGER;
SELECT I_ID INTO ID_ARTICLE FROM MATERIAL WHERE T_CODE = CODE_ART;
DELETE FROM DETAILED_INCO WHERE I_ID_ROOT_EXE = ID_ARTICLE;
CALL PROC_DETAILED_INCO (ID_ARTICLE, ID_ARTICLE, NULL, 1, #CHILD_WEIGHT);
select
*
from final_result
order by tt_order_full;
END$$
DELIMITER ;
CALL PROC_INCO ('Meal');
Apologies if there is any mistakes, I simplified the procedure for better readability.
The weight calculation is not working as I want it... I do not really understand the calculated weight that I obtain but it seems that it resets each time the procedure enters in the loop and then fetches the latest child weight at the parent level.
Thanks in advance for your help on this,
Nico

Is there something like a FOR loop in MySql?

I have a table with matches information, and I need to return a row for each goal and each team. So for example:
+--------+-------+-------+-------+-------+
| Match | Team1 | goal1 | goal2 | Team2 |
+--------+-------+-------+-------+-------+
| 1 | Red | 1 | 0 | Blue |
+--------+-------+-------+-------+-------+
| 2 | Green | 2 | 1 | Black |
+--------+-------+-------+-------+-------+
I want to run a function for each row that returns a row for each goal for each team. So my function result would be:
+--------+-------+-------+
| Goal | Match | Team |
+--------+-------+-------+
| 1 | 1 | Red |
+--------+-------+-------+
| 2 | 2 | Green |
+--------+-------+-------+
| 3 | 2 | Green |
+--------+-------+-------+
| 4 | 2 | Black |
+--------+-------+-------+
My ultimate objective is that I need to have one row for each match/team/goal to fill in manually the Scorer and the minute. Since I hace over 40000 matches, copy pasting each row counting the amount of goals is a pain.
I would like to start with a goal table pre populated with as much information as I already have.
Create a table that contains numbers from 1 to the maximum number of possible goals, i.e.
CREATE TABLE numbers (
num INT PRIMARY KEY
);
INSERT INTO numbers VALUES (1), (2), (3), (4), (5), (6), ...
You can then join this table with your original table:
SELECT num AS Goal, `Match`, Team
FROM numbers
JOIN (
SELECT Team1 AS Team, goal1 AS goals, `Match`
FROM matches
UNION
SELECT Team2 AS Team, goal2 AS goals, `Match`
FROM matches
) ON num <= goals
While loop syntax example in MySQL:
delimiter //
CREATE procedure yourdatabase.while_example()
wholeblock:BEGIN
declare str VARCHAR(255) default '';
declare x INT default 0;
SET x = 1;
WHILE x <= 5 DO
SET str = CONCAT(str,x,',');
SET x = x + 1;
END WHILE;
select str;
END//
Which prints:
mysql> call while_example();
+------------+
| str |
+------------+
| 1,2,3,4,5, |
+------------+
FOR loop syntax example in MySQL:
delimiter //
CREATE procedure yourdatabase.for_loop_example()
wholeblock:BEGIN
DECLARE x INT;
DECLARE str VARCHAR(255);
SET x = -5;
SET str = '';
loop_label: LOOP
IF x > 0 THEN
LEAVE loop_label;
END IF;
SET str = CONCAT(str,x,',');
SET x = x + 1;
ITERATE loop_label;
END LOOP;
SELECT str;
END//
Which prints:
mysql> call for_loop_example();
+-------------------+
| str |
+-------------------+
| -5,-4,-3,-2,-1,0, |
+-------------------+
1 row in set (0.00 sec)
Tutorial: http://www.mysqltutorial.org/stored-procedures-loop.aspx

Get root path of a tree with pure MySQL

I want to get path from node to root of a tree. This is my tree for example:
id | name | parent_id
------------------------------
1 | mike | 0
2 | danny | 1
3 | peter | 1
4 | clark | 2
5 | lily | 1
6 | stefan | 3
7 | simon | 3
8 | boby | 1
9 | john | 4
10 | elly | 4
I write an algoritm with php and mysql but it is slowly
public function GetRootPath($a_id) {
$root="";
$results="";
while(1==1){
$result = DB::select("SELECT id, parent_id FROM users WHERE id=$a_id");
if($result[0]->refr!=0) {
if($root==""){
$root=$result[0]->parent_id;
}
else {
$root=$result[0]->parent_id.'.'.$root;
}
$a_id=$result[0]->parent_id;
}
else {
break;
}
}
return $root;
}
How could this be written in pure MySQL? I'm not very aware with MySQL procedures and functions.
I think stored procedures could work:
DELIMITER $$
DROP PROCEDURE IF EXISTS get_root;
CREATE PROCEDURE get_root(
IN parentID INT,
OUT rootID INT
)
BEGIN
SELECT parent_id FROM tree WHERE id = parentID INTO rootID;
IF rootID = 0
THEN SET rootID = parentID;
ELSE
CALL get_root(rootID, rootID);
END IF;
END$$
DELIMITER ;
SET ##GLOBAL.max_sp_recursion_depth = 255;
SET ##session.max_sp_recursion_depth = 255;
CALL get_root(4, #rootID);
SELECT #rootID;

recursive update trigger is not working properly

I have created 2 tables abc and xyz. Both table are having the same data as shown below.
Table abc:
+------+-------+--------+
| roll | name | status |
+------+-------+--------+
| 1 | john | I |
| 1 | ken | I |
| 1 | abel | I |
| 2 | aston | I |
| 2 | ron | I |
+------+-------+--------+
Table xyz:
+-------+-------+---------+
| roll1 | name1 | status1 |
+-------+-------+---------+
| 1 | john | I |
| 1 | ken | I |
| 1 | abel | I |
| 2 | aston | I |
| 2 | ron | I |
+-------+-------+---------+
The status in both tables can be updated to 'D' or 'E'. Suppose in table abc, if the status of John become 'D' then I need to take John's roll (that is 1) and update all the status in xyz to 'D' where roll1 = 1.
Similarly, suppose if the status of ron changed to 'E' in xyz table, then aston and ron status should be 'E' in table abc.
This need to be achieved using update triggers. I have created 2 update trigger for both the tables. The trigger used are
DELIMITER $$
DROP TRIGGER /*!50032 IF EXISTS */ `test2`.`abc_trigg`$$
create trigger `test2`.`abc_trigg` BEFORE UPDATE on `test2`.`abc`
for each row BEGIN
IF #__disable_trigger is null THEN
SET #__disable_trigger = 1;
if new.status = 'D' then
update xyz set status1='D' where roll1 = new.roll;
elseif new.status = 'E' then
update xyz set status1='E' where roll1 = new.roll;
end if;
end if;
end;
$$
DELIMITER ;
DELIMITER $$
DROP TRIGGER /*!50032 IF EXISTS */ `test2`.`xyz_trigg`$$
create trigger `test2`.`xyz_trigg` BEFORE UPDATE on `test2`.`xyz`
for each row BEGIN
IF #__disable_trigger is null THEN
SET #__disable_trigger = 1;
if new.status1 = 'D' then
update abc set status='D' where roll = new.roll1;
elseif new.status1 = 'E' then
update abc set status='E' where roll = new.roll1;
end if;
end if;
end;
$$
DELIMITER ;
But these triggers are not correct and I am not getting the expected output. Kindly guide me to fix the issue and help me to get the relevant output?

get days in column mysql

I wrote a Stored Procedure in MySQL.
I am trying to get days in column of any month respectively.
below is the sql query
DELIMITER $$
DROP PROCEDURE IF EXISTS `GenerateReport`$$
CREATE
DEFINER = `FreeUser`#`localhost`
PROCEDURE `databasename`.`GenerateReport`(
IN gDate DATE,
IN gUserID VARCHAR(10)
)
BEGIN
DECLARE gStart INT;
DECLARE gDays INT;
SET gStart = 1;
SET gDays = DAY(LAST_DAY(gDate));
SELECT e.AssociateID, CONCAT(e.FirstName, ' ', e.MiddleName, ' ', e.LastName) AS `EmployeeName`, d.DesignationName,
ts.TSDate,
/* Trying to get days in column, Starts here */
loopOne: LOOP
IF gStart <= gDays THEN
gStart = gStart + 1;
case gStart IS NOT NULL THEN 'ItsDate' ELSE 'NoDate' END,
ITERATE loopOne;
END IF;
LEAVE loopOne;
END LOOP loopOne;
/* Trying to get days in column, ends here */
gStart AS `Start`, gDays AS `NoofDays`
FROM timesheet ts
LEFT JOIN employee e ON e.EmpID = ts.EmpID
LEFT JOIN designation d ON d.DesignationId = e.DEsignationID
WHERE DATE_FORMAT(ts.TSDate, '%Y-%m') = DATE_FORMAT(gDate, '%Y-%m')
GROUP BY e.AssociateID;
END$$
DELIMITER ;
Desired Output
consider an image for extra UI, below may not be good respresntation
----------------------------------------------------------
AssociateID | EmployeeName | DesignationName | 1 | 2 | 3 | 4 | .... | 31 | Start | gDays
---------------------------------------------------------
001 |John Carter | Dae ja | ItsDate | ItsDate| .... | ItsDate | 1 | 31
----------------------------------------------------------------------------------
It's not possible to build a query mixed with loops, if statements or any other flow control that way.
You need to build an dynamic prepared statement and then execute it.
drop procedure if exists dynamiccolumns;
delimiter //
create procedure dynamiccolumns()
begin
declare v_count int default 1;
set #ps := 'select now()';
oneToTen: loop
if v_count = 10 then
leave oneToTen;
end if;
set #ps := concat(#ps, ", ");
set #ps := concat(#ps, v_count);
set v_count := v_count + 1;
end loop oneToTen;
set #ps := concat(#ps, " from dual");
prepare ps from #ps;
execute ps;
deallocate prepare ps;
end//
delimiter ;
And calling it
mysql> call dynamiccolumns;
+---------------------+---+---+---+---+---+---+---+---+---+
| now() | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---------------------+---+---+---+---+---+---+---+---+---+
| 2013-07-10 06:11:43 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 |
+---------------------+---+---+---+---+---+---+---+---+---+
1 row in set (0.00 sec)