Select "many to many" and "belongs to" in single statement - sql

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.

Related

Is it true that JOINS can be used everywhere to replace Subqueries in SQL

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.

Select all entities which do not have children -- getting a lot of rows back, is this correct?

I've got 84,000 rows in my Users table. Users are created automatically. So, I thought it would be nice to see how many users actually did anything after being created. I wrote this query:
SELECT COUNT(*) FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
WHERE 0 = (SELECT COUNT(*) FROM PlaylistItems WHERE PlaylistId = p.Id)
My intent is to only count users which have no playlist items in any of their playlists. This query returned 74,000 results which seems high.
I'm wondering if this query is selecting all users which have at least one playlist with no items in it. That is, if a user has two playlists -- one empty and one populated -- are they still counted in my query? And, if so, how can I modify it to select only users which have only empty playlists.
If that's vastly more difficult then I might try my hand at counting only users with 1 playlist which is empty.
The database structure is:
Many users. 1:1 user:folder, 1:many folder:playlists, 1:many playlists:playlistItems
A better pattern than counting every single playlist and comparing is simply finding all the users who don't have anything in any playlist. I like NOT EXISTS for this:
SELECT COUNT(u.Id)
FROM dbo.Users AS u
WHERE NOT EXISTS
(
SELECT 1 FROM dbo.PlayLists AS pl
INNER JOIN dbo.PlayListItems AS pli
ON pl.id = pli.PlayListID
INNER JOIN dbo.Folders AS f
ON p.FolderID = f.ID
WHERE f.UserID = u.Id
);
As an aside, calling a column Id in its primary table and something else everywhere else might seem like a good idea, but I find it quite confusing. Why isn't a FolderID called a FolderID everywhere in the data model?
Break down your query:
SELECT u.id, COUNT(*) FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
join PlaylistItems on PlaylistId = p.Id
group by u.id
This should provide you with a list of all users and the count of the number of rows in playlists by userID. a couple ways to go...
Take a count of all users not in that list:
select count(*) from users where id not in (SELECT u.id FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
join PlaylistItems on PlaylistId = p.Id
group by u.id)
MySQL performs poorly on that...same thing using left join:
select count(*)
from users u left join (SELECT u.id, COUNT(*) FROM Users u
JOIN Folders f ON UserId = u.Id
JOIN Playlists p ON FolderId = f.Id
join PlaylistItems on PlaylistId = p.Id
group by u.id)a
on a.id = u.id
where a.id is null

Sql Query - Selecting rows where user can be both friend and user

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

SQL- How do you retrieve records matching all values in a linked 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
)

Selecting records in SQL based on another table's contents

I'm a bit new to SQL and have trouble constructing a select statement. I have two tables:
Table users
int id
varchar name
Table properties
int userID
int property
and I want all user records which have a certain property. Is there a way to get them in one SQL call or do I need to first get all userIDs from the properties table and then select each user individually?
Use a JOIN:
SELECT U.id, U.name, P.property FROM users U
INNER JOIN properties P ON P.userID = U.id
WHERE property = 3
If there's only one property row per user you want to select on, I think this is what you want:
select
users.*
from
users,
properties
where
users.id = properties.userID
and properties.property = (whatnot);
If you have multiple property rows matching "whatnot" and you only want one, depending your database system, you either want a left join or a distinct clause.
Check out the JOIN command. You could write a query like the following:
SELECT
name
FROM
users u
INNER JOIN properties p
ON u.id = p.userID
WHERE
p.property = <some value>
You're looking to JOIN tables.
Assuming the id and userID columns have the same meaning, it's like this:
select u.name
from users u inner join properties p
on u.id = p.userID
where p.property = :ValueToFind
SELECT [Name] FROM Users u
JOIN Properties p on p.UserID=u.ID
WHERE p.Property=1
Obviously it depends what flavour of RDBMS and TSQL you are using.