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.
Related
I have two tables. One with user info, one with payment info. I would like to find out users that are either the sender or the receiver of a payment.
Eample data:
user
id
other columns
1
2
3
payments:
sender
receiver
other columns
1
4
1
3
5
3
4
5
ideal output
id
1
3
what I tried:
SELECT id
FROM user u
where exists
(
SELECT 1
FROM payments p
where u.id = p.sender or u.id = p.receiver
)
BigQuery gave the error:
LEFT SEMI JOIN cannot be used without a condition that is an equality of fields from both sides of the join
which is quite confusing to me
It's because WHERE u.id = p.sender or u.id = p.receiver makes LEFT SEMI JOIN to be non-equi join.
You can separate WHERE condition into 2 EXITS clauses.
SELECT id
FROM user u
WHERE EXISTS (SELECT 1 FROM payments p WHERE u.id = p.sender)
OR EXISTS (SELECT 1 FROM payments p WHERE u.id = p.receiver)
;
output:
But this approach sometimes shows very poor performance in real circumstances.
So, below query would be another option you can use in that case.
SELECT id FROM user u WHERE EXISTS (SELECT 1 FROM payments p WHERE u.id = p.sender)
UNION ALL
SELECT id FROM user u WHERE EXISTS (SELECT 1 FROM payments p WHERE u.id = p.receiver)
;
I think below is the most optimal solution
select distinct id
from payments, unnest([sender, receiver]) id
join user
using(id)
if applied to sample data in your question - output is
I am trying to select a User where that User DOES NOT have an associated record with a certain value.
I have a User model, a User has_one Feed, a Feed has_many FeedTracks, a FeedTrack belongs_to a Track. I want to select Users only where they DO NOT have a FeedTrack with a certain TrackId.
I'm using rails but I would be open to strict SQL for this.
The best I've got is:
SELECT TOP 1000 *
FROM Users u
LEFT JOIN Feeds f ON f.user_id = u.id
LEFT JOIN FeedTracks ft ON ft.feed_id = f.id
ONLY IF ALL OF THOSE feedTracks' track_id !== <<track_id>>
Obviously that last part statement is not real SQL and that's what my question is. How would I say, hey, get me the Users where this related record doesn't exist. But if that record does exist, don't return that User.
In other words, if that user has a feed, with a feed track with that track id, don't return that User. But if it doesn't, do return that User.
You can use GROUP BY and HAVING:
SELECT u.id
FROM Users u
LEFT JOIN Feeds f
ON f.id = u.feed_id
LEFT JOIN FeedTracks ft
ON ft.feed_id = f.id
GROUP BY u.id
HAVING
SUM(CASE WHEN ft.track_id = #track_id THEN 1 ELSE 0 END) = 0
Modify the columns in the SELECT and GROUP BY as needed.
Try
User.joins(feed: :feed_tracks)
.where('feed_tracks.track_id <> ?', unwanted_track_id)
.order('id desc')
.limit(1000)
.to_sql
You should use not existst operation like this
SELECT TOP 1000 *
FROM Users u
LEFT JOIN Feeds f ON f.id = u.feed_id
LEFT JOIN FeedTracks ft ON ft.feed_id = f.id
where not exists
(select 1
from FeedTracks ft1
where ft1.feed_id = f.id
and ft1.track_id = <<track_id>>)
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.
Suppose that I have a table rating which is unique to the pair user and client.
The schema of rating would hold user_id and client_id along with some other information (like score and comment).
At some point I might have the following situation where a * represents the existence of a rating for the corresponding user_id and client_id.
user1 user2 user3 user4
client1 * * * *
client2 * * * *
client3 * *
client4 * * *
In pure SQL, how can one select the rows with elements of user_id and client_id where a * does not exist?
In my example, the correct result should be
user_id client_id
-------- ---------
2 3
2 4
4 3
Try something like:
SELECT *
FROM users
CROSS JOIN clients
WHERE NOT EXISTS (SELECT 1
FROM rating
WHERE rating.user_id = users.user_id
AND rating.client_id = clients.client_id)
The way I would approach this would be by constructing a query, involving LEFT JOIN, which included every combination whether valid or not, and then filtering to just those which have no data from the rating part of the JOIN.
In this case, we want a row for every combination of user and client, so that part requires a CROSS JOIN, with the ratings table LEFT JOINed onto that.
Here's the query with all possible combinations, and ratings where available (Live Demo):
SELECT
U.user_name,
C.client_name,
R.rating
FROM
users as U
CROSS JOIN
clients as C
LEFT JOIN
ratings as R
On R.user_id = U.user_id
And R.client_id = C.client_id
ORDER BY
U.user_name,
C.client_name;
From that output, it's hopefully clear that to find combinations where no rating exists, we can just add WHERE R.rating IS NULL (Live Demo):
SELECT
U.user_name,
C.client_name,
R.rating
FROM
users as U
CROSS JOIN
clients as C
LEFT JOIN
ratings as R
On R.user_id = U.user_id
And R.client_id = C.client_id
WHERE
R.rating IS NULL
ORDER BY
U.user_name,
C.client_name;
(Note that there is no specific syntax in SQL to say "where the Left Join didn't match anything in the Right table", you just have to pick a non-nullable column, like I have with rating in this example.)
I have 3 tables: Post, User and Favorites
Post
----------
PostId
Nombre
UserId
Banned
User
----------
UserId
UserName
Info
Favorites
-----------
PostId
UserId
I'm trying to get all posts and telling if the posts are favourites or not, so if a post isn't among favourites, it will appear on the query but favourite flag will be false.
this is my query:
SELECT
p.Nombre as title,
CASE ISNULL(f.PostId,0) WHEN 0 THEN 'false' ELSE 'true' END as favorito
FROM Post p
inner join User u on p.UserId = u.UserId
left outer join Favorite f on p.PostId = f.PostId
where
p.Banned = 0
and it returns everything, and tells if there is a record in favorites, but when I add the filter on where section
and f.UsuarioId = 4
only returns the posts the user likes. Shouldn't be when using left outer join or full outer join that if a record from secondary table doesn't exist should return the records from main table anyways?
¿How can I return all post using filter on secondary table? thanks!
Since you have filtered on f.UsuarioId = 4, that effectively converts the LEFT OUTER JOIN into an INNER JOIN by requiring that the Favorite have a record of a specific value. To still return all those with no related Favorite, you must test for a NULL in that table:
SELECT
p.Nombre as title,
CASE ISNULL(f.PostId,0) WHEN 0 THEN 'false' ELSE 'true' END as favorito
FROM
Post p
INNER JOINUser u ON p.UserId = u.UserId
LEFT OUTER JOIN Favorite f ON p.PostId = f.PostId
WHERE
p.Banned = 0
-- Return either those related Favorite records for UsuarioId = 4 or the NULLs
-- meaning a related record does not exist
AND (f.UsuarioId = 4 OR f.UsuarioId IS NULL)