How to cummulative sum in MySql with grupBy and interval? - mysql

Table Sales
date id_product total sales
2018-10-01 1 40
2019-09-01 1 20
2019-11-01 1 5
2019-12-01 1 40
2020-01-01 1 10
2020-02-01 1 15
2020-03-01 1 20
2020-08-01 1 10
2021-01-01 1 5
2021-02-01 1 8
2021-04-01 1 12
Table Product
id name
1 Book
2 Pen
How to query in MySql to get the number (serial/sequential number) and the total cumulative sales with an interval of 3 years to get results like this?
number name date sum_sales cummulative_sales
1 Book 2018 40 40
2 Book 2019 65 105
3 Book 2020 55 160
4 Book 2021 25 145

Assuming you are running MySQL 8+, you may try using SUM as an analytic function:
SELECT
ROW_NUMBER() OVER (PARTITION BY p.name ORDER BY YEAR(s.date)) number,
p.name,
YEAR(s.date) AS date,
SUM(s.total_sales) AS sum_sales,
SUM(SUM(s.total_sales)) OVER (PARTITION BY p.name ORDER BY YEAR(s.date)) AS cummulative_sales
FROM Sales s
INNER JOIN Product p
ON p.id = s.id_product
GROUP BY
p.name,
YEAR(s.date)
ORDER BY
p.name,
YEAR(s.date);

