MYSQL stock gap prediction - mysql

I've been scratching my head on a way to get this working for quite some time now, and as it's so specific I can't seem to find the answer elsewhere on StackOverflow. Any answers or suggestions would be appreciated.
I'm working to produce a report on that will inform a user of stock management and consumption. Calculating this with the values of current_stock and daily_stock_consumption is easy... But when we add shipments incoming into the process things become very complicated. Getting MYSQL to return the first day that each stock reaches 0 is leaving me blank.
For instance, let's say we are selling fruit and we have three tables fruit, fruit_sold and fruit_orders
fruit
|---------------------|------------------|
| id | fruit |
|---------------------|------------------|
| 1 | Apples |
|---------------------|------------------|
| 2 | Oranges |
|---------------------|------------------|
| 3 | pears |
|---------------------|------------------|
fruit_sold
|---------------------|------------------|------------------|------------------|
| id | fruit_id | sold_per_day | day_sold |
|---------------------|------------------|------------------|------------------|
| 1 | 1 | 101 | 1585695600 |
|---------------------|------------------|------------------|------------------|
| 2 | 2 | 445 | 1585695600 |
|---------------------|------------------|------------------|------------------|
| 3 | 3 | 214 | 1585782000 |
|---------------------|------------------|------------------|------------------|
| 4 | 1 | 512 | 1585782000 |
|---------------------|------------------|------------------|------------------|
fruit_orders
|---------------------|------------------|------------------|
| id | arriving | fruit_id |
|---------------------|------------------|------------------|
| 1 | 1592607600 | 1 |
|---------------------|------------------|------------------|
| 2 | 1586905200 | 3 |
|---------------------|------------------|------------------|
| 3 | 1590534000 | 2 |
|---------------------|------------------|------------------|
| 4 | 1588287600 | 3 |
|---------------------|------------------|------------------|
| 5 | 1593126000 | 1 |
|---------------------|------------------|------------------|
| 6 | 1592607600 | 2 |
|---------------------|------------------|------------------|
So far I have
SELECT SUM(fruit_sold.sold_per_day) / 2 AS consumption, fruit.fruit
FROM fruit_sold
LEFT JOIN fruit
ON fruit_sold.fruit_id = fruit.id
GROUP BY fruit_sold.fruit_id
How would I also return the number of days until the stock runs out (taking into account arriving orders and their arrival dates) for each fruit?
We want to return the following:
|---------------------|------------------|------------------|------------------|
| id | fruit | consumption | days_until_gone | |
|---------------------|------------------|------------------|------------------|
| 1 | apples | 306.5 | 2 |
|---------------------|------------------|------------------|------------------|
| 2 | oranges | 222.5 | 16 |
|---------------------|------------------|------------------|------------------|
| 3 | pears | 107 | 3 |
|---------------------|------------------|------------------|------------------|

You can get the numbers per day using union all, aggregation, and window functions:
select fruit_id, day_sold, sum(sold_per_day), sum(orders),
sum(sum(sold_per_day - orders)) over (partition by fruit_id order by day_sold) as net
from ((select fruit_id, day_sold, sold_per_day, 0 as orders
from fruit_sold
) union all
(select fruit_id, as day_sold, 0 as sold_per_day, count(*) as orders
from fruit_sold
group by fruit_id, day_sold
)
) f
group by fruit_id, day_sold;
I'm not 100% how this applies to your sample data, but it seems to basically provide the information that you want.

Related

How to write a SQL query on a table with nested record?

I want to know what is the top fruit on 2021-08-15 (completed with highest total price), table below:
product
------------------
id | name
------------------
1 | banana
2 | orange
3 | apple
4 | watermelon
5 | pineapple
sales
--------------------------------------------------------------------------------
s_id | sn.id | sn.product_id | sn.status | sn.total_price | created_at
--------------------------------------------------------------------------------
1 | 1 | 2 | BOOKED | 300 | 2021-08-15 12:20:32
| 2 | 5 | COMPLETED | 800 |
| 3 | 5 | COMPLETED | 200 |
2 | 4 | 2 | COMPLETED | 500 | 2021-08-16 09:00:59
| 5 | 1 | CANCELLED | 1000 |
How to write a query on a table with nested records?
Does MySQL even have nested record data type?
select *
from
(select p.id,p.name,sum(s.total_price) as sumOfSales
from product p join sales s on p.id = s.product_id
where s.status = "COMPLETED"
group by p.id,p.name) T
order by sumOfSales desc

