MySql Select - row subtract previous row - mysql

I'm trying to extract stats from DB.
Table's structure is:
UpdatedId product_name revenue
980 Product1 1000
975 Product1 950
973 Product1 900
970 Product1 800
965 Product21 1200
So revenue = previous revenue + new revenue.
In order to make graphs, the goal is to get the output for Product1 like this
UpdateId Difference
980 50
975 50
973 100
970 0
I tried this query but MySQL gets stuck :)
select a.product_name, a.revenue, b.revenue, b.revenue- a.revenue as difference from updated_stats a, updated_stats b where a.product_name=b.product_name and b.revenue= (select min(revenue) from updated_stats where product_name=a.product_name and revenue > a.revenue and product_name= 'Product1')
Could you please tell me, how it should be queried? Thanks.

I would do this with a correlated subquery:
select u.*,
(select u.revenue - u2.revenue
from updated_stats u2
where u2.product_name = u.product_name and
u2.updatedid < u.updatedid
order by u2.updatedid desc
limit 1
) as diff
from updated_stats u;
Note: This returns NULL instead of 0 for 970. That actually makes more sense to me. But you can use COALESCE() or a similar function to turn it into a 0.
If updated_stats is even moderately sized, you will want an index on updated_status(product_name, updated_id, revenue). This index covers the subquery.

Related

MySQL Query to get the first user that sums X sales

I'm looking for help with a query.
I have a users table and a sales table. Sales are stored with their date, amount and quantity.
I need to get the first user that reaches an X sum of amount or Y sum of sales.
So I would need to take the sale date in account in order to know which user gets it first.
Can anyone give me a hand with this query or should I use another approach?
I'm currenlty using MySQL 8.0.32.
Here are some sample tables and an example of what is needed.
Users
id
username
1
user1
2
user2
Sales
id
amount
quantity
date
user_id
1
1000
2
2023-01-05
1
2
1500
3
2023-01-05
2
3
500
1
2023-01-07
1
4
1500
3
2023-01-10
1
5
500
1
2023-01-12
1
6
2500
5
2023-01-12
2
7
1000
2
2023-01-15
1
In this case, lets assume that the goal is the first user to sum >=4000. So user2 would be the winner as it reaches to 4000 before user1 even when at the end user1 sums 4500.
I would expect to get the lists of users untill the date that the first user get to the goal ie:
user_id
sum_amount
date
2
4000
2023-01-12
1
3500
2023-01-12
That would be super, but I could handle getting the first user got to the goal.
Thanks.
For MySql 8.0+ you can use SUM() window function to calculate the running total an sort the rows first by the rows that are equal or greater than 4000 and then the date:
SELECT user_id
FROM sales
ORDER BY SUM(amount) OVER(PARTITION BY user_id ORDER BY date) >= 4000 DESC,
date
LIMIT 1;
For previous versions of MySql use a correlated subquery that emulates the functionality of SUM() window function:
SELECT s1.user_id
FROM sales s1
ORDER BY (SELECT SUM(s2.amount)
FROM sales s2
WHERE s2.user_id = s1.user_id AND s2.date <= s1.date
) >= 4000 DESC,
s1.date
LIMIT 1;
See the demo.

How to get sum/expenditure in salary with minimum query