Using MySQL 5.7, this will give you a sum and a cumulative sum of sales in a window of 3 years.
SELECT (#row = #row + 1) AS number, name, YEAR(date) AS date,
SUM(total_sales) as sum_sales,
iF(
#window > 0,
#cummulative_sales := #cummulative_sales + SUM(total_sales),
#cummulative_sales := SUM(total_sales)
) AS cummulative_sales,
#window := #window + 1,
#window := #window % 3
FROM Sales
JOIN Product ON Sales.id_product = Product.id,
(
SELECT #row := 0, #cummulative_sales := 0, #window := 0
) as a
GROUP BY name, YEAR(date);

Related

Get the first n lines for group after summing up

I am practising my skills in MySQL using the Sakila DB.
I would I have created a view called rentals_customer_store_film_category with is the union of customers, payments, rentals, film, and category tables.
I would now like to get the top 5 films by income. Meaning that I would lie to sum up all incomes of each film by store and then return the first 5.
I tried the below code but it does not work.
I cannot figure out what is wrong with it
Any help?
SELECT store_id, film_id, income
FROM
(SELECT film_id, store_id, sum(amount) as income,
#store_rank := IF(#current_store = store_id, #store_rank + 1, 1) AS store_rank,
#current_store := store_id
FROM rentals_customer_store_film_category
group by store_id, film_id
ORDER BY store_id, income DESC, film_id
) ranked
WHERE store_rank <= 5
RESULTS BELOW. As you can see, it does not stop at the fifth row per store. It shows all films by store while I would like only the top 5 for store:id 1 and top 5 for store id 2.
store_id film_id income
1 971 134.82
1 879 132.85
1 938 127.82
1 973 123.83
1 865 119.84
1 941 117.82
1 267 116.83
1 327 110.85
1 580 106.86
1 715 105.85
1 897 104.85
...
...
...
...
2 878 127.83
2 791 127.82
2 854 123.83
2 946 117.86
2 396 113.81
2 369 111.84
2 764 110.85
2 260 110.84
2 838 110.82
2 527 109.83
2 893 106.84
2 71 102.87
2 8 102.82
...
...
...
...
The order in this case is important to compare the previous store_id with the current,try this:
SELECT store_id, film_id, income
FROM
(SELECT film_id, store_id, sum(amount) as income,
#First compare previus with current
#store_rank := IF(#prev_store = store_id, #store_rank + 1, 1) AS store_rank,
#asign previus store
#prev_store := store_id
FROM films
group by store_id, film_id
ORDER BY store_id, income DESC, film_id
) ranked
WHERE store_rank <= 5

How to GROUP BY in groups of N rows?

I have a table called PEOPLE with the following data:
MYNAME AGE MYDATE
==========================
MARIO 20 2015/02/03
MARIA 10 2015/02/02
PEDRO 40 2015/02/01
JUAN 15 2015/01/03
PEPE 20 2015/01/02
JULIA 30 2015/01/01
JUANI 50 2014/02/03
MARTIN 10 2014/02/03
NASH 21 2014/02/03
Then I want to get the average of age grouping the people in groups of 3 ordering by MYDATE descending.
I mean, the result that I'm looking for would be something like:
23,3
21,6
27
Where 23,3 is the average of the age of:
MARIO 20 2015/02/03
MARIA 10 2015/02/02
PEDRO 40 2015/02/01
And 21,6 is the average of the age of:
JUAN 15 2015/01/03
PEPE 20 2015/01/02
JULIA 30 2015/01/01
And 27 is the average of the age of:
JUANI 50 2014/02/03
MARTIN 10 2014/02/03
NASH 21 2014/02/03
How could I handle this? I know how to use GROUP BY but only to group for a particular field of the table.
SQL tables are inherently unordered, so I assume that you want to order by mydate descending. You can enumerate the rows using variables, use arithmetic to define the groups, and then get the average:
select avg(age)
from (select t.*, (#rn := #rn + 1) as seqnum
from table t cross join
(select #rn := 0) vars
order by mydate desc
) t
group by floor((seqnum - 1) / 3);
Try - You can also then use Grp to get which three people the average relates to
select MYNAME, AGE, MYDATE, RN / 3 As Grp into #x
from
(select MYNAME, AGE , MYDATE, ROW_NUMBER() over(order by MyDate) + 2 as RN
from MYdata)x
select Grp, AVG(Age) as AvgAge
From #x
Group By Grp

Sql Dynamic Column for Position of AgentID

I had Collection Table for Agents
Agentid logintime AmountReceived
1 2013/10/01 00:10:10 10000
1 2013/10/01 17:23:10 200
1 2013/10/01 00:30:41 3000
2 2013/10/02 05:10:52 1000
3 2013/10/02 09:10:25 2000
3 2013/10/03 10:10:18 2000
2 2013/10/03 13:10:35 7000
I want a query that should display the output as
Agentid Amount Rank
1 13200 1
2 8000 2
3 4000 3
Displaying UI:
agentID : 1
Today: 13200 (Today amount yesturady amount should be place in this month)
Month: Need to display month total amount(30 days amount last month amount in this year)
Year : Need to display year total amount(12 months amount)
Try this:
SELECT Agentid,SUM(AmountReceived) as Amount,#rownum := #rownum + 1 AS Rank
FROM TableName,(SELECT #rownum := 0) r
GROUP BY AgentID
ORDER BY SUM(AmountReceived) DESC
Result:
AGENTID AMOUNT RANK
1 13200 1
2 8000 2
3 4000 3
See result in SQL Fiddle.
EDIT:
For year-wise and month-wise result:
SELECT Agentid,year(logintime) as Year,monthname(logintime) as Month,SUM(AmountReceived) as Amount,#rownum := #rownum + 1 AS Rank
FROM TableName,(SELECT #rownum := 0) r
GROUP BY AgentID,year(logintime),monthname(logintime)
ORDER BY SUM(AmountReceived) DESC
Result:
AGENTID YEAR MONTH AMOUNT RANK
1 2013 October 13200 1
2 2013 October 8000 2
3 2013 October 4000 3
Result in SQL Fiddle.

MySQL complex ranking query

I need to rank users in MySQL where the rank takes into account both ties and continues to counts the tied users as part of the rank.. For example..
points rank
100 1
100 1
100 1
70 4
70 4
60 5
50 6
40 7
40 7
10 8
0 9
0 9
Using the code below I'm ranking as follows...
points rank game
100 1 1
100 1 1
100 1 1
70 2 1
70 2 1
60 3 1
50 4 1
40 5 1
40 5 1
10 6 1
0 7 1
0 7 1
UPDATE rank_table
JOIN (SELECT f.points ,
IF (#lastPoint <> f.points,
#curRank := #curRank +1,
#curRank) AS rank,
#lastPoint := f.points
FROM rank_table f
JOIN (SELECT #curRank := 0, #lastPoint := -1) r
WHERE f.game =1
ORDER BY f.points DESC
) ranks ON (ranks.points = rank_table.points)
SET rank_table.rank = ranks.rank WHERE rank_table.game =1;
Would anyone know it this is possible..?
You do not need any mysql-variable.
Your new rank is the number of players having more points than you.
update
result
join (
select
n.id, count(distinct q.id) total
from result n
left join result q
on
q.points > n.points
group by n.id) m
on
m.id = id
set rank=m.total + 1
(assuming there is some kind of id like player_id)

How to make a fake column with an autoincrement number in a "group by" query

I have data in a table like this:
fgid qty ntid
1 100 10
2 90 10
6 200 11
1 80 11
1 120 12
6 100 12
6 30 13
And i make query :
SELECT fgid, SUM(qty) AS total_qty, COUNT(ntid) AS nt_count FROM sofg
GROUP BY fgid
AND the result is :
fgid total_qty nt_count
1 300 3
2 90 1
6 330 3
Then i want to make the result like this :
no fgid total_qty nt_count
1 1 300 3
2 2 90 1
3 6 330 3
How to do that with a query? where 'no' is (like) autoincrement number.
Try this query.
SELECT
#rownum := #rownum + 1 rownum,
t.*
FROM (SELECT #rownum:=0) r,
(
SELECT fgid, SUM(qty) AS total_qty, COUNT(ntid) AS nt_count FROM sofg GROUP BY fgid
) t;
Basically the same as Dhinakaran's answer, but there's no need to put the whole main query into a subquery. There's no difference to his answer appart from maybe being more pleasing to the eye, but please accept Dhinakaran's answer, as he was faster.
SELECT
#rownum:=#rownum + 1 as rownumber,
fgid,
SUM(qty) AS total_qty,
COUNT(ntid) AS nt_count
FROM sofg
, (select #rownum:=0) v
GROUP BY fgid