SQL Inner Join Two Foreign Keys - sql

I have two tables (Users and Pairs). The Pairs table contains 3 columns, an ID and then a user1ID and user2ID.
Users
ID firstName surname
------------------------------
1043 john doe
2056 jane doe
Pairs
ID user1ID user2ID
------------------------------
1 1043 2056
I'm then looking at using a select statement to get the user details base on the ID of the Pairs table:
SELECT users1.*, users2.*
FROM Pairs
JOIN Users users1 ON Pairs.user1ID = users1.IDNumber
JOIN Users users2 ON Pairs.user2ID = users2.IDNumber
WHERE Pairs.ID = 1
Which returns the right details for the two users, however they're all on one row, how can I get it to return each user on a separate row as they are in the Users table?

SELECT users1.*, users2.*
FROM Pairs
JOIN Users
ON Pairs.user1ID = users.IDNumber
OR Pairs.user2ID = users.IDNumber
WHERE Pairs.ID = 1
Just use an OR statement in your ON condition instead of 2 joins.

IN will work also.
SELECT *
FROM Pairs p
JOIN Users u ON u.ID IN (p.user1ID, p.User2ID)
WHERE p.ID = 1

Related

Get only users who do not have the id equal to a condition (SQL)

I have two tables, user:
id
full_name
1
Beatriz
2
Mauro
3
Jose
4
fran
approver :
id
subordinate_id
approver_id
1
1
2
2
3
4
I would like to bring up the names of people who are not registered in the subordinate_id column
I did the following query:
SELECT
U.full_name
FROM user AS U
INNER JOIN approver as A
ON U.id <> A.subordinate_id ;
enter image description here
and still users are coming in that are in the subordinate_id column of the approver table.
I would like to get the result only for user names that are not subordinate_id, can someone help with this?
I would like a result with only the users that are not subordinate_id
This is simple to accomplish an ansi-sql with a not exists semi join:
Select full_name
from user u
where not exists (
select * from approver a
where a.approver_id = u.id <-- or (subordinate_id, whichever it should be)
);

Unify columns from different tables while selecting distinct rows

Tables
User
id
name
email
is_active
1
john
john#albert.com
FALSE
2
mike
mike#ss.com
TRUE
3
monica
monica#dunno.com
TRUE
4
joey
joey#as.com
FALSE
5
ross
ross#boss.com
FALSE
Subscriptions
id
house_id
plan name
status
1
1
A banana a month
inactive
2
2
An apple a month
active
3
3
A pear a month
active
House
id
name
1
John's House
2
Mike's House
3
Monica's House
4
Joey's House
5
Ross's House
House_Contact (legacy table)
id
house_id
is_primary
1
1
TRUE
2
2
FALSE
2
3
TRUE
House_User (new table)
id
house_id
is_owner
user_id
1
2
FALSE
2
2
4
FALSE
4
3
5
FALSE
5
Expected Results
The resulting table should include the following:
Does the user have a subscription regardless of status? If so, include, if not, disregard.
Get email & is_active from User table (if they have subscription)
Get is_primary OR is_owner (if they have a subscription)
Results should be distinct (no duplicate users)
house_id
email
is_owner
is_active
1
john#albert.com
TRUE
FALSE
2
mike#ss.com
FALSE
TRUE
3
monica#dunno.com
TRUE
TRUE
What I tried
SELECT
u.email AS "email",
u.is_active AS "is_active",
h.id AS "house_id",
is_owner
FROM
house c
INNER JOIN (
SELECT
house_id,
user_id
FROM
house_user) hu ON h.id = hu.house_id
INNER JOIN (
SELECT
id,
email,
is_active
FROM
USER) u ON hu.user_id = u.id
INNER JOIN (
SELECT
id,
email,
is_primary
FROM
house_contact) hc ON u.email = ch.email
INNER JOIN (
SELECT
house_id,
is_primary is_owner
FROM
house_contact
UNION
SELECT
house_id,
is_owner is_owner
FROM
house_user) t ON u.id = t.house_id)
ORDER BY
u.email
Results are half than if I remove the INNER JOIN with UNION statement. No idea how to proceed.
I'm particularly confused with unifying the column and the possible duplication.
My educated guess:
SELECT DISTINCT ON (u.id)
u.id, u.email, u.is_active, h.house_id, h.is_primary
FROM "user" u
LEFT JOIN (
SELECT hu.user_id, hu.house_id
, GREATEST(hc.is_primary, hu.is_owner) AS is_primary
FROM house_user hu
LEFT JOIN house_contact hc USING (house_id)
WHERE EXISTS (SELECT FROM subscription WHERE house_id = hu.house_id)
) h ON h.user_id = u.id
ORDER BY u.id, h.is_primary DESC NULLS LAST, h.house_id;
We don't need table house in the query at all.
I see three possible sources of conflict:
house_contact.is_primary vs. house_user.is_owner. Both seem to mean the same. The DB design is broken in this respect. Taking GREATEST() of both, which means true if either is true.
We don't care about subscription.status, so just make sure the house has at least one subscription of any kind with EXISTS, thereby avoiding possible duplicates a priori.
A user can live in multiple houses. We want only one row per user. So show the first house with is_primary (the one with the smallest house_id) if any. If there is no house, there is also no subscription. But the outer LEFT JOIN keeps the user in the result. Change to JOIN to skip users without subscription.
About DISTINCT ON:
Select first row in each GROUP BY group?
About sorting boolean values:
Sorting null values after all others, except special
Sort NULL values to the end of a table
You can use the joins as follows:
Select distinct hu.house_id, u.email, hu.is_owner, hc.is_primary
From user u join house_user hu on u.id = hu.user_id
Join subscriptions s on s.house_id = hu.house_id
Join house_contract hc on hc.house_id = s.house_id;
I have used distinct to remove duplicates if you have multiple data in the table for matching condition. You can remove it if not required in case it is not required.
From what I can tell, you want to start with a query like this:
select s.house_id, u.email, hu.is_owner, u.is_active
from subscriptions s left join
house_user hu
on s.house_id = hu.house_id left join
users u
on hu.user_id = u.id;
This does not return what you want, but it is rather unclear how your results are derived.