Hi here is the sql statement in mySql.
Can any body suggest best mySql statement for the following.
SELECT A.id, A.salary, A.salary+ IFNULL((SELECT SUM(B.salary) FROM test_salary B WHERE B.id < A.id ORDER BY id DESC),0) AS tot FROM test_salary A
How many times this query runs?
- number of rows available is table + 1.
I want result like this:
id salary tot
1 200 200
2 300 500
3 400 900
4 500 1400
5 600 2000
6 700 2700
7 800 3500
8 900 4400
where salary table has only id and salary field.
It is not clear from what you are trying to do here but the following is at least syntactically correct.
SELECT A.id, A.salary,
A.salary+IFNULL((SELECT SUM(B.salary) FROM test_salary B WHERE B.id = a.id),0)
from a
Based on your edit I can see that your query works. The sub query will run for every row encountered in your main query. An alternative is to use a variable
select a.id,a.salary,
#tot := #tot + a.salary as tot
from test_salary a, (select #tot:=0) t;
I suggest you compare your query to this for performance.

MySQL: Range based on rows in external table

I am using MySQL to solve this problem. I need to give points to a user based on the total time spent by him on a question. I have calculated the time spent by the user. Let's say it is in user_time table.
user_id question_id time_spent
1 1 7
1 2 50
2 1 11
My points are range based:
[0-10) seconds: 100 points,
[10-20) seconds: 300 points,
[20-30) seconds: 500 points,
[30, inf): 1000 points
Exact 10 seconds will fetch me 300 points. Though, the changes of an exact number would be low given that I am computing from the system clock difference.
This information is currently scored in an external table points_table
time_spent points
0 100
10 300
20 500
30 1000
I need a query which finds out which range the seconds belong to and give me that result.
user_id question_id points
1 1 100
1 2 1000
2 1 300
I tried thinking of different type of joins but couldn't think of one which will answer this specific requirement.
I think the easiest approach is a correlated subquery. Something like this:
select ut.*,
(select pt.points
from points_table pt
where pt.time_spent <= ut.time_spent
order by pt.time_spent desc
limit 1
) as points
from user_time ut
Try this:
SELECT ut.user_id, ut.time_spent, A.points
FROM user_time ut
INNER JOIN (SELECT p1.time_spent AS time_spent1,
p2.time_spent AS time_spent2,
p1.points
FROM points_table p1
INNER JOIN points_table p2 ON p1.time_spent < p2.time_spent
GROUP BY p1.time_spent
) AS A ON ut.time_spent BETWEEN A.time_spent1 AND A.time_spent2
For another take on this, you could achieve the same result without having the points table:
SELECT *,
CASE
WHEN time_spent >= 30 THEN 1000
WHEN time_spent >= 20 THEN 500
WHEN time_spent >= 10 THEN 300
ELSE 100
END 'Points'
FROM user_time;

How can I have a column with sum of smaller IDs in MySQL?

Assume I have a table like this:
id pay
-- ---
1 10
2 20
3 30
4 40
5 50
6 60
I want to create a view from table above with this result:
id pay paid_before
-- --- -------------
1 10 0
2 20 10
3 30 30
4 40 60
5 50 100
6 60 150
which "paid_before" is sum of pay rows that have smaller id.
How could I do this job?
This accomplishes what you want.
SELECT p1.id,p1.pay, sum(p2.pay) as Paid_Before FROM PAYMENTS P1 LEFT JOIN
PAYMENTS P2 ON p1.id > p2.id
GROUP BY p1.id, p1.pay
See this sql fiddle
In MySQL, this is most efficiently done with variables:
select p.id, p.pay, (#p := #p + p.pay) - p.pay as PaidBefore
from payments p cross join
(select #p := 0) vars
order by id;
Although this is not standard SQL (which I usually prefer), that is okay. The standard SQL solution is to use cumulative sum:
select p.id, p.pay, sum(p.pay) over (order by p.id) - p.pay as PaidBefore
from payments p;
Many databases support this syntax, but not MySQL.
The SQL Fiddle (courtesy of Atilla) is here.

Group by various column (with various joins) but sum distinct other column

I have to do some reporting, involving various tables, and having couple of SUMs, COUNTs, etc and everything is OK. But the last thing I have to resolve is SUM by another which is not in the grouped columns.
I'll give you an example (stripped down from what I have) so you can understand the tongue-twister in the previous paragraph.
Suppose I have a query with a couple of joins that get me this result, or a temporary table, or whatever:
(this is a trimmed down version, in the original I have much more columns and groupbys)
APP_ID CAT_ID CAT_DESCRIP APP_START APP_END DETAIL_ID DET_QTY DETAIL_PRICE
1 1 Categ One 900 960 1 10 150.00
1 1 Categ One 900 960 2 8 20.00
1 1 Categ One 900 960 3 12 30.00
1 1 Categ One 900 960 4 5 100.00
2 2 Categ Two 600 720 5 12 150.00
2 2 Categ Two 600 720 6 10 50.00
3 2 Categ Two 1200 1260 7 5 20.00
I need to get something like this: (the bolded column is the important)
SELECT
CAT_ID,
CAT_DESCRIP,
SUM(DET_QTY) as TotalQTY,
SUM(DETAIL_PRICE) as TotalPrice,
COUNT(DISTINCT APP_ID) as CountOfApps,
(GET THE SUM OF (APP_END - APP_START) ONLY ONE TIME BY APP_ID INTO THIS CATEG) as TimeInMinutesByCategory
FROM
MyTable
GROUP BY
CAT_ID
And the result has to give me this:
CAT_ID CAT_DESCRIP TotalQTY TotalPrice CountOfApps TimeInMinutesByCategory
1 Categ One 35 300.00 1 60
2 Categ Two 27 220.00 2 180
Thanks for your help!
I think this will do the job... or if not, a little tweaking on the sytnax for max(app_start) - max(app_end) should do the job
The idea is, summarize the data in a subquery by app_id and cat_id. Select the max value of start and end, grouped by app_id and cat_id. Since there will only be one value per each distinct pair of app_id and cat_id, we're essentially just deduping.
Then, join the subquery to the main query and summarize by category id.
SELECT
a.CAT_ID,
a.CAT_DESCRIP,
SUM(a.DET_QTY) as TotalQTY,
SUM(a.DETAIL_PRICE) as TotalPrice,
COUNT(DISTINCT a.APP_ID) as CountOfApps,
SUM(b.TimeInMinutesByCategory) AS TimeInMinutesByCategory
FROM
MyTable AS a
INNER JOIN (
SELECT APP_ID, CAT_ID, max(app_start) - max(app_end) AS TimeInMinutesByCategory
FROM MyTable
GROUP BY APP_ID, CAT_ID) AS b
ON a.cat_id = b.cat_id
AND a.app_id = b.app_id
GROUP BY
a.CAT_ID