MySQL query help with grouping and adding - mysql

I have a table called user_logins which tracks user logins into the system. It has three columns, login_id, user_id, and login_time
login_id(INT) | user_id(INT) | login_time(TIMESTAMP)
------------------------------------------------------
1 | 4 | 2010-8-14 08:54:36
1 | 9 | 2010-8-16 08:56:36
1 | 9 | 2010-8-16 08:59:19
1 | 3 | 2010-8-16 09:00:24
1 | 1 | 2010-8-16 09:01:24
I am looking to write a query that will determine the number of unique logins for each day if that day has a login and only for the past 30 days from the current date. So for the output should look like this
logins(INT) | login_date(DATE)
---------------------------
1 | 2010-8-14
3 | 2010-8-16
in the result table 2010-8-16 only has 3 because the user_id 9 logged in twice that day and him logging into the system only counts as 1 login for that day. I am only looking for unique logins for a particular day. Remember I only want the past 30 days so its like a snapshot of the last month of user logins for a system.
I have attempted to create the query with little success what I have so far is this,
SELECT
DATE(login_time) as login_date,
COUNT(login_time) as logins
FROM
user_logins
WHERE
login_time > (SELECT DATE(SUBDATE(NOW())-1)) FROM DUAL)
AND
login_time < LAST_DAY(NOW())
GROUP BY FLOOR(login_time/86400)
I know this is wrong and this returns all logins only starting from the beginning of the current month and doesn't group them correctly. Some direction on how to do this would be greatly appreciated. Thank you

You need to use COUNT(DISTINCT ...):
SELECT
DATE(login_time) AS login_date,
COUNT(DISTINCT login_id) AS logins
FROM user_logins
WHERE login_time > NOW() - interval 30 day
GROUP BY DATE(login_time)
I was a little unsure what you wanted for your WHERE clause because your question seems to contradict itself. You may need to modify the WHERE clause depending on what you want.

