calculate 2 days prior and 2 days after sales - mysql

wants to calculate 2 days prior and 2 days after sales
total_2_days_prior_sales = before 2 days to current date sales sum
total_2_days_prior_sales = from current date to next 2 days sales sum
Requirement:
Live Code:
http://sqlfiddle.com/#!9/d88bee/15
My Try:
CREATE TABLE test (
end_date date,
sales int
);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-01',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-01',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-02',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-02',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-03',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-04',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-05',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-06',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-07',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-08',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-08',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-09',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-10',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-11',10);
INSERT INTO test (end_Date, sales)
VALUES ('2022-01-12',10);
Code:
select
end_Date,
-- DATE_SUB(date(end_Date), INTERVAL 2 day),
-- DATE_SUB(date(end_Date), INTERVAL -2 day),
-- DATE_SUB(date(end_Date), INTERVAL 0 day),
SUM(sales) as CurrentSales,
SUM(case when end_Date between DATE_SUB(date(end_Date), INTERVAL 2 day) and DATE_SUB(date(end_Date), INTERVAL 0 day) then sales else 0 end) total_2_days_prior_sales,
SUM(case when end_Date between DATE_SUB(date(end_Date), INTERVAL 0 day) and DATE_SUB(date(end_Date), INTERVAL -2 day) then sales else 0 end) total_2_days_after_sales
from test
group by end_Date

Since MySQL 5.6 does not support window functions and CTE, you can use subqueries to get the totals for prior sales and after sales, like this
SELECT
*,
(SELECT SUM(t.sales)
FROM test t
WHERE t.end_date BETWEEN DATE_SUB(s.end_Date, INTERVAL 1 day) AND s.end_date
GROUP BY s.end_date) total_2_days_prior_sales,
(SELECT SUM(t.sales)
FROM test t
WHERE t.end_date BETWEEN s.end_date AND DATE_ADD(s.end_Date, INTERVAL 1 day)
GROUP BY s.end_date) total_2_days_after_sales
FROM (
SELECT
end_Date,
SUM(sales) CurrentSales
FROM test
GROUP BY end_Date
) s
Output
end_Date
CurrentSales
total_2_days_prior_sales
total_2_days_after_sales
2022-01-01
20
20
40
2022-01-02
20
40
30
2022-01-03
10
30
20
2022-01-04
10
20
20
2022-01-05
10
20
20
2022-01-06
10
20
20
2022-01-07
10
20
30
2022-01-08
20
30
30
2022-01-09
10
30
20
2022-01-10
10
20
20
2022-01-11
10
20
20
2022-01-12
10
20
10
If to change the interval from 2 days to 3 days, output is like this
end_Date
CurrentSales
total_3_days_prior_sales
total_3_days_after_sales
2022-01-01
20
20
50
2022-01-02
20
40
40
2022-01-03
10
50
30
2022-01-04
10
40
30
2022-01-05
10
30
30
2022-01-06
10
30
40
2022-01-07
10
30
40
2022-01-08
20
40
40
2022-01-09
10
40
30
2022-01-10
10
40
30
2022-01-11
10
30
20
2022-01-12
10
30
10
You can check a sqlfiddle here
Also you can use a self JOIN with your table and calculate sub totals conditionally like this
SELECT
s.end_date,
s.sales,
SUM(CASE WHEN t.end_date <= s.end_date
THEN t.sales
END) prev_sales,
SUM(CASE WHEN t.end_date >= s.end_date
THEN t.sales
END) after_sales
FROM (
SELECT
end_date,
SUM(sales) sales
FROM test
GROUP BY end_date
) s
JOIN test t ON t.end_date BETWEEN DATE_SUB(s.end_Date, INTERVAL 1 day) AND DATE_ADD(s.end_Date, INTERVAL 1 day)
GROUP BY end_date
This query produces the same output.
You can check it here
In general, a join should perform better.
Also you need a composite index on the end_date and sales columns in your table.

Related

How to retrieve data from previous 4 weeks (mySQL)

