Group and make row become header - mysql

I have this data in my MySQL table fact
+------------+-------+--------+
| timestamp | code | unique |
+------------+-------+--------+
| 1416157200 | 7E001 | 100 |
| 1416157200 | 7E002 | 200 |
| 1416243600 | 7E001 | 100 |
| 1416243600 | 7E002 | 200 |
+------------+-------+--------+
I want to get this result
+-------+------------+------------+
| code | 2014-11-18 | 2014-11-17 |
+-------+------------+------------+
| 7E001 | 100 | 100 |
| 7E002 | 200 | 200 |
+-------+------------+------------+
I use this query select code, from_unixtime(timestamp, '%Y-%m-%d') as date, unique from fact; to produce this result and have no idea to aggregate this result became above desirable result.
+-------+------------+--------+
| code | date | unique |
+-------+------------+--------+
| 7E001 | 2014-11-17 | 100 |
| 7E002 | 2014-11-17 | 200 |
| 7E001 | 2014-11-18 | 100 |
| 7E002 | 2014-11-18 | 200 |
+-------+------------+--------+
Is it possible? And how to achieve that?
PS: Please help me editing the title to be more descriptive since I can't explain this problem in such short

Case based aggregation can be used
As the dates can be many, you need to use dynamic SQL
select code,
Max( case when date=1416157200 then unique end) as '2014-11-17' ,
Max( case when date=1416243600 t hen unique end) as '2014-11-18'
from fact
Group by date

Yes it is possible creating a dynamic query using that values as columns.
I use some code from article describing a way to get a pivot table in MySQL:
mysql> call get_fact_pivot_table();
+-------+------------+------------+
| code | 2014-11-16 | 2014-11-17 |
+-------+------------+------------+
| 7E001 | 100 | 100 |
| 7E002 | 200 | 200 |
+-------+------------+------------+
2 rows in set (0,06 sec)
The procedure get_fact_pivot_table contains a cursor to make a dynamic query, you can change it as you need:
DELIMITER $$
DROP PROCEDURE if exists get_fact_pivot_table$$
CREATE PROCEDURE `get_fact_pivot_table`()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE p_sql text;
DECLARE p_col_date VARCHAR(20);
DECLARE p_col_value int;
DECLARE c_columns cursor FOR
select distinct from_unixtime(timestamp, '%Y-%m-%d') as `col_date` ,
`timestamp` as col_value
from fact;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = 1;
SET p_sql = 'select f.code ';
OPEN c_columns;
read_loop: LOOP
FETCH c_columns INTO p_col_date, p_col_value;
IF done THEN
LEAVE read_loop;
END IF;
SET p_sql = concat(p_sql,
', (select c.`unique` from fact as c where
c.`timestamp` = ', p_col_value ,'
and c.code = f.code limit 1) as `',p_col_date,'` ');
END LOOP;
SET #SQL = concat(p_sql,' from fact as f group by f.code');
close c_columns;
PREPARE stmt1 FROM #SQL;
EXECUTE stmt1;
DEALLOCATE PREPARE stmt1;
END$$
delimiter ;

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

using a user variable in where clause without subquery

