join tables to display posts of people who I follow - mysql

I want to display the posts of people who I follow
The 3 tables I have are:
Users:
+---------+------+
| id_user | name | last_logout
+---------+------+
| 1 | A | 22-02-2018 00:00:10
| 2 | B |
| 3 | C |
| 4 | D |
| 5 | E |
+---------+------+
Community:
+-------------+-------------+
| id_follower | id_followed |
+-------------+-------------+
| 1 | 2 |
| 1 | 3 |
| 1 | 5 |
+-------------+-------------+
Posts:
+---------+--------------+---------------+
| id_post | id_user_post | post | date
+---------+--------------+---------------+
| 1 | 2 | hi |
| 2 | 3 | hello |
| 3 | 5 | hey you |
| 4 | 4 | come on |
| 5 | 5 | where are you | 22-02-2018 00:01:00
+---------+--------------+---------------+
I'm using the following code but it doesn't return anything
SELECT u.name AS n
,p.post AS t
FROM community AS c
LEFT JOIN users AS u ON u.id_user = c.id_followed
LEFT JOIN posts AS p ON c.id_followed = p.id_user_post
WHERE u.id_follower = 1

Users.id_follower does not exist, so that is why you get nothing, in fact you are likely getting an error (Invalid column name 'id_follower'.).
Use Community.id_follower instead. I would also recommend using more descriptive column names (like 'username' and 'comment')
For the pure reason of answering your specific question, I have used 'n' and 't' in the query.
SELECT u.name as n, p.post as t
FROM Community c
LEFT JOIN Users u ON c.id_followed = u.id_user
LEFT JOIN Posts p ON c.id_followed = p.id_user_post
WHERE c.id_follower = 1
Test:
;WITH USERS AS(
SELECT *
FROM (VALUES (1,'A'),
(2,'B'),
(3,'C'),
(4,'D'),
(5,'E')) U(id_user, name))
, Community AS(
SELECT *
FROM (VALUES (1,2),
(1,3),
(1,5)) C(id_follower, id_followed))
, posts AS(
SELECT *
FROM (VALUES (1,2,'hi'),
(2,3,'hello'),
(3,5,'hey you'),
(4,4,'come on'),
(5,5,'where are you')) P(id_post, id_user_post, post))
SELECT u.name as n, p.post as t
FROM Community c
LEFT JOIN Users u ON c.id_followed = u.id_user
LEFT JOIN Posts p ON c.id_followed = p.id_user_post
WHERE c.id_follower = 1

Related

How to limit and search number of joined rows from table in multiple joins in mysql

