Aggregate data by last n days for each row in MySQL - mysql

I have data in below format -
Date Amount
1/1/2000 1
2/1/2000 1
3/1/2000 1
4/1/2000 2
5/1/2000 1
6/1/2000 1
7/1/2000 1
Here, each row represents Amount collected on that day, I want to prepare a table where each row represents amount collected on last 3 days. Thus for 3/1/2000 - it will show amount =3 ( Amount 1 on 1/1 , 1 on 2/1 and 1 on 3/1 , so 1+1+1 = 3
So, from above data, table I want is -
Date Amount
1/1/2000 1 //1
2/1/2000 2 //1+1
3/1/2000 3 //1+1+1
4/1/2000 4 //1+1+2
5/1/2000 4 //1+2+1
6/1/2000 4 //2+1+1
7/1/2000 3 //1+1+1
How to write a SQL query for this?
I tried this -
select date, sum(amount) as amount_sum
from SQLTABLE
where DATEDIFF(date,date)<=2
group by date

You can use correlated subqueries in order to get the Amount values of the two previous records:
SELECT `Date`,
Amount +
COALESCE((SELECT Amount
FROM mytable AS t2
WHERE t2.`Date` < t1.`Date`
ORDER BY `Date` DESC LIMIT 1), 0) +
COALESCE((SELECT Amount
FROM mytable AS t2
WHERE t2.`Date` < t1.`Date`
ORDER BY `Date` DESC LIMIT 1, 1), 0) AS Amount
FROM mytable AS t1
The above query works even if there are gaps between consecutive records.
Edit:
If there are no gaps between consecutive records, then you can use the following query:
SELECT `Date`,
COALESCE((SELECT SUM(Amount)
FROM mytable AS t2
WHERE t2.date <= t1.date AND DATEDIFF(t1.date,t2.date) <= 2
ORDER BY `Date` DESC LIMIT 1), 0) AS Amount
FROM mytable AS t1

This can be done by using a sub-select.
SELECT date,
(SELECT sum(amount)
from SQLTABLE t2
WHERE DATEDIFF(t1.date,t2.date) IN (0,1,2)) amount_sum
from SQLTABLE t1

Related

Get sum of previous records in query and add or subtract the following results

Case:
I select an initial date and an end date, it should bring me the movements of all the products in that date range, but if there were movements before the initial date (records in table), I want to obtain the previous sum (prevData)
if the first move is exit 5 and the second move is income 2.
I would have in the first row (prevData-5), second row would have (prevData-5 + 2) and thus have a cumulative.
The prevData would be calculated as the sum of the above, validating the product id of the record, I made the query but if the product has 10 movements, I would do the query 10 times, and how would I identify the sum of another product_id?
SELECT
ik.id,
ik.quantity,
ik.date,
ik.product_id,
#balance = (SELECT SUM(quantity) FROM table_kardex WHERE product_id = ik.product_id AND id < ik.id)
from table_kardex ik
where ik.date between '2021-11-01' and '2021-11-15'
order by ik.product_id,ik.id asc
I hope you have given me to understand, I will be attentive to any questions.
#table_kardex
id|quantity|date|product_id
1 8 2020-10-12 2
2 15 2020-10-12 1
3 5 2021-11-01 1
4 10 2021-11-01 2
5 -2 2021-11-02 1
6 -4 2021-11-02 2
#result
id|quantity|date|product_id|saldo
3 5 2021-11-01 1 20 (15+5)
5 -2 2021-11-02 1 18 (15+5-2)
4 10 2021-11-01 2 18 (8+10-4)
6 -4 2021-11-02 2 14 (15+5-2)
Use MySQL 5.7
If you're using MySQL 8+, then analytic functions can be used here:
WITH cte AS (
SELECT *, ROW_NUMBER() OVER (PARTITION BY product_id ORDER BY date) rn,
SUM(quantity) OVER (PARTITION BY product_id ORDER BY date) saldo
FROM table_kardex
WHERE date BETWEEN '2021-11-01' AND '2021-11-15'
)
SELECT id, quantity, date, product_id, saldo
FROM cte
WHERE rn > 1
ORDER BY product_id, date;
MySQL 5.7
Try this:
SELECT *
FROM (
SELECT product_id,
t1.`date`,
SUM(t2.quantity) - t1.quantity cumulative_quantity_before,
SUM(t2.quantity) cumulative_quantity_after
FROM table t1
JOIN table t2 USING (product_id)
WHERE t1.`date` >= t2.`date`
AND t1.`date` <= #period_end
GROUP BY product_id, t1.`date`, t1.quantity
) prepare_data
WHERE `date` >= #period_start;
The easiest solution is to use the window function SUM OVER to get the running total. In the second step reduce this to the date you want to have this started:
SELECT id, quantity, date, product_id, balance
FROM
(
SELECT
id,
quantity,
date,
product_id,
SUM(quantity) OVER (PARTITION BY product_id ORDER BY id) AS balance
from table_kardex ik
where date < DATE '2021-11-16'
) cumulated
WHERE date >= DATE '2021-11-01'
ORDER BY product_id, id;
UPDATE: You have changed your request to mention that you are using an old MySQL version (5.7). This doesn't support window functions. In that case use your original query. If I am not mistaken, though, #balance = (...) is invalid syntax for MySQL. And according to your explanation you want id <= ik.id, not id < ik.id:
SELECT
ik.id,
ik.quantity,
ik.date,
ik.product_id,
(
SELECT SUM(quantity)
FROM table_kardex
WHERE product_id = ik.product_id AND id <= ik.id
) AS balance
FROM table_kardex ik
WHERE ik.date >= DATE '2021-11-01' AND ik.date < DATE '2021-11-16'
ORDER BY ik.product_id, ik.id;
The appropriate indexes for this query are:
create index idx1 on table_kardex (date, product_id, id);
create index idx2 on table_kardex (product_id, id, quantity);

How to find condition when sum is specific value by MySQL

I'd like to know some condition from this table.
date value
2022-01-01 5
2022-01-02 1
2022-01-03 3
2022-01-04 0
2022-01-05 2
2022-01-06 2
When is the date if sum of values exceed 10?
Actually, the answer is '2022-01-05'. Because sum from '2022-01-01' to '2022-01-05' is 11. It's easy for us as a human.
But how do I express in MySQL? Please let me know.
If you are using MySQL 8+ then window functions makes your requirement easy:
WITH cte AS (
SELECT *, SUM(value) OVER (ORDER BY date) sum_value
FROM yourTable
)
SELECT date
FROM cte
WHERE sum_value > 10
ORDER BY date
LIMIT 1;
On earlier versions of MySQL we can express the rolling sum with a correlated subquery:
SELECT date
FROM yourTable t1
WHERE (SELECT SUM(t2.value)
FROM yourTable t2
WHERE t2.date <= t1.date) >= 10
ORDER BY date
LIMIT 1;
Another approach for MySQL < 8, using a user variable to store the rolling sum -
SELECT `date`
FROM (
SELECT t.*, #sum_value := #sum_value + `value` AS `sum_value`
FROM t, (SELECT #sum_value := 0) z
ORDER BY `date` ASC
) y
WHERE `sum_value` >= 10
ORDER BY `date` ASC
LIMIT 1;

comparing between rows

How do I compare all the rows value(sum) in my table to the first row (which is the first date)
for example:
ID Date Sum
1 01-01-2020 60
2 01-02-2020 70
3 01-05-2020 80
4 01-06-2020 25
I want all the IDs which the sum in them is greater than 60 (the first date)
I tried to set the first date as min(date) but I can't compare the sums inside the date.
The result should be:
ID Date Sum
2 01-02-2020 70
3 01-05-2020 80
Select * from mytable where sum > (select sum from mytable order by date limit 1).
In MySQL 8+, you can use first_value():
select t.*, first_value(sum) over (order by date) as first_sum
from t;
You can then incorporate this into a subquery to get the rows that are at or exceed the value:
select t.*
from (select t.*, first_value(sum) over (order by date) as first_sum
from t
) t
where sum > first_sum;
You can also do this with a cross join:
select t.*
from t cross join
(select t.*
from t
order by date asc
limit 1
) t1
where t.sum > t1.sum;
Or do the same thing with a subquery in the where clause:
select t.*
from t
where t.sum > (select t2.sum
from t t2
order by t2.date
limit 1
);

Hide first row in database query

I have a query that exports its results via email in a table format, i would really like to hide the first row of my data so it never gets exported with my results.
Example database table:
+------+--------+--------+
|Number|Language|Date |
+------+--------+--------+
|2039 |text 1 |20/01/14|
+------+--------+--------+
|1 |text 2 |20/01/14|
+------+--------+--------+
|2 |text 3 |20/01/14|
+------+--------+--------+
The query that i am using at the moment is :
SELECT
COUNT(*) as `count`, `lang`, DATE(NOW()) as `week_ending`
FROM
mydata.table
WHERE
`date` > DATE_ADD(DATE(NOW()), INTERVAL - 1 WEEK) AND
`date` < DATE(NOW())
GROUP BY `lang` , DATE(NOW());
Is it possible to hide the row 2039 text 1 20/01/14
SELECT COUNT(*) as `count`,`lang`, DATE(NOW()) as `week_ending` FROM mydata.table WHERE `date` > DATE_ADD(DATE(NOW()), INTERVAL -1 WEEK) AND `date` < DATE(NOW()) GROUP BY `lang`, DATE(NOW()) LIMIT 1,x;
replace x with a number big enough to contain all your records.
or use, instead of x, 18446744073709551615, that is maximum value of big INT unsigned.
It really depends on which row you think appears first depending on the order. If no order is specified, you usually get your records based on insertion order.
If you are ordering by Number column in descending then add where clause
WHERE Number < MAX(Number)
If you are ordering by Number in ascending then add where clause
WHERE Number > MIN(Number)
Use LIMIT 1, N at the End of query.
Where N is the number of rows you want to retrieve.
if 'number' is the primary key you can get the same result using following query also.
SELECT
COUNT(*) as 'count', t1.language, DATE(NOW()) as 'week_ending'
FROM
(select * from 'mydata.table') t1 left join
(select * from 'mydata.table' limit 1) t2 on t1.number = t2.number
where t2.number is null
And
t1.date > DATE_ADD(DATE(NOW()), INTERVAL - 1 WEEK)
AND t1.date < DATE(NOW())
GROUP BY t1.language , DATE(NOW());

How to count consecutive number of 10 days

I have table with columns: id, name, date, present
Column present have values 0 or 1 or 2 and ... more
I need to count how many 0 valous is in current month 2013-07-01 - 2013-07-31 but count only when there are or more than 10 times.
for example if i have
2013-07-01 to 2013-07-10 valoues 0 it should count it and let me know that is 10 or more consecutives days like 11, 12 or more, but if it was less than 10 should count nothing.
I was trying some examples from stack... but they are different problems... so i need little help with that mysql query.
i have smth like this but need consecutives 10 days like >= 10
$sql = mysql_query("SELECT COUNT(name) as count FROM `table` WHERE (`present` = 0) AND (`date` BETWEEN '2013-07-01' AND '2013-07-31')");
while($row = mysql_fetch_array($sql)){
$result = $row['count'];
}
It counts me every 0 values in date between 2013-07-01 and 2013-07-31 but i need count how many days start from 10 or more consecutives days
column present have 0 and other numbers like 1, 2, 3... so i need count only 0 with 10 or more consecutives days
here is SqlFiddle i was trying to make warking from answer
http://sqlfiddle.com/#!2/1bde8/2
best regards
m.
This approach uses correlated subqueries to calculate two values.
The first value is the date of the previous record where Present = 1. This allows you to get the number of days in a row where Present = 0 by using datediff().
The second is the Present value of tomorrow, which will be NULL on the last day of the month. When today has Present = 0 and tomorrow is either 1 or NULL, then we can use this record. It is the end of a sequence of 0s.
From there is it just a question of adding up the values according to the conditions that you set. The following query assumes that you want to do this for each name:
select name, sum(case when datediff(date, lastPresentDate) >= 10
then datediff(date, lastPresentDate)
else 0 end) as DaysCounted
from (select t.*,
(select coalesce(max(date), '2013-06-30')
from t t2
where t2.name = t.name and
t2.present <> 0 and
t2.date <= t.date and
t2.date between '2013-07-01' and '2013-07-31'
) as lastPresentDate,
(select t2.present
from t t2
where t2.name = t.name and
t2.date = adddate(t.date, 1)
order by t2.date
limit 1
) as TomorrowPresent
from t
where date between '2013-07-01' and '2013-07-31'
) t
where Present = 0 and (TomorrowPresent = 1 and TomorrowPresent is null)
group by name
This query should give you a count only when it is 10 or greater than 10.
SELECT COUNT(`name`) as `count`
FROM `table`
WHERE (`present` = 0)
AND (`date` BETWEEN '2013-07-01' AND '2013-07-31')
HAVING `count` >= 10;
Hope it helps!
Not tested, but you could use user variables like this:-
SELECT SUM(if(ConsCounter=10, 1, 0))
FROM
(
SELECT id, name, date, present, #Counter := IF(#PrevPresent = present AND present = 0, #Counter + 1, 0) AS ConsCounter, #PrevPresent = present
FROM
(
SELECT id, name, date, present
FROM `table`
ORDER BY date
) Sub1
CROSS JOIN (SELECT #PrevPresent:=-99999, #Counter:=0) Sub2
) Sub4
Get all the records in date order and add a sequence number for the count since the present was first 0. Then count the number of times that counter is 10.