I am trying to write a query to get the last 4 weeks (Mon-Sun) of data. I want every week of data to be stored with an individual and shared table.
every week data store based on name if same name repeated on single week amt should sum and if multiple name it should be show data individual, To see an example of what I am looking for, I have included the desired input and output below.
this is my table
date
amt
name
2022-04-29
5
a
2022-04-28
10
b
2022-04-25
11
a
2022-04-23
15
b
2022-04-21
20
b
2022-04-16
20
a
2022-04-11
10
a
2022-04-10
5
b
2022-04-05
5
b
i want output like this
date
sum(amt)
name
2022-04-25 to 2020-04-29
16
a
2022-04-25 to 2020-04-29
10
b
2022-04-18 to 2022-04-24
35
b
2022-04-11 to 2022-04-17
30
a
2022-04-04 to 2022-04-10
10
b
I would appreciate any pointers or 'best-practises' which I should employ to achieve this task.
You can try to use DATE_ADD with WEEKDAY get week first day and end day.
SELECT
CASE WHEN
weekofyear(`date`) = weekofyear(NOW())
THEN 'current week'
ELSE
CONCAT(date_format(DATE_ADD(`date`, interval - WEEKDAY(`date`) day), '%Y-%m-%d'),' to ',date_format(DATE_ADD(DATE_ADD(`date`, interval -WEEKDAY(`date`) day), interval 6 day), '%Y-%m-%d'))
END 'date',
SUM(amt)
FROM T
GROUP BY
CASE WHEN
weekofyear(`date`) = weekofyear(NOW())
THEN 'current week'
ELSE
CONCAT(date_format(DATE_ADD(`date`, interval - WEEKDAY(`date`) day), '%Y-%m-%d'),' to ',date_format(DATE_ADD(DATE_ADD(`date`, interval -WEEKDAY(`date`) day), interval 6 day), '%Y-%m-%d'))
END
sqlfiddle
EDIT
I saw you edit your question, you can just add name in group by
SELECT
CONCAT(date_format(DATE_ADD(`date`, interval - WEEKDAY(`date`) day), '%Y-%m-%d'),' to ',date_format(DATE_ADD(DATE_ADD(`date`, interval -WEEKDAY(`date`) day), interval 6 day), '%Y-%m-%d')) 'date',
SUM(amt),
name
FROM T
GROUP BY
CONCAT(date_format(DATE_ADD(`date`, interval - WEEKDAY(`date`) day), '%Y-%m-%d'),' to ',date_format(DATE_ADD(DATE_ADD(`date`, interval -WEEKDAY(`date`) day), interval 6 day), '%Y-%m-%d')),
name
ORDER BY 1 desc
sqlfiddle
This is in SQL Server, and just a mess about. Hopefully it can be of some help.
with cteWeekStarts
as
(
select
n,dateadd(week,-n,DATEADD(week, DATEDIFF(week, -1, getdate()), -1)) as START_DATE
from
(values (1),(2),(3),(4)) as t(n)
), cteStartDatesAndEndDates
as
(
select *,dateadd(day,-1,lead(c.start_date) over (order by c.n desc)) as END_DATE
from cteWeekStarts as c
)
,cteSalesSumByDate
as
(
select s.SalesDate,sum(s.salesvalue) as sum_amt from
tblSales as s
group by s.SalesDate
)
select c3.n as WeekNum,c3.START_DATE,isnull(c3.END_DATE,
dateadd(day,6,c3.start_date)) as END_DATE,
(select sum(c2.sum_amt) from cteSalesSumByDate as c2 where c2.SalesDate
between c3.START_DATE and c3.END_DATE) as AMT
from cteStartDatesAndEndDates as c3
order by c3.n desc

Select date interval inside WHERE clause

