MySQL reference row in sub query - mysql

I'm trying to count all messages sent by users AFTER they uploaded a photo.
I'm trying something like this.
select messages.created_at, count(*) as count from messages
inner join users on messages.user_id = users.id
inner join photos on photos.user_id = users.id
where
some_users_messages.created_at > some_users_first_photo.created_at
group by YEARWEEK(messages.created_at)
I'm thinking this needs to be a subquery? I'm not sure how to do this concept of one particular user's messages/photos in MySQL. Any ideas?
Thanks!

This would count the number of messages sent after the first photo per user:
select messages.user_id
, count(*) as count
from messages
where messages.created_at >
(
select min(created_at)
from photos
where photos.user_id = messages.user_id
)
group by
messages.user_id

Related

How can I use the MySQL COUNT() statement on a generated query?

I've created a query that finds the users of a specific site that have liked every photo on the site and stand out as potential bots.
I'm trying to count the number of bots.
My query currently returns all the bot usernames and the number of photos that they've liked, but I'm having trouble simply counting them.
It would be ideal to be able to COUNT(*) on the table that the below query generates.
SELECT
username,
COUNT(*) AS total_likes
FROM users
JOIN likes
ON likes.user_id = users.id
GROUP BY likes.user_id
HAVING total_likes = (SELECT COUNT(*) FROM photos);
You can do this with a subquery:
SELECT COUNT(*) as num_potential_bots
FROM (SELECT u.username, COUNT(*) AS total_likes
FROM users u JOIN
likes l
ON l.user_id = u.id
GROUP BY l.user_id
HAVING total_likes = (SELECT COUNT(*) FROM photos)
) u;

Left join to get most recent entries

I have the tables users and statuses . I want to select all the users, plus any statuses they might have, but only the most recent status from each user.
Here is the code that doesn't work:
SELECT users.id, alias, gender, login, logout, users.create_date, statustext as statustxt, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN statuses s ON users.id = s.user_id
WHERE s.ID = (
SELECT MAX(s2.ID)
FROM statuses s2
WHERE s2.user_id = s.user_id
)
This gets the users with the most recent statuses, but not the users from the users table as well. Maybe it can be fixed by some small adjustment?
I got the sub query by searching, but I don't understand how that code works. It seems to compare two versions of the same table (For example: WHERE s2.user_id = s.user_id ) . Where can I read about this sort of technique?
Is a sub query required in this case by the way?
If you can find a solution would be great, and some basic explanation of how it works would highly appreciated.
----------EDIT---------------
I took one of the responses (by maresa) and combined with the sub query of my initial code , and this works(!) It has 3 selects and looks a bit over complicated maybe?:
SELECT users.id, alias, gender, login, logout, users.create_date, statustext as statustxt, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN (
SELECT user_id, statustext FROM statuses s
WHERE s.ID = (
SELECT MAX(s2.ID)
FROM statuses s2
WHERE s2.user_id = s.user_id
)
) as s ON users.id = s.user_id
I've encountered similar problem. This post is relevant: http://www.microshell.com/database/sql/optimizing-sql-that-selects-the-maxminetc-from-a-group/.
Regarding your specific query, since you care only the latest status, you want to first get the latest status from each users. Assuming that the latest status has the latest id (based on your sample), the SQL would be below:
SELECT
MAX(ID), statustext, user_id
FROM
statuses
GROUP BY
user_id
What the above query does is, to get the latest status per user_id. Once you get that, you can think of it as if it's a table. Then simply join on this "table" (the query) instead of the real one (statuses table). Therefore, your query would be like below:
SELECT
users.id, alias, gender, login, logout, users.create_date, statustext as statustxt, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM
users
LEFT JOIN (
SELECT
MAX(ID), user_id
FROM
statuses
GROUP BY
user_id
) as s ON users.id = s.user_id
LEFT JOIN statuses ON statuses.ID = s.ID -- EDIT: Added this line.
You might use a subselect as the join, and limit to show only 1 row:
SELECT
users.id,
alias,
gender,
login,
logout,
users.create_date,
statustext as statustxt,
TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN (
SELECT user_id,statustext
FROM statuses s1
WHERE s1.user_id = users.id
ORDER BY status_date DESC
LIMIT 1
) s ON users.id = s.user_id
You could use a in clause with a tuple
SELECT
users.id
, alias
, gender
, login
, logout
, users.create_date
, statustext as statustxt
, TIMESTAMPDIFF(YEAR,birthdate,CURDATE()) AS age
FROM users
LEFT JOIN statuses s ON users.id = s.user_id
WHERE (s.user_id, s.ID) in (
SELECT user_id, MAX(s2.ID)
FROM statuses s2
group by user_id
)

how to select the last text from message table