Precalculate numbers of records for each possible combination

I have a mySQL database table containing cellphones information like this:
ID Brand Model Price Type Size
==== ===== ===== ===== ====== ====
1 Apple A71 3128 A 40
2 Samsung B7C 3128 B 20
3 Apple ZX5 3128 A 30
4 Huawei Q32 2574 B 40
5 Apple A21 2574 A 25
6 Apple A71 3369 A 30
7 Samsung A71 7413 C 40
Now I want to create another table, that would contain counts for every possible combination of the parameters.
Params Count
============================================== =======
ALL 1000000
Brand(Apple) 20000
Brand(Apple,Samsung) 40000
Brand(Apple),Model(A71) 7100
Brand(Apple),Type(A) 6000
Brand(Apple),Model(A71,B7C),Type(A,B) 7
Model(A71) 12514
Model(A71,B7C) 26584
Model(A71),Type(A) 6521
Model(A71),Type(A,B) 8958
Model(A71),Type(A,B),Size(40) 85
And so on for every possible combination. I was thinking about creating a stored procedure (that i would execute periodically), that would perform queries with every existing condition like that, but I am a little stuck on how exactly should it look like. Or is there a better way how to do this?
Edit: the reason why I want to store information like this is to be able to show number of results in filter in client application, like in the picture.
I would like to create index on the Params column to be able to get the Count number for given hash instantly, improving performance.
I also tried querying and caching the values dynamically, but I want to try this approach as well, so I can compare which one is more effective.
This is how I am calculating the counts now:
SELECT COUNT(*) FROM products;
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple');
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple', 'Samsung');
SELECT COUNT(*) FROM products WHERE Brand IN ('Apple') AND Model IN ('A71');
etc.
You can use a ROLLUP for this.
SELECT
model, type, size, COUNT(*)
FROM mytab
GROUP BY 1, 2, 3
WITH ROLLUP
With your sample data, we get the following:
| model | type | size | COUNT(*) |
| ----- | ---- | ---- | -------- |
| A21 | A | 25 | 1 |
| A21 | A | | 1 |
| A21 | | | 1 |
| A71 | A | 30 | 1 |
| A71 | A | 40 | 1 |
| A71 | A | | 2 |
| A71 | C | 40 | 1 |
| A71 | C | | 1 |
| A71 | | | 3 |
| B7C | B | 20 | 1 |
| B7C | B | | 1 |
| B7C | | | 1 |
| Q32 | B | 40 | 1 |
| Q32 | B | | 1 |
| Q32 | | | 1 |
| ZX5 | A | 30 | 1 |
| ZX5 | A | | 1 |
| ZX5 | | | 1 |
| | | | 7 |
The subtotals are present in the rows with null values in different columns, and the total is the last row where all group by columns are null.

mysql pivot using column and row numbers

