How must the mysql query be like to achieve the shown result? - mysql

Say, I have two tables like these:
Table group Table user
+----+-----------+ +----+----------+------+----------+
| id | groupname | | id | username | rank | group_id |
+----+-----------+ +----+----------+------+----------+
| 1 | Friends | | 1 | Frank | 1 | 1 |
| 2 | Family | | 2 | Mike | 3 | 1 |
+----+-----------+ | 3 | Steve | 2 | 1 |
| 4 | Tom | 1 | 2 |
+----+----------+------+----------+
And I want to select all groups and get the user with the highest rank (the highest number) for each group. So basically I want to get this result:
+-----------------+----------+---------+---------------+
| group.groupname | group.id | user.id | user.username |
+-----------------+----------+---------+---------------+
| Friends | 1 | 2 | Mike |
| Family | 2 | 4 | Tom |
+-----------------+----------+---------+---------------+
How has the select to be like?
It maybe very simple, but I'm not getting it right now....

Edit 2:
My previous answer was wrong, the max() call destroyed the result. Here's a correct solution:
SELECT g.groupname, g.id AS group_id, u.id AS user_id, u.username
FROM `user` u
LEFT JOIN `group` g ON (u.group_id=g.id)
WHERE u.rank=(
SELECT MAX(rank)
FROM `user` u2
WHERE u.group_id=u2.group_id
)
The check in the WHERE clause should be more understandable, too.
mysql> SELECT g.groupname, g.id AS group_id, u.id AS user_id, u.username
-> FROM `user` u
-> LEFT JOIN `group` g ON (u.group_id=g.id)
-> WHERE u.rank=(
-> SELECT MAX(rank)
-> FROM `user` u2
-> WHERE u.group_id=u2.group_id
-> );
+-----------+----------+---------+----------+
| groupname | group_id | user_id | username |
+-----------+----------+---------+----------+
| Friends | 1 | 2 | Mike |
| Family | 2 | 4 | Tom |
+-----------+----------+---------+----------+
2 rows in set (0.00 sec)

select g.groupname, u.group_id, u.id as user_id, u.username
from group g
inner join (
select group_id, max(rank) as MaxRank
from user
group by group_id
) um on g.id = um.group_id
inner join user u on um.group_id = u.group_id and um.MaxRank = u.rank

Related

Left join without using view