SQL query (Join without duplicates)

I have tables users and topics. Every user can have from 0 to several topics (one-to-many relationship).
How I can get only those users which have at least one topic?
I need all columns from users (without columns from topics) and without duplicates in table users. In last column I need number of topics.
UPDATED:
Should be like this:
SELECT user.*, count(topic.id)
FROM ad
LEFT JOIN topic ON user.id = topic.ad
GROUP BY user.id
HAVING count(topic.id) > 0;
but it takes 0 result. But it should not be 0.
Firstly you need to have your two tables, because you have left limited information about your table structure I will use an example to explain how this works, you should then be able to easily apply this to your own tables.
Firstly you need to have two tables (which you do)
Table "user"
id | name
1 | Joe Bloggs
2 | Eddy Ready
Table "topic"
topicid | userid | topic
1 | 1 | Breakfast
2 | 1 | Lunch
3 | 1 | Dinner
Now asking for a count against each user is done using the follwing;
SELECT user.name, count(topic.topicid)
FROM user
INNER JOIN topic ON user.id = topic.userid
GROUP BY user.name
If you use a left join, this will include records from the "user" table which does not have any rows in the "topic" table, however if you use an INNER JOIN this will ONLY include users who have a matching value in both tables.
I.e. because the user id "2" (which we use to join) is not listed in the topic table you will not get any results for this user.
Hope that helps!
use inner join and distinct
select distinct user_table.id
from user_table
inner join topics_table on topic_table.user_id = user_table.id
select u.id
, u.name
, count(b.topicName)
from user u
left join topic t on t.userid = u.id
group by u.id, u.name
You can select topic number per user and then join it with user data. Something like this:
with t as
(
select userid, count(*) as n
from topic
group by userid
)
SELECT user.*, t.n
FROM user
JOIN t ON user.id = t.userid

Given a specific user I want to find all users having at least all of the roles this specific user has