I'm trying to query a list of loans that have been opened within the past 36 months. I'm also trying to query a count of loans that have been opened within the past 12 months as a separate column.
The query is returning the same values for both columns, how can I get get the count from both 36 month and 12 month intervals without running a separate query?
SELECT
`XDL-NAME`,
COUNT(distinct`XLN-LOANDATE`) as '36 Month Count',
COUNT(IF(`XLN-LOANDATE` >= DATE_SUB(NOW(),INTERVAL 12 MONTH), 1 , NULL)) AS '12 Month Count'
from
LOAN
JOIN
DEALER ON `XLN-DLNO` = `XDL-NUM` WHERE `XLN-LOANDATE` >= DATE_SUB(NOW(),INTERVAL 36 MONTH)
GROUP BY `XDL-NAME`
Instead of count for 12 months, change to sum like below
SELECT
`XDL-NAME`,
COUNT(distinct`XLN-LOANDATE`) as '36 Month Count',
SUM(IF(`XLN-LOANDATE` >= DATE_SUB(NOW(),INTERVAL 12 MONTH), 1 , 0)) AS '12 Month Count'
from
LOAN
JOIN
DEALER ON `XLN-DLNO` = `XDL-NUM` WHERE `XLN-LOANDATE` >= DATE_SUB(NOW(),INTERVAL 36 MONTH)
GROUP BY `XDL-NAME`
Try this:
SELECT
`XDL-NAME`,
count((SELECT `XLN-LOANDATE` WHERE `XLN-LOANDATE` >= DATE_SUB(NOW(),INTERVAL 36 MONTH))) AS '36 Month Count',
count((SELECT `XLN-LOANDATE` WHERE `XLN-LOANDATE` >= DATE_SUB(NOW(),INTERVAL 12 MONTH))) AS '12 Month Count',
from
LOAN
JOIN
DEALER ON `XLN-DLNO` = `XDL-NUM`
GROUP BY `XDL-NAME`
What I did was use subqueries to count each interval you wanted. This way they should output with Name, 36 month count, 12 month count all on one line (record).
I would expect this query to return the two separate counts:
SELECT `XDL-NAME`,
COUNT(*) as `36 Month Count`,
SUM(`XLN-LOANDATE` >= DATE_SUB(NOW(), INTERVAL 12 MONTH)) AS `12 Month Count`
FROM loan l join
dealer d
ON `XLN-DLNO` = `XDL-NUM`
WHERE `XLN-LOANDATE` >= DATE_SUB(NOW(), INTERVAL 36 MONTH)
GROUP BY `XDL-NAME`;
Try SUM like:
SUM(IF('2016-12-21' >= NOW() -INTERVAL 12 MONTH, 1, 0)) AS '12 Month Count'
Sample
mysql> SELECT IF('2016-12-21' >= NOW() -INTERVAL 12 MONTH, 1, 0);
+----------------------------------------------------+
| IF('2016-12-21' >= NOW() -INTERVAL 12 MONTH, 1, 0) |
+----------------------------------------------------+
| 1 |
+----------------------------------------------------+
1 row in set (0,00 sec)
mysql> SELECT IF('2015-12-22' >= NOW() -INTERVAL 12 MONTH, 1, 0);
+----------------------------------------------------+
| IF('2015-12-22' >= NOW() -INTERVAL 12 MONTH, 1, 0) |
+----------------------------------------------------+
| 1 |
+----------------------------------------------------+
1 row in set (0,00 sec)
mysql> SELECT IF('2015-12-21' >= NOW() -INTERVAL 12 MONTH, 1, 0);
+----------------------------------------------------+
| IF('2015-12-21' >= NOW() -INTERVAL 12 MONTH, 1, 0) |
+----------------------------------------------------+
| 0 |
+----------------------------------------------------+
1 row in set (0,00 sec)
mysql>

Select results if the newest date is older than 30 days

I have a MySQL table with 2 fields: id_type and created_at
There are several rows with the same id_type and different timestamps. Eg:
3 - 2015-06-10 12:01:20
1 - 2015-03-21 04:14:10
1 - 2015-03-17 04:14:10
0 - 2015-05-06 21:43:00
3 - 2015-05-13 19:34:32
3 - 2015-07-18 03:47:55
I need to select id_type if the newest corresponding created_at is older than 30 days (Or in other words, any id_type that was last recorded more than 30 days ago)
Expected result:
1
0
I've tried:
SELECT id_type FROM table WHERE MAX(created_at) < DATE_SUB(NOW(), INTERVAL 30 day)
Which has given me the error:
invalid use of group function
How should I build it properly?
Try this,
SELECT id_type FROM table_b WHERE created_at IN (Select MAX(created_at) from table_b where created_at < DATE_SUB(NOW(), INTERVAL 30 day));
You can use this :
select id_type from `table` where `created_at` >= DATE_SUB(CURDATE(), INTERVAL 3 MONTH)

Running total per id within sliding time window in MySql

