Is there something like a FOR loop in MySql? - 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

Related

create sql in Mysql to always make a value in a field the max value (even when another value grows larger)

I have created a before update trigger which works fine but it creates duplicate rows and gets rid of the old value. I can't figure out a way to get rid of duplicate and add the old value back with triggers. Is before update trigger the right way to go?
Create trigger beforeUpdate
Before Update
on city for each row
begin
If new.population > old.population Then
Begin
Declare maxPop integer;
set #maxPop := (select max(population) from city);
if new.population > #maxPop Then
Set new.name = 'Chicago';
set new.CountryCode = 'USA';
set new.District = 'Illinois';
set new.Population = new.population+1;
end if;
End;
end if;
end;
SQL > select id, name, population from city where name in ('Chicago','Mumbai (Bombay)');
| id | name | population |
| 1024 | Mumbai (Bombay) | 10500000 |
| 3795 | Chicago | 2896016 |
SQL > Update city set population = 10500001 where id = 1024;
SQL > select id, name, population from city where name in ('Chicago','Mumbai (Bombay)');
| id | name | population |
| 1024 | Chicago | 10500002 |
| 3795 | Chicago | 2896016 |
How do I get the old update data to give Mumbai population of 1 less than Chicago?

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

error in loop of procedure using a cursor

I have this MySQL procedure where I used a cursor to:
CREATE DEFINER=`root`#`localhost` PROCEDURE `tax_to_salary`()
BEGIN
DECLARE basic_salary INTEGER;
DECLARE new_salary INTEGER;
DECLARE done INTEGER;
declare count INTEGER;
DECLARE counter INTEGER default 0;
DECLARE cur1 CURSOR FOR SELECT salary FROM employee;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
SELECT count(id) INTO count FROM employee;
SET #counter:=0;
OPEN cur1;
l1:LOOP
FETCH cur1 INTO basic_salary;
SET #counter:=#counter+1;
IF #counter>count THEN
leave l1;
end if;
IF basic_salary>2500 THEN
SET #new_salary := 500;
SET #basic_salary := #basic_salary - #new_salary;
else
SET #new_salary := 200;
SET #basic_salary := #basic_salary - #new_salary;
END IF;
SELECT emp_name, salary, basic_salary AS 'Salary after taxes' FROM employee;
END LOOP;
END
And I got this result:
But my procedure should remove 500 from all salaries over 2500 and remove 200 from salaries less than 2500.
I tried to put the final SELECT query inside the loop but I get 5 tabs and every tab contain the same of image below.
Schema
create table employee
( id int auto_increment primary key,
emp_name varchar(100) not null,
salary int not null
);
insert employee (emp_name,salary) values
('John',4400),
('Sarah',2700),
('Peter',2150),
('Ali',2650),
('Ashley',2650);
Note your language was greater than 2500 and also you said less than 2500. yet it has no condition for salary equaling 2500 exactly. So the below is one fix to that concept (otherwise there is no reduction).
Case when
best for many conditions, not that yours has it
select emp_name,salary,
CASE when salary>=2500 then salary-500
ELSE
salary-200
END as modified_salary
from employee;
+----------+--------+-----------------+
| emp_name | salary | modified_salary |
+----------+--------+-----------------+
| John | 4400 | 3900 |
| Sarah | 2700 | 2200 |
| Peter | 2150 | 1950 |
| Ali | 2650 | 2150 |
| Ashley | 2650 | 2150 |
+----------+--------+-----------------+
If
for simple conditions like yours
select emp_name,salary,
if(salary>=2500,salary-500,salary-200) as modified_salary
from employee;
+----------+--------+-----------------+
| emp_name | salary | modified_salary |
+----------+--------+-----------------+
| John | 4400 | 3900 |
| Sarah | 2700 | 2200 |
| Peter | 2150 | 1950 |
| Ali | 2650 | 2150 |
| Ashley | 2650 | 2150 |
+----------+--------+-----------------+
There is no reason to be using a row-by-row cursor the way you are. That is what people sometimes do just starting out with sql. Not only are they slow, often unbearably slow, but they keep you from harnessing the power of relations that make sql shine.
Said another way, you are trying to write procedural code and getting in the middle of it all by helping the sql engine figure it out with that mindset. It doesn't want it that way for optimization. You can, but you will slow it down horribly.
The reason you are getting multiple tabs as you say is that with your strategy, even if it worked well number-wise, each select statement returns a result set. And by going the dangerous cursor route, you returned five of them.

how to match keys to an alphabetic sort in SQL

so I have a table, which I'll simplify:
(and by the way this is a legacy so the idx column can't be removed and just go purely alphabetic)
components
----------
id int(11) unsigned
idx mediumint(4) unsigned
value char(100)
here is a sample of a "out of synch" listing
1 1 apples
2 2 bananas
3 3 cherries
4 4 aaron
I was thinking I could have a table that was just one column with number values of 1 to a large quantity, and somehow do a join on these two tables, and thereby do an update on the previous table so that the idx values numbered from 1 to 4 to match aaron through cherries respectively. Does anyone have a solution like that, or any other for that matter, that is a pure MySQL query?
[EDIT: the actual join of tables is more complex and #barranca posted a incrementing query that "almost" works. Here is the actual query that I'm doing with all related tables:
SELECT a.ID, a.Idx, a.Components_ID, c.Label, #n := #n + 5 AS NewIdx
FROM (SELECT #n := 0) as init, (em__assemblies a JOIN em__node_components c ON a.Components_ID=c.ID)
WHERE a.Groupings_ID=26
ORDER BY c.Label
I used a five to make it clear to myself that the sorting is happening AFTER the incrementing
Honestly, I think that the idx column is not needed (you'll see why in a moment).
If all you need is to show an integer column that "matches" the order of value, you can use a variable:
select id, #n := #n + 1 as idx, value /*edit:it was n + 1, changed it to #n + 1*/
from
(select #n := 0) as init
, components
order by components.value
If you want to update the idx table, I think a stored procedure could do the trick:
delimiter $$
create procedure update_idx()
begin
declare this_id, n int unsigned;
declare done int default false;
declare cur_values cursor for select id from components order by value;
declare continue handler for not found set done = true;
open cur_values;
set n = 1;
loop_update: loop
fetch cur_values into this_id;
if done then
leave cur_values;
end if;
-- Update the table
update components set idx = n where id = this_id;
set n = n + 1;
end loop;
close cur_values;
end $$
delimiter ;
-- Call it:
call update_idx();
A working example using SQL Fiddle:
MySQL 5.5.32 Schema Setup:
create table example(
id int unsigned not null primary key,
idx int unsigned,
value char(100)
);
insert into example
values (1,1,'apples'),(2,2,'bananas'),(3,3,'cherries'),(4,4,'aaron');
Query 1 (the original data):
select * from example;
Results:
| ID | IDX | VALUE |
|----|-----|----------|
| 1 | 1 | apples |
| 2 | 2 | bananas |
| 3 | 3 | cherries |
| 4 | 4 | aaron |
Query 2:
select id, #n := #n+1 as idx, value
from (select #n := 0) as init, example
order by value;
Results:
| ID | IDX | VALUE |
|----|-----|----------|
| 4 | 1 | aaron |
| 1 | 2 | apples |
| 2 | 3 | bananas |
| 3 | 4 | cherries |

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)