Suppose I have user_role table having user and role columns.
| User | Role |
---------------
| 100 | 10 |
| 101 | 10 |
| 101 | 11 |
| 102 | 11 |
I want to write a query that will return users with same or lesser roles. For example:
Return user 100 for user 100
Return user 100,101,102 for user 101
Return user 102 for user 102
Business requirement: Suppose User X belongs to Asia group only. So X should have access permission to users who belongs to Asia group only. But say Y belongs to Asia and Europe groups. So Y should have access permission to users who belongs to:
Asia group only
Europe group only
Asia and Europe group only
Now, X should not access the data of Y as X does not belong to all the groups that Y belongs to. Similarly, say Z belongs to Asia, Europe and America. So, Z should access all the data of X, Y and Z but the reverse is not true.
My initial SQL:
select distinct(user) from user_role where role in
(select role from user_role where user=?);
Above query returns all the users sharing at least one common groups and not all common groups.
Can anybody please help me with a SQL example?
This can be done with much more less effort. The idea is left join roles on roles of particular user and then filter only those users for which all roles are found in that particular user's roles:
;with c as(select roleid from userroles where userid=100)
select r.userid from userroles r left join c on r.roleid = c.roleid
group by r.userid
having sum(case when c.roleid is null then 1 else 0 end) = 0
Fiddle http://sqlfiddle.com/#!6/bca579/7
Try this:
-- Create a CTE that will help us know the number of roles any user have.
;WITH CTE (UserId, RoleId, NumberOfRoles)
AS (
SELECT T1.UserId, RoleId, NumberOfRoles
FROM UsersToRoles T1 INNER JOIN
(
-- Derived table is needed so that we can have
-- the user, he's roleId and he's number of roles in the CTE
SELECT UserId, COUNT(RoleId) As NumberOfRoles
FROM UsersToRoles
GROUP BY UserId
) T2 ON(T1.UserId = T2.UserId)
)
-- We join the CTE with itself on the RoleId to get only users that have the same roles,
-- and on the NumberOfRoles to ensure that the users we get back have at least the nomber of roles as the user we testing.
SELECT DISTINCT T1.UserId
FROM CTE T1
INNER JOIN CTE T2 ON(T1.RoleId = T2.RoleId AND T1.NumberOfRoles <= T2.NumberOfRoles)
WHERE T2.UserId = #UserId
Play with it yourself in this sql fiddle
CTE, or Common Table Expressions is a concept introduced in Sql Server 2008. basically, you define a select statement that the rest of your sql can refer to as if it was a view.
In this case, you could have this CTE written as a view and it would give you the same result.

joining tables while keeping the Null values

I have two tables:
Users: ID, first_name, last_name
Networks: user_id, friend_id, status
I want to select all values from the users table but I want to display the status of specific user (say with id=2) while keeping the other ones as NULL. For instance:
If I have users:
? first_name last_name
------------------------
1 John Smith
2 Tom Summers
3 Amy Wilson
And in networks:
user_id friend_id status
------------------------------
2 1 friends
I want to do search for John Smith for all other users so I want to get:
id first_name last_name status
------------------------------------
2 Tom Summers friends
3 Amy Wilson NULL
I tried doing LEFT JOIN and then WHERE statement but it didn't work because it excluded the rows that have relations with other users but not this user.
I can do this using UNION statement but I was wondering if it's at all possible to do it without UNION.
You need to put your condition into the ON clause of the LEFT JOIN.
Select
u.first_name,
u.last_name,
n.status
From users u
Left Join networks n On ( ( n.user_id = 1 And n.friend_id = u.id )
Or ( n.friend_id = 1 And n.user_id = u.id )
Where u.id <> 1
This should return you all users (except for John Smith) and status friend if John Smith is either friend of this user, or this user is friend of John Smith.
You probably don't need a WHERE clause, and instead of that, put the condition into the "ON" clause that follows your "LEFT JOIN". That should fix your issues. Also, make sure that the main table is on the left side of the left join, otherwise, you should use a right join.
In addition to the (correct) replies above that such conditions should go in the ON clause, if you really want to put them in the WHERE clause for some reason, just add a condition that the value can be null.
WHERE (networks.friendid = 2 OR networks.friendid IS NULL)
From what you've described, it should be a case of joining a subset of networks to users.
select id, first_name, last_name, status
from users u
left join networks n on u.id = n.user_id
and n.friend_id = 1
where id <> 1;
The left join will keep rows from users that do not have a matching row in networks and adding the and n.friend_id = 1 limits when the 'friends' status is returned. Lastly, you may choose to exclude the row from users that you are running the query for.