I am stuck in this situation where I need to use Row Number and Column Number values from table's columns to derive the output mentioned below. I have tried everything - if/else, case when/then but not helping.
Any help/suggestions are really appreciated!
Here is a mocked up sample data present in db table -
+--------+--------+--------+----------+-------------+
| Record | ColNbr | RowNbr | ColTitle | CellContent |
+--------+--------+--------+----------+-------------+
| 1 | 1 | 1 | Unit | sqf |
| 1 | 1 | 2 | Unit | cm |
| 1 | 2 | 1 | Desc | roof |
| 1 | 2 | 2 | Desc | rod |
| 1 | 3 | 1 | Material | concrete |
| 1 | 3 | 2 | Material | steel |
| 1 | 4 | 1 | Quantity | 100 |
| 1 | 4 | 2 | Quantity | 12 |
| 1 | 1 | 1 | Unit | liter |
| 1 | 1 | 2 | Unit | ml |
| 1 | 2 | 1 | Desc | bowl |
| 1 | 2 | 2 | Desc | plate |
| 1 | 3 | 1 | Material | plastic |
| 1 | 3 | 2 | Material | glass |
| 1 | 4 | 1 | Quantity | 2 |
| 1 | 4 | 2 | Quantity | 250 |
+--------+--------+--------+----------+-------------+
Expected Output -
+--------+--------+--------+----------+-------------+
| Record | Unit | Desc | Material | Quantity |
+--------+--------+--------+----------+-------------+
| 1 | sqf | roof | concrete | 100 |
| 1 | cm | rod | steel | 12 |
| 2 | liter | bowl | plastic | 2 |
| 2 | ml | plate | glass | 250 |
+--------+--------+--------+----------+-------------+
If your actual data is like that, I suggest that you consider to separate the data to; for example, 4 different tables (unit,description,material & a table to store all that ids+quantity). The former 3 tables will store the prerequisite info that get minor updates throughout time and the last table will store all the quantity records. Let's say your tables will look something like this:
CREATE TABLE `Unit` (
unit_id INT,
unit_name VARCHAR(50));
+---------+-----------+
| unit_id | unit_name |
+---------+-----------+
| 1 | sqf |
| 2 | cm |
| 3 | liter |
| 4 | ml |
+---------+-----------+
CREATE TABLE `Description` (
desc_id INT,
desc_name VARCHAR(50));
+---------+-----------+
| desc_id | desc_name |
+---------+-----------+
| 1 | roof |
| 2 | rod |
| 3 | bowl |
| 4 | plate |
+---------+-----------+
CREATE TABLE `Material` (
mat_id INT,
mat_name VARCHAR(50));
+--------+----------+
| mat_id | mat_name |
+--------+----------+
| 1 | concrete |
| 2 | steel |
| 3 | plastic |
| 4 | glass |
+--------+----------+
CREATE TABLE `Records` (
unit_id INT,
desc_id INT,
mat_id INT,
quantity DECIMAL(14,4));
+---------+---------+--------+----------+
| unit_id | desc_id | mat_id | Quantity |
+---------+---------+--------+----------+
| 1 | 1 | 1 | 100 |
| 2 | 2 | 2 | 12 |
| 3 | 3 | 3 | 2 |
| 4 | 4 | 4 | 250 |
+---------+---------+--------+----------+
Then you insert the data accordingly.
Anyhow, for your existing data example, it could be done but there are some concern over whether the unit+desc+material+quantity matching are correct. The only way I can maybe at least think that it's correctly matched is by giving all of the query a similar ORDER BY clause. Hence, the following:
SELECT A.record,A.unit,B.Desc,C.Material,D.Quantity FROM
(SELECT #rn:=#rn+1 AS record,CASE WHEN coltitle='unit' THEN cellcontent END AS Unit
FROM yourtable, (SELECT #rn :=0 ) v
HAVING unit IS NOT NULL
ORDER BY colnbr) A LEFT JOIN
(SELECT #rn1:=#rn1+1 AS record,CASE WHEN coltitle='Desc' THEN cellcontent END AS `Desc`
FROM yourtable, (SELECT #rn1 :=0 ) v
HAVING `Desc` IS NOT NULL
ORDER BY colnbr) B ON a.record=b.record LEFT JOIN
(SELECT #rn2:=#rn2+1 AS record,CASE WHEN coltitle='material' THEN cellcontent END AS Material
FROM yourtable, (SELECT #rn2:=0 ) v
HAVING Material IS NOT NULL
ORDER BY colnbr) C ON a.record=c.record LEFT JOIN
(SELECT #rn3:=#rn3+1 AS record,CASE WHEN coltitle='Quantity' THEN cellcontent END AS Quantity
FROM yourtable, (SELECT #rn3:=0 ) v
HAVING Quantity IS NOT NULL
ORDER BY colnbr) D ON a.record=d.record;
The idea here is to make a sub-query based on COLTITLE then assign a numbering/ranking (#rn,#rn1,#rn2,#rn3) variable to each of the sub-query and join them up using LEFT JOIN. Now, this experiment works to exactly return the output that you need but its not a definite answer because there are some part that is questionable especially on matching the combination correctly. Hopefully, this will give you some idea.

Query to get most recent game results

I keep track of certain game results in a MySQL database. Now I want to print the latest results in a nice HTML table. I have three tables:
Table persons contains all participants of the game.
+-----+---------+
| id | name |
+-----+---------+
| 2 | Jon |
| 3 | Philip |
| 4 | Tom |
| 5 | Joey |
| 6 | Joanna |
+-----+---------+
The table rounds contains information about each round of the game. Among other things, the week in which the game was fought.
+-----+------+
| id | week |
+-----+------+
| 1 | 9 |
| 2 | 10 |
| 3 | 11 |
| 4 | 12 |
| 5 | 13 |
+-----+------+
And the table results contains the results for each person and round. The result column is a score in the game.
+------------+----------+--------+
| personId | roundId | result |
+------------+----------+--------+
| 2 | 1 | 2 |
| 4 | 1 | 6 |
| 5 | 1 | 6 |
| 3 | 1 | 10 |
| 2 | 2 | 16 |
| 4 | 2 | 14 |
| 5 | 2 | 5 |
| 3 | 2 | 11 |
+------------+----------+--------+
Now I want to print a table with the players scores each week. I want my output to look like the table below. Note that if a player did not participate one week, the cell should be empty.
+------+-----+--------+-----+------+--------+
| Week | Jon | Philip | Tom | Joey | Joanna |
+------+-----+--------+-----+------+--------+
| 9 | 2 | 10 | 6 | 6 | |
| 10 | 16 | 11 | 14 | 5 | |
+------+-----+--------+-----+------+--------+
So my question is: How do I do to get such output?
This is not a duplicate. See comments below.
So as stated in comments, you want to make a PIVOT, but MySQL does not support it.
However since your number of players is low and fixed, you can hardcode the players in a GROUP BY query like this :
SELECT R.Week,
SUM(CASE WHEN P.name = 'Jon' THEN S.result END) AS Jon,
SUM(CASE WHEN P.name = 'Philip' THEN S.result END) AS Philip,
SUM(CASE WHEN P.name = 'Tom' THEN S.result END) AS Tom,
SUM(CASE WHEN P.name = 'Joey' THEN S.result END) AS Joey,
SUM(CASE WHEN P.name = 'Joanna' THEN S.result END) AS Joanna
FROM persons P
LEFT JOIN results S ON P.id=S.personId
LEFT JOIN rounds R ON R.id=S.roundId
WHERE R.week IS NOT NULL
GROUP BY R.Week
SqlFiddleDemo

mysql query using inner joins and subquery

These are the tables:
professor:
+-------+--------+--------+--------+------+
| empid | name | status | salary | age |
+-------+--------+--------+--------+------+
| 1 | Arun | 1 | 2000 | 23 |
| 2 | Benoy | 0 | 3000 | 25 |
| 3 | Chacko | 1 | 1000 | 36 |
| 4 | Divin | 0 | 5000 | 32 |
| 5 | Edwin | 1 | 2500 | 55 |
| 7 | George | 0 | 1500 | 46 |
+-------+--------+--------+--------+------+
works:
+----------+-------+---------+
| courseid | empid | classid |
+----------+-------+---------+
| 1 | 1 | 10 |
| 2 | 2 | 9 |
| 3 | 3 | 8 |
| 4 | 4 | 10 |
| 5 | 5 | 9 |
| 6 | 1 | 9 |
| 2 | 3 | 10 |
| 2 | 1 | 7 |
+----------+-------+---------+
course:
+----------+------------+--------+
| courseid | coursename | points |
+----------+------------+--------+
| 1 | Maths | 100 |
| 2 | Science | 80 |
| 3 | English | 85 |
| 4 | Social | 90 |
| 5 | Malayalam | 99 |
| 6 | Arts | 40 |
+----------+------------+--------+
The question is :
Return list of employees who have taught course Maths or Science but
not both
The query which I wrote is :
select distinct professor.name from professor
inner join works
on professor.empid=works.empid
where works.courseid in
(select courseid from course where coursename ='Maths' or coursename='Science');
The output I received is:
Arun
Benoy
Chacko
Here the employee 'Arun' shouldnt have been displayed as he as taught both Maths and Science.
Please help me out !!
You may use an aggregate COUNT() to check that the total number of DISTINCT courses taught is exactly 1, while still filtering to the two different types of courses. That ensures that only one, never both, is returned.
Because the IN () limits all rows initially returned only to the two desired courses, professors can have a maximum of 2 possible different courses via COUNT(DISTINCT coursename). A HAVING clause then prohibits those with 2 from the final result set.
SELECT
DISTINCT professor.name
FROM
professor
INNER JOIN works ON professor.empid = works.empid
/* Join against course to get the course names */
INNER JOIN course ON works.courseid = course.courseid
WHERE
/* Restrict only to Maths, Science */
course.coursename IN ('Maths', 'Science')
GROUP BY professor.name
/* Only those with exactly one type of course */
HAVING COUNT(DISTINCT course.coursename) = 1
Here is a demonstration: http://sqlfiddle.com/#!2/2e9610/2
You want to use an xor here instead of an or.
select distinct professor.name from professor
inner join works
on professor.empid=works.empid
where works.courseid in
(select courseid from course where coursename ='Maths' xor coursename='Science');