I need a query that can tell me number of idObj for the given idTest for Yesterday, 2 day before and 3 day before.
Number of idObj flagged 2 day before should include idObj flagged Yesterday i.e. It only should display number of idObj flagged yesterday as well as 2 day before.
Number of idOjb flagged 3 day before should include idObj flagged Yesterday, 2 day before and 3 day before.
I also need the name of the column as date instead of 'DayBeforeYest' and 'TwoDayBefore'.
Below is what I am looking for:
idTest 2013-06-29 2013-06-28 2013-06-27
104 9 7 5
105 7 6 2
106 5 3 0
Here, on 2013-06-29, idObj counts includes those idObj that has been flagged on only 2013-06-29. On 2013-06-28, idObj count includes those idObj that has been flagged on 2013-06-29 and on 2013-06-28. On 2013-06-27, idObj count includes those idObj that has been flagged on
2013-06-29, 2013-06-28 and 2013-06-27. Therefore, 3 days ago column will have less idObj counts compared to yesterday.
Query
create table tblTest (dateFact date, idTest int, idObj int);
insert into tblTest values
('2013-06-29', 104, 4433), ('2013-06-29', 105, 3345), ('2013-06-29', 106, 5543),
('2013-06-28', 104, 4433), ('2013-06-28', 105, 3345), ('2013-06-28', 106, 4356),
('2013-06-27', 104, 3439), ('2013-06-07', 105, 3345), ('2013-06-07', 106, 8955);
Below is the query I came up with but it just counts number of idObj flagged on 2nd and 3rd day per idTest. It does not take into account of idObj flagged in prevous days. It also doesn't display column name in the date format.
select idTest, max(Yest) as Yest, max(DayBeforeYest) as DayBeforeYest, max(TwoDayBefore) as TwoDayBefore from
(
(select idTest, count(idObj) as Yest, 0 as DayBeforeYest, 0 as TwoDayBefore from tblTest
where dateFact =date_sub(curdate(), interval 1 day) group by idTest)
union
(select idTest, 0 as Yest, count(idObj) DayBeforeYest, 0 as TwoDayBefore from tblTest
where dateFact = date_Sub(curdate(), interval 2 day) group by idTest)
union
(select idTest, 0 as Yest, 0 as DayBeforeYest , count(idObj) TwoDayBefore from tblTest
where dateFact = date_sub(curdate(), interval 3 day) group by idTest) )x
group by idTest;
Thank you!
====================================
Edited:
create table tblTest (dateFact date, idTest int, idObj int);
INSERT INTO tblTest
select CURDATE() - INTERVAL 1 DAY, 104, 4433 UNION ALL
SELECT CURDATE() - INTERVAL 1 DAY, 105, 3345 UNION ALL
SELECT CURDATE() - INTERVAL 1 DAY, 106, 5543 UNION ALL
SELECT CURDATE() - INTERVAL 2 DAY, 104, 4433 UNION ALL
SELECT CURDATE() - INTERVAL 2 DAY, 105, 3345 UNION ALL
SELECT CURDATE() - INTERVAL 2 DAY, 106, 4356 UNION ALL
SELECT CURDATE() - INTERVAL 3 DAY, 104, 3439 UNION ALL
SELECT CURDATE() - INTERVAL 3 DAY, 105, 3345 UNION ALL
SELECT CURDATE() - INTERVAL 3 DAY, 106, 8955;
For the given example, output should be as below:
idTest 2013-06-30 2013-06-29 2013-06-28
104 1 1 0
105 1 1 1
106 1 0 0
On 2013-06-30 for idTest 104, we have 1 idObj 4433. On 2013-06-29 for idTest 104 we have 1 idObj 4433 which is also in 2013-06-30 for idTest 104.
On 2013-06-28 for idTest 104 we have 1 idObj 3439 which is not in either 2013-06-30 or 2013-06-29 for idTest 104. Therefore, you will see row values for 104 to be 1 1 0.
On 2013-06-30 for idTest 105, we have 1 idObj 3345. On 2013-06-29 for idTest 105 we have 1 idObj 3345 which is also in 2013-06-30 idTest 105.
On 2013-06-28 for idTest 105, we have 1 idObj 3345 which is also in 2013-06-30 and 2013-06-29. Therefore, you will see row values to be 1 1 1.
And so on...
On 2013-06-28, to count the idObj, it should be present in 2013-06-28, 213-06-29, 2013-06-30.
On 2013-06-29, to count the idObj, it should be present in 2013-06-29 and 2013-06-30.
On 2013-06-30, to count the idObj, it should be present in 2013-06-30.
UPDATED To make column names to be dates you have to use dynamic SQL.
SET #sql = CONCAT(
'SELECT idTest
, SUM(d1) `', DATE_FORMAT(CURDATE() - INTERVAL 1 DAY, '%Y-%m-%d'),
'`, SUM(d2 = 1 AND d1 = 1) `', DATE_FORMAT(CURDATE() - INTERVAL 2 DAY, '%Y-%m-%d'),
'`, SUM(d3 = 1 AND d2 = 1 AND d1 = 1) `', DATE_FORMAT(CURDATE() - INTERVAL 3 DAY, '%Y-%m-%d'),
'` FROM
(
SELECT idTest
,idObj
,SUM(CASE WHEN dateFact = CURDATE() - INTERVAL 1 DAY THEN 1 ELSE 0 END) d1
,SUM(CASE WHEN dateFact = CURDATE() - INTERVAL 2 DAY THEN 1 ELSE 0 END) d2
,SUM(CASE WHEN dateFact = CURDATE() - INTERVAL 3 DAY THEN 1 ELSE 0 END) d3
FROM tblTest
WHERE dateFact BETWEEN CURDATE() - INTERVAL 3 DAY AND CURDATE() - INTERVAL 1 DAY
GROUP BY idTest, idObj
) q
GROUP BY idTest');
PREPARE stmt FROM #sql;
EXECUTE stmt;
DEALLOCATE PREPARE stmt;
Sample output:
| IDTEST | 2013-07-02 | 2013-07-01 | 2013-06-30 |
-------------------------------------------------
| 104 | 1 | 1 | 0 |
| 105 | 1 | 1 | 1 |
| 106 | 1 | 0 | 0 |
Here is SQLFiddle demo
To make life easier on client (calling) side you can wrap this code into a stored procedure
DELIMITER $$
CREATE PROCEDURE sp_test_report()
BEGIN
-- put above mentioned code here
END$$
DELIMITER ;
And then use it
CALL sp_test_report();
Here is SQLFiddle demo