I have the following scenario:
+-----------+
| my_column |
+-----------+
| A |
| B |
| C |
| D |
| E |
+-----------+
I have simplified my_function bellow for this example;
DROP FUNCTION IF EXISTS my_function;
CREATE FUNCTION my_function(
phrase VARCHAR(255),
column_value VARCHAR(255)
)
RETURNS FLOAT(20,10)
READS SQL DATA
SQL SECURITY INVOKER
BEGIN
IF(column_value = 'A') THEN RETURN 1.0000000000;
ELSEIF(column_value = 'B') THEN RETURN 0.7500000000;
ELSEIF(column_value = 'C') THEN RETURN 0.7500000000;
ELSEIF(column_value = 'D') THEN RETURN 0.5000000000;
ELSEIF(column_value = 'E') THEN RETURN 0.0000000000;
END IF;
END;
Here is my main stored procedure:
DROP PROCEDURE IF EXISTS my_procedure;
CREATE PROCEDURE my_procedure(
IN phrase VARCHAR(255)
)
READS SQL DATA
SQL SECURITY INVOKER
BEGIN
SET #phrase = phrase;
SET #query = "
SELECT
my_column,
#score_var := my_function(?,my_column) as score,
#score_var
FROM my_table
ORDER BY score DESC;
";
PREPARE stmt FROM #query;
EXECUTE stmt USING #phrase;
DEALLOCATE PREPARE stmt;
END;
Now if I call my_procedure
call my_procedure('anything');
The result is:
+-----------+--------------+------------+
| my_column | score | #score_var |
+-----------+--------------+------------+
| A | 1.0000000000 | 1 |
| B | 0.7500000000 | 0.75 |
| C | 0.7500000000 | 0.75 |
| D | 0.5000000000 | 0.5 |
| E | 0.0000000000 | 0 |
+-----------+--------------+------------+
But if I add WHERE #score_var > 0.5 inside of the query in my_procedure, the result is:
+-----------+--------------+------------+
| my_column | score | #score_var |
+-----------+--------------+------------+
| A | 1.0000000000 | 1 |
| C | 0.7500000000 | 0.75 |
| E | 0.0000000000 | 0 |
+-----------+--------------+------------+
Expected result ´> 0.5´:
+-----------+--------------+------------+
| my_column | score | #score_var |
+-----------+--------------+------------+
| A | 1.0000000000 | 1 |
| B | 0.7500000000 | 0.75 |
| C | 0.7500000000 | 0.75 |
+-----------+--------------+------------+
I have seen some answers that use a subquery, but my question is: can (in this case) I not use a subquery?
Alternative approaches are also welcome.
When you read and write a user variable in the same statement, the behavior is documented as "undocumented". In other words the result is unpredictable unlesss you read and understand the source code of your MySQL version.
However - I think you are complicating things here unnecessarily. I don't see a reason to use a prepared statement, neither to use u user variable. Your procedure body could be just:
SELECT
my_column,
my_function(phrase, my_column) as score,
FROM my_table
HAVING score > 0.5
ORDER BY score DESC
Also your function could be written with less code duplicatin:
RETURN
CASE column_value
WHEN 'A' THEN 1.0000000000
WHEN 'B' THEN 0.7500000000
WHEN 'C' THEN 0.7500000000
WHEN 'D' THEN 0.5000000000
WHEN 'E' THEN 0.0000000000
END

MySql: ORDER BY parent and child

