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).
Related
I have 5 tables:
users
+-----------+
| id | name |
+-----------+
| 1 | John |
+-----------+
| 2 | Doe |
+-----------+
tests
+-----------+
| id | test |
+-----------+
| 1 | t1 |
+-----------+
questions
+----+----------+---------+
| id | question | test_id |
+---------------+---------+
| 1 | q1 | 1 |
+---------------+---------+
| 2 | q2 | 1 |
+---------------+---------+
answers
+----+--------+------------+-------------+
| id | answer | is_correct | question_id |
+----+--------+------------+-------------+
| 1 | a1 | 1 | 1 |
+---------------+----------+-------------+
| 2 | a2 | 0 | 1 |
+---------------+----------+-------------+
user_answers
+----+---------+---------+-------------+-----------+----------+
| id | user_id | test_id | question_id | answer_id | duration |
+----+---------+---------+-------------+-----------+----------+
| 1 | 1 | 1 | 1 | 1 | 1 |
+--------------+----------+------------+-----------+----------+
| 2 | 1 | 1 | 2 | 2 | 1 |
+--------------+----------+------------+-----------+----------+
I'm trying to get the name of the user, the correct/incorrect answer and total duration.
The output would look like:
+------+-----------+----------+
| name | correct | duration |
+------+-----------+----------+
| John | 1,0,1,1,0 | 5 |
+------+-----------+----------+
| Doe | 0,0,0,0,0 | 0 |
+------+-----------+----------+
Or:
+------+-----------+----------+
| name | correct | duration |
+------+-----------+----------+
| John | 1,0,1,1,0 | 5 |
+------+-----------+----------+
| Doe | 0 | 0 |
+------+-----------+----------+
The SQL query I'm trying with:
SELECT u.name, GROUP_CONCAT(COALESCE(a.is_correct, 0)) AS correct, SUM(ua.duration) AS duration
FROM user_answers ua
RIGHT JOIN users u
ON u.id = ua.user_id
RIGHT JOIN answers a
ON a.question_id = ua.question_id
JOIN questions q
ON q.id = a.question_id
JOIN tests t
ON t.id = q.test_id
WHERE t.id = 1 GROUP BY u.id
It's just returning the user who answered the questions. I want to get the other user too who didn't answer the questions
Here is a fiddle with data and the SQL query:
http://sqlfiddle.com/#!9/6a806a/3
for quick edit of the existing query, we can join it with the users table
and use case when to set the null record to be like that
SELECT users.name, CASE WHEN correction.correct IS NULL THEN '0' ELSE correction.correct END as correct from
(SELECT u.name,u.id, GROUP_CONCAT(COALESCE(a.is_correct, 0)) AS correct
FROM user_answers ua
RIGHT JOIN users u
ON u.id = ua.user_id
RIGHT JOIN answers a
ON a.question_id = ua.question_id
JOIN questions q
ON q.id = a.question_id
JOIN tests t
ON t.id = q.test_id
WHERE t.id = 1 GROUP BY u.id ) correction
right join users on users.id = correction.id
fiddle with query
http://sqlfiddle.com/#!9/cf4c84/67
Start with all possible combinations of users, questions and correct answers. Left join real answers
SELECT u.name, GROUP_CONCAT(case when ua.id is null then 0 else 1 end order by q.id) AS correct
from questions q
cross join users u
join answers a on a.question_id = q.id and a.is_correct = 1
left join user_answers ua
on q.id = ua.question_id
and u.id = ua.user_id
and ua.answer = a.answer
WHERE q.test_id = 1
GROUP BY u.id, u.name
Note the order by q.id clause. You may want a different ordering.
Returns
name correct
John 0,0,0,0,1,0,0,1,0,0
Doe 0,0,0,0,0,0,0,0,0,0
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).
I am using mysql and have two tables user and result.
`user` `result`
--------------------------- -------------------------------------
| `id` | `name` |`active`| | `id` |`user_id` |`status`|`identity`|
-------------------------- ------------------------------------
| 1 | Apia | 1 | | 1 | 1 | 2 | 1 |
| 2 | Tang | 1 | | 2 | 1 | 2 | 1 |
| 3 | Jemrom | 1 | | 3 | 2 | 1 | 1 |
| 4 | Akwet | 1 | | 4 | 5 | 3 | 1 |
| 5 | Lamte | 1 | -------------------------------------
---------------------------
So the result should look like:
------------------------------
| `name` |`active` |`status` |
------------------------------
| Apia | 1 |`passed` |
| Tang | 1 |`passed` |
| Akwet | 1 |`awaiting`|
| Lamte | 1 |`failed` |
| Jerome | 1 |`unavailable` |
------------------------------
So far, I have:
SELECT u.*,
r.status
FROM user u
LEFT JOIN result r
ON r.user_id = u.id
WHERE u.active = 1;
But I don't know how to write the query for result table aspect.
NB: For status in result table (1=awaiting, 2=passed,3=failed)
I want to select everything from user table using left join where active = 1 (ordered by names) and match with related user_id in result table where identity =1. The result table must be grouped by user_id.
Where a user does not have data in result, it should display unavailable for status instead of null.
How do I write this query in MYSQL?
Thanks
You seem to want a left join and aggregation. Your question is unclear about how you want to compute the status when a user has more than one result, so I assumed you want the maximum value:
select
u.name,
u.active,
case max(r.status)
when 1 then 'awaiting'
when 2 then 'passed'
when 3 then 'failed'
else 'unavailable'
end as status
from users u
left join result r on r.user_id = u.id and r.identity = 1
where u.active = 1
group by u.id, u.name, u.active
Note that user is a language keyword in MySQL (as well as in most other databases), hence a poor choice for a table name. I renamed it to users inthe query.
I have two mysql tables Users & Messages. Please will you help me to create an sql query which returns only the most recent message either sent or received by the logged in user Mike ( user_id=1 ) along with the message_id, message_text & first_name of the person who the message was sent to or received from. Ordered by message_id in descending order.
So far i have the query:-
SELECT message_id, first_name, message_text FROM tbl_users, tbl_messages
WHERE
(tbl_users.user_id = tbl_messages.sender_id AND tbl_messages.receiver_id = 1
OR tbl_users.user_id = tbl_messages.receiver_id AND tbl_messages.sender_id = 1)
GROUP BY tbl_users.first_name
ORDER BY message_id DESC
Which gives the result in the image below. It seems the ORDER BY is being ignored
Thank you in advance
tbl_users
---------------------------
| user_id | first_name |
---------------------------
| 1 | Mike |
| 2 | John |
| 3 | George |
| 4 | Peter |
| 5 | Sarah |
---------------------------
tbl_messages
----------------------------------------------------------------
| message_id | sender_id | receiver_id | message_text |
----------------------------------------------------------------
| 1 | 2 | 1 | Hello |
| 2 | 3 | 1 | How are you |
| 3 | 1 | 5 | Hi there |
| 4 | 2 | 1 | Greetings |
| 5 | 1 | 4 | Good day |
| 6 | 3 | 1 | Hi |
| 7 | 5 | 1 | A message |
| 8 | 5 | 4 | Good morning |
| 9 | 1 | 5 | Hello dear |
| 10 | 1 | 3 | Howdy |
----------------------------------------------------------------
Desired result
----------------------------------------------
| message_id | first_name | message_text |
----------------------------------------------
| 10 | George | Howdy |
| 9 | Sarah | Hello dear |
| 5 | Peter | Good day |
| 4 | John | Greetings |
----------------------------------------------
Database is available here
Just adding a GROUP BY clause may solve your problem, try this...
SELECT message_id, first_name, message_text FROM tbl_users, tbl_messages
WHERE
(tbl_users.user_id = tbl_messages.sender_id AND tbl_messages.receiver_id = 1
OR tbl_users.user_id = tbl_messages.receiver_id AND tbl_messages.sender_id = 1)
GROUP BY tbl_users.user_id
ORDER BY message_id DESC
UPDATE: Changing the whole query
SELECT t2.message_id, t2.first_name, t2.message_text FROM
(SELECT u.user_id, m.message_id, u.first_name, m.message_text FROM `tbl_users` u
JOIN `tbl_messages` m ON (u.user_id = m.sender_id AND m.receiver_id = 1)
OR (u.user_id = m.receiver_id AND m.sender_id = 1)
ORDER BY m.message_id DESC
) t2
GROUP BY t2.user_id
ORDER BY t2.message_id DESC
RESULT:
+------------+------------+--------------+
| message_id | first_name | message_text |
+------------+------------+--------------+
| 10 | George | Howdy |
| 9 | Sarah | Hello dear |
| 5 | Peter | Good Day |
| 4 | John | Greetings |
If you GROUP BY the field which you want to de-duplicate on. In this case GROUP BY tbl_users.first_name should do the trick.
You are missing the table name in the ORDER BY clause. Also I suggest using aliases on the tables. Thus, your query might look like this:
/* All Mike's messages */
SELECT
m.message_id,
u.first_name,
m.message_text
FROM
tbl_users AS u,
tbl_messages AS m
WHERE
(
u.user_id = m.sender_id AND
m.receiver_id = 1
) OR (
u.user_id = m.receiver_id AND
m.sender_id = 1
)
ORDER BY
m.message_id DESC;
However, I think it would be better to get the sent and received messages results separately. So:
/* Messages sent my Mike */
SELECT
m.message_id,
u.first_name,
m.message_text
FROM
tbl_users AS u
INNER JOIN tbl_messages AS m ON m.receiver_id = u.user_id
WHERE
m.sender_id = 1
ORDER BY
m.message_id DESC;
/* Messages received my Mike */
SELECT
m.message_id,
u.first_name,
m.message_text
FROM
tbl_users AS u
INNER JOIN tbl_messages AS m ON u.user_id = m.sender_id
WHERE
m.receiver_id = 1
ORDER BY
m.message_id DESC;
I am not sure you can achieve this through join. A sub select query may help you.
select a.first_name,
(select message_text from tbl_messages b
where (a.user_id = b.sender_id and b.sender_id = 1)
or (a.user_id = b.receiver_id and b.receiver_id = 1)
order by message_id DESC
LIMIT 1)
from tbl_users a;
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