Joining the result of multiple select statements

I'd like to merge the results of the following three select statements horizontally. I tried using joins but no idea how to proceed since it involves COUNT and GROUP BY too.
SELECT DATE(created_at) as date,COUNT(*) as countd1 FROM b_users WHERE last_loggedin_at < DATE_ADD(created_at,INTERVAL 1 DAY) GROUP BY DATE(created_at)
SELECT DATE(created_at) as date,COUNT(*) as countd2 FROM b_users WHERE last_loggedin_at < DATE_ADD(created_at,INTERVAL 2 DAY) GROUP BY DATE(created_at)
SELECT DATE(created_at) as date,COUNT(*) as countd3 FROM b_users WHERE last_loggedin_at < DATE_ADD(created_at,INTERVAL 3 DAY) GROUP BY DATE(created_at)
The individual results would be
date countd1
2011-12-01 100
2011-12-02 120
2011-12-03 130
date countd2
2011-12-01 200
2011-12-02 220
2011-12-03 230
date countd3
2011-12-01 300
2011-12-02 320
2011-12-03 330
But I'd like to merge them so that I'll get the following result
date countd1 countd2 countd3
2011-12-01 100 200 300
2011-12-02 120 220 320
2011-12-03 130 230 330
How do I do this?
Is it possible to do something like the query below
SELECT a, COUNT(b where condition), COUNT(c where condition) FROM table GROUP BY a
.
Update
biziclop provided a great work around
SELECT DATE(created_at) AS date,
SUM(last_loggedin_at < DATE_ADD( created_at,INTERVAL 1 DAY )) AS countd1,
SUM(last_loggedin_at < DATE_ADD( created_at,INTERVAL 2 DAY )) AS countd2,
SUM(last_loggedin_at < DATE_ADD( created_at,INTERVAL 3 DAY )) AS countd3
FROM b_users GROUP BY DATE(created_at)
Solved, thank you! :)
In MySQL the results of comparisons are 1 if true, 0 if false, so you could SUM() them:
SELECT
DATE(created_at) AS date,
SUM( last_loggedin_at < DATE_ADD( created_at,INTERVAL 1 DAY )) AS countd1,
SUM( last_loggedin_at < DATE_ADD( created_at,INTERVAL 2 DAY )) AS countd2,
SUM( last_loggedin_at < DATE_ADD( created_at,INTERVAL 3 DAY )) AS countd3,
FROM b_users
GROUP BY DATE(created_at)