i have a following tables in MySQL database:
+------------------------+
| Users |
+----+--------+----------+
| id | name | role |
+----+--------+----------+
| 1 | Martin | admin |
+----+--------+----------+
| 2 | George | admin |
+----+--------+----------+
| 3 | John | employee |
+----+--------+----------+
+-------------------------+
| Forms |
+----+--------------------+
| id | type |
+----+--------------------+
| 10 | marketing_form |
+----+--------------------+
| 11 | client_survey_form |
+----+--------------------+
| 12 | client_survey_form |
+----+--------------------+
+---------------------------------------------+
| UsersAssignToForms |
+----+---------+---------+--------------------+
| id | user_id | form_id | additional_comment |
+----+---------+---------+--------------------+
| 20 | 1 | 10 | Lorem ipsum... |
+----+---------+---------+--------------------+
| 21 | 2 | 10 | Lorem ipsum.... |
+----+---------+---------+--------------------+
| 22 | 3 | 10 | null |
+----+---------+---------+--------------------+
| 23 | 3 | 11 | null |
+----+---------+---------+--------------------+
I would like to have result:
+---------+---------+------------+--------------------+--------------------+
| user_id | form_id | first_name | form_type | additional_comment |
+---------+---------+------------+--------------------+--------------------+
| 1 | 10 | Martin | marketing_form | Lorem ipsum... |
+---------+---------+------------+--------------------+--------------------+
| 3 | 11 | John | client_survey_form | null |
+---------+---------+------------+--------------------+--------------------+
| null | 12 | null | client_survey_form | null |
+---------+---------+------------+--------------------+--------------------+
First of all i would like to limit number of users returned from join query (one user per one form). If user with admin role is assigned to form i would like to display this user (prioritize admin role over employee role) and limit number of returned users to 1, if admin is not assign, but employee is assigned query should return this user, if no-one is assign query should return nulls (left or right join probably).
I saw this question on stackoverflow - MySQL JOIN with LIMIT 1 on joined table, but unfortunately first answer has n+1 issue and rest of answers was made with simple one join. For my purposes i need to join more tables but wouldn't like to design this tables above to clarify what i would like to achieve, but it's very important.
So my query will looks like probably:
SELECT u.id, f.id, u.name, f.type, uf.additional_comment, [more selects from other tables...] FROM Forms as f
LEFT JOIN Users as u ON ......
INNER JOIN UsersAssignToForms as uf ON .....
[here i would like to put more and more inner joins.....]
In MySql >= 8.0 you can number the rows using some criteria (for each Form starting from one and order by u.role ASC and u.id ASC), then you can filter rows with number one:
WITH sq AS (SELECT u.id AS user_id, f.id AS form_id, u.name, f.type, uf.additional_comment,
ROW_NUMBER() OVER (PARTITION BY f.id ORDER BY u.role ASC, u.id ASC) AS num
FROM Forms AS f
LEFT JOIN UsersAssignToForms AS uf ON f.id = uf.form_id
LEFT JOIN Users AS u ON u.id = uf.user_id)
SELECT *
FROM sq
WHERE num = 1;
Before MySql 8.0 you can try something like this (the idea is the same but with different implementation):
SELECT sq2.user_id, sq2.form_id, sq2.name, sq2.type, sq2.additional_comment
FROM (
SELECT
sq1.*,
#row_number:=CASE WHEN #form_id = sq1.form_id THEN #row_number + 1 ELSE 1 END AS num,
#form_id:= sq1.form_id
FROM (SELECT u.id AS user_id, f.id AS form_id, u.name, f.type, uf.additional_comment
FROM Forms AS f
LEFT JOIN UsersAssignToForms AS uf ON f.id = uf.form_id
LEFT JOIN Users AS u ON u.id = uf.user_id
ORDER BY f.id ASC, u.role ASC, u.id ASC) AS sq1
ORDER BY sq1.form_id) AS sq2
WHERE sq2.num = 1;

i want to show an one particular blog have how much likes and how much comment using sql query

Blog table:
| bid | btitle |
| 29 | ...... |
| 38 | ...... |
likes table:
| lid | bid |
| 1 | 29 |
| 2 | 29 |
| 3 | 29 |
| 4 | 38 |
| 5 | 38 |
comment table
| commid | bid |
| 1 | 29 |
| 2 | 29 |
| 3 | 38 |
I had tried the following query but that will not work for me:
SELECT blog.bid,blog.btitle,COUNT(likes.lid) AS likecnt,COUNT(comment.comid) AS commentcnt FROM blog,likes,comment WHERE blog.bid=likes.bid AND blog.bid=comment.bid GROUP BY blog.bid
i want output like:
| bid | btitle | likecnt | commentcnt |
| 29 | ...... | 3 | 2 |
| 38 | ...... | 2 | 1 |
You can do left join with separate aggregation :
select b.bid, b.btitle,
coalesce(l.likecnt, 0) as likecnt,
coalesce(c.commentcnt, 0) as commentcnt
from blog b left join
(select l.bid, count(*) as likecnt
from likes l
group by l.bid
) l
on l.bid = b.bid left join
(select c.bid, count(*) as commentcnt
from comment c
group by c.bid
) c
on c.bid = l.bid;
If you want only matching bids the use INNER JOIN instead of LEFT JOIN & remove COALESCE().
Under many circumstances, correlated subqueries may be the fastest solution:
select b.bid, b.btitle,
(select count(*) from likes l where l.bid = b.bid) as num_likes,
(select count(*) from comment c where c.bid = b.bid) as num_comments
from blog b;
When is this a win performance wise. First, you want indexes on likes(bid) and comments(bid). With those indexes, it might be the fastest approach for your query.
It is particularly better if you have a where clause filtering the blogs in the outer query. It only has to do the counts for the blogs in the result set.
Use proper joins and count DISTINCT values because multiple joins increase the number of returned rows:
SELECT b.bid, b.btitle,
COUNT(DISTINCT l.lid) AS likecnt,
COUNT(DISTINCT c.comid) AS commentcnt
FROM blog b
LEFT JOIN likes l ON b.bid = l.bid
LEFT JOIN comment c ON b.bid = c.bid
GROUP BY b.bid, b.btitle
See the demo.
I use LEFT joins just in case there are no comments or likes for a post.
Results:
| bid | btitle | likecnt | commentcnt |
| --- | ------ | ------- | ---------- |
| 29 | ...... | 3 | 2 |
| 38 | ...... | 2 | 1 |

