SQL: Get users not loggedIn in given date range - mysql

I have a scenario with two tables in MySQL.
Table 1: Users
Table 2: login_history
I'm trying to come up with a query to get those users who were not logged In, in given time period, for example between 2017-09-25 AND 2017-10-2.
I tried to use the sub-query, but that query is quite slow. In the example, I have given dummy data, but actually two tables specially login_history has huge amount of data, thus sub-query is taking time.

It would be something like this:
select u.*
from users u
where not exists (select 1
from logins l
where l.user_id = u.id and
l.login_at >= '2017-09-25' and
l.login_at <= '2017-10-02'
);
If this is slow, then try creating an index on logins(user_id, login_at).
Assuming you only want users who have logged in at some point, you could all try aggregation:
select l.user_id
from logins l
group by l.user_id
having sum(l.login_at >= '2017-09-25' and l.login_at <= '2017-10-02') = 0;
However, the not exists should be faster.

Couldnt you just use a basic query?
SELECT * FROM USERS U
JOIN login_history L
ON U.id = L.user_id
WHERE login_at NOT BETWEEN '2017-09-25' AND '2017-10-2'

Related

Combing a select statement with a count on a different table

I have two tables: users and lessons.
Currently I select all the users using:
SELECT * FROM users WHERE user_type = 1;
Then use PHP to loop through them and count their number of lessons using
SELECT COUNT(*) FROM lessons WHERE student_id=users.user_id;
I would like to combine this into a single query and I'm pretty sure this is possible with a JOIN but it is beyond my basic SQL knowledge.
Do this all in one query. If you want a count per user:
select u.user_id, count(l.student_id)
from users u left join
lessons l
on u.user_id = l.student_id
where u.user_type = 1
group by u.user_id
You can use a join, or a correlated subquery:
select
u.*,
(select count(*) from lessons l where l.student_id = u.user_id) no_lessons
from users u
The upside of the subquery solution is that it does not require aggregation in the outer query. With an index on lessons(student_id), this should be an efficient option.
You can write the following query for this:
SELECT * FROM users u LEFT JOIN lessons l ON u.user_id=l.student_id WHERE u.user_type=1 GROUP BY u.user_id
This will join both the tables (users and lessons) based on the id of both tables and the GROUP BY clause will group all the records of same id as you just want the number of lessons per user.

Retrieve data based on COUNT() and MONTH() in MySQL

I have users and likes tables. A foreign key of the latter references id from users table. The task at hand is to retrieve all distinct users who have more than 100 likes in March 2018. I'm trying to extract date-related values from a column with a type TIMESTAMP
I've come up with only seeing that pretty much all of them have some likes in that period:
SELECT DISTINCT u.name
FROM users AS u
JOIN likes AS l ON u.id = l.user_id
WHERE MONTH(l.timestamp) = 3 AND YEAR(l.timestamp) = 2018;
I guess I have to make use of COUNT() and GROUP BY somehow, but all my struggles were leading to syntax errors. Please give a hand.
You don't want select distinct. You want group by and having:
SELECT u.name
FROM users u JOIN
likes l
ON u.id = l.user_id
WHERE MONTH(l.timestamp) = 3 AND YEAR(l.timestamp) = 2018
GROUP BY u.name
HAVING COUNT(*) > 100;
To be honest, it is better to write the WHERE clause as:
WHERE l.timestamp >= '2018-03-01' AND l.timestamp < '2018-04-01'
This allows the SQL engine to use an index on timestamp, if one is available.

Query to get all users activity since last login

I want to create a query to get back all users, their last login date and how many questions they have answered since their last login.
I have the following query that does what I am looking for but with an individual user instead of all users.
SELECT l.user_id,
Count(*) attemptsSinceLastLogin
FROM production.score s
JOIN processedquestion pq
ON s.attempt_id = pq.attempt_id
JOIN login l
ON l.user_id = pq.user_id
WHERE l.user_id = 123
AND s.selected_answer IS NOT NULL
AND pq.attempt_datetime > (SELECT Max(in_datetime)
FROM production.login
WHERE user_id = 123);
I also have this query that gets all users and their last log in date but it doesn't have how many questions they have answered.
SELECT user_id,
Max(in_datetime)
FROM production.login
GROUP BY user_id
I am having trouble using these two queries to try and come up with one that brings back the data I am looking for all in one go. Any help appreciated
Try this:
SELECT l.user_id,
Count(*) attemptsSinceLastLogin
FROM production.score s
JOIN processedquestion pq
ON s.attempt_id = pq.attempt_id
JOIN login l
ON l.user_id = pq.user_id
JOIN (SELECT user_id,
Max(in_datetime) AS last_login
FROM production.login
GROUP BY user_id) t
ON l.user_id = t.user_id
WHERE s.selected_answer IS NOT NULL
AND pq.attempt_datetime > t.last_login
GROUP BY l.user_id

PHP / MySQL Query merge 2 queries

I am wondering if there is an easier way to execute this logic in 1 query rather than using 2 queries and having to loop the second query within the first one.
Firstly I grab the all the users which have been referred by the current user_id.
SELECT user_id FROM users WHERE referral = $user_id
Then, I want to count the number of page hits (or whatever else I'm counting), for each of the user's referrals.
SELECT COUNT(hits) FROM tracking WHERE user = $user_id
Is there an easier way of doing this with a join?
Try with a left join and group by:
select u.user_id, count(t.hits) as hits
from users u left join tracking t on u.user_id = t.user
where u.referral = $user_id
group by u.user_id;

Can I perform a SELECT within a WHERE in mysql?

I'm trying to perform a select within a where clause.
Basically, I have a number of users, and trying to see which were active. Active means they logged activity in last 30 days. But, if I join the user table with activity table, then I get duplicate user IDs (because each user may have logged multiple actions).
So I was looking at putting a select inside a where that would check, for each user, that there was at least one action.
SELECT u FROM `users` u
where (
select count(*) FROM `user_activity` ua
where ua.user_id = u.user_id and ua.last_login between "2012-04-01 00:00:00" and "2012-04-30 23:59:59"
) >= 1
SELECT u
FROM users u
where EXISTS ( select null
FROM user_activity ua
where ua.user_id = u.user_id
and ua.last_login between "2012-04-01 00:00:00" and "2012-04-30 23:59:59"
LIMIT 1)
Thanks to #Ami for pointing about about LIMIT 1 in subquery that potentially could improve performance a bit
Yes, you can nest a select inside a where clause like so:
SELECT * FROM mytable WHERE users in(SELECT users FROM user_activity);
But I don't think you can nest an aggregate function like count(*) inside a where clause. So I would try this first and then the aggregate function, but try to write your select without the aggregate. I don't have you data in front of me so I can't help there.
Yes, you can put a SELECT in a WHERE clause.
I would avoid the correlated subquery with a JOIN to see if it improved the performance:
SELECT DISTINCT `user`
FROM users u
JOIN user_activity ua
ON ua.user_id = u.user_id
AND ua.last_login BETWEEN '2012-04-01 00:00:00' AND '2012-04-30 23:59:59'