I have a table structure similar to this:
users
| id | name |
|----|--------|
| 1 | jane |
| 2 | john |
client
| id | name | user_id |
|----|------------|---------|
| 1 | client 1 | 2 |
| 2 | client 2 | 2 |
| 3 | client 3 | 2 |
| 4 | client 4 | 1 |
| 5 | client 5 | 1 |
products
| id | name | user_id |
|----|------------|---------|
| 1 | product 1 | 1 |
| 2 | product 2 | 1 |
| 3 | product 3 | 1 |
| 4 | product 4 | 2 |
| 5 | product 5 | 2 |
and I also created two views
client_total
SELECT user_id, COUNT(id) AS client_total FROM client GROUP BY user_id
| user_id | client_total |
|---------|--------------|
| 1 | 2 |
| 2 | 3 |
products_total
SELECT user_id, COUNT(id) AS products_total FROM products GROUP BY user_id
| user_id |products_total|
|---------|--------------|
| 1 | 3 |
| 2 | 2 |
and that was a result
SELECT
users.*,
client_total.client_total,
products_total.products_total
FROM
users
LEFT JOIN client_total ON users.user_id = client_total.user_id
LEFT JOIN products_total ON users.user_id = products_total.user_id
| id | name |products_total| client_total |
|----|--------|--------------|--------------|
| 1 | jane | 3 | 2 |
| 2 | john | 2 | 3 |
My question is:
Can I get this same result without using these two views?
GROUP BY in derived tables (the subqueries), before you LEFT JOIN:
SELECT
u.id,
u.name,
c.client_total,
p.products_total
FROM users u
LEFT JOIN
(SELECT user_id, COUNT(id) AS client_total FROM client GROUP BY user_id) c
ON u.id = c.user_id
LEFT JOIN
(SELECT user_id, COUNT(id) AS products_total FROM products GROUP BY user_id) p
ON u.id = p.user_id
(Doesn't even some people call these derived tables inline views?)
You may try the following direct aggregation join query:
SELECT
u.id,
u.name,
COUNT(DISTINCT p.id) AS products_total,
COUNT(DISTINCT c.id) AS client_total
FROM users u
LEFT JOIN client c
ON u.id = c.user_id
LEFT JOIN products p
ON u.id = p.user_id
GROUP BY
u.id,
u.name;
Demo
Often the most performant way to accomplish this uses correlated subqueries:
SELECT u.id, u.name,
(SELECT COUNT(*)
FROM client c
WHERE c.user_id = u.id
) as client_total,
(SELECT COUNT(*)
FROM products p
WHERE p.user_id = u.id
) as products_total
FROM users u;
In particular, this can take advantage of indexes on client(user_id) and products(user_id).

How to COUNT and SELECT with WHERE in sql?

I have two tables user and group and also a usergroup as a link table which contains which users exist in a group.
user table
+-----+-------+
| id | name |
+-----+-------+
| U12 | John |
| U13 | Rick |
| U14 | Morty |
| U15 | Alex |
| U16 | Felix |
+-----+-------+
group table
+-----+--------+--------+
| id | name | points |
+-----+--------+--------+
| G12 | Red | 11 |
| G13 | Blue | 22 |
| G14 | Green | 55 |
| G15 | Yellow | 64 |
| G16 | Orange | 23 |
+-----+--------+--------+
usergroup table
+----+---------+----------+
| id | user_id | group_id |
+----+---------+----------+
| 1 | U12 | G12 |
| 2 | U14 | G12 |
| 3 | U15 | G12 |
| 4 | U15 | G15 |
| 5 | U12 | G13 |
+----+---------+----------+
To select the groups that a particular user is in, I can just do
SELECT group.*
FROM usergroup
INNER JOIN group
ON group.id = usergroup.group_id
WHERE usergroup.user_id = ?
But how to also simultaneously select the number of total users in the same group?
Expected Sample Output - for the groups that user U12 is in, along with total user count
+-----+------+--------+-------------+
| id | name | points | users_count |
+-----+------+--------+-------------+
| G12 | Red | 11 | 3 |
| G13 | Blue | 22 | 1 |
+-----+------+--------+-------------+
use scalar subquery
SELECT `group`.* , (select count(id) from usergroup a where a.group_id=usergroup.group_id ) as user_count
FROM usergroup
INNER JOIN `group` ON `group`.id = usergroup.group_id
WHERE usergroup.user_id = 'U12'
SELECT y.id, y.name, y.points, y.users_count
FROM usergroup x INNER JOIN
( SELECT b.id, b.name, b.points, count(a.user_id) as users_count
FROM usergroup a INNER JOIN group b
ON a.group_id = b.id
GROUP BY b.id ) AS y
ON x.group_id = y.id
WHERE x.user_id = 'U12'
SELECT a.group_id,
a.NAME,
a.points,
c.user_count
FROM group a,
usergroup b,
(SELECT x.group_id,
Count(*) AS user_count
FROM usergroup x
GROUP BY x.group_id) c
WHERE a.group_id = b.group_id
AND a.group_id = c.group_id
AND user_id = 'U12';
SELECT group.*, count(usergroup.group_id),usergroup.user_id
FROM usergroup
INNER JOIN group
ON group.id = usergroup.id
GROUP BY usergroup.group_id
Having usergroup.user_id = 'U12'
You need to use group by with having.
Check it on SQLFiddle: sqlfiddle.com/#!9/b63e13/3/0

SQL cross match data from different columns and rows

I've been having hard times figuring out how to select the following...
I have two tables
Table_users Table_followers
| id | name | | follow_id | user_id |
| 1 | John | | 1 | 2 |
| 2 | Max | | 3 | 1 |
| 3 | Mary | | 2 | 1 |
| 4 | Robert | | 6 | 1 |
| 5 | Robin | | 1 | 5 |
| 6 | Sarah | | 1 | 6 |
I'd like to return in a single query users who are following John and John is following them back, so that would be called MATCH.
Then users who are following John, FOLLOWERS
And finally users followed by John, FOLLOWING
I've used the following query, but it returns duplicates and it's far from what I'm looking for
SELECT u.id, u.name, f.follower_id, f.user_id
FROM table_users u
LEFT JOIN table_followers f ON f.follower_id = u.id OR f.user_id = u.id
WHERE (f.user_id != 1 OR f.follower_id != 1) AND u.id != 1
ORDER BY u.id ASC";
Desired result would be like...
| uid | name | match | follower | following |
| 2 | Max | 1 | null | null |
| 6 | Sarah | 1 | null | null |
| 3 | Mary | null | 1 | null |
| 5 | Robin | null | null | 1 |
Would it be possible at all with SQL?
One way to solve this is to join the follower table twice (once for followers, once for following) and do a query like this:
select
u.id,
u.name,
case when follow_id and user_id then 1 end as `match`,
case when follow_id and user_id is null then 1 end as follower,
case when user_id and follow_id is null then 1 end as following
from Table_users u
left join (select user_id from Table_followers where follow_id = 1) followers
on u.id = followers.user_id
left join (select follow_id from Table_followers where user_id = 1) following
on u.id = following.follow_id
where u.id <> 1 and (follow_id or user_id)
order by `match` desc, follower desc, following desc, u.id;
I'm sure there are more efficient and cleaner ways to do this, but it's late and the old brain is only working at half speed ;)
Sample SQL Fiddle
With MySQL the select part can be further reduced to this:
select
u.id,
u.name,
ifnull((follow_id and user_id),0) as `match`,
(follow_id and user_id is null) as follower,
(user_id and follow_id is null) as following
from Table_users u
But this would give you 0 instead of null for the missing values. (Sample).

