mysql group by week , one month and three recent month - mysql

I have a table with column named started_at
I want to get statistics of new inserted row by last day , week , one month and three month .
the started_at column format is default MySQL timestamp which is string .
before posting this question , I try this querys
SELECT WEEK(`started_at`) , COUNT(*) AS nbr FROM users_in_bots WHERE `bot_id` = 5529 GROUP BY WEEK (`started_at`);
SELECT MONTH(`started_at`), COUNT(*) AS nbr FROM users_in_bots WHERE `bot_id` = 5529 GROUP BY MONTH(`started_at`);
and the result is not what I want .
I want get all statistics with just one query .
the table structure :
CREATE TABLE `users_in_bots` (
`user_id` bigint(20) NOT NULL,
`bot_id` bigint(20) NOT NULL,
`started_at` timestamp NOT NULL DEFAULT current_timestamp() ON UPDATE current_timestamp()
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb3;
sample row :
INSERT INTO `users_in_bots` (`user_id`, `bot_id`, `started_at`) VALUES
(2314, 509492849, '2022-02-27 03:59:21'),
(28779, 210686266, '2022-03-03 21:51:38'),
(28779, 503513058, '2022-04-01 12:28:37'),
(28779, 515774720, '2022-03-25 08:25:16'),
(28779, 518099352, '2022-03-22 17:22:38'),
(28779, 519646468, '2022-03-04 22:02:02'),
(84588, 517141146, '2022-03-28 12:36:45'),
(87075, 509498849, '2022-02-27 03:59:21'),
(116264, 210509102, '2022-02-27 00:02:54'),
(116264, 212268136, '2022-02-27 00:29:06');
expected output ( what i wish to use in my application ):
new users in last 24 hour : 42
new users in last week : 532
new users in last month : 4568
and same with 3 and six month and all the time .

You can use conditional aggregation to get the results you want. For example:
SELECT SUM(started_at BETWEEN NOW() - INTERVAL 1 HOUR AND NOW()) last_hour,
SUM(started_at BETWEEN CURDATE() - INTERVAL 1 WEEK AND CURDATE()) last_week,
SUM(started_at BETWEEN CURDATE() - INTERVAL 1 MONTH AND CURDATE()) last_month,
SUM(started_at BETWEEN CURDATE() - INTERVAL 3 MONTH AND CURDATE()) last_3month,
SUM(started_at BETWEEN CURDATE() - INTERVAL 6 MONTH AND CURDATE()) last_6month
FROM users_in_bots
Output (for your sample data as of 2022-04-13):
last_hour last_week last_month last_3month last_6month
0 0 4 10 10
Demo on dbfiddle

Related

Count Number of a Specific Day(s) Between Two Dates

I have a single line in MySQL table: volunteers
user_id | start_date | end_date
11122 | 2017-04-20 | 2018-02-17
How can I find how many times the 3rd day or 24th day of a month appears? (i.e. 2017-05-03, 2017-06-03, 2017-12-24, 2018-01-24) I'm trying to get to the following count:
Sample Output:
user_id | number_of_third_day | number_of_twenty_fourth_day
11122 | 10 | 10
I look at the documentation online to see if there is a way I can say (pseudo):
SELECT
day, COUNT(*)
FROM volunteers
WHERE day(between(start_date, end_date)) in (3,24)
I tried to create a calendar table to no avail, but I would try to get the days, GROUP BY day, and COUNT(*) times that day appears in the range
WITH calendar AS (
SELECT start_date AS date
FROM volunteers
UNION ALL
SELECT DATE_ADD(start_date, INTERVAL 1 DAY) as date
FROM volunteers
WHERE DATE_ADD(start_date, INTERVAL 1 DAY) <= end_date
)
SELECT date FROM calendar;
Thanks for any help!
This one is more optimized since I generate date range by months not days as other questions, so its faster
WITH RECURSIVE cte AS
(
SELECT user_id, DATE_FORMAT(start_date, '%Y-%m-03') as third_day,
DATE_FORMAT(start_date, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM table1
UNION ALL
SELECT user_id,
DATE_FORMAT(third_day + INTERVAL 1 MONTH, '%Y-%m-03') as third_day,
DATE_FORMAT(twenty_fourth_day + INTERVAL 1 MONTH, '%Y-%m-24') as twenty_fourth_day,
start_date, end_date
FROM cte
WHERE third_day + INTERVAL 1 MONTH <= end_date
)
SELECT user_id,
SUM(CASE WHEN third_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_third_day,
SUM(CASE WHEN twenty_fourth_day BETWEEN start_date AND end_date THEN 1 ELSE 0 END) AS number_of_twenty_fourth_day
FROM cte
GROUP BY user_id;
Demo here
A dynamic approach is.
but creating the dateranges, takes a lot of time, so you should have a date table to get the dates
CREATE TABLE table1
(`user_id` int, `start_date` varchar(10), `end_date` varchar(10))
;
INSERT INTO table1
(`user_id`, `start_date`, `end_date`)
VALUES
(11122, '2017-04-20', '2018-02-17')
,(11123, '2019-04-20', '2020-02-17')
;
Records: 2 Duplicates: 0 Warnings: 0
WITH RECURSIVE cte AS (
SELECT
user_id,
`start_date` as date_run ,
`end_date`
FROM table1
UNION ALL
SELECT
user_id,
DATE_ADD(cte.date_run, INTERVAL 1 DAY),
end_date
FROM cte
WHERE DATE_ADD(date_run, INTERVAL 1 DAY) <= end_date
)SELECT user_id,
SUM(DAYOFMONTH(date_run) = 3) as day_3th,
SUM(DAYOFMONTH(date_run) = 24) as day_24th
FROM cte
GROUP BY user_id
user_id
day_3th
day_24th
11122
10
10
11123
10
10
fiddle
In last MySQL version you can use recursion:
-- get list of all dates in interval
with recursive dates(d) as (
select '2017-04-20'
union all
select date_add(d, interval 1 day) from dates where d < '2018-02-17'
) select
-- calculate
sum(day(d) = 10) days_10,
sum(day(d) = 24) days_24
from dates
-- filter 10 & 24 days
where day(d) = 10 or day(d) = 24;
https://sqlize.online/sql/mysql80/c00eb7de69d011a85502fa538d64d22c/
As long as you are looking for days that occur in every month (so not the 29th or beyond), this is just straightforward math. The number of whole calendar months between two dates (exclusive) is:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
Then add one if the start month includes the target day and one if the end month includes it:
timestampdiff(month,start_date,end_date) - (day(start_date) <= day(end_date))
+ (day(start_date) <= 3) + (day(end_date) >= 3)

SQL MAX datetime - 1 day

I want to do a comparison between two dates. The highest date (currently via MAX datetime) is working, but I can't get the day after the highest date to compare the data with.
I'm using the following to get the data of the highest available date:
SELECT `datetime`, `standardSubscriptionDuration`,
SUM(`activeStandardPriceSubscriptions`) AS OneMonthActiveStandard
FROM `Subscription_totals`
WHERE `standardSubscriptionDuration` = '1 Month'
AND `datetime` = (SELECT MAX(`datetime`) AS Date FROM `Subscription_totals`)";
I already tried:
(SELECT MAX(`datetime`) -1 AS Date
But this won't give the result. How am I able to get the data of yesterday and eventually compare them?
I think that you want the following date arithmetics:
WHERE
`standardSubscriptionDuration` = '1 Month'
AND `datetime` = (
SELECT MAX(`datetime`) - interval 1 day AS Date FROM `Subscription_totals`
)

MySQL multiple conditions - concatenate or subquery?

We had a MySQL query that selected records and was required to also show Sold records for 60days before being removed from the shown results.
Logic was such that after changing a record from some invStatus to 1 which equals sold, we would filter it from our records after 60 days.
The problem is that if the filter is applied in a single query even active records are getting dropped if they are not updated within that 60 day window.
So how to select all records and then only filter a subset of those records based on date interval?
Should I select ALL the Ids and then filter those that are status sold and then apply the date interval in a subquery or run two queries and concatenate the two?
UPDATE:
SQLFiddle created that shows (10) records.
The goal is to not lose any invStatus = 0 but filter invStatus records that = 1 by NOT returning them if Update_date is older than 60 days from today
There are (7) records that have a invStatus = 0 (not Sold)
and (3) records that have invStatus = 1 (Sold)
SELECT
tblinventory.invId,
tblinventory.`Update_date`,
tblinventory.invStatus
FROM
tblinventory
WHERE NOT (Update_date < NOW() - INTERVAL 60 DAY)
ORDER BYtblinventory.invId
results in (6) records
5 which are invStatus = 0
1 that is invStatus = 1
Should be
(7) invStatus 0’s as they ALL should be present
(1) invStatus = 1 that is within 60 days
SQLFiddle schema:
CREATE TABLE IF NOT EXISTS `tblinventory` (
`invId` int(4) NOT NULL,
`Update_date` timestamp NULL DEFAULT CURRENT_TIMESTAMP,
`invStatus` int(3) NOT NULL DEFAULT '0'
) DEFAULT CHARSET=utf8;
INSERT INTO `tblinventory` (`invId`,`Update_date`,`invStatus`)
VALUES
("3777","2019-08-06 00:00:00","1"),
("3782","2019-08-30 00:00:00","0"),
("3820","2019-04-04 00:00:00","0"),
("3821","2019-03-21 00:00:00","1"),
("3835","2019-02-20 00:00:00","0"),
("3836","2019-06-30 00:00:00","1"),
("4035","2019-08-25 00:00:00","0"),
("4036","2019-09-01 00:00:00","0"),
("4037","2019-09-01 00:00:00","0"),
("4038","2019-09-01 00:00:00","0");
Query:
SELECT
tblinventory.invId,
tblinventory.`Update_date`,
tblinventory.invStatus
FROM
tblinventory
WHERE NOT (Update_date < NOW() - INTERVAL 60 DAY) AND invStatus = 1
ORDER BY
tblinventory.invId
Comparative Query:
WHERE NOT (Update_date < NOW() - INTERVAL 60 DAY)
https://www.db-fiddle.com/f/3KuYDHgYaNtaB8mMuatrz2/0
I guess you need a simple UNION ALL clause. Presumably invStatus <> 1 means non sold entities, You can try below query -
SELECT
tblinventory.invId,
tblinventory.`Update_date`,
tblinventory.invStatus
FROM
tblinventory
WHERE `invStatus` = 0 OR (Update_date >= NOW() - INTERVAL 60 DAY)
ORDER BY
tblinventory.invId

MYSQL: Getting all results from last week starting Monday)

CREATE TABLE `sport_data` (
`id` int(255) NOT NULL,
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`sport` varchar(255) NOT NULL,
`musclePlan` varchar(255) NOT NULL,
`sport_time` varchar(255) NOT NULL,
`kcal` varchar(255) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
How can i get all data from this table from the last week (from Monday to Sunday)?
I have tried:
WHERE date >= curdate() - INTERVAL DAYOFWEEK(curdate())+5 DAY AND date < curdate() - INTERVAL DAYOFWEEK(curdate())-2 DAY
I don't know if this is correct that way?
Thanks in advance.
The >= and < pattern is what we usually use. That part looks right.
I think the question is about the expressions that returning the range start and end values.
I suggest that we test those expressions for a variety of date values, not just CURDATE(). We can use a value in place of CURDATE(), and check the results, and do that for a series of date values.
Conveniently, those expressions will evaluate the same in a SELECT list as they do in a WHERE clause. So we can run a SELECT statement, and check the results.
For example:
SELECT t.dt AS dt
, t.dt - INTERVAL DAYOFWEEK(t.dt)+5 DAY AS _ge
, t.dt - INTERVAL DAYOFWEEK(t.dt)-2 DAY AS _lt
FROM (
SELECT CURDATE() + INTERVAL 0 DAY AS dt
UNION ALL SELECT CURDATE() + INTERVAL -1 DAY
UNION ALL SELECT CURDATE() + INTERVAL -2 DAY
UNION ALL SELECT CURDATE() + INTERVAL -3 DAY
UNION ALL SELECT CURDATE() + INTERVAL -4 DAY
UNION ALL SELECT CURDATE() + INTERVAL -5 DAY
UNION ALL SELECT CURDATE() + INTERVAL -6 DAY
UNION ALL SELECT CURDATE() + INTERVAL -7 DAY
UNION ALL SELECT CURDATE() + INTERVAL -8 DAY
UNION ALL SELECT CURDATE() + INTERVAL -9 DAY
UNION ALL SELECT CURDATE() + INTERVAL -10 DAY
) t
If the expressions are returning the values we expect, in accordance with the specification, for each possible date value, then the expressions are right.
If the expressions are returning values that don't meet the spec, then we need to make adjustments. Note that an expression that "works" on a Wednesday date might not "work" on a Sunday date.)
DEMO: Showing 3 approaches and why current doesn't work.
If we assume by "last week" you mean the last full week Monday - Sunday of a week prior to the day you're presently on...
So if today was 20180422, you'd want 20180409-20180415
SELECT *
FROM SO50026532_sport_data
CROSS JOIN (SELECT #Today:=curdate()) z
WHERE date >= #today - interval (6 + case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end) day
and date <=#today - interval (case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end) day;
Or if your a fan of the >= and < then
SELECT *
FROM SO50026532_sport_data
CROSS JOIN (SELECT #Today:=curdate()) z
WHERE date >= #today - interval (6 + case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end) day
and date <#today - interval (case when dayofweek(#today)=1 then 7 else dayofWeek(#today)-1 end-1) day;
in the 2nd example we had to subtract by 1 since dayofweek isn't a 0 based. Of course I could have just date shifted everything down 2 and set sunday to 6... then I wouldn't' need to subtract by 1. and then we'd be adding 7 instead of 6 on the 1st part of the where. (demo has these 3 and your initial example showing what happens on 4/22.
Dayofweek starts on Sunday being 1. So I use a case statement to shift 1 to 7 and and all the others down 1 giving us Monday = 1 and sunday = 7
The cross join to derived table z was so I could control the curdate() easier and test. You could replace the variable with curdate() if you want and eliminate the cross join and derived table.
The first where clause subtracts 6 days (1 week and then the # of days from current date back to monday. This ensures we always start 1 week back from current date and on a monday. then we only get dates to the Sunday of that week.

How I can count the number of times a value appears in a column grouped by day?

My table structure is:
CREATE TABLE `survey` (
`id` int(11) NOT NULL auto_increment,
`submitdate` datetime default NULL,
`answer` varchar(5) collate utf8_unicode_ci default NULL,
PRIMARY KEY (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci AUTO_INCREMENT=499 ;
Here answer contains values such as a1, a2, a3.
I want to calculate the last 10 days records depending upon answer. Is there is no record on a particular day, it should be zero.
The output I want is
date count answer
19-11-2012 10 a1
19-11-2012 8 a2
19-11-2012 0 a3
18-11-2012 30 a1
18-11-2012 30 a2
18-11-2012 30 a3
I used a query like
SELECT days.day, count(survey.id)
FROM
(select curdate() as day
union select curdate() - interval 1 day
union select curdate() - interval 2 day
union select curdate() - interval 3 day
union select curdate() - interval 4 day
union select curdate() - interval 5 day
union select curdate() - interval 6 day
union select curdate() - interval 7 day
union select curdate() - interval 8 day
union select curdate() - interval 9 day) days
left join survey
on days.day = date(survey.submitdate)
group by
days.day
You can look back 10 days by using the SUBDATE function and compare dates using '>'. You'll also want to GROUP BY the answer as well as the day, since you're attempting to calculate the count of each individual answer per day.
SELECT DATE(submitdate) AS day
answer,
COUNT(*) AS answer_count
FROM survey
WHERE DATE(submitdate) > DATE(SUBDATE(CURRENT_DATE, 10))
GROUP BY day, answer;