Sorry the title is not very clear. This is a follow up to my earlier question where one of the members helped me with a query.
I have a following friends Table
Friend
friend_id - primary key
user_id
user_id_friend
status
The way the table is populated is - when I send a friend request to John - my userID appears in user_id and Johns userID appears in user_id_friend.
Now another scenario is say Mike sends me a friend request - in this case mike's userID will appear in user_id and my userID will appear in user_id_friend
So to find all my friends - I need to run a query to find where my userID appears in both user_id column as well as user_id_friend column
What I am trying to do now is - when I search for user say John - I want all users Johns listed on my site to show up along with the status of whether they are my friend or not and if they are not - then show a "Add Friend" button.
Based on the previous post - I got this query which does part of the job - My example user_id is 1:
SELECT u.user_id, f.status
FROM user u
LEFT OUTER JOIN friend f ON f.user_id = u.user_id and f.user_id_friend = 1
where u.name like '%'
So this only shows users with whom I am friends where they have sent me request ie my userID appears in user_id_friend.
Although I am friends with others (where my userID appears in user_id column) - this query will return that as null
To get those I need another query like this
SELECT u.user_id, f.status
FROM user u
LEFT OUTER JOIN friend f ON f.user_id_friend = u.user_id and f.user_id = 1
where u.name like '%'
So how do I combine these queries to return 1 set of users and what my friendship status with them is. I hope my question is clear
Thanks
You need to join to your friend table twice:
select u.user_id, f1.status, f2.status
from user u left outer join friend f1 on f1.user_id = u.user_id and f1.user_friend_id = 1
left outer join friend f2 on f2.user_friend_id = u.user_id and f2.user_id = 1
where u.name like '%'
The first status should show people who have you as a friend, the second status should show people who are your friends. Not sure how this is going to help you find who are friends of a friend, though...
The simplest way to combine the two queries is by performing a union:
SELECT u.user_id, f.status
FROM user u
LEFT OUTER JOIN friend f ON f.user_id = u.user_id and f.user_id_friend = 1
where u.name like '%'
UNION
SELECT u.user_id, f.status
FROM user u
LEFT OUTER JOIN friend f ON f.user_id_friend = u.user_id and f.user_id = 1
where u.name like '%'
will produce the results that you want
This is what I'm currently using a - union of 2 queries - they are written in Zend framework but pretty straight forward to see the sql:
$select1 = $this->select()
->from(array('f'=>'friend'), array('user_id_friend as friends_id'))
->where('user_id='.(int)$user_id)
->where('status = 1');
$select2 = $this->select()
->from(array('f'=>'friend'), array('user_id as friends_id'))
->where('user_id_friend='.(int)$user_id)
->where('status = 1');
$select = $this->getAdapter()->select()->union(array( '('.$select1.')', '('.$select2.')'));
$stmt = $select->query();
return $stmt->fetchAll();
Hope this helps
Related
Tried to learn SQL like seven months ago and had to quit for a while. Came back to my baby level SQL course and tried to do the next lesson and got instantly jammed since I hardly remember what I have been taught before. I tried my best to read through my older lessons, but since these assignments are teaching how to apply your learnt skills (that were quite shaky from the start) I'm having hard time.
Assignment goes like this:
I have 3 tables with 2 columns:
[Users] [Groups] [Rights]
id name id gname user_id group_id
------------------ / ------------------------ / ---------------------------- /
1 Paul / 1 Ducks / 1 1 /
2 Jake / 2 Tadpoles / 1 2 /
3 Lilly / ------------------------ / 2 1 /
4 Anne / / 4 2 /
------------------ / /------------------------------/
I need to search for every user who belong to atleast one same group with Paul, and and print out their names.
I can manage to copy something like this from the assignment which came before this:
SELECT U.name
FROM Rights R
LEFT JOIN Groups G ON G.id = U.group_id
LEFT JOIN User U ON U.id = U.user_id
GROUP BY U.name
It magically produces
Paul
Jake
Anne
Which happens to be right answer, but if you change anything or add users/groups/rights it ofcourse stops working. (I'm not even quite sure why it is leaving poor Lilly out). Anyone who could push me to the right direction? I know that I should somehow probably use "HAVING X" where X is the list of group ids that Paul rocks.
P.S.: I'm VERY new to this matter and also English is my second language.
Start with one thing first... You are looking for PAUL, because you are looking for other users in the same group as Paul. Lets start there.
Select
u.id,
u.Name
from
User u
where
u.Name = 'Paul'
Now, you need to know which group(s) that Paul is in, so now you can associate that to the RIGHTS table on his ID only. I can also join to groups table to get the description. In this case Paul is associated with BOTH groups which will obviously pull in everyone since there are only two groups. However, you can at least see the steps from A -> B -> C for the relationships this far.
Select
u.id,
u.Name,
r.group_id,
g.gname
from
User u
JOIN Rights r
on u.id = r.user_id
JOIN Groups g
on r.group_id = g.id
where
u.Name = 'Paul'
Now, we have these rights for Paul. Now we can take this as a query in the FROM clause of an outer query to get other people that are NOT Paul
select
u2.id,
u2.Name
PaulsGroup.gname
from
( Select
u.id,
u.Name,
r.group_id,
g.gname
from
User u
JOIN Rights r
on u.id = r.user_id
JOIN Groups g
on r.group_id = g.id
where
u.Name = 'Paul' ) PaulsGroups
-- now, back to the groups table
JOIN Groups g2
-- joined on the same group Paul was in
on PaulsGroups.group_id = g2.group_id
-- and the user is NOT EQUAL (!=) to Paul as he is the basis of query
AND PaulsGroups.id != g.user_id
-- and from the second groups instance back to the users table
-- so we can get the OTHER person's name
JOIN Users u2
on g2.user_id = u2.id
order by
-- now we can put the order in the other person's name and then group name
u2.Name,
PaulsGroup.gname
Here you go. Basically you are going to use a sub query to select the group id (or group ids) that Paul belongs to. Then select all users that belong to a group id that Paul belongs to. It can be achieved with the following query.
select *
from Users u
join Rights gu
on u.id = gu.user_id
where gu.group_id in (select gu.group_id
from Users u
join Rights gu
on u.id = gu.user_id
where u.id = '1')
After sweating for one hour with all the info I gathered from here, I got this answer that works!
SELECT
U.name
-- I only need the name in this case.
FROM
(Select
R.group_id
FROM Users U
JOIN Rights R
ON U.id = R.user_id
JOIN Groups G
ON R.group_id = G.id
WHERE U.name = 'Paul' )UG
-- UG is now my table containing only the groups that Paul is in
JOIN Rights R ON R.group_id = UG.group_id
-- Comparing UG group_id:s to Rights group_id:s, filtering out every user_id that has same group_id with Paul.
JOIN Users U ON U.id = R.user_id
-- Fetching names for the user_id:s
GROUP BY U.name
-- Grouping up the names. Done!
This was right answer in my case since I had to include Pauls name in the list with the other names since technically Paul has same courses with Paul ;P.
While I got it finally done, this raised few questions. Why I didn't have to use the "Users U2" as DRapp had to do in his answer? Also I was wondering if its better to use WHERE as dporth did. (Tho this way I got bit more familiar with the JOIN)
For this requirement you don't need the table Groups at all, because you have the group ids in the table Rights (you don't need their names which is the only extra info that you would get from the table Groups).
You must join 2 copies of Users to 2 copies of Rights.
The 1st copy of Users will return the rows that you want and the 2nd is the one that will join to the row of 'Paul':
SELECT DISTINCT u1.*
FROM Users u1
INNER JOIN Rights r1 ON r1.user_id = u1.id
INNER JOIN Rights r2 ON r2.group_id = r1.group_id
INNER JOIN Users u2 ON u2.id = r2.user_id
WHERE u2.name = 'Paul' AND u1.name <> u2.name
The condition AND u1.name <> u2.name in the WHERE clause makes sure that only users other than 'Paul' will be returned. If you want 'Paul' also you can remove it.
I use DISTINCT because without it you may get the same user more than once.
See the demo.
There are 2 tables:
- User(id,pseudo)
- Link(id1,id2), those 2 columns are FK on id User
I want to select all Users that have no link with the id = 10. That means I want all id of User but only if a Link(10, id2) doesn't exists.
I have try with a left join but the result is not OK
select distinct *
from USER
left join LINK on USER.id = LINK.id1
where LINK.id1 != 10
An exists query seems to be the most straightforward here:
SELECT u.id, u.pseudo
FROM User u
WHERE NOT EXISTS (SELECT 1 FROM Link l
WHERE (l.id1 = u.id AND l.id2 = 10) OR (l.id2 = u.id AND l.id1 = 10));
In plain English, this says to retain every record from the User table such that no relationship exists between this user and id = 10. Note that I check both sides of a relationship in the Link table, assuming we don't know on which side any user might fall.
Add the condition LINK.id1 = 10 in the ON clause and in the WHERE clause return only the unmatched rows:
select distinct USER.*
from USER left join LINK
on USER.id = LINK.id1 and LINK.id1 = 10
where LINK.id1 is null
It is not clear if you want only id1 not to be 10 or id2 also.
user_playlist
-------------
user_id
playlist_id
user
-------------
id
name
playlist
-------------
id
user_id
name
Broken down, playlists can be either public (assigned to users using the junction table) or private (belonging to a user by setting the playlists user_id). How can I query all playlists for a user in a single select? Is that even possible?
This query should give you the results you want. It JOINs user to playlist, either via user_playlist (for public playlists) or directly (for private playlists):
SELECT DISTINCT p.id, p.name
FROM user u
LEFT JOIN user_playlist up ON up.user_id = u.id
JOIN playlist p ON p.id = up.playlist_id OR p.user_id = u.id
You can just add an appropriate WHERE clause (e.g. u.id = 4 or u.name = 'bill') to select the user of interest.
Demo on dbfiddle
I would suggests exists:
select pl.*
from playlist pl
where pl.user_id = :user or
exists (select 1
from user_playlist upl
where upl.play_list_id = pl.id and
upl.user_id = :user
);
The user table is not actually needed for this, unless you want to run the query for all users or you need to access the name. Your question seems to be about only one user at a time.
I heard people saying that table joins can be used everywhere to replace sub-queries. I tested it in my query, but found that appropriate data set was only retrieved when I used sub-queries. I was not able to get same data set using joins. I am not sure if what I found is right because I am a newcomer in RDBMS, thus not so much experienced. I will try to draw the schema (in words) of the database in which I was experimenting:
The database has two tables:
Users (ID, Name, City) and Friendship (ID, Friend_ID)
Goal: Users table is designed to store simple user data and Friendship table represents Friendship between users. Friendship table has both the columns as foreign keys, referencing to Users.ID. Tables have many-to-many relationship between them.
Question: I have to retrieve Users.ID and Users.Name of all the Users, which are not friends with a particular user x, but are from same city (much like fb's friend suggestion system).
By using subquery, I am able to achieve this. Query looks like:
SELECT ID, NAME
FROM USERS AS U
WHERE U.ID NOT IN (SELECT FRIENDS_ID
FROM FRIENDSHIP,
USERS
WHERE USERS.ID = FRIENDSHIP.ID AND USERS.ID = x)
AND U.ID != x AND CITY LIKE '% A_CITY%';
Example entries:
Users
Id = 1 Name = Jon City = Mumbai
Id=2 Name=Doe City=Mumbai
Id=3 Name=Arun City=Mumbai
Id=4 Name=Prakash City=Delhi
Friendship
Id= 1 Friends_Id = 2
Id = 2 Friends_Id=1
Id = 2 Friends_Id = 3
Id = 3 Friends_Id = 2
Can I get the same data set in a single query by performing joins. How? Please let me know if my question is not clear. Thanks.
Note: I used inner join in the sub-query by specifying both tables: Friendship, Users. Omitting the Users table and using the U from outside, gives an error (But if not using alias for the table Users, query becomes syntactically okay but result from this query includes ID's and names of users, who have more than one friends, including the user having ID x. Interesting, but is not the topic of the question).
For not in you can use left join and check for is null:
select u.id, u.name
from Users u
left join Friends f on u.id = f.id and f.friend_id = #person
where u.city like '%city%' and f.friend_id is null and u.id <> #person;
There are some cases where you can't work out your way with just inner/left/right joins, but your case is not one of them.
Please check sql fiddle: http://sqlfiddle.com/#!9/1c5b1/14
Also about your note: What you tried to do can be achieved with lateral join or cross apply depending on the engine you are using.
You can rewrite your query using only joins. The trick is to join to the User tables once with an inner join to identify users within the same city and reference the Friendship table with a left join and a null check to identify non-friends.
SELECT
U1.ID,
U1.Name
FROM
USERS U1
INNER JOIN
USERS U2
ON
U1.CITY = U2.CITY
LEFT JOIN
FRIENDSHIP F
ON
U2.ID = F.ID AND
U1.ID = F.FRIEND_ID
WHERE
U2.id = X AND
U1.ID <> U2.id AND
F.id IS NULL
The above query doesn't handle the situation where USER x's primary key is in the FRIEND_ID column of the FRIENDSHIP table. I assume because your subquery version doesn't handle that situation, perhaps you create 2 rows for each friendship, or friendships are not bi-directional.
Joins and subqueries can be used to achieve similar results in some cases, but certainly not all. As an example, this query with a subquery could not be achieve vis-a-vis a join:
SELECT ID, COLUMN1, COUNT(*) FROM MYTABLE
WHERE ID IN (
SELECT DISTINCT ID FROM MYTABLE
WHERE COLUMN2 NOT IN (VALUES1, VALUES2)
)
GROUP BY ID;
This is only one example, but there are many.
Conversely, you cannot get information from another table by using a subquery without joining it.
As to your example
SELECT ID, NAME FROM USERS AS U
WHERE U.ID NOT IN (
SELECT FRIENDS_ID FROM FRIENDSHIP, USERS
WHERE USERS.ID = FRIENDSHIP.ID AND USERS.ID = x)
AND U.ID != x AND CITY LIKE '% A_CITY%';
This could be constructed as:
select ID, NAME from users u
join FRIENDSHIP f on f.ID = u.ID
where u.ID = x
and u.ID != y
and CITY like '%A_CITY';
I changed your second x to a y assumptively, so it wouldn't cause confusion.
Of course, you may also want to LEFT JOIN aka LEFT OUTER JOIN if there is a chance that there may be multiple results in the FRIENDSHIP table.
I have a many to many relationship within my database. For example I have a USER table, a ROLE Table, and USERINROLE table. I have a search on my website that needs to find users in specified roles. For example I would like to retrieve User records who are in roles "reader" AND "writer"
My Query before the where looks like this:
SELECT * FROM User u INNER JOIN UserInRole ur ON
u.UserId= ur.UserId INNER JOIN Role r ON
Ur.RoleId = r.RoleId
the WHERE would be something like
WHERE roleid IN (1,2)
but that brings users in role 1 OR role 2 and I need them to be both Role 1 AND role 2
I need to retrieve the user row and the role row together for the ORM (Nhibernate)
Edit: I am using NHibernate so if there is a native way to do this, that would be awesome
Join a second copy of UserInRole. Say the alias for the second copy is ur2, then your where condition can be
Where ur.roleId = 1 and ur2.roleId = 2
Couldn't you try something like this:
Select * from User u
inner join UserInRole ur1 on u.UserID = ur1.UserID
inner join UserInRole ur2 on u.UserID = ur2.UserID
where ur1.RoleID = 1
and ur2.RoleID = 2
Untested and unoptimised...
You can also use the INTERSECT operator for this.
SELECT * FROM User
WHERE UserId IN
(
SELECT UserId FROM UserInRole
WHERE RoleId =1
INTERSECT
SELECT UserId FROM UserInRole
WHERE RoleId =2
)