I have two tables (messages and user). I want to select the last (msg_id,text) from the messages table for a particular ad_id and need to select the name of the user from the user table.
SELECT u.id
, m.date
, m.ad_id
, max(m.msg_id)as msg_id
, u.first_name
, m.text
, m.u_to_id
, m.u_from_id
FROM user u
JOIN messages m
ON CASE WHEN m.u_from_id ='14' THEN u.id = m.u_to_id
ELSE u.id = m.u_from_id END
AND (m.u_from_id='14' OR m.u_to_id='14')
AND m.ad_id='20'
GROUP BY CONCAT(m.ad_id,u.id)
ORDER by m.msg_id DESC
this query is working but I can't select t the last m.textTable structure
SELECT u.id, m.text
FROM user u
JOIN messages m ON m.msg_id = (SELECT max(msg_id) FROM messages WHERE u_from_id = u.id)
I simplified your query to show the logic relevant to your question. Basically you want to join your messages table on the msg_id that is equal to the inner query of the max msg_id with that user.
After so many experiments added a new column(bargainer) for identify the recipient and this query working fine for me
select m.msg_id,m.text,m.status,m.date,m.bargainer,m.ad_id,u.first_name,u.id from user u JOIN messages m where msg_id in (select max(msg_id) from messages m where m.ad_id=20 and u.id=m.bargainer group by(m.bargainer))group by(m.msg_id) order by msg_id DESC

Mysql count and return just one row of data

I need to count the amount of users that have have answered all of those 3 profile_options (so they have at least 3 records in the profile_answers table).
SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3
The problem is that this query is return a table with rows for each user and how many they answered (in this case always 3). What I need is to return just one row that has the total number of users (so the sum of all rows of this example)
I know how to do it with another subquery but the problem is that I am running into "Mysql::Error: Too high level of nesting for select"
Is there a way to do this without the extra subquery?
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) sum_sub
Please give this query a shoot
SELECT COUNT(DISTINCT(u.id)) AS users_count
FROM users AS u
INNER JOIN (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a ON a.user_id = u.id
If you have lots of data in your tables, you will get a better/faster performance by using temporary tables like so
CREATE TEMPORARY TABLE a (KEY(user_id)) ENGINE = MEMORY
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3;
Then your final query will look like this
SELECT COUNT(DISTINCT(u.id)) as users_count
FROM a
INNER JOIN on a.user_id = u.id
Unless there is a need to join the users table you can go with this
SELECT COUNT(*) AS users_count
FROM (
SELECT user_id, COUNT(DISTINCT profile_option_id) AS total
FROM profile_answers
WHERE profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT profile_option_id) = 3
) AS a
Should you need another solution, please consider providing us you EXPLAIN EXTENDED for the query and the table definitions along with a better problem description.
I hope this helps
You can give the queries a name using the AS clause. See the updated query below.
SELECT SUM(sum_sub.users_count) FROM (
(SELECT COUNT(DISTINCT(users.id)) as users_count
FROM users
INNER JOIN profile_answers ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
GROUP BY users.id
HAVING COUNT(DISTINCT(profile_answers.id))>=3)
) as sum_sub
You should not group by on a field not present in select statement.
select id, count(*) from users group by id is fine
select count(id) from users group by id is NOT
Regarding your query I think the link to user table is not necessary. Just using foreign key should be fine.
Try this one:
select count(*) from
(SELECT users_id count(*) as cnt
FROM profile_answers
INNER JOIN users ON profile_answers.user_id = users.id
WHERE profile_answers.profile_option_id IN (37,86,102)
group by users_id
having count(*) >3)

SQL with 2 INNER JOIN's on the same column not working

I am trying to use the following code to get the 6 users with which the current user has most recently chatted. I have two problems. First of all, if the current user has recieved a message from the other user but has only sent, that other user isnt fetched. Second of all, the ORDER BY clause is causing an error. Im a beginner in SQL so I have no idea what's going on.
Thanks in Advance!
Here's the code:
SELECT users.*
FROM users INNER JOIN
messages fromuser
ON (fromuser.fromid = users.id) INNER JOIN
messages touser
ON (touser.toid = users.id)
WHERE fromuser.toid = :userid OR touser.fromid = :meid
GROUP BY users.id
ORDER BY MAX(messages.datetime)
LIMIT 6;
This should do your job, and it relies less on MySQL extensions than your other answer so far. I estimate that it would perform about the same, but it's surely wordier.
SELECT u.*
FROM (
SELECT DISTINCT otherid
FROM (
SELECT
m.fromid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.toid = :userid
GROUP BY m.fromid
UNION ALL
SELECT
m.toid AS otherid,
MAX(m.datetime) as maxts
FROM messages m
WHERE m.fromid = :userid
GROUP BY m.toid
) um
ORDER BY maxts DESC
LIMIT 6
) otheru
INNER JOIN users u
ON u.id = otheru.otherid
Your logic is doomed to fail, because one users.id cannot be two different values at the same time. I think this query does what you want:
SELECT u.*
FROM messages m INNER JOIN
users u
ON (m.fromid = u.id AND m.toid = :userid) OR
(m.toid = u.id AND m.fromid = :userid )
GROUP BY u.id
ORDER BY MAX(m.datetime) DESC
LIMIT 6;
Notice that it joins to the users table by the id that is not the current user.