How do I select last message for each conversation?

Here is how my database looks like:
table: conversations
+----+--------+--------+
| id | user_1 | user_2 |
+----+--------+--------+
| 1 | 1 | 2 |
| 2 | 2 | 3 |
| 3 | 1 | 3 |
+----+--------+--------+
table: messages
+----+--------------+------+
| id | conversation | text |
+----+--------------+------+
| 1 | 1 | hej |
| 2 | 1 | test |
| 3 | 2 | doh |
| 4 | 2 | hi |
| 5 | 3 | :) |
| 6 | 3 | :D |
+----+--------------+------+
Then when I run the followin query:
SELECT
*
FROM `messages`
INNER JOIN `conversations`
ON `conversations`.`id` = `messages`.`convesations`
GROUP BY `conversations`.`id`
ORDER BY `messages`.`id` DESC
Then I get those out from messages:
+----+--------------+------+
| id | conversation | text |
+----+--------------+------+
| 1 | 1 | hej |
| 3 | 2 | doh |
| 5 | 3 | :) |
+----+--------------+------+
But, is it somehow possible to do so that I will get the messages with the highest messages.id from that group, instead of the lowest?
EDIT: Here is the output I want from messages:
+----+--------------+------+
| id | conversation | text |
+----+--------------+------+
| 2 | 1 | test |
| 4 | 2 | hi |
| 6 | 3 | :D |
+----+--------------+------+
As those are the messages in same conversation with the highest id.
SELECT *
FROM conversations c
JOIN messages m
ON m.id =
(
SELECT id
FROM messages mi
WHERE mi.conversation = c.id
ORDER BY
mi.conversation DESC, mi.id DESC
LIMIT 1
)
Create an index on messages (conversation, id) for this to work fast.
You simply need to use nested query like this:
SELECT * FROM Messages
WHERE ID IN(
SELECT Max(m.ID) FROM Messages m
INNER JOIN conversations c
ON c.id = m.conversation
GROUP BY m.conversation
);
Output:
| ID | CONVERSATION | TEXT |
----------------------------
| 2 | 1 | test |
| 4 | 2 | hi |
| 6 | 3 | :D |
If you want data from both tables try this:
SELECT * FROM Messages m
JOIN conversations c
ON c.id = m.conversation
WHERE m.ID IN (
SELECT MAX(ID) FROM Messages
GROUP BY conversation
)
GROUP BY m.conversation;
Output:
| ID | CONVERSATION | TEXT | USER_1 | USER_2 |
----------------------------------------------
| 2 | 1 | test | 1 | 2 |
| 4 | 2 | hi | 2 | 3 |
| 6 | 3 | :D | 1 | 3 |
See this SQLFiddle
You are making your Join on the wrong column. 'Id' in Conversation cannot be equal to 'Id' in messages.
I thin, 'Conversation' in table messsages is 'id_conversation' right?
So, if I understood well :
SELECT *
FROM messages
INNER JOIN conversations
ON conversations.id = messages.conversation
GROUP BY conversations.id
ORDER BY messages.id DESC
I think you just have an incorrect table join:
SELECT *
FROM `messages`
INNER JOIN `conversations`
ON `conversations`.`id` = `messages`.`conversation`
GROUP BY `conversations`.`id`
ORDER BY `messages`.`id` DESC
Edit
You can try this:
SELECT *
FROM `messages`
WHERE `messages`.`id` IN (
SELECT MAX(id)
FROM messages
GROUP BY conversation
)
A couple of different approaches:
This approach relies on known but undocumented behaviour within MySQL, where the unaggregated, ungrouped values returned in a grouped query are the first in the sort order - it's fast, but should not be viewed as reliable:
SELECT * FROM
(SELECT * FROM messages
ORDER BY conversation, id desc) a
GROUP BY conversation
Alternatively, an approach that should always be reliable:
SELECT m.*, c.user_1, c.user_2 FROM messages m
JOIN (select conversation, max(id) max_id from messages group by conversation) l
ON m.id = l.max_id
JOIN conversations c
ON c.id = m.conversation
GROUP BY conversation
SQLFiddle here.

