Select entries in relation to fields in another table [closed] - mysql

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
I have the following tables, Table1 shows all users, and Table2 shows a teacher-to-student relationship:
Table1 (users):
ID name age
-------------
1 Al 30
2 Bob 5
3 Cam 6
4 Dan 7
Table2 (classes):
teacher_id student_id
----------------------
1 2
1 3
Essentially, this shows Al (id=1) is the teacher of Bob (id=2) and also Cam (id=3). I want to return users rows of a teacher and his students. For example, if I'm looking for Al (id=1), I want to return users info of Al and his students (Bob and Cam). What I thought was
SELECT * FROM users
LEFT JOIN classes ON users.id=classes.teacher_id
WHERE id=1 OR teacher_id=1
But I know it must be wrong because "teacher_id=1" doesn't get values from classes.student_id. It should be "... WHERE id={the student_id's from classes where teacher_id=1}". How do you do this?

You can use a Query like this to show the students search by teachers name.
SELECT u.* FROM users u
where u.id IN (
SELECT t.student_id
FROM classes t
LEFT JOIN users tu ON t.`teacher_id` = tu.id
WHERE tu.name = 'Al'
);
Sample
MariaDB [bernd]> SELECT * from users;
+----+------+------+
| id | name | age |
+----+------+------+
| 1 | Al | 30 |
| 2 | Bob | 5 |
| 3 | Cam | 6 |
| 4 | Dan | 7 |
+----+------+------+
4 rows in set (0.00 sec)
MariaDB [bernd]> SELECT * FROM classes;
+----+------------+------------+
| id | teacher_id | student_id |
+----+------------+------------+
| 1 | 1 | 2 |
| 2 | 1 | 3 |
+----+------------+------------+
2 rows in set (0.00 sec)
MariaDB [bernd]> SELECT u.* FROM users u
-> where u.id IN (
-> SELECT t.student_id
-> FROM classes t
-> LEFT JOIN users tu ON t.`teacher_id` = tu.id
-> WHERE tu.name = 'Al'
-> );
+----+------+------+
| id | name | age |
+----+------+------+
| 2 | Bob | 5 |
| 3 | Cam | 6 |
+----+------+------+
2 rows in set (0.00 sec)
MariaDB [bernd]>

try this query
SELECT * FROM classes
LEFT JOIN user ON classes.teacher_id= users.id
WHERE classes.teacher_id=1 or user.id = 1

You can join classes to 2 copies of users:
select u1.name teacher, u2.name student
from classes c
inner join users u1 on u1.id = c.teacher_id
inner join users u2 on u2.id = c.student_id
See the demo.
Results:
| teacher | student |
| ------- | ------- |
| Al | Bob |
| Al | Cam |
Or if you want a comma separated list of all the students of each teacher:
select u1.name teacher, group_concat(u2.name) students
from classes c
inner join users u1 on u1.id = c.teacher_id
inner join users u2 on u2.id = c.student_id
group by teacher
See the demo.
Results:
| teacher | students |
| ------- | -------- |
| Al | Bob,Cam |

Related

Query to get subjects of interest for all User Y where Y shares >=3 interests with a User X

These are two tables from a part of supposed Twitter like database where users can follow other users. The User.name field is unique.
mysql> select uID, name from User;
+-----+-------------------+
| uID | name |
+-----+-------------------+
| 1 | Alice |
| 2 | Bob |
| 5 | Iron Maiden |
| 4 | Judas Priest |
| 6 | Lesser Known Band |
| 3 | Metallica |
+-----+-------------------+
6 rows in set (0.00 sec)
mysql> select * from Follower;
+-----------+------------+
| subjectID | observerID |
+-----------+------------+
| 3 | 1 |
| 4 | 1 |
| 5 | 1 |
| 6 | 1 |
| 3 | 2 |
| 4 | 2 |
| 5 | 2 |
+-----------+------------+
7 rows in set (0.00 sec)
mysql> call newFollowSuggestionsForName('Bob');
+-------------------+
| name |
+-------------------+
| Lesser Known Band |
+-------------------+
1 row in set (0.00 sec)
I want to make an operation that will suggest for a user X a list of users they may be interested in following. I thought one heuristic could be to show X for all y who user y follows where X and y follow at least 3 of the same Users. Below is the SQL I came up with to do this. My question is if it could be done more efficiently or nicer in some other ways.
DELIMITER //
CREATE PROCEDURE newFollowSuggestionsForName(IN in_name CHAR(60))
BEGIN
DECLARE xuid INT;
SET xuid = (select uID from User where name=in_name);
select name
from User, (select subjectID
from follower
where observerID in (
select observerID
from Follower
where observerID<>xuid and subjectID in (select subjectID from Follower where observerID=xuid)
group by observerID
having count(*)>=3
)
) as T
where uID = T.subjectID and not exists (select * from Follower where subjectID=T.subjectID and observerID=xuid);
END //
DELIMITER ;
Consider the following refactored SQL code (untested without data) for use in stored procedure.
select u.`name`
from `User` u
inner join
(select subf.observerID, subf.subjectID
from follower subf
where subf.observerID <> xuid
) f
on u.UID = f.subjectID
inner join
(select f1.observerID
from follower f1
inner join follower f2
on f1.subjectID = f2.subjectID
and f1.observerID <> xuid
and f2.observerID = xuid
group by f1.observerID
having count(*) >= 3
) o
on f.observerID = o.observerID
I think the basic query starts as getting all "observers" who share three "subjects" with a given observer:
select f.observerid
from followers f join
followers f2
on f.subjectid = f2.subjectid and
f2.observerid = 2
group by f.observerid
having count(*) = 3;
The rest of the query is just joining in the names to fit into your paradigm of using names for references rather than ids.

