subquery equivalent for a join solution - sql

We have two tables, User, Emails. They have relationship on User.id == Emails.user_id. If we are interested in those users who have more than one different emails, using join we can accomplish by this JOIN:
SELECT * FROM User
JOIN Emails as EM1 ON User.id = Emails.user_id
JOIN Emails as EM2 ON User.id = Emails.user_id
Where EM1.address_line != EM2.address_line;
How can we achieve the same result using subquery?

You can use a correlated subquery:
select u.*
from users u
where (select count(*)
from emails e
where e.user_id = u.id
) >= 2;

You could use a subquery that only returned user_id from Emails where the user has more than one email address.
Then join this to the User table on that user_id:
SELECT *
FROM User u
INNER JOIN (
SELECT user_id
FROM Emails
GROUP BY user_id
HAVING COUNT(*) > 1
) a ON a.user_id = u.id
The subquery will only return user_id when there is more than one row in the Emails table for that user, which is what you asked for.
Note: SQL is untested.

Related

Get users with item count <= 1 in sql

We have these tables in PostgreSQL 12:
User -> id, name, email
items -> id, user_id, description
We want to run a query to find users that have 1 item or less.
I tried using a join statement and in the WHERE clause tried to put the count of users < 1 with this query
select * from "user" inner join item on "user".id = item.user_id where count(item.user_id) < 1;
but it failed and gave me this error.
ERROR: aggregate functions are not allowed in WHERE
LINE 1: ...inner join item on "user".id = item.user_id where count(item...
so im thinking the query needs to be more techincal.
Can anyone please help me with this? thanks
You can do:
select u.*
from user u
left join (
select user_id, count(*) as cnt from items group by user_id
) x on x.user_id = u.id
where x.cnt = 1 or x.cnt is null
You don't technically need a JOIN for this. You can get all the necessary data from the item table with GROUP BY. The trick is you need to use HAVING instead of WHERE for aggregated data like COUNT()
SELECT user_id
FROM item
GROUP BY user_id
HAVING COUNT(id) > 1
But we can add a JOIN if you want to see more fields from the user table:
SELECT u.id, u.name, u.email
FROM item i
INNER JOIN "user" u on u.id = i.user_id
GROUP BY u.id, u.name, u.email
HAVING COUNT(i.id) > 1

How to print two attribute values from your Sub query table

Suppose I have two tables,
User
Post
Posts are made by Users (i.e. the Post Table will have foreign key of user)
Now my question is,
Print the details of all the users who have more than 10 posts
To solve this, I can type the following query and it would give me the desired result,
SELECT * from USER where user_id in (SELECT user_id from POST group by user_id having count(user_id) > 10)
The problem occurs when I also want to print the Count of the Posts along with the user details. Now obtaining the count of user is not possible from USER table. That can only be done from POST table. But, I can't get two values from my subquery, i.e. I can't do the following,
SELECT * from USER where user_id in (SELECT user_id, **count(user_id)** from POST group by user_id having count(user_id) > 10)
So, how do I resolve this issue? One solution I know is this, but this I think it would be a very naive way to resolve this and will make the query much more complex and also much more slow,
SELECT u.*, (SELECT po.count(user_id) from POST as po group by user_id having po.count(user_id) > 10) from USER u where u.user_id in (SELECT p.user_id from POST p group by user_id having p.count(user_id) > 10)
Is there any other way to solve this using subqueries?
Move the aggregation to the from clause:
SELECT u.*, p.num_posts
FROM user u JOIN
(SELECT p.user_id, COUNT(*) as num_posts
FROM post p
GROUP BY p.user_id
HAVING COUNT(*) > 10
) p
ON u.user_id = p.user_id;
You can do this with subqueries:
select u.*
from (select u.*,
(select count(*) from post p where p.user_id = u.user_id) as num_posts
from users u
) u
where num_posts > 10;
With an index on post(user_id), this might actually have better performance than the version using JOIN/GROUP BY.
You can try by joining the tables, Prefer to do a JOIN than using SUBQUERY
SELECT user.*, count( post.user_id ) as postcount
FROM user LEFT JOIN post ON users.user_id = post.user_id
GROUP BY post.user_id
HAVING postcount > 10 ;

How to count and group by column across one to many relationship while handling 0 case?

I am trying to formulate a single SQL query that will count a table across a one to many relationship. Here is the short version of my schema:
User(id)
Group(id)
UserGroup(user_id, group_id)
Post(id, user_id, group_id)
The goal is to return the count of posts for each user in a group. The specific issue I am running into is my current query cannot return 0 for a user that has no posts. Here is my naive query:
SELECT
COUNT(*) as total,
user_id
FROM
posts
WHERE
group_id = ?
GROUP BY user_id
ORDER BY
total DESC
This works fine when every user has a post, but when some have no posts, they do not show up in the list. How can I write a single query that handles this scenario and returns count 0 for said users? I know I need to somehow incorporate UserGroup to get the list of users, but am stuck from there.
Use a left join:
SELECT u.id, COUNT(*) as total
FROM users u LEFT JOIN
posts p
ON p.user_id = u.id AND
p.group_id = ?
GROUP BY u.id
ORDER BY total DESC
I think I got it, but not sure how performant.
select count(p), u.id from users u left join (select * from workouts where group_id = ?) p on p.user_id = u.id where u.id in (select user_id from user_group where group_id = ?) group by u.id;

How can I get records from one table which do not exist in a related table?

I have this users table:
and this relationships table:
So each user is paired with another one in the relationships table.
Now I want to get a list of users which are not in the relationships table, in either of the two columns (user_id or pair_id).
How could I write that query?
First try:
SELECT users.id
FROM users
LEFT OUTER JOIN relationships
ON users.id = relationships.user_id
WHERE relationships.user_id IS NULL;
Output:
This is should display only 2 results: 5 and 6. The result 8 is not correct, as it already exists in relationships. Of course I'm aware that the query is not correct, how can I fix it?
I'm using PostgreSQL.
You need to compare to both values in the on statement:
SELECT u.id
FROM users u LEFT OUTER JOIN
relationships r
ON u.id = r.user_id or u.id = r.pair_id
WHERE r.user_id IS NULL;
In general, or in an on clause can be inefficient. I would recommend replacing this with two not exists statements:
SELECT u.id
FROM users u
WHERE NOT EXISTS (SELECT 1 FROM relationships r WHERE u.id = r.user_id) AND
NOT EXISTS (SELECT 1 FROM relationships r WHERE u.id = r.pair_id);
I like the set operators
select id from users
except
select user_id from relationships
except
select pair_id from relationships
or
select id from users
except
(select user_id from relationships
union
select pair_id from relationships
)
This is a special case of:
Select rows which are not present in other table
I suppose this will be simplest and fastest:
SELECT u.id
FROM users u
WHERE NOT EXISTS (
SELECT 1
FROM relationships r
WHERE u.id IN (r.user_id, r.pair_id)
);
In Postgres, u.id IN (r.user_id, r.pair_id) is just short for:(u.id = r.user_id OR u.id = r.pair_id).
The expression is transformed that way internally, which can be observed from EXPLAIN ANALYZE.
To clear up speculations in the comments: Modern versions of Postgres are going to use matching indexes on user_id, and / or pair_id with this sort of query.
Something like:
select u.id
from users u
where u.id not in (select r.user_id from relationships r)
and u.id not in (select r.pair_id from relationships r)

Select without Group By Sql

I got some problems with this query i need all my selectors but i only want to group aspnet_Users.Userid how should i do this?
Here is my query:
SELECT
aspnet_Users.UserId, aspnet_Users.UserName,
Friends.Verified, Friends.FriendUserId
FROM
[aspnet_Users]
INNER JOIN
Friends ON Friends.UserId = aspnet_Users.UserId OR Friends.FriendUserId = aspnet_Users.UserId
WHERE
aspnet_Users.UserId IN
(SELECT UserId as Id FROM Friends
WHERE FriendUserId='3d1224ac-f2ad-45d4-aa84-a98e748e3e57'
UNION
SELECT FriendUserId as Id FROM Friends
WHERE UserId='3d1224ac-f2ad-45d4-aa84-a98e748e3e57')
GROUP BY
aspnet_Users.UserId, aspnet_Users.UserName,
Friends.Verified, Friends.FriendUserId
Stored Procedure structure:
Tables [aspnet_Users] and [Friends]
Columns [aspnet_Users.UserId], [aspnet_Users.UserName], [Friends.Verified], [Friends.FriendUserId]
That is the data that i need to get using the procedure, but the problem is that each user can have multiply friends which means if so there will be multiply Friends values. This is the reason why i can't group using those values cause it gives me dublicates with the wrong value.
Expected Output:
aspnet_Users.UserId (This is the user id of the friend, note that it doesn't mean that its the FriendUserId)
aspnet_Users.UserName (This is the UserName of the friend based on the UserId explained above)
Friends.Verified (True or False. The Verified value in the Friends table on the friendship)
Friends.FriendUserId (The FriendUserId value from the table Friends)
If your relationship is not fully reciprocal in the table Friends, you can use this query.
Otherwise either part of the UNION would work on its own
SELECT U.UserId, U.UserName, F.Verified, F.FriendUserId
FROM Friends F
JOIN aspnet_Users U ON U.UserId=F.UserId
WHERE F.FriendUserId='3d1224ac-f2ad-45d4-aa84-a98e748e3e57'
UNION
SELECT U.UserId, U.UserName, F.Verified, F.FriendUserId
FROM Friends F
JOIN aspnet_Users U ON U.UserId=F.FriendUserId
WHERE F.UserId='3d1224ac-f2ad-45d4-aa84-a98e748e3e57'
Do you need to group? You aren't using any aggregate functions (eg sum, count)
You haven't made it clear what results you're looking for - I'm guessing all details of users connected with a given user, including the user themselves?
Surely you can just do something like:
Select a.userid, a.username, f.verified, f.userid
From aspnet_users a
Full outer join friends f on f.frienduserid = a.userid
Where a.userid = #userid or f.frienduserid = #userid
You can use DISTINCT
SELECT DISTINCT
aspnet_Users.UserId, aspnet_Users.UserName,
Friends.Verified, Friends.FriendUserId
FROM
[aspnet_Users]
INNER JOIN
Friends ON Friends.UserId = aspnet_Users.UserId OR Friends.FriendUserId = aspnet_Users.UserId
WHERE
aspnet_Users.UserId IN
(SELECT UserId as Id FROM Friends
WHERE FriendUserId='3d1224ac-f2ad-45d4-aa84-a98e748e3e57'
UNION
SELECT FriendUserId as Id FROM Friends
WHERE UserId='3d1224ac-f2ad-45d4-aa84-a98e748e3e57')