As Mark suggests you can use COUNT(DISTINCT...
Alternatively:
SELECT login_day, COUNT(*)
FROM (
SELECT DATE_FORMAT(login_time, '%D %M %Y') AS login_day,
user_id
FROM user_logins
WHERE login_time>DATE_SUB(NOW(), INTERVAL 1 MONTH)
GROUP BY DATE_FORMAT(login_time, '%D %M %Y'),
user_id
)
GROUP BY login_day

Related

How to select rows in every day between two dates using MySQL

I want to keep track of users logged in every day between two dates.
Let's say I have a table my_table like this:
user_id
login_datetime
1
2021-10-02 10:00:00
1
2021-10-02 12:00:00
2
2021-10-02 12:20:00
1
2021-10-03 17:00:00
1
2021-10-04 22:00:00
2
2021-10-04 23:00:00
and given date range is from '2021-10-02' to '2021-10-04'.
I want to get user_id = 1 in this case, because user_id = 2 is not logged in at '2021-10-03'
result
user_id
login_date
1
2021-10-02
1
2021-10-03
1
2021-10-04
Is there any solution for this?
One approach uses aggregation:
SELECT user_id
FROM my_table
WHERE login_datetime >= '2021-10-02' AND login_datetime < '2021-10-05'
GROUP BY user_id
HAVING COUNT(DISTINCT DATE(login_datetime)) = 3; -- range has 3 dates in it
Demo
The HAVING clause asserts that any matching user must have 3 distinct dates present, which would imply that such a user would have login activity on all dates from 2021-10-02 to 2021-10-04 inclusive.
Edit:
To get the exact output in your question, you may use:
SELECT DISTINCT user_id, DATE(login_datetime) AS login_date
FROM my_table
WHERE user_id IN (
SELECT user_id
FROM my_table
WHERE login_datetime >= '2021-10-02' AND login_datetime < '2021-10-05'
GROUP BY user_id
HAVING COUNT(DISTINCT DATE(login_datetime)) = 3
);

Select nearest date in the interval

I'm trying to select rows in which 3+ posts is in the interval 14 days.
For example:
User | id_post | date
1 | 12 | 2018-01-01
1 | 13 | 2018-01-05
1 | 14 | 2018-01-21
1 | 15 | 2018-01-27
1 | 16 | 2018-01-29
2 | 17 | 2018-01-01
2 | 18 | 2018-01-20
2 | 19 | 2018-02-17
2 | 20 | 2018-03-07
2 | 21 | 2018-04-29
User = OwnerUserId
date = CreationDate
In this case I need to return just User 1 because he has posts which are in 14 days.
Please, help me how I can get it. Thank you
Update: A user should have posts which were published in the interval of 14 days. It can be more, for example if the last day is in 2019 but in 2018 there was 3posts published within 14 days - it's ok
now i have (data get from data.stackexchange stackoverflow) and tried to apply
select OwnerUserId from Posts as p
where OwnerUserId in (select Users.id from Users WHERE YEAR (Users.CreationDate) >= 2017)
AND YEAR (p.CreationDate) >= 2018
AND p.Tags like '%sql%'
join (select OwnerUserId, CreationDate as startdate, dateadd(day,14,CreationDate) as enddate
from Posts) as r
on p.OwnerUserId = r.OwnerUserId and p.CreationDate between r.startdate and r.enddate
group by p.OwnerUserId, CreationDate
having count(*) >= 3
but it replies
Incorrect syntax near the keyword 'join'.
Incorrect syntax near the keyword 'as'.
I'm a begginner here and in the sql, so i dont exactly know how to combine my previous 'filtr' and current join with date
I'll not tell you the solution, but give you some pseudo-code and you figure out how to code it in SQL-
a) You should restrict your data for just 14 days.
b) Now, make groupings by User and find the count of records/lines present (for each User).
c) Now, again do a filter check to find users whose count of records is greater than 3.
Now, tell us which SQL keywords will be used for each points above.
I think something like
select p.user_id
from posts p
join (select user_id, xdate start_date, date_add(xdate, interval 14 day) end_date
from posts) r
on p.user_id = r.user_id and p.xdate between r.start_date and r.end_date
group by user_id, start_date
having count(*) >= 3
can help. It may not be the best possible solution, but it works.
Check it on SQL Fiddle
If you just want to select users by id you may try
Select id_post, date from yourtable where user = 2 order by id DESC limit 10;
You should have Colum called id with auto increment so new posts will have higher id so when it's sorted in descending it will start with post with higher id also you should have index on that id colum auto increment and index
If you don't want to use the above method then you will do that with date range like this
$date = gmdate() - (3600*24); 24 is 24 hours past
Select id_post, title from mutable where add_date > 'value of $date'
In both cases you should have index on user id
The second query is what you need but you should get the date from the equation first then apply it to the query
First, I think you mean user 1 not 2.
In MySQL 8+, this is pretty easy. If you want the first such post:
select t.*
from (select t.*,
lead(date, 2) over (partition by user order by date) as next_date2
from t
) t
where next_date2 <= date + interval 14 day;

group by year on multiple date columns mysql