replace the id with the username from an other table in a CROSS JOIN in MySQL

This question is regarding this one: Joining multiple tables to get NOT EQUAL values in MySQL
I want to extend the following query:
SELECT
d.dataid,
d.colors,
u.userid,
u.username
FROM
users u
CROSS JOIN
datas d
WHERE
(u.userid , d.dataid) NOT IN (SELECT
c.userid, c.dataid
FROM
collections c)
AND u.userid = 1
For this data sample:
table datas table users table collections
dataid | colors | addedby userid | username collectionid | userid | dataid
-------------------------- ------------------- ------------------------------
1 | blue | 1 1 | Brian 1 | 1 | 1
2 | red | 1 2 | Jason 2 | 2 | 3
3 | green | 2 3 | Marie 3 | 1 | 3
4 | yellow | 3 4 | 3 | 2
These results are expected:
for Brian
dataid | colors | userid | username
-----------------------------------
2 | red | 1 | Brian
4 | yellow | 1 | Marie
for Jason
dataid | colors | userid | username
-----------------------------------
1 | blue | 2 | Brian
2 | red | 2 | Brian
4 | yellow | 2 | Marie
The row "addedby", which inherits the userid from users, has been added.
At the moment my query replaces the userid from users instead of the addedby from datas with the username.
I really need the userid from datas replaced, not the userid from users. :-)
Does anyone have a clue how to solve this?
Thanks in advance!
cheers
Just join users table once again with datas table. And in the output use username from this join.
SELECT
d.dataid,
d.colors,
uo.userid,
uo.username
FROM
users u
CROSS JOIN
datas d
INNER JOIN
users uo
ON d.added_by = uo.id
WHERE
(u.userid , d.dataid) NOT IN (SELECT
c.userid, c.dataid
FROM
collections c)
AND u.userid = 1
And I believe, that you might even write your query in this way
SELECT u.userid, u.username, d.dataid, d.colors
FROM username u
INNER JOIN datas d
ON u.userid = d.addedby
WHERE d.dataid NOT IN (
SELECT dataid
FROM collections
WHERE userid = 1
)

Nested query in From clause syntax and performance

