SQL - selecting last row ordered by a date - mysql

This is my SQL:
"SELECT users.username, users.id_user, friends.name, friends.meet_date
FROM users INNER JOIN friends ON (users.id_user = friends.id_user)
GROUP BY username ORDER BY username";
This is the output on my webpage:
Username Friend's Name Meeting Date
George Nicolas 2010
Man Anatol 2008
For now, it selects just the first rows from database for each user.
Each table has an auto_increment, id_user for the first table and id_friend for the second one.
I would like it to show that friend which were meet last by each user.
I've tried to add an order by "meet_date DESC" but it doesn't work.
How could I achieve my wish?

One standard trick is to use an outer self join:
SELECT users.username, users.id_user, friends.name, friends.meet_date
FROM users
INNER JOIN friends ON (users.id_user = friends.id_user)
LEFT JOIN friends f2 ON friends.meet_date > f2.meet_date
WHERE f2.(primary key) IS NULL
GROUP BY username
ORDER BY username
which finds the date for which there is no other record with a greater date value. This avoids the inefficiencies of correlated subqueries and extra aggregates; or the doubtful assumption that records are always added in meet-date order which is necessary if you want to use the primary key.
* Edited by other user *
You should replace (primary key) by primary key column table or just by a f2 column:
SELECT users.username, users.id_user, friends.name, friends.meet_date
FROM users
left JOIN friends ON (users.id_user = friends.id_user)
LEFT JOIN friends f2 ON friends.meet_date > f2.meet_date
WHERE f2.id_user IS NULL
GROUP BY username
ORDER BY username
Query runs as spected:
| USERNAME | ID_USER | NAME | MEET_DATE |
-------------------------------------------
| a | 1 | c | 0 |
| b | 2 | (null) | (null) |

The easy way is a subquery:
SELECT
users.username,
users.id_user,
( select friends.name
from friends
where (users.id_user = friends.id_user)
ORDER BY friends.meet_date desc
LIMIT 1 ) as friend_name,
( select friends.meet_date
from friends
where (users.id_user = friends.id_user)
ORDER BY friends.meet_date desc
LIMIT 1 ) as friend_meet_date
FROM users
;
* Testing *
create table users (username varchar(50), id_user int);
create table friends ( name varchar(50), id_user int, meet_date int);
insert into users values ( 'a', 1),('b',2);
insert into friends values ('c', 1, 0), ('d',1,1);
Results:
| USERNAME | ID_USER | FRIEND_NAME | FRIEND_MEET_DATE |
-------------------------------------------------------
| a | 1 | d | 1 |
| b | 2 | (null) | (null) |
Try it at sql fiddle.
Notice thant a correlated subquery is not an elegant approach, is the easy approach.

Do you not have an auto_increment field? This would allow you to sort by that. The other option is to change meet_date to a date field so you can properly sort by it. No reason to store a DateTime as a varchar field

Assuming that you have got id as a primary key with auto_increment in users table, this should do the trick:
SELECT MAX(users.id), users.username, users.id_user, friends.name, friends.meet_date
FROM users INNER JOIN friends ON (users.id_user = friends.id_user)
GROUP BY username ORDER BY username

SELECT users.username, users.id_user, friends.name, friends.meet_date
FROM users INNER JOIN friends ON (users.id_user = friends.id_user)
GROUP BY username ORDER BY username
having max(friends.meet_date)=friends.meet_date

Related

How to join the same table more than once to select different columns?

I need to find out users who have either made or received a booking.
I have two tables that look like this:
Users:
+----+
| id |
+----+
| 1 |
| 2 |
| 3 |
+----+
Bookings:
+----+-----+-----+
| id | rid | oid |
+----+-----+-----+
| 1 | 1 | 2 |
| 2 | 2 | 1 |
| 3 | 3 | 4 |
+----+-----+-----+
A booking has two users, a 'rider' (rid), and an 'owner' (oid).
The rider and owner can't be the same for each booking but riders can also be owners.
My output should be a list of user IDs that correspond with users who have made or received a booking.
So far I have written
select u.id, b1.rid, b2.oid
from users u
left join bookings b1
on u.id = b1.rid
left join bookings b2
on u.id = b2.oid;
And various other permutations, but I'm not getting the desired result. Any help would be appreciated.
You want all User IDs that are either in Bookings.rid or Bookdings.oid. So you could do something like:
select
users.id
from
users
where
users.id in (select bookings.rid from bookings)
or
users.id in (select bookings.oid from bookings);
You should be able to utilize a UNION clause here.
However, you don't define what the "time window" is, so I am not sure we can come up with a complete solution for you. However, try something like the following:
SELECT
users.id,
bookings.rid,
bookings.oid
FROM
users
LEFT JOIN bookings ON users.id = bookings.rid
UNION ALL
SELECT
users.id,
bookings.rid,
bookings.oid
FROM
users
LEFT JOIN bookings ON users.id = bookings.oid
My output should be a list of user IDs that correspond with users who have made or received a booking.
To do that, you only need to look at the bookings table :
SELECT DISTINCT rid id FROM bookings
UNION ALL SELECT DISTINCT oid FROM bookings
The DISTINCT removes the duplicates returned by each query, and the UNION ALL removes duplicates across both queries.
If you are looking to filter by time frame :
SELECT DISTINCT rid id FROM bookings WHERE some_date BETWEEN :start_date AND :end_date
UNION ALL SELECT DISTINCT oid FROM bookings WHERE some_date BETWEEN :start_date AND :end_date
Where some_date is the field that contains the booking date, and :start_date/end_date are the beginning and the end of the date interval.
I guess there is a name column in Users table.
If you want this too then:
select users.id, users.name from (
select rid userid from bookings
union
select oid userid from bookings
) t inner join users
on users.id = t.userid
group by users.id, users.name
See the demo
If not you only need to scan the bookings table:
select distinct userid from (
select rid userid from bookings
union
select oid userid from bookings
) t
See the demo

