Find records combining two joins - sql

I have three tables:
User, House and HouseEvent
A House has a foreign_key (user_id) to User and an HouseEvent has a foreign_key (house_id) to House
You can see the schema here: http://sqlfiddle.com/#!9/f08db/5 -
I know how I can get all the users which do not have a house:
SELECT * FROM User
LEFT OUTER JOIN House u ON u.user_id = user.id
WHERE u.user_id IS NULL
But how could I get in a single query, all the users who do not have a house and those users who have a house with at (least) a suspended event.
So, in the example, I would get Lee because he does not have a house, and I would also get John, because even though he has houses, one of its houses has an associated suspended event.

UNION the 2 queries together?
SELECT u.id, u.name FROM User u
JOIN House h ON h.user_id = u.id
JOIN HouseEvent he ON he.house_id = h.id AND he.name = 'suspended'
UNION
SELECT u.id, u.name FROM user u
LEFT JOIN house h ON h.user_id = u.id
WHERE h.user_id IS NULL
Fiddle

You can do that without using SUB QUERY, just use LEFT JOIN here is the query:
SELECT User.name FROM User
LEFT JOIN House h on h.user_id = user.id
LEFT JOIN HouseEvent e on h.user_id = e.house_id
WHERE h.user_id IS NULL OR e.name = 'suspended'
GROUP BY User.name
I modified your SQL Fiddle

SELECT * FROM User
Where id not in (SELECT user_id FROM House) OR
id in (select user_id from House h, HouseEvent he where he.house_id = h.id AND he.id = 2)
http://sqlfiddle.com/#!9/f08db/41

Related

Distinct Count with data from another table

