So we log when we send our clients promotional emails and sometimes clients are in our database for a while before they receive their first email so we want to know how many clients received their first ever email by month for the past 12 months.
So far I can only think to get the information month by month but there has to be a way to query all 12 months in a single query.
SELECT DISTINCT
`id`
FROM
`table1`
WHERE
`sendtime` BETWEEN '2019-08-01' AND '2019-09-01'
AND `id` NOT IN (SELECT
`id`
FROM
`table1`
WHERE
`sendtime` < '2019-08-01');
Check for the minimum sendtime for each user:
SELECT id
FROM table1
GROUP BY id
HAVING MIN(sendtime) BETWEEN '2019-08-01' AND '2019-09-01'
If you want the number of these ids:
SELECT COUNT(*) counter
FROM (
SELECT id
FROM table1
GROUP BY id
HAVING MIN(sendtime) BETWEEN '2019-08-01' AND '2019-09-01'
) t
You can use two levels of aggregation:
select date_format(min_sendtime, '%Y-%m') yyyy_mm, count(*) no_clients
from (
select id, min(sendtime) min_sendtime
from table1
group by id
) t
where min_sendtime >= date_format(current_date, '%Y-%m-01') - interval 1 year
group by yyyy_mm
order by yyyy_mm
This gives you one row for each of the last twelve months (that has a least one customer that received their first email), with the count of "new" email over the month.
Related
I have a Users table (id, name, created_at) and a Transaction table(id, user_id, created_at, amount).
For each month, I would like to know the number of users who did not have any transaction in the 3 months interval before that month.
For example, for April 2022, the query would return number of users who did not have a transaction in January 2022, February 2022 and March 2022. And so on for every month.
Can I do this with a single MySQL query, and without PHP loop?
If I wanted it for April 2022 only, then I guess this would do the trick:
SELECT count(distinct(users.id)) FROM users
INNER JOIN transactions
on users.id = transactions.user_id
WHERE transactions.user_id NOT IN
(SELECT user_id FROM transactions WHERE created_at > "2022-01-01" AND created_at < "2022-04-01" );
How to get it for all months?
In a normal situation, you would have a calendar table that, for examples, stores all starts of months over a wide period of time, like calendar(start_of_month).
From there on, you can cross join the calendar with the users table to generate all possible combinations of months and customers (with respect to the user's creation time). The last step is to check each user/month tuple for transations in the last 3 months.
select c.start_of_month, count(*) as cnt_inactive_users
from calendar c
cross join users u
where not exists (
select 1
from transactions t
where t.user_id = u.id
and t.created_at >= c.start_of_month - interval 3 month
and t.created_at < c.start_of_month
)
where c.start_of_month >= '2021-01-01' and c.start_of_month < '2022-01-01'
group by c.start_of_month
order by c.start_of_month
This gives you one row per month that has at least one "inactive" customers,with the corresponding count.
You control the range of months over which the query applies with the where clause to the query (as an example, the above gives you all year 2021).
SELECT count(*)
FROM users
WHERE NOT EXISTS (
SELECT NULL
FROM transactions
WHERE users.id = transactions.user_id
AND created_at > '2022-01-01' AND created_at < '2022-04-01'
);
I expect this query to give me the avg value from daily active users up to date and grouped by month (from Oct to December). But the result is 164K aprox when it should be 128K. Why avg is not working? Avg should be SUM of values / number of current month days up to today.
SELECT sq.month_year AS 'month_year', AVG(number)
FROM
(
SELECT CONCAT(MONTHNAME(date), "-", YEAR(DATE)) AS 'month_year', count(distinct id_user) AS number
FROM table1
WHERE date between '2020-10-01' and '2020-12-31 23:59:59'
GROUP BY EXTRACT(year_month FROM date)
) sq
GROUP BY 1
Ok guys thanks for your help. The problem was that on the subquery I was pulling the info by month and not by day. So I should pull the info by day there and group by month in the outer query. This finally worked:
SELECT sq.day_month, AVG(number)
FROM (SELECT date(date) AS day_month,
count(distinct id_user) AS number
FROM table_1
WHERE date >= '2020-10-01' AND
date < '2021-01-01'
GROUP BY 1
) sq
GROUP BY EXTRACT(year_month FROM day_month)
Do not use single quotes for column aliases!
SELECT sq.month_year, AVG(number)
FROM (SELECT CONCAT(MONTHNAME(date), '-', YEAR(DATE)) AS month_year,
count(distinct id_user) AS number
FROM table1
WHERE date >= '2020-10-01' AND
date < '2021-01-01'
GROUP BY month_year
) sq
GROUP BY 1;
Note the fixes to the query:
The GROUP BY uses the same columns as the SELECT. Your query should return an error (although it works in older versions of MySQL).
The date comparisons have been simplified.
No single quotes on column aliases.
Note that the outer query is not needed. I assume it is there just to illustrate the issue you are having.
I have an table with the following columns
email ---- created at
abc#gmail.com 2019-12-12 16:03:34
rp#gamil.com 2019-11-12 16:03:34
abc#gmail.com 2020-1-12 16:03:34
er#gmail.com 2020-1-12 16:03:34
I want to design a query that return the back number of emails that registered in consecutive 2 months. I am no novice with queries and have been struggling to come up with a query for this.
For the above the query abc#gmail.com was registered twice in consecutive months
By doing a self-join for Month + 1 and email (and also taking December-to-January transitions into account) this should work:
SELECT
*
FROM
(
SELECT
email,
YEAR( created ) AS createdYear,
MONTH( created ) AS createdMonth
FROM
table
) AS t
INNER JOIN
(
SELECT
email,
YEAR( created ) AS createdYear,
MONTH( created ) AS createdMonth
FROM
table
) AS monthPlus1 ON
t.email = monthPlus1.email
AND
(
(
t.createdMonth = monthPlus1.createdMonth + 1
AND
t.createdYear = monthPlus1.createdYear
)
OR
(
t.createdMonth = 12
AND
monthPlus1.createdMonth = 1
AND
t.createdYear + 1 = monthPlus1.createdYear
)
)
The date logic in this query is a bit gnarly - it can probably be improved by representing the month as a single date value or integer months-since-epoc rather than a year + month tuple.
You can use an EXISTS query to check if an email exists that also had a registration in the previous month:
SELECT DISTINCT email
FROM yourtable t1
WHERE EXISTS (SELECT *
FROM yourtable t2
WHERE t2.email = t1.email
AND DATE_FORMAT(t2.createdat, '%Y%m') = DATE_FORMAT(t1.createdat - INTERVAL 1 MONTH, '%Y%m'))
Output for your sample data
abc#gmail.com
Demo on dbfiddle
We use DISTINCT so we don't get multiple copies of the same email if an email address is registered in more than one consecutive month.
You can use lag(). If this occurs, then lag() will be in two adjacent months.
select t.email
from (select t.*
lag(created_at) over (partition by t.email order by created_at) as prev_created_at
from t
) t
where extract(year_month from created_at) = extract(year_month from (prev_created_at + interval 1 month));
You may need select distinct, if this can occur multiple times.
I need to create a query that looks like this image with the result:
You can ignore the names of the user, user_id is fine for now. Each user can have several timesheets for one day. So I need to count the hours and place it in its own column for day of the week. Then have a total at the end. Here is a screen shot of the database:
Here is what I have so far that gets me the days of the week totals but not grouped in one record with the day of the week as its own column and a total. Any help would be appreciated. Thanks!
SELECT user_id, WEEKDAY(start_date) AS day, (select time_to_sec(timediff(end_date, start_date )) / 3600) AS hours FROM `timesheet_table` WHERE id > 0 GROUP BY day, user_id
If you need a totat you can use a sum and group by
In Group by you can't use the alias but you should use the expression
SELECT
user_id
, WEEKDAY(start_date) AS day
, sum((select time_to_sec(timediff(end_date, start_date )) / 3600)) AS hours
FROM `timesheet_table`
WHERE id > 0
GROUP BY WEEKDAY(start_date), user_id
E.g.:
SELECT user_id
, DATE(start_date) dt
, SEC_TO_TIME(SUM(TIME_TO_SEC(end_date)-TIME_TO_SEC(start_date))) day_total
-- [or , SUM(TIME_TO_SEC(end_date)-TIME_TO_SEC(start_date))/3600 day_total]
FROM my_table
WHERE start_date BETWEEN '2016-10-01 00:00:00' AND '2016-10-07 23:59:59'
GROUP
BY user_id
, DATE(start_date);
The rest of the problem (missing days, display issues, weekly totals, etc.) would normally be handled in application level code.
I have a table, activity that looks like the following:
date | user_id |
Thousands of users and multiple dates and activity for all of them. I want to pull a query that will, for every day in the result, give me the total active users in the last 30 days. The query I have now looks like the following:
select date, count(distinct user_id) from activity where date > date_sub(date, interval 30 day) group by date
This gives me total unique users on only that day; I can't get it to give me the last 30 for each date. Help is appreciated.
To do this you need a list of the dates and join that against the activities.
As such this should do it. A sub query to get the list of dates and then a count of user_id (or you could use COUNT(*) as I presume user_id cannot be null):-
SELECT date, COUNT(user_id)
FROM
(
SELECT DISTINCT date, DATE_ADD(b.date, INTERVAL -30 DAY) AS date_minus_30
FROM activity
) date_ranges
INNER JOIN activity
ON activity.date BETWEEN date_ranges.date_minus_30 AND date_ranges.date
GROUP BY date
However if there can be multiple records for a user_id on any particular date but you only want the count of unique user_ids on a date you need to count DISTINCT user_id (although note that if a user id occurs on 2 different dates within the 30 day date range they will only be counted once):-
SELECT activity.date, COUNT(DISTINCT user_id)
FROM
(
SELECT DISTINCT date, DATE_ADD(b.date, INTERVAL -30 DAY) AS date_minus_30
FROM activity
) date_ranges
INNER JOIN activity
ON activity.date BETWEEN date_ranges.date_minus_30 AND date_ranges.date
GROUP BY date
A bit cruder would be to just join the activity table against itself based on the date range and use COUNT(DISTINCT ...) to just eliminate the duplicates:-
SELECT a.date, COUNT(DISTINCT a.user_id)
FROM activity a
INNER JOIN activity b
ON a.date BETWEEN DATE_ADD(b.date, INTERVAL -30 DAY) AND b.date
GROUP by a.date