Count with Join in mysql

So I have this two table where it records what kind of food is the user's favorite:
users table
------------
id | country
------------
1 | US
2 | PH
3 | US
4 | US
5 | PH
food_favourites table
-----------------
food_id | user_id
-----------------
3 | 1
7 | 1
3 | 2
3 | 3
3 | 4
I want to know how many unique users from US tagged food_id 3 as their favorite.
So far I have this query:
select *, count(user_id) as total
from food_favourite
inner join users on users.id = food_favourites.user_id
where food_favourites.food_id = 3
and users.country = 'US'
group by users.id
Well This doesn't work coz it returns total to 4 instead of just 3.
I also tried doing subqueries - no luck, I think I'm missing something.
DROP TABLE IF EXISTS users;
CREATE TABLE users
(user_id SERIAL PRIMARY KEY
,country CHAR(2) NOT NULL
);
INSERT INTO users VALUES
(1,'US'),
(2,'EU'),
(3,'US'),
(4,'US'),
(5,'EU');
DROP TABLE IF EXISTS favourite_foods;
CREATE TABLE favourite_foods
(food_id INT NOT NULL
,user_id INT NOT NULL
,PRIMARY KEY(food_id,user_id)
);
INSERT INTO favourite_foods VALUES
(3,1),
(7,1),
(3,2),
(3,3),
(3,4);
SELECT COUNT(DISTINCT u.user_id) distinct_users
FROM users u
JOIN favourite_foods f
ON f.user_id = u.user_id
WHERE u.country = 'US'
AND f.food_id = 3;
+----------------+
| distinct_users |
+----------------+
| 3 |
+----------------+
First of all the answer to the above question should be 3 as id 1,3,4 all have food_id 3 as their favorite food.
To just print the query try this, it will surely work:
select count(*) as total from food_favourites
inner join users on users.id=food_favourites.user_id
where food_id=3 and country='US';
I want to know how many unique users from US tagged food_id 3 as their favorite.
You count unique values with COUNT DISTINCT:
select count(distinct ff.user_id) as total
from food_favourite ff
inner join users u on u.id = ff.user_id
where ff.food_id = 3
and u.country = 'US';
Don't group by user, because you don't want a result per user. You want one row with one number, telling you how many US users prefer food 3.
An alternative that I prefer over the join. The query reads like I would word the task: count users from US that like food 3.
select count(*) as total
from users
where country = 'US'
and id in (select user_id from food_favourites where food_id = 3);
No unnecessary join and hence no need to get back to distinct values.
The sub_query is
SELECT u.country, f.food_id, COUNT(u.id) AS 'Total users'
FROM users u
INNER JOIN food_favourites AS f ON (u.id = f.[user_id])
WHERE u.country = 'US'
GROUP BY u.country, f.food_id
select count(user_id) as total, Country
from food_favourites
inner join users on users.id = food_favourites.user_id
where food_favourites.food_id = 3
and users.country = 'US'
group by country
Untested, but I think this is what you're after? This will return results only for the US and a food id of 3. If you want something more reusable that you can simply loop through the results for ALL countries...something like this should work (once again, untested...):
select count(user_id) as total, Country, food_id
from food_favourites
inner join users on users.id = food_favourites.user_id
group by country, food_id
order by country, food_id
Try:
select count(user_id) as total
from food_favourites
inner join users on users.id = food_favourites.user_id
where food_favourites.food_id = 3
and users.country = 'US'

mysql search with two joins and a count