SELECTing rows where no other rows match

This seemed pretty simple to start with, but it's getting awkward.
Suppose we have a table containing...
+---------+-----------+
| chat_id | friend_id |
+---------+-----------+
| A | 1 |
| A | 2 |
| A | 3 |
| B | 1 |
| B | 2 |
| C | 1 |
| C | 2 |
| C | 3 |
| D | 1 |
| D | 2 |
| D | 3 |
| D | 4 |
| D | 5 |
| E | 0 |
| E | 1 |
| E | 2 |
| E | 3 |
| E | 4 |
| E | 5 |
| E | 6 |
| E | 7 |
| F | 0 |
| F | 1 |
| G | 1 |
| G | 2 |
+---------+-----------+
And I wish to select only those chat_id that have friend_ids 1 and 2 and no other friend_id, what would the SQL be to get B and G returned?
So far, the best I've come up with is:
SELECT DISTINCT a.chat_id, COUNT(*)
FROM tt2 a
LEFT JOIN tt2 b
ON a.chat_id = b.chat_id
AND b.friend_id NOT IN (1,2)
WHERE a.friend_id in (1,2)
and b.chat_id IS NULL GROUP BY a.chat_id HAVING COUNT(*) = 2;
+---------+----------+
| chat_id | count(*) |
+---------+----------+
| B | 2 |
| G | 2 |
+---------+----------+
2 rows in set (0.00 sec)
And just in case I was looking for chat_id where only 1,2,3 exist...
SELECT DISTINCT a.chat_id, COUNT(*)
FROM tt2 a
LEFT JOIN tt2 b
ON a.chat_id = b.chat_id
AND b.friend_id not in (1,2,3)
WHERE a.friend_id IN (1,2,3)
AND b.chat_id IS NULL
GROUP BY a.chat_id
HAVING COUNT (*) = 3;
+---------+----------+
| chat_id | count(*) |
+---------+----------+
| A | 3 |
| C | 3 |
+---------+----------+
But this table could get massive and I need the SQL to be swift, does anyone know a better way?
To try and clarify... I get given a bunch of friend_id's and I want to get chat_id where only those friend_id exist for that chat_id.... with the SQL being quick (on sqlite)
Many thanks in advance!
Here's an option that should be able to limit the amount of data needed
SELECT
d.chat_id,
COUNT(DISTINCT s.friend_id) AS matchedFriends,
COUNT(DISTINCT d.friend_id) AS totalFriends
FROM tt2 AS d
INNER JOIN tt2 AS s
ON s.chat_id = d.chat_id
AND s.friend_id IN (1,2)
GROUP BY d.chat_id
HAVING matchedFriends = 2
AND totalFriends = matchedFriends
The INNER JOIN s makes sure that it only hits rows that have got at least one of the requested friends in. The matchedFriends count checks how many of the requested friends are found.
The totalFriends count then checks how many friends in total are on that chat.
Finally the HAVING first makes sure there are 2 matched friends, and then checks the number of friends in total equals the number of matched friends.
This will require you to supply both a list of friends, and a number of friends you are looking for, but should be efficient.
For increased efficiency, have an index on (chat_id,friend_id) (if you don't already, assuming it's a 2-part PK at time of writing)
Try this:
SELECT chat_id, GROUP_CONCAT(DISTINCT friend_id ORDER BY friend_id) AS friends
FROM table_1
GROUP BY chat_id
HAVING friends = '1,2'
Note: This works in mysql but I doubt that it will work on sqlite.