I have table as following:
hours | ... | task_assigned | task_deadline | task_completion
----------------------------------------------------------------
123 | ... | 2019-08-01 | - | -
234 | ... | - | 2018-08-01 | 2019-08-01
145 | ... | 2017-08-01 | 2017-08-01 | 2018-01-01
I want to calculate total hours for each year, i.e. grouping by year.
Currently I'm only taking into account task_completion field.
If there's no value in task_completion field, the record is not included in SUM calculation.
To elaborate further, say for year 2019, row 1 and 1 both should be considered. Hence the total hours should be 123 + 234 = 357.
And for year 2018, row 2 and 3.
Similarly, for year 2017, row 3.
SELECT YEAR(task_completion) as year, ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN '$year_from' AND '$year_to'
The resultset:
year | hours
--------------------
2017 | <somevalue>
2018 | <somevalue>
2019 | <somevalue>
How can I include other two date fields too?
You want to consider each row once for each of its years. Use UNION to get these years:
select year, round(sum(total_hours), 2) as hours
from
(
select year(task_assigned) as year, total_hours from task
union
select year(task_deadline) as year, total_hours from task
union
select year(task_completion) as year, total_hours from task
) years_and_hours
group by year
having year between $year_from and $year_to
order by year;
If you want to consider a row with one year twice or thrice also as often in the sum, then change UNION to UNION ALL.
Basically, you want to unpivot the data. I will assume that the - represents a NULL value and your dates are real dates.
select year(dte) as year, sum(total_hours) as hours
from ((select task_assigned as dte, total_hours
from task
) union all
(select task_deadline, total_hours
from task
) union all
(select task_completion, total_hours
from task
)
) d
where dte is not null
group by year(dte)
order by year(dte);
Based on your sample data, the round() is not necessary so I removed it.
If you want to filter for particular years, the filtering should be in a where clause -- so it filters the data before aggregation.
Change the where to:
where year(dte) >= ? and year(dte) <= ?
or:
where dte >= ? and dte <= ?
to pass in the dates.
The ? are for parameter placeholders. Learn how to use parameters rather than munging query strings.
This answer is no langer valid with the updated request.
If I understand correctly, you want to use task_assigned if the task_completion is still null. Use COALEASCE for this.
SELECT
YEAR(COALESCE(task_completion, task_assigned)) as year,
ROUND(SUM(total_hours), 2) as hours
FROM task
GROUP BY year
HAVING year BETWEEN $year_from AND $year_to
ORDER BY year;
(I don't think you actually want to use task_deadline, too, for how could a task get completed before getting assigned first? If such can occur, then include it in the COALESCE expression. Probably: COALESCE(task_completion, task_assigned, task_deadline)` then.)

Count unique days grouped by user

I have the following SQL table
|user|log_date |
| 2 |2016-06-23 10:55:52 |
| 2 |2016-06-23 10:55:54 |
| 2 |2016-06-24 10:53:54 |
| 2 |2016-06-24 10:54:54 |
and so on with many other users and log_dates. What I want is to check for whole month :
where left(log_date,7)="2016-06"
But I want to count one day only once per day. So the result for my table in this example should be :
|user|count of unique days|
| 2 | 2 |
and I want it to be grouped by users. So for every user in table I want to count unique days.
Can anybody give me a hint?
Writing the condition this way allows the use of an index...
SELECT t.user
, COUNT(DISTINCT DATE(t.log_date)) unique_days
FROM my_table t
WHERE t.log_date BETWEEN '2016-06-01 00:00:00' AND '2016-06-31 23:59:59' -- or t.log_date >= '2016-06-01 00:00:00' AND t.log_date < '2016-07-01 00:00:00'
GROUP
BY t.user;
(Not sure why Sagi deleted their answer after correcting it)
Try this:
select
user,
count(distinct day(log_date)) as `count of unique days`
from yourtable
where left(log_date, 7) = '2016-06'
group by user
SQLFiddle Demo

Mysql Unique records, where multiple records exist

I am struggling with a Mysql call and was hoping to borrow your expertise.
I believe that what I want may only be possible using two selects and I have not yet done one of these and am struggling to wrap my head around this.
I have a table like so:
+------------------+----------------------+-------------------------------+
| username | acctstarttime | acctstoptime |
+------------------+----------------------+-------------------------------+
| bill | 22.04.2014 | 23.04.2014 |
+------------------+----------------------+-------------------------------+
| steve | 16.09.2014 | |
+------------------+----------------------+-------------------------------+
| fred | 12.08.2014 | |
+------------------+----------------------+-------------------------------+
| bill | 24.04.2014 | |
+------------------+----------------------+-------------------------------+
I wish to select only unique records from the username column ie I only want one record for bill and I need the one with most recent start_date, providing they were weren't in the last three months (end_date is not important to me here) else I do not want any data. In summary I just need anyone where there most recent start date is over 3 months old.
The command I am using currently is:
SELECT DISTINCT(username), ra.acctstarttime AS 'Last IP', ra.acctstoptime
FROM radacct AS ra
WHERE ra.acctstarttime < DATE_SUB(now(), interval 3 month)
GROUP BY ra.username
ORDER BY ra.acctstarttime DESC
However, this simply gives me details about the date_start for that particular customer where they had a start date over 3 months ago.
I have tired a few other combinations of this and have tried a command with a double select but I'm currently hitting brick walls. Any help or a push in the right direction would be much appreciated.
Update
I have created the following:
http://sqlfiddle.com/#!2/f47b2/1
Effectively I should only see 1 row when the query is as it should be. This would be the row for bill. As he is the only one that does not have a start date within the last three months. The result I would expect to see is the following:
24 bill April, 11 2014 12:11:40+0000 (null)
As this is the latest start date for bill, but this start date is not within the last three months. Hopefully this will help clarify. Many thanks for your help thus far.
http://sqlfiddle.com/#!2/f47b2/14
This is another example. If the acctstartdate for bill would show as the April entry, then I could add my where clause for the last three months and this would give me my desired result.
SQLFiddle
http://sqlfiddle.com/#!2/444432/9 (MySQL 5.5)
I am looking at the question in 2 ways based on the current text:
I only want one record for bill and I need the one with most recent start_date, providing they were in the last three months (end_date is not important to me here) else I do not want any data
Structure
create table test
(
username varchar(20),
date_start date
);
Data
Username date_start
--------- -----------
bill 2014-09-25
bill 2014-09-22
bill 2014-05-26
andy 2014-05-26
tim 2014-09-25
tim 2014-05-26
What we want
Username date_start
--------- -----------
bill 2014-09-25
tim 2014-09-25
Query
select *
from test a
inner join
(
select username, max(date_start) as max_date_start
from test
where date_start > date_sub(now(), interval 3 month)
group by username
) b
on
a.username = b.username
and a.date_start = b.max_date_start
where
date_start > date_sub(now(), interval 3 month)
Explanation
For the most recent last 3 months, let's get maximum start date for each user. To limit the records to the latest 3 months we use where date_start > date_sub(now(), interval 3 month) and to find the maximum start date for each user we use group by username.
We, then, join main data with this small subset based on user and max date to get the desired result.
Another angle
If we desire to NOT look at the latest 3 months and instead find the most recent date for each user, we would be looking at this kind of data:
What we want
Username date_start
--------- -----------
bill 2014-05-26
tim 2014-05-26
andy 2014-05-26
Query
select *
from test a
inner join
(
select username, max(date_start) as max_date_start
from test
where date_start < date_sub(now(), interval 3 month)
group by username
) b
on
a.username = b.username
and a.date_start = b.max_date_start
where
date_start < date_sub(now(), interval 3 month)
Hopefully you can change these queries to your liking.
EDIT
Based on your good explanation, here's the query
SQLFiddle: http://sqlfiddle.com/#!2/f47b2/17
select *
from activity a
-- find max dates for users for records with dates after 3 months
inner join
(
select username, max(acctstarttime) as max_date_start
from activity
where acctstarttime < date_sub(now(), interval 3 month)
group by username
) b
on
a.username = b.username
and a.acctstarttime = b.max_date_start
-- find usernames who have data in the recent three months
left join
(
select username, count(*)
from activity
where acctstarttime >= date_sub(now(), interval 3 month)
group by username
) c
on
a.username = c.username
where
acctstarttime < date_sub(now(), interval 3 month)
-- choose users who DONT have data from recent 3 months
and c.username is null
Let me know if you would like me to add explanation
Try this:
select t.*
from radacct t
join (
select ra.username, max(ra.acctstarttime) as acctstarttime
from radacct as ra
WHERE ra.acctstarttime < DATE_SUB(now(), interval 3 month)
) s on t.username = s.username and t.acctstarttime = s.acctstarttime
SQLFiddle