I am currently trying to write a general query which returns the content of 1 table and another joined table plus the count of resulting rows from a third table.
Now my description might seem abstract so I'll try to visualize it
Tables:
posts
| ID | title | description | creator_id |
1 Title1 Descr1 1
2 Title2 Descr2 1
users
| ID | name | avatar |
1 User1 PATH
interactions
| ID | type | target_id | identifier |
1 view 1 IP
2 view 1 IP
Now what I am looking for is an output like this:
| ID | title | description | name | avatar | view_count |
1 Title1 Descr1 User1 PATH 2
2 Title2 Descr2 User1 PATH 0
My current query looks like following:
SELECT
posts.id, posts.title, posts.description,
users.name, users.avatar,
COUNT(interactions.id) AS view_count
FROM
posts
LEFT JOIN
users
ON
posts.creator_id = users.id
LEFT JOIN
interactions
ON
posts.id = interactions.target_id
But only prints out the posts result which has an interaction like this:
| ID | title | description | name | avatar | view_count |
1 Title1 Descr1 User1 PATH 2
How do I need to alter the query in order to also get the other rows which happen to not have any interactions yet?
Thank you for your help!
You can simply subquery third table to count entries:
SELECT
posts.id, posts.title, posts.description,
users.name, users.avatar,
(SELECT COUNT(*) FROM interactions i WHERE i.target_id = posts.id) AS view_count
FROM
posts
LEFT JOIN
users
ON
posts.creator_id = users.id
This is also better for performance (no groups, no unoptimized joins)
Try this:
SELECT P.ID
, P.title
, P.description
, U.name
, U.avatar
, IFNULL(COUNT(I.ID), 0) AS view_count
FROM posts P
LEFT JOIN users U ON U.ID = P.creator_id
LEFT JOIN interactions I ON I.target_id = P.ID
GROUP BY P.ID
It seems like you missed the GROUP BY clause. Without this, when you use an aggregate function like COUNT, the documentation says:
there is a single group and it is indeterminate
which name value to choose for the group
That's why your query only returned 1 row.
Try this;)
select posts.id, posts.title, posts.description, users.name, users.avatar, coalesce(t3.view_count, 0) as view_count
from posts
left join users on posts.creator_id = users.id
left join (
select target_id, count(1) as view_count from interactions group by target_id
) t3 on posts.id = t3.target_id
SQLFiddle HERE

How to write inner query that returns latest message for a given user?

I have some tables like this:
USERS TABLE:
| id | created | active | fname | lname |
MESSAGES TABLE:
| id | userId| active | date | content |
I am trying to return some user information, along with the most recently added message for a given user.
Below is the structure of the results that I am rying to achieve:
| userId | userCreated | latestMessageDate| latestMessageContent |
The following query returns the user information:
SELECT
user.id,
user.created
FROM user
WHERE user.active = 1
... But how do I now attach the date of the latest message, along with the actual latest message?
I believe using an inner query is one such approach, but how do you write such a query??
SELECT u.fname, u.lname, m.id, m.userID, m.datem, m.content
FROM USERS AS u
LEFT JOIN ( SELECT id, userID, date, content
FROM MESSAGES
WHERE active
ORDER BY date DESC) AS m
ON u.id = m.userId
WHERE u.active
# AND u.id = {$int_user_id}
GROUP BY u.id
Maybe something like this:
SELECT
Users.id AS userId,
Users.created AS userCreated,
LatestMessage.LatestMessageDate,
MESSAGES.content AS latestMessageContent
FROM
Users
LEFT JOIN
(
SELECT
MAX(date) AS LatestMessageDate,
MESSAGES.userId
FROM
MESSAGES
GROUP BY
MESSAGES.userId
) AS LatestMessage
ON Users.id=LatestMessage.userId
LEFT JOIN MESSAGES
ON LatestMessage.LatestMessageDate=MESSAGES.date
AND LatestMessage.userId=MESSAGES.userId

Counting records from table that appear is one but not other: MYSQL

I have a two simple tables
users
+----+--------+-----------+
| id | gender | birthdate |
+----+--------+-----------+
userpreference
+----+------------------+-----------------+
| id | preference value | preference type |
+----+------------------+-----------------+
Question:
I want to query all people who have not listed a specific preference value such as 'shopping'.This includes all people who have not listed any preference types as well so that column could be null, however since userpreference's column 'id' references users as a foreign key, I also want to include in my count all people who don't show up in the second table (user preference)?
Total # of people who do not have preference value 'shopping' as their preference value:
Here is what i have tried:
SELECT
(
SELECT COUNT(DISTINCT userpreference.id) FROM userpreference
WHERE preferencevalue != 'shopping')
+
(
SELECT COUNT(users.id)
FROM users
WHERE users.id NOT IN
(SELECT userpreference.Id
FROM userpreference )
)
AS'Total'
Select Count(*)
From Users
Where Not Exists (
Select 1
From UserPreference As UP1
Where UP1.id = Users.id
And UP1.PreferenceValue = 'Shopping'
)
Try a RIGHT JOIN, that will include all people who dont show up in the second table
SELECT *
FROM Users
RIGHT JOIN Userpreference ON ( users.userID = Users.userID)
WHERE preference_value = 'shopping'
Try this:
SELECT COUNT(DISTINT U.id) FROM users U NATURAL LEFT JOIN userpreference UP
WHERE UP.preferencevalue IS NULL OR UP.preferenceValue != 'shopping';
The LEFT JOIN should bring in all the users records whether or not they have a UP record.