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

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')

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.

Group by and aggregate by multiple columns

Example tables
taccount
tuser
tproject
What I want to achieve:
accountName count(u.id) count(p.id)
-----------------------------------
Account A 1 1
Account B 1 1
Account C 2 3
In other words I want a single query to join these tables together and count user's and project's per account
I tried:
SELECT
a.name as "accountName",
count(u.name),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY u.name, a.name, p.id
But it's not grouping by account. It's giving me the following result
Any advice?
You can try below
SELECT
a.name as "accountName",
count(distinct u.name),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY a.name
When you do Aggregate Function and If there are Column are not do Aggregate you must put in your Group By, because Aggregate functions perform a calculation on a set of rows and return a single row.
SELECT
a.name as "accountName",
count(distinct u.name),
count(p.id)
FROM
"taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY
a.name
So you need just Group By your column "accountName"
change your group by column name
SELECT
a.name as "accountName",
count(distinct u.account_id),
count(p.id)
FROM "taccount" a
INNER JOIN "tuser" u ON u.account_id = a.id
INNER JOIN "tproject" p ON p.admin_id = u.id
GROUP BY a.name
this will work:
select a.name,count(u.id),count(p.id) from
taccount a,tuser b, tproject where
a.id=b.account_id and
b.id=c.admin_id
group by a.name;

SQL: a better way to include & exclude on the same field than, both, WHERE and NOT EXISTS?

I have this query which needs to, both, include and exclude on the same role_id field, so I'm calling the same table for both the NOT EXISTS() subquery and the INNER JOIN.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
WHERE ur.role_id = 3
AND NOT EXISTS (SELECT 1
FROM users_roles
WHERE role_id = 4
AND user_id= u.id)
ORDER BY c.country, c.postal
This approach works, but seems kinda clunky, so I'm wondering whether there's a more standard (performant) approach that I'm not aware of?
I recommend running performance analysis on both queries, but you can also do this with a left outer join.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
LEFT OUTER JOIN users_roles ur2
ON (u.id = ur2.user_id)
AND role_id = 4
WHERE ur.role_id = 3
AND ur2.user_id IS NULL
ORDER BY c.country, c.postal
This will end up with NULL in place of values for the ur2 table if no matching rows in the table are found; if you assert in the WHERE clause that the value is NULL, it will exclude anything that did have a match.
This one makes just one join, it uses grouping, sums the role_ids and checks on the aggregate result, the execution time on sqlfiddle is 6ms vs 24ms
SELECT
u.fname
,u.lname
,c.country
,c.postal
,sum(ur.role_id)
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
INNER JOIN users_roles ur
ON (u.id = ur.user_id)
and role_id in(3,4)
group BY u.fname, u.lname, c.country, c.postal
having sum(ur.role_id) = 3
go
You could join user_roles just once and it might help performance a tiny bit. I cannot make up my mind right now which query I like more.
SELECT
u.fname
,u.lname
,c.country
,c.postal
FROM [user] u
INNER JOIN company c
ON (u.company_id = c.id)
CROSS APPLY (
SELECT SUM (IIF(role_id = 3, 1, 0)) AS role3Count, SUM (IIF(role_id = 4, 1, 0)) AS role4Count
FROM users_roles ur
WHERE role_id BETWEEN 3 AND 4 AND u.id = ur.user_id
) ur
WHERE role3Count > 0 AND role4Count = 0
ORDER BY c.country, c.postal
This has the chance of being slightly faster because a single index seek on users_roles with range role_id BETWEEN 3 AND 4 is enough to retrieve the roles. In your query, there were two seeks. On the other hand this query does some aggregates and computations. You'd need to measure.
I'm not convinced this is a nicer query. It is more complex than yours. I'll propose it anyway. You decide.

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 with MySQL Query?

I have two tables rooms and users. I want to get only rooms.room_id, users.user_name with user_id = 1. I can get the result of all users with following sql...
select rooms.room_id,
rooms.user_id,
users.user_name
from rooms
LEFT JOIN users ON rooms.user_id = users.user_id
When I do like this to filter the result with user_id = 1 ... I got error.
select rooms.room_id,
rooms.user_id,
users.user_name
from rooms where rooms.user_id = 1
LEFT JOIN users ON rooms.user_id = users.user_id
What should I do?
ANSI-92 JOIN syntax (when you see LEFT JOIN ...) dictates that the WHERE clause comes after the JOIN(s):
SELECT r.room_id,
r.user_id,
u.user_name
FROM ROOMS r
LEFT JOIN users ON u.user_id = r.user_id
WHERE r.user_id = 1
You were close.
Write the query as:
select rooms.room_id,
rooms.user_id,
users.user_name
from rooms
LEFT JOIN users ON rooms.user_id = users.user_id
WHERE roows.user_id = 1
Try:
select rooms.room_id,
rooms.user_id,
users.user_name
from rooms
LEFT JOIN users ON rooms.user_id = users.user_id
WHERE rooms.user_id = 1
The syntax of a simple SQL SELECT query is:
SELECT [a list of fields]
FROM [a single table name maybe with an alias, or a join of tables]
WHERE [a filter, applied over some fields of the tables in the FROM clause]
You could read an introductory tutorial here.
It helps to state what error you get.
I would guess that the problem is that the where clause needs to be after the joins
select rooms.room_id,
rooms.user_id,
users.user_name
from rooms
LEFT JOIN users ON rooms.user_id = users.user_id
where rooms.user_id = 1