I have 4 tables
All ID related things are ints and the rest are texts.
I want to count the number of albums the user is tagged at so if a user is tagged in album1 once album2 once and album3 once it will show 3 and if more in any of them it will still show 3.
I tried to do:
SELECT COUNT(DISTINCT ALBUM_ID) FROM PICTURES WHERE ID=(SELECT PICTURE_ID FROM TAGS WHERE USER_ID=userId);
But this returned 1 although it was supposed to return 3 and the same happened without DISTINCT.
How can I get the amount?
EDIT:
I want to check only one user(I have the user's ID and name)
You must join users with LEFT joins to tags and pictures and aggregate:
SELECT u.id, u.name, COUNT(DISTINCT p.album_id) counter
FROM users u
LEFT JOIN tags t ON t.user_id = u.id
LEFT JOIN pictures p ON p.id = t.picture_id
GROUP BY u.id, u.name
If you want the result for a specific user only:
SELECT u.id, u.name, COUNT(DISTINCT p.album_id) counter
FROM users u
LEFT JOIN tags t ON t.user_id = u.id
LEFT JOIN pictures p ON p.id = t.picture_id
WHERE u.id = ?
GROUP BY u.id, u.name -- you may omit this line, because SQLite allows it
Or with a correlated subquery:
SELECT u.id, u.name,
(
SELECT COUNT(DISTINCT p.album_id)
FROM tags t INNER JOIN pictures p
ON p.id = t.picture_id
WHERE t.user_id = u.id
) counter
FROM users u
WHERE u.id = ?
Replace ? with the id of the user that you want.

How to combine two SQL queries in one

I have tables user, participant and chat. I need to get all users in a specific chat and amount of chats that user in by chat name. For example current tables:
user chat participant
id|name id|name user_id|chat_id
1|Mike 1|School 1|1
2|John 2|Football 2|1
3|Sara 3|Gym 1|2
3|3
And by keyword "School" I want to get this
Mike|2
John|1
I have two queries to get first and second column in result but don't know how to combine it:
SELECT user.name FROM user
JOIN participant ON (user.id = participant.user_id)
JOIN chat ON (participant.chat_id = chat.id) WHERE chat.name = 'School';
That gives me
Mike
John
And
SELECT user.name, COUNT(*) FROM user
JOIN participant ON (user.id = participant.user_id) GROUP BY user.name;
returns
John|1
Mike|2
Sara|1
So how to combine it?
TRY THIS
SELECT p1.name, COUNT(p.user_id) totUser
FROM participant p
INNER JOIN (select u.id, u.name FROM participant p
inner JOIN chat c ON c.id = p.[chat_id]
INNER JOIN user u ON u.id = p.user_id
AND c.name = 'School') p1 ON p1.id = p.user_id
GROUP BY p1.name
using Subquery and joins :
select u.name,count(p.chat_id) as 'Count' from user u
inner join participant p on p.user_id = u.id
where
p.user_id in ( select user_id from participant pp inner join chat cc on cc.id = pp.chat_id where
cc.name = 'School' )
group by u.name
order by Count desc
Output :

How do I match against multiple conditions on a table join?

I have two tables:
users attributes
id|name id|name|user_id
------- ---------------
1 |foo 1 |bla | 1
2 |bar 1 |blub| 1
1 |bla | 2
How do I create a query gives users with both the "bla" AND "blub" attributes?
In this case it should only return the user "foo".
I know that the data is not normalized.
SELECT u.*, a.id, b.Id, a.name, b.name FROM users u
JOIN attributes a
ON a.User_id = u.User_id AND a.name = 'bla'
JOIN attributes b
ON u.User_Id = b.User_id AND b.name = 'blub'
Assuming an attribute association to a user is unique...
if you need 3 conditions to be true add the conditions to the in and adjust count up 1.
SELECT u.name
FROM users u
INNER JOIN attributes a on A.user_Id = u.id
WHERE a.name in ('bla','blub')
GROUP by u.name
HAVING count(*)=2
and if you don't have an unique association, or you need to join to another table you could always do...
SELECT u.name
FROM users u
INNER JOIN attributes a on A.user_Id = u.id
WHERE a.name in ('bla','blub')
GROUP by u.name
HAVING count(distinct A.name)=2
for a slight performance hit. but this allows you to join and get back additional fields which others have indicated was a detriment to this method.
This allows for scaling of the solution instead of incurring the cost of joining each time to different tables. In addition, if you needed thirty-something values to associate, you may run into restrictions on the number of allowed joins.
SELECT U.NAME
FROM USERS U
INNER JOIN
ATTRIBUTES A1
ON U.ID = A1.USER_ID
INNER JOIN
ATTRIBUTES A2
ON U.ID = A2.USER_ID
WHERE A1.NAME = 'bla'
AND A2.NAME = 'blub'
You can use the INTERSECT operator
SELECT
u.id
,u.name
FROM users AS u
INNER JOIN attributes AS a
ON u.id = a.user_id
WHERE a.name = 'bla'
INTERSECT
SELECT
u.id
,u.name
FROM users AS u
INNER JOIN attributes AS a
ON u.id = a.user_id
WHERE a.name = 'blub'
;
Here is a demo on SQL Fiddle: http://sqlfiddle.com/#!6/68986/5
More info on SET operations in SQL: http://en.wikipedia.org/wiki/Set_operations_(SQL)
SELECT u.name
FROM attributes a
JOIN users u
ON u.id = a.user_id
WHERE a.name IN ('bla','bulb')

List all friends for a userid SQL query

I have 2 tables a user table (user_id, fname, lname, dob, etc) and a are_friends table
(userA_id, userB_id). I have been trying to do this query for a while now, I need it to list all friends for a user_id.
What I have got so far,
SELECT
U.user_id,
U.fname,
U.lname
FROM are_friends A, user U
WHERE
A.user_id = U.user_id
AND (
A.user_id = 1
OR A.user_id IN (SELECT userB_id FROM are_friends WHERE userA_id = 1)
);
Any help will be much appreciated.
Try using an INNER JOIN like this:
SELECT u2.user_id, u2.fname, u2.lname
FROM user u
INNER JOIN are_friends f ON f.userA_id = u.user_id
INNER JOIN user u2 ON u2.user_id = f.userB_id
WHERE u.user_id = 1
You can change the WHERE clause to specifically get the friends of another user id.

Help me with this SQL Query

3 tables are defined as follows:
Users
User_ID INT
First_Name VARCHAR
Last_Name VARCHAR
Email VARCHAR
Roles
Role_ID INT
Role_Name VARCHAR
Access_Level INT
Roles_Users
User_ID INT
Role_ID INT
Roles_Users is a many-to-many linking table between Users and Roles. I want to pull back the following info:
First_Name, Last_Name, Email, Role_Name
What I have so far is:
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
The tricky part (at least for me) is that I want to only pull back the Role_Name with the MIN(Access_Level) for that particular user. So basically the record set I want to pull will have each user only listed once with their lowest access level role name.
I'm sure this is pretty simple but it's just stumping me right now.
Thanks
YOu can use a CTE (Common Table Expression) in conjunction with the ROW_NUMBER windowing function like this:
;WITH MinAccessData AS
(
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name,
ROW_NUMBER() OVER(PARTITION BY U.User_ID ORDER BY R.Access_Level) AS RowNum
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
)
SELECT *
FROM MinAccessData
WHERE RowNum = 1
The CTE "partitions" your data by User_ID, e.g. each user gets a "partition". Inside that partition, the roles are ordered by Access_level and the smallest is the first one - so it gets RowNum = 1 - for each user.
So you then select from that CTE all those entries where the RowNum = 1 - this delivers all the entries for each user which have the smallest Access_Level value.
Alternatives without a CTE (just to have another tool in your box)
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
INNER JOIN Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
INNER JOIN (SELECT
MIN(r.Access_Level) access_level,
ru.UserID,
FROM Roles r
INNER JOIN Roles_Users ru
ON r.Role_ID = ru.Role_ID
GROUP BY UserID
) minAccess
ON ru.UserId = minAccess.UserId
and ru.
ON r.access_level = minAccess .access_level
You can also use a CROSS APPLY
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Name AS Role_Name
FROM Users U
CROSS APPLY (SELECT TOP 1
Role_Name
FROM Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
WHERE u.user_id = ru.user_id
ORDER BY
Access_Level desc
)
Correlated subquery:
SELECT
U.First_Name,
U.Last_Name,
U.Email,
(
select top 1 R.RName from Roles_Users RU ON U.User_ID = RU.User_ID
INNER JOIN Roles R ON RU.Role_ID = R.Role_ID
ORDER BY R.Access_Level
)
AS Role_Name
FROM Users U
In my opinion using a subquery is easier to read and write. In this code the correlated subquery will execute 1x per row returned. I like #Conrad's inner join solution, easiest and probably the most performant, and probably what i would use, just giving this as another option.
Not tested, but it goes something like this
SELECT
U.First_Name,
U.Last_Name,
U.Email,
R.Role_Name
FROM Users U
JOIN Roles_Users RU ON U.User_ID = RU.User_ID
JOIN (
SELECT ROLE_ID, MIN(ROLE_NAME) ROLE_NAME
FROM ROLES
GROUP BY ROLE_ID
HAVING ACCESS_LEVEL = MIN(ACCESS_LEVEL)
) R ON RU.Role_ID = R.Role_ID
SELECT Users.*, Roles.*
FROM
Users
JOIN Roles_Users ON Users.User_ID = Roles_Users.User_ID
JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID
WHERE
Access_Level = (
SELECT MIN(Access_Level)
FROM
Roles_Users
JOIN Roles ON Roles.Role_ID = Roles_Users.Role_ID
WHERE Users.User_ID = Roles_Users.User_ID
)
NOTE: This will not list users without any role.