Can I perform a SELECT within a WHERE in mysql? - 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'

Related

Mysql - Run a user query for all users

I have a user specific query which i need to run for all users.
I am struggling on how to replace the hard coded uuid with a reference or if it needs a different approach altogether?
select max(MaxDate), users.created_at
from (
select max(`moment`.`created_at`) as MaxDate
from `moment`
where `moment`.`user_uuid` = "7dd668af-241a-4176-a1da-f5689214b206"
union (
select max(`module`.`updated_at`) as MaxDate
from `module`
where `module`.`user_uuid` = "7dd668af-241a-4176-a1da-f5689214b206"
)
) as stuff, `users`
where `users`.`uuid` = "7dd668af-241a-4176-a1da-f5689214b206"
the end goal is to get the date a user was created and a date the same user last updated something and then get the avage time between them. But for all users not a single user.
Here is a general query which would report all users, sorted by user:
SELECT
u.user_uuid,
GREATEST(COALESCE(t1.max_created_at, t2.max_updated_at),
COALESCE(t2.max_updated_at, t1.max_created_at)) AS max_date
FROM users u
LEFT JOIN
(
SELECT user_uuid, MAX(created_at) AS max_created_at
FROM moment
GROUP BY user_uuid
) t1
ON u.user_uuid = t1.user_uuid
LEFT JOIN
(
SELECT user_uuid, MAX(updated_at) AS max_updated_at
FROM module
GROUP BY user_uuid
) t2
ON u.user_uuid = t2.user_uuid
ORDER BY
u.user_uuid;
If you want to restrict to a single user, you may still do so via a WHERE clause or via a WHERE IN clause for a group of users.
Note that there is a bit of a smell in your database design, because you have your user information strewn across multiple tables. My answer assumes that in general every user would appear in both tables, but maybe this is not the case.
Use group by
select `users`.`uuid`,max(MaxDate) as maxdate, min(users.created_at) as createddate
from (
select `moment`.`user_uuid`,max(`moment`.`created_at`) as MaxDate
from `moment`
group by `moment`.`user_uuid`
union
select `module`.`user_uuid`,max(`module`.`updated_at`) as MaxDate
from `module` group by `module`.`user_uuid`
) as stuff inner join `users` on `users`.`uuid`=stuff.user_uuid
group by `users`.`uuid`

How to make complicated SQL query in doctrine

I have 2 tables: users and their points.
Users have fields:
id
name
Points have fields:
id
start_date
end_date
count_of_points
user_id
So some users may have or not have points. Points entries limited by time interval (from start_date to end_date) and have count of points that user have at this interval.
I need display table of users sorted by total sum of points at this moment (timestamp must be between start_date and end_date) and display this sum value later in view. If user have no points this count must be equals 0.
I have something like this:
$qb = $this->getEntityManager()
->getRepository('MyBundle:User')
->createQueryBuilder('u');
$qb->select('u, SUM(p.count_of_points) AS HIDDEN sum_points')
->leftJoin('u.points', 'p')
->orderBy('sum_points', 'DESC');
$qb->groupBy('u');
return $qb->getQuery()
->getResult();
But this have no limits by date interval and have no field for sum points that I can use in view from object.
I tried to find how to solve this task and I made something like this in SQL:
SELECT u.*, up.points FROM users AS u LEFT OUTER JOIN
(SELECT u.*, SUM(p.count_of_points) AS points FROM `users` AS u
LEFT OUTER JOIN points AS p ON p.user_id = u.id
WHERE p.start_date <= 1463578691 AND p.end_date >= 1463578691
) AS up ON u.id = up.id ORDER BY up.points DESC
But this query give me only users with entries in points table, so I think I must use another JOIN to add users without points. It's complicated query. I have no idea how implements this in doctrine because DQL can't use inner queries with LEFT JOIN.
Maybe there are other ways to solve this task? Maybe my tables schema is wrong and I can do this different way?
EDIT: forgot the date conditions. Corrected answer:
In plain MySQL your query would look like this:
SELECT u.id, u.name, COALESCE(SUM(p.count_of_points),0) AS sum_points
FROM Users u
LEFT JOIN Points p ON p.user_id=u.id
WHERE (p.start_date <= 1463578691 AND p.end_date >= 1463578691) OR p.id IS NULL
GROUP BY u.id
ORDER BY sum_points DESC
The COALESCE function sends back the first not NULL argument, so if a user doesn't have points, the sum would result in NULL, but the COALESCE in 0.
I'm not sure of the translation using the Doctrine query builder, but you could try:
$qb = $this->getEntityManager()->createQueryBuilder();
$qb->select('u')
->addSelect('COALESCE(SUM(p.count_of_points),0) AS sum_points')
->from('User', 'u')
->leftjoin('u.points', 'p')
->where('(p.start_date <= ?1 AND p.end_date >= ?1) OR p.id IS NULL')
->groupBy('u.id')
->orderBy('sum_points','DESC')
->setParameter(1, $date_now);

Using an SQL LEFT JOIN with the MAX() and MIN() functions