I'm having two tables: one for user information, the second for mapping some relation between users (two column table with two ids, from id to id relation)
I'm trying to find for a specific userid all his users' relations ids (inner select) and then get more info about them by joining to a table which has more info to show.
Given the following error:
Error: #1064 - You have an error in your SQL syntax; check the manual list that corresponds to your mySQL server version for the right syntax to use near ') AS i Limit 0,30' at line 6
What wrong with my query?
Is this query is okay in terms of performance, or there are other way to do so?
Query:
SELECT i.*
FROM
((SELECT uc.contactId
FROM tbl_users AS u
JOIN tbl_users_contacts AS uc ON u.Id = uc.userId
WHERE uc.userId =1) AS contacts_ids JOIN tbl_users AS u
ON contacts_ids.contactId = u.Id) AS i;
Edit: Fixed to:
SELECT *
FROM
((SELECT uc.contactId
FROM tbl_users AS u
JOIN tbl_users_contacts AS uc ON u.Id = uc.userId
WHERE uc.userId =1) AS contacts_ids JOIN tbl_users AS u
ON contacts_ids.contactId = u.Id);
Don't know why the final As i was a problem, so I ask for question 2 mainly for this post.
Consider the following
mysql> create table tbl_users ( iduser int,name varchar(100),email varchar(100));
Query OK, 0 rows affected (0.10 sec)
mysql> insert into tbl_users values
-> (1,'A','a#a.com'),
-> (2,'B','b#b.com'),
-> (3,'C','c#c.com'),
-> (4,'D','d#d.com'),
-> (5,'E','e#e.com');
Query OK, 5 rows affected (0.09 sec)
Records: 5 Duplicates: 0 Warnings: 0
mysql> create table contacts (iduser int, contactid int );
Query OK, 0 rows affected (0.14 sec)
mysql> insert into contacts values
-> (1,2),(1,3),(1,5),(2,1),(2,5),(3,1),(3,4);
mysql> select * from tbl_users ;
+--------+------+---------+
| iduser | name | email |
+--------+------+---------+
| 1 | A | a#a.com |
| 2 | B | b#b.com |
| 3 | C | c#c.com |
| 4 | D | d#d.com |
| 5 | E | e#e.com |
+--------+------+---------+
5 rows in set (0.00 sec)
mysql> select * from contacts ;
+--------+-----------+
| iduser | contactid |
+--------+-----------+
| 1 | 2 |
| 1 | 3 |
| 1 | 5 |
| 2 | 1 |
| 2 | 5 |
| 3 | 1 |
| 3 | 4 |
+--------+-----------+
7 rows in set (0.00 sec)
Now as we can see userid = 1 has 3 contacts and we can get them as
select u.* from tbl_users u
join contacts c on c.contactid = u.iduser
where c.iduser = 1 ;
The output will be as
+--------+------+---------+
| iduser | name | email |
+--------+------+---------+
| 2 | B | b#b.com |
| 3 | C | c#c.com |
| 5 | E | e#e.com |
+--------+------+---------+
To boost up the performance you may add the following indexes
alter table tbl_users add index userid_idx(iduser);
alter table contacts add index cu_idx(iduser,contactid);
Change the table and column name into the query as per your need.

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.

twitter-style follower/following/friend sql query

I'm working on a twitter type of following system. I'm joining two tables, users and followers to get the first and lastname of users who are in the followers table. Then I'm running an inner join on the followers table to capture follower and friend relationships. I'm displaying the results as followers (who follows you), following (who you follow), and friends (mutual following).
With the query below, I'm only able to show the name of the user who wants to see their friends. I'd like to show the FRIENDS of the user, not the user's own name, but can't figure out how to get the users table to do double duty--that is, show me the name of the user and the name of their friend, or just the friend's name.
Thanks.
SELECT users.id, users.firstname, users.lastname, followers.follower_user_id, followers.followee_user_id
FROM users
JOIN followers ON followers.follower_user_id = users.id
INNER JOIN followers ff ON followers.followee_user_id = ff.follower_user_id AND followers.follower_user_id = ff.followee_user_id
I believe that your schema requires a union table to assemble the information you need; and it may be more efficient to do this in multiple tables. To maintain a separate table of followers with (possible) duplicate information from users may also be undesireable. A more efficient schema would be:
mysql> select * from users;
+-----+------------+---------+
| uid | fname | lname |
+-----+------------+---------+
| 1 | Phillip | Jackson |
| 2 | Another | Name |
| 3 | Some Crazy | User |
| 4 | Nameless | Person |
+-----+------------+---------+
4 rows in set (0.00 sec)
mysql> select * from follows;
+---------+-----------+
| user_id | follow_id |
+---------+-----------+
| 1 | 4 |
| 2 | 3 |
| 3 | 2 |
| 4 | 2 |
+---------+-----------+
4 rows in set (0.00 sec)
And then your query would look like:
select users.uid,
users.fname,
users.lname,
u.uid,
u.fname,
u.lname from users
inner join follows f on (f.user_id=users.uid)
inner join users u on (u.uid=f.follow_id)
Which returns:
mysql> select users.uid,
-> users.fname,
-> users.lname,
-> u.uid,
-> u.fname,
-> u.lname from users
-> inner join follows f on (f.user_id=users.uid)
-> inner join users u on (u.uid=f.follow_id);
+-----+------------+---------+-----+------------+--------+
| uid | fname | lname | uid | fname | lname |
+-----+------------+---------+-----+------------+--------+
| 1 | Phillip | Jackson | 4 | Nameless | Person |
| 4 | Nameless | Person | 2 | Another | Name |
| 2 | Another | Name | 3 | Some Crazy | User |
| 3 | Some Crazy | User | 2 | Another | Name |
+-----+------------+---------+-----+------------+--------+
4 rows in set (0.00 sec)
SELECT u.id, u.first_name, u.last_name, uf.id, uf.first_name, uf.last_name
FROM users u
JOIN followers f
ON f.follower_user_id = u.id
JOIN followers ff
ON (ff.followee_user_id, ff.follower_user_id) = (f.follower_user_id, f.followee_user_id)
JOIN users uf
ON uf.id = f.followee_user_id