MySQL - Left Outer Join not updating original table

I created a toy dataset where I am trying to count the number of posts for each user. I seem to be getting the correct count values but the count column in the users table is not updated with the values.
I'm new to mysql and very confused! Can somebody tell me what I'm doing wrong?
users:
+---------+------+-------+
| user_id | user | pword |
+---------+------+-------+
| 1 | Amy | abcd |
| 2 | Jess | efgh |
| 3 | Lori | ijkl |
+---------+------+-------+
posts:
+---------+-------------+------+
| post_id | post | user |
+---------+-------------+------+
| 1 | hi | Lori |
| 2 | hello | Jess |
| 3 | hello again | Jess |
| 4 | and again | Jess |
+---------+-------------+------+
mysql> ALTER TABLE users ADD COLUMN post_count INT;
mysql> SELECT u.user_id, COUNT(p.user) AS post_count FROM users u LEFT JOIN posts p ON u.user LIKE p.user GROUP BY u.user_id;
+---------+------------+
| user_id | post_count |
+---------+------------+
| 1 | 0 |
| 2 | 3 |
| 3 | 1 |
+---------+------------+
mysql> SELECT * FROM users;
+---------+------+-------+------------+
| user_id | user | pword | post_count |
+---------+------+-------+------------+
| 1 | Amy | abcd | NULL |
| 2 | Jess | efgh | NULL |
| 3 | Lori | ijkl | NULL |
+---------+------+-------+------------+
Thanks!!
Please try the following...
UPDATE users
JOIN ( SELECT u.user_id AS user_id,
COUNT( p.user ) AS post_count
FROM users u
LEFT JOIN posts p ON u.user LIKE p.user
GROUP BY u.user_id ) postCountFinder
ON users.user_id = postCountFinder.user_id
SET users.post_count = postCountFinder.post_count;
This question takes your list of users and post counts obtained from the following...
SELECT u.user_id,
COUNT( p.user ) AS post_count
FROM users u
LEFT JOIN posts p ON u.user LIKE p.user
GROUP BY u.user_id;
... and performs an INNER JOIN with Users on shared value of user_id, creating a dataset with every row from users having the corresponding count tacked on the end.
We then use the SET command to set the empty post_count from users to its corresponding joined count.
If you have any questions or comments, thenplease feel free to post a Comment accordingly.
You need update statement to update the value in the newly added column.Try this:
Update usr
set usr.post_count=tbl.post_count
from users usr
inner join
(select u.user_id,COUNT(p.user)
AS post_count FROM users u
LEFT JOIN posts p ON u.user LIKE p.user GROUP BY u.user_id ) tbl
on tbl.user_id=usr.user_id

SQL query to get my followers except me

I try to make a sql query to get all my followers except me.
I have the following tables:
users
| id_user | username |
| 1 | xaxa |
| 2 | toto |
| 3 | bubu |
| 4 | yiyi |
| 5 | pepe |
| 6 | sisi |
| 7 | fifi |
| 8 | mama |
| 9 | juju |
| 10 | cece | => me
friends
| id_friend | id_user | id_user_to |
| 1 | 10 | 1 |
| 2 | 2 | 10 |
| 3 | 2 | 1 |
| 4 | 6 | 3 |
| 5 | 2 | 9 |
| 6 | 6 | 7 |
| 7 | 5 | 3 |
| 8 | 10 | 5 |
| 9 | 9 | 8 |
| 10 | 8 | 10 |
I want to have this:
my friends
| id_user | name |
| 1 | xaxa |
| 2 | toto |
| 5 | pepe |
| 8 | mama |
actually I have id_user 10 (me) in the result with this query =>
SELECT id_user, name
FROM `users`
WHERE id_user NOT IN (
SELECT `id_user` FROM `friends`
WHERE ( `id_user` = 10 OR `id_user_to` = 10 ))
OR id_user NOT IN (
SELECT `id_user_to` FROM `friends`
WHERE ( `id_user` = 10 OR `id_user_to` = 10 ))
GROUP BY `id_user`
The solution is a union of two simple joins:
SELECT u.id_user, u.username name
FROM friends f
JOIN users u ON u.id_user = f.id_user_to
WHERE f.id_user = 10
UNION
SELECT u.id_user, u.username
FROM friends f
JOIN users u ON u.id_user = f.id_user
WHERE f.id_user_to = 10
Note that the keyword UNION removes duplicates from the result, so no need to code anything special to handle the case when there's a friend link in both directions between two users (FYI, UNION ALL retains duplicates).
Because at most one index is used per table per query, by splitting the query into two parts, if indexes are created on the user id columns of the friends table, this query will scale well (to millions of users)
There was no need to code anything to handle an "except me" condition, unless you have a row in the friends table for you being your own friend, which you don't.
SqlFiddle: http://sqlfiddle.com/#!9/7cbb3/4
This should do:
SELECT u.id_user, u.username name
FROM `friends` f
JOIN `users` u
ON u.id_user = f.id_user_to and f.id_user = 10
or u.id_user = f.id_user and f.id_user_to = 10
ORDER BY u.id_user
SqlFiddle: http://sqlfiddle.com/#!9/7cbb3/1
So essentially you need to get users who are related in either direction (id_user -> id_user_to OR id_user_to -> id_user)
You can do either one of those with these queries:
SELECT friends.id_user, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user_to = 10
SELECT friends.id_user_to, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user = 10
But you want both sides. One way to do it is to do both queries and UNION them together. You could do it like this whilst also adding in the names
SELECT friends.id_user, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user_to = 10
UNION
SELECT friends.id_user_to, users.name
FROM users
JOIN friends on users.id_user = friends.id_user
WHERE friends.id_user = 10
It's also worth noting that the UNION will only show you distinct rows so if you have users in both directions (for example 1 -> 10 and 10 -> 1) they will not show twice.

Fastest way to select min row with join

In this example, I have a listing of users (main_data), a pass list (pass_list) and a corresponding priority to each pass code type (pass_code). The query I am constructing is looking for a list of users and the corresponding pass code type with the lowest priority. The query below works but it just seems like there may be a faster way to construct it I am missing. SQL Fiddle: http://sqlfiddle.com/#!2/2ec8d/2/0 or see below for table details.
SELECT md.first_name, md.last_name, pl.*
FROM main_data md
JOIN pass_list pl on pl.main_data_id = md.id
AND
pl.id =
(
SELECT pl2.id
FROM pass_list pl2
JOIN pass_code pc2 on pl2.pass_code_type = pc2.type
WHERE pl2.main_data_id = md.id
ORDER BY pc2.priority
LIMIT 1
)
Results:
+------------+-----------+----+--------------+----------------+
| first_name | last_name | id | main_data_id | pass_code_type |
+------------+-----------+----+--------------+----------------+
| Bob | Smith | 1 | 1 | S |
| Mary | Vance | 8 | 2 | M |
| Margret | Cough | 5 | 3 | H |
| Mark | Johnson | 9 | 4 | H |
| Tim | Allen | 13 | 5 | M |
+------------+-----------+----+--------------+----------------+
users (main_data)
+----+------------+-----------+
| id | first_name | last_name |
+----+------------+-----------+
| 1 | Bob | Smith |
| 2 | Mary | Vance |
| 3 | Margret | Cough |
| 4 | Mark | Johnson |
| 5 | Tim | Allen |
+----+------------+-----------+
pass list (pass_list)
+----+--------------+----------------+
| id | main_data_id | pass_code_type |
+----+--------------+----------------+
| 1 | 1 | S |
| 3 | 2 | E |
| 4 | 2 | H |
| 5 | 3 | H |
| 7 | 4 | E |
| 8 | 2 | M |
| 9 | 4 | H |
| 10 | 4 | H |
| 11 | 5 | S |
| 12 | 3 | S |
| 13 | 5 | M |
| 14 | 1 | E |
+----+--------------+----------------+
Table which specifies priority (pass_code)
+----+------+----------+
| id | type | priority |
+----+------+----------+
| 1 | M | 1 |
| 2 | H | 2 |
| 3 | S | 3 |
| 4 | E | 4 |
+----+------+----------+
Due to mysql's unique extension to its GROUP BY, it's simple:
SELECT * FROM
(SELECT md.first_name, md.last_name, pl.*
FROM main_data md
JOIN pass_list pl on pl.main_data_id = md.id
ORDER BY pc2.priority) x
GROUP BY md.id
This returns only the first row encountered for each unique value of md.id, so by using an inner query to order the rows before applying the group by you get only the rows you want.
A version that will get the details as required, and should also work across different flavours of SQL
SELECT md.first_name, md.last_name, MinId, pl.main_data_id, pl.pass_code_type
FROM main_data md
INNER JOIN pass_list pl
ON md.id = pl.main_data_id
INNER JOIN pass_code pc
ON pl.pass_code_type = pc.type
INNER JOIN
(
SELECT pl.main_data_id, pl.pass_code_type, Sub0.MinPriority, MIN(pl.id) AS MinId
FROM pass_list pl
INNER JOIN pass_code pc
ON pl.pass_code_type = pc.type
INNER JOIN
(
SELECT main_data_id, MIN(priority) AS MinPriority
FROM pass_list a
INNER JOIN pass_code b
ON a.pass_code_type = b.type
GROUP BY main_data_id
) Sub0
ON pl.main_data_id = Sub0.main_data_id
AND pc.priority = Sub0.MinPriority
GROUP BY pl.main_data_id, pl.pass_code_type, Sub0.MinPriority
) Sub1
ON pl.main_data_id = Sub1.main_data_id
AND pl.id = Sub1.MinId
AND pc.priority = Sub1.MinPriority
ORDER BY pl.main_data_id
This does not rely on the flexibility of MySQLs GROUP BY functionality.
I'm not familiar with the special behavior of MySQL's group by, but my solution for these types of problems is to simply express as where there doesn't exist a row with a lower priority. This is standard SQL so should work on any DB.
select distinct u.id, u.first_name, u.last_name, pl.pass_code_type, pc.id, pc.priority
from main_data u
inner join pass_list pl on pl.main_data_id = u.id
inner join pass_code pc on pc.type = pl.pass_code_type
where not exists (select 1
from pass_list pl2
inner join pass_code pc2 on pc2.type = pl2.pass_code_type
where pl2.main_data_id = u.id and pc2.priority < pc.priority);
How well this performs is going to depend on having the proper indexes (assuming that main_data and pass_list are somewhat large). In this case indexes on the primary (should be automatically created) and foreign keys should be sufficient. There may be other queries that are faster, I would start by comparing this to your query.
Also, I had to add distinct because you have duplicate rows in pass_list (id 9 & 10), but if you ensure that duplicates can't exist (unique index on main_data_id, pass_code_type) then you will save some time by removing the distinct which forces a final sort of the result set. This savings would be more noticeable the larger the result set is.