I have a table like:
+------+---------+-
| id | parent |
+------+---------+
| 2043 | NULL |
| 2044 | 2043 |
| 2045 | 2043 |
| 2049 | 2043 |
| 2047 | NULL |
| 2048 | 2047 |
| 2049 | 2047 |
+------+---------+
which shows a simple, 2-level "parent-child"-corelation. How can I ORDER BY an SELECT-statement to get the order like in the list above, which means: 1st parent, childs of 1st parent, 2nd parent, childs of 2nd parent and so on (if I have that, I can add the ORDER BYs for the children... I hope). Is it possible withoug adding a sort-field?
Including sorting children by id:
ORDER BY COALESCE(parent, id), parent IS NOT NULL, id
SQL Fiddle example
Explanation:
COALESCE(parent, id): First sort by (effectively grouping together) the parent's id.
parent IS NOT NULL: Put the parent row on top of the group
id: Finally sort all the children (same parent, and parent is not null)
If your table uses 0 instead of null to indicate an entry with no parent:
id | parent
-------------
1233 | 0
1234 | 1233
1235 | 0
1236 | 1233
1237 | 1235
Use greatest instead of coalesce and check the value does not equal 0:
ORDER BY GREATEST(parent, id), parent != 0, id
The solution above didn't work for me, my table used 0 instead of NULL.
I found this other solution: you create a column with the concatened parent id and child id in your query and you can sort the result by it .
SELECT CONCAT(IF(parent = 0,'',CONCAT('/',parent)),'/',id) AS gen_order
FROM table
ORDER BY gen_order
This question still shows as one of the first search results. So I would like to share a my solution and hope it will help more people out. This will also work when you have a table with many levels of parent and child relations. Although it is quite a slow solution. The top level has NULL as parent.
+---------+---------+
| id | parent |
+---------+---------+
| 1 | NULL |
| 2 | 1 |
| 3 | 1 |
| 4 | 2 |
+---------+---------+
In my approach I will use a procedure that will recursively call itself and keep prepending the path with the parent of the requested id until it reaches the NULL parent.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` PROCEDURE `PATH`(IN `input` INT, OUT `output` VARCHAR(128))
BEGIN
DECLARE _id INT;
DECLARE _parent INT;
DECLARE _path VARCHAR(128);
SET `max_sp_recursion_depth` = 50;
SELECT `id`, `parent`
INTO _id, _parent
FROM `database`.`table`
WHERE `table`.`id` = `input`;
IF _parent IS NULL THEN
SET _path = _id;
ELSE
CALL `PATH`(_parent, _path);
SELECT CONCAT(_path, '-', _id) INTO _path;
END IF;
SELECT _path INTO `output`;
END $$
DELIMITER ;
To use the results in an ORDER BY clause you will need a FUNCTION too that wraps the results of the PROCEDURE.
DELIMITER $$
CREATE DEFINER=`root`#`localhost` FUNCTION `GETPATH`(`input` INT) RETURNS VARCHAR(128)
BEGIN
CALL `PATH`(`input`, #path);
RETURN #path;
END $$
DELIMITER ;
Now we can use the recursive path to sort the order of the table. On a table with 10000 rows it takes just over a second on my workstation.
SELECT `id`, `parent`, GETPATH(`id`) `path` FROM `database`.`table` ORDER BY `GETPATH`(`id`);
Example output:
+---------+---------+---------------+
| id | parent | path |
+---------+---------+---------------+
| 1 | NULL | 1 |
| 10 | 1 | 1-10 |
| 300 | 10 | 1-10-300 |
| 301 | 300 | 1-10-300-301 |
| 302 | 300 | 1-10-300-302 |
+---------+---------+---------------+
5 rows in set (1,39 sec)

MYSQL Function with a calcuation based on data in a db column

I Have a table that is a lookup for scoring points based on Place (P) and Number of Racers(R)
and scoring formats indicated by points_id. Two cases are shown in the table.
Sometime the points are determined directly by the values of P and N as in points_id =3
other times they are most easily determined by a simple calculation shown in the pts_calc column.
|points_id| P | N |points|pts_calc|
| 1 | 0 | 0 | NULL | pin |
| 1 |DNS| 0 | NULL | nin+1 |
| 3 | 1 | 0 |102.00| NULL |
| 3 | 2 | 0 | 98.00| NULL |
| 3 | 3 | 0 | 96.00| NULL |
| 3 | 4 | 0 | 93.00| NULL |
| 3 | 5 | 0 | 91.00| NULL |
| 3 | 6 | 0 | 89.00| NULL |
| 3 |DNF| 0 | 85.00| NULL |
I was hoping to create a function that returned the points from the three input variables.
points_id, P, N.
Below is what I tried.
CREATE FUNCTION POINTS(pid INT,pin VARCHAR(3),nin INT)
RETURNS DEC(6,2)
DETERMINISTIC
BEGIN
DECLARE pts DECIMAL(6,2);
DECLARE pcalc VARCHAR(20);
SELECT points,pts_calc INTO pts,pcalc FROM scoring_points WHERE points_id=pid AND (P=pin OR P='0') AND (N=nin or N=0);
IF(pts IS NULL) THEN
SET #s= CONCAT('SET pts = ',pcalc);
PREPARE stmt FROM #s;
EXECUTE stmt;
END IF;
RETURN pts;
END
But i got this error.
1336 - Dynamic SQL is not allowed in stored function or trigger
Further research show the Prepare statement is not allowed in functions only but procedures.
I was hoping to do something like;
SELECT SUM(Points(pid,place,numb)) FROM t1 GROUP BY racer.id
But onto plan B (tbd) unless someone has great idea.
I think you might fare better having three numeric columns instead of your pts_calc column:
cPIN - coefficient of pin term
cNIN - coefficient of nin term
cnst - constant term
Your function could then perform:
SELECT IFNULL(points, cPIN*pin + cNIN*nin + cnst) INTO pts
FROM scoring_points
WHERE ...
Depending on your needs, you might even be able to get rid of the points column by just using cnst and leaving the other two equal to 0.

Getting limited amount of records from hierarchical data

Let's say I have 3 tables (significant columns only)
Category (catId key, parentCatId)
Category_Hierarchy (catId key, parentTrail, catLevel)
Product (prodId key, catId, createdOn)
There's a reason for having a separate Category_Hierarchy table, because I'm using triggers on Category table that populate it, because MySql triggers work as they do and I can't populate columns on the same table inside triggers if I would like to use auto_increment values. For the sake of this problem this is irrelevant. These two tables are 1:1 anyway.
Category table could be:
+-------+-------------+
| catId | parentCatId |
+-------+-------------+
| 1 | NULL |
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 3 |
| 6 | 4 |
| ... | ... |
+-------+-------------+
Category_Hierarchy
+-------+-------------+----------+
| catId | parentTrail | catLevel |
+-------+-------------+----------+
| 1 | 1/ | 0 |
| 2 | 1/2/ | 1 |
| 3 | 1/2/3/ | 2 |
| 4 | 1/2/3/4/ | 3 |
| 5 | 1/2/3/5/ | 3 |
| 6 | 1/2/3/4/6/ | 4 |
| ... | ... | ... |
+-------+-------------+----------+
Product
+--------+-------+---------------------+
| prodId | catId | createdOn |
+--------+-------+---------------------+
| 1 | 4 | 2010-02-03 12:09:24 |
| 2 | 4 | 2010-02-03 12:09:29 |
| 3 | 3 | 2010-02-03 12:09:36 |
| 4 | 1 | 2010-02-03 12:09:39 |
| 5 | 3 | 2010-02-03 12:09:50 |
| ... | ... | ... |
+--------+-------+---------------------+
Category_Hierarchy makes it simple to get category subordinate trees like this:
select c.*
from Category c
join Category_Hierarchy h
on (h.catId = c.catId)
where h.parentTrail like '1/2/3/%'
Which would return complete subordinate tree of category 3 (that is below 2, that is below 1 which is root category) including subordinate tree root node. Excluding root node is just one more where condition.
The problem
I would like to write a stored procedure:
create procedure GetLatestProductsFromSubCategories(in catId int)
begin
/* return 10 latest products from each */
/* catId subcategory subordinate tree */
end;
This means if a certain category had 3 direct sub categories (with whatever number of nodes underneath) I would get 30 results (10 from each subordinate tree). If it had 5 sub categories I'd get 50 results.
What would be the best/fastest/most efficient way to do this? If possible I'd like to avoid cursors unless they'd work faster compared to any other solution as well as prepared statements, because this would be one of the most frequent calls to DB.
Edit
Since a picture tells 1000 words I'll try to better explain what I want using an image. Below image shows category tree. Each of these nodes can have an arbitrary number of products related to them. Products are not included in the picture.
So if I'd execute this call:
call GetLatestProductsFromSubCategories(1);
I'd like to effectively get 30 products:
10 latest products from the whole orange subtree
10 latest products from the whole blue subtree and
10 latest products from the whole green subtree
I don't want to get 10 latest products from each node under catId=1 node which would mean 320 products.
Final Solution
This solution has O(n) performance:
CREATE PROCEDURE foo(IN in_catId INT)
BEGIN
DECLARE done BOOLEAN DEFAULT FALSE;
DECLARE first_iteration BOOLEAN DEFAULT TRUE;
DECLARE current VARCHAR(255);
DECLARE categories CURSOR FOR
SELECT parentTrail
FROM category
JOIN category_hierarchy USING (catId)
WHERE parentCatId = in_catId;
DECLARE CONTINUE HANDLER FOR SQLSTATE '02000' SET done = TRUE;
SET #query := '';
OPEN categories;
category_loop: LOOP
FETCH categories INTO current;
IF `done` THEN LEAVE category_loop; END IF;
IF first_iteration = TRUE THEN
SET first_iteration = FALSE;
ELSE
SET #query = CONCAT(#query, " UNION ALL ");
END IF;
SET #query = CONCAT(#query, "(SELECT product.* FROM product JOIN category_hierarchy USING (catId) WHERE parentTrail LIKE CONCAT('",current,"','%') ORDER BY createdOn DESC LIMIT 10)");
END LOOP category_loop;
CLOSE categories;
IF #query <> '' THEN
PREPARE stmt FROM #query;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
END IF;
END
Edit
Due to the latest clarification, this solution was simply edited to simplify the categories cursor query.
Note: Make the VARCHAR on line 5 the appropriate size based on your parentTrail column.