Let's assume I have the following two tables:
CREATE TABLE users (
id MEDIUMINT NOT NULL AUTO_INCREMENT,
name CHAR(30) NOT NULL,
PRIMARY KEY (id)
) ENGINE=MyISAM;
CREATE TABLE logins (
user_id NOT NULL,
day DATE NOT NULL,
PRIMARY KEY (`user_id, `day`)
) ENGINE=MyISAM;
What I'm trying to do here is get a query for all users with the first day they logged in and the last day they logged in. The query I was executing to achieve this looks like the following:
SELECT u.id AS id, u.name AS name, MIN(l.day) AS first_login,
MAX(l.day) AS last_login
FROM users u
LEFT JOIN logins l ON u.id = l.user_id
The problem is that because of the use of MIN() and MAX(), I'm only receiving one row back in the entire result. I'm sure it's my use of those functions that's causing this. I should have one row per user, even if they do not have any login entries. This is the reason for me using a LEFT JOIN vs an INNER JOIN.
in order to use aggregate functions (min, max, ...) you need grouping. Try something like this:
SELECT u.id AS id, u.name AS name, MIN(l.day) AS first_login, MAX(l.day) AS last_login
FROM users u
LEFT JOIN logins l ON u.id = l.user_id
GROUP BY u.id
Any sensible database except MySQL would have given you an error on mixing row-terms and aggregate terms, making the error clearer. MySQL, unfortunately allows this behavior, making it harder to notice that you forgot the group by clause needed to generate a row per user:
SELECT u.id AS id,
u.name AS name,
MIN(l.day) AS first_login,
MAX(l.day) AS last_login
FROM users u
LEFT JOIN logins l ON u.id = l.user_id
GROUP BY u.id, u.name -- missing in the original query
Grouping is a waste of resources.
Use nested select statement instead.
eg.
SELECT
u.id AS id,
u.name AS name,
(
SELECT MAX(logins.day) FROM logins WHERE logins.user_id=u.id
) AS last_login
FROM users u;
MIN and MAX are aggregate functions.
You should use GROUP BY with some field from u, like id.

MySQL AVG(TIMESTAMPDIFF) with GROUP BY

I have two tables user (one) and transaction (many) and I need to get the average time in days from when a user was created to when they made their first transaction. I'm using AVG(TIMESTAMPDIFF) which is working well, except that the GROUP BY returns an average against every user instead of one single average for all unique users in the transaction table. If I remove the GROUP BY, I get a single average figure but it takes into account multiple transactions from users, whereas I just want to have one per user (the first they made).
Here's my SQL:
SELECT AVG(TIMESTAMPDIFF(DAY, u.date_created, t.transaction_date)) AS average
FROM transaction t
LEFT JOIN user u ON u.id = t.user_id
WHERE t.user_id IS NOT NULL AND t.status = 1
GROUP BY t.user_id;
I'd appreciate it if someone can help me return the average for unique users only. It's fine to break the query down into two, but the tables are large so returning lots of data and putting it back in is a no-go. Thanks in advance.
SELECT AVG(TIMESTAMPDIFF(DAY, S.date_created, S.transaction_date)) AS average
FROM (
SELECT u.date_created, t.transaction_date
FROM transaction t
INNER JOIN user u ON u.id = t.user_id
WHERE t.status = 1
GROUP BY t.user_id
HAVING u.date_created = MIN(u.date_created)
) s
I replaced the LEFT JOIN with an INNER JOIN because I think that's what you want, but it's not 100% equivalant to your WHERE t.user_id IS NOT NULL.
Feel free to put the LEFT JOIN back if need be.
select avg( TIMESTAMPDIFF(DAY, u.date_created, min_tdate) ) as average
from user u
inner join
(select t.user_id, min(t.transaction_date) as min_tdate
from transaction t
where t.status=1;
group by t.user_id
) as min_t
on u.id=min_t.user_id;

MySQL Query: retrieve information from two tables without having to do two queries

I have two tables in my database, and I would like to retrieve information from both of them without having to do two queries. Basically, the user_ID(s) retrieved from the tasks table needs to be used to get those respective user(s) names from the users table. This is what I have so far, but the query is returning false:
SELECT t.user_id, t.nursery_ss, t.nursery_ws, t.greeter, t.date
u.user_first_name, u.user_last_name
FROM tasks_tbl AS t
INNER JOIN users_tbl AS u ON t.user_id = u.user_id
WHERE t.date = '2009-11-29'
You forgot a comma after t.date in your select-list:
SELECT t.user_id, t.nursery_ss, t.nursery_ws, t.greeter, t.date -- comma needed here
u.user_first_name, u.user_last_name
FROM tasks_tbl AS t
INNER JOIN users_tbl AS u ON t.user_id = u.user_id
WHERE t.date = '2009-11-29'
Actually, your query looks good; you might try checking to see if other aspects of the query are faulty. For example, try just a SELECT * from your join as specified. Or try omitting your WHERE clause; you'll get more data than you want, but it may help you debug your query.