How to do joins with conditions? - sql

I always struggle with joins within Access. Can someone guide me?
4 tables.
Contest (id, user_id, pageviews)
Users (id, role_name, location)
Roles (id, role_name, type1, type2, type3)
Locations (id, location_name, city, state)
Regarding the Roles table -- type1, type2, type3 will have a Y if role_name is this type. So if "Regular" for role_name would have a Y within type1, "Moderator" for role-name would have a Y within type2, "Admin" for role_name would have a Y within type3. I didn't design this database.
So what I'm trying to do. I want to output the following: user_id, pageviews, role_name, city, state.
I'm selecting the user_id and pageviews from Contest. I then need to get the role_name of this user, so I need to join the Users table to the Contest table, right?
From there, I need to also select the location information from the Locations table -- I assume I just join on Locations.location_name = Users.location?
Here is the tricky part. I only want to output if type1, within the Roles table, is Y.
I'm lost!

As far as I can see, this is a query that can be built in the query design window, because you do not seem to need left joins or any other modifications, so:
SELECT Contest.user_id,
Contest.pageviews,
Roles.role_name,
Locations.city,
Locations.state
FROM ((Contest
INNER JOIN Users
ON Contest.user_id = Users.id)
INNER JOIN Roles
ON Users.role_name = Roles.role_name)
INNER JOIN Locations
ON Users.location = Locations.location_name
WHERE Roles.type1="Y"
Lots of parentheses :)

select *
from users u
inner join contest c on u.id = c.user_id and
inner join locations l on l.id = u.location and
inner join roles r on r.role_name = u.role_name
where r.type1 = 'Y'
This is assuming that location in users refers to the location id, if it is location name then it has to be joined to that column in locations table.
EDIT: The answer accepted is better, I did not consider that access needs parentheses.

Can you show what query you are currently using? Can't you just join on role_name and just ignore the type1, type2, type3? I am assuming there are just those 3 role_names available.

I know you didn't design it, but can you change the structure? Sometimes it's better to move to a sturdy foundation rather than living in the house that is about to fall on your head.
SELECT u.user_id, c.pageviews,
IIF(r.role_Name = "Moderator", r.type1 = Y,
IIF(r.role_name="Admin", r.type2="Y", r.type3="Y")),
l.location_name FROM users as u
INNER JOIN roles as r On (u.role_name = r.role_name)
INNER JOIN contest as c On (c.user_id = u.Id)
INNER JOIN locations as l On (u.location = l.location_name or l.id)
depending on whether the location in your user table is an id or the actual name reference.

I think I need to see some sample data....I do not understand the relationship between Users and Roles because there is a field role_name within the Users table, and how does that relate the the Roles Table?
EDIT NOTE Now using SQL Explicit Join Best Practice
SELECT
C.user_id
, C.pageviews
, U.role_name
, L.city
, L.state
FROM
Contest C
INNER JOIN Users U ON C.user_id = U.id
INNER JOIN Locations L ON U.location = L.id
INNER JOIN Roles R ON U.role_name = R.role_name
WHERE
R.type1='Y'

Related

Performance of JOIN operation

Lets suppose I have two huge tables
users(user_id, name, country)
location(id, user_id, country, city)
If i want to fetch for particular country
1.
select * from users u
inner join location l on u.user_id = l.user_id
where l.country = 'heaven'
2.
select * from users u
inner join location l on u.user_id = l.user_id
where u.country = 'heaven'
3.
select * from users u
inner join location l on u.user_id = l.user_id
where u.country = 'heaven' and l.country = 'heaven'
Which of the three would be better approach?
and suppose the result of filtering data by country would be
users table has 1000 record with country='heaven'
location table has 2 record with country='heaven'
What will be performance now?
Your queries would only return the same results, if one of the following is true:
location records did not exist for all users.
users has duplicate user_ids.
It is unclear which. And, having country in both tables suggests that something is wrong with your data model.
That said, if you need both filtering by the join and filtering on the country, I would recommend:
select *
from users u join
locations l
on u.user_id = l.user_id
where l.country = 'heaven';
And you want an index on locations(country, user_id). This should find the two records in locations and look up the corresponding values in users. That seems like the fastest way to do what you want.

1052: Column 'user_id' in field list is ambiguous when

I have 3 tables which share a user id, now I want to get data from all 3 but I don't know where I'm doing it wrong in this query
SELECT user_id, first_name, image_id, description, gender
FROM users a
JOIN user_services b ON b.user_id = a.user_id
JOIN user_timeframe c ON c.user_id = a.user_id
Two key best practices:
Always qualify all column references.
Use table aliases that are meaningful rather than arbitrary letters.
So:
SELECT u.user_id, u.first_name, ?.image_id, ?.description,
u.gender
FROM users u JOIN
user_services us
ON us.user_id = u.user_id JOIN
user_timeframe ut
ON ut.user_id = u.user_id;
The ? is because it is not clear what table those columns come from. (The other columns are guesses so they might not be right either.)
The preferred way should be explicit aliasing. There is another way to fix this query by using JOIN ... USING syntax:
SELECT user_id, first_name, image_id, description, gender
FROM users a
JOIN user_services b USING(user_id)
JOIN user_timeframe c USING(user_id);
db<>fiddle demo

SQL Where on different table

SELECT * FROM student_mentor sm INNER JOIN users u
ON sm.student_id = u.user_id
WHERE sm.teacher_id = $teacher_id
Teacher_id being the session id,
I want to see all the students that have the same mentor.
Right now if I run this I just see all of the students twice, maybe one of you knows why?
My db scheme
You are not specifying on which columns you want to do the join, so you're getting a cross reference where all records are joined to all records.
You should do something like (not sure about your column names):
SELECT * FROM student_mentor sm INNER JOIN users u
ON sm.student_id = u.user_id
WHERE sm.teacher_id = $teacher_id

Is it true that JOINS can be used everywhere to replace Subqueries in SQL

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.

Using binary logic in PostgreSQL JOIN queries

I've got 3 tables that look vaguely like this:
Users
----------
UserID
Name
Phone
User Groups
-----------
GroupID
Activity
Group Membership
---------------
UserID
GroupID
Independent Actives
-------------------
UserID
Activity
The idea is that a user can perform an activity either as part of a group or on their own. What I want to do is return all the people that partake in a certain activity. What I have been able to write so far lets me return all the users which are in groups that undertake that activity. What I want to add to this is the ability to see the people that do the activity independently. This is what I have so far:
SELECT
users.name, users.phone, user_groups.activity
FROM users
INNER JOIN group_membership ON group_membership.userID = users.userID
INNER JOIN user_groups ON user_groups.groupID = group_membership.groupID
WHERE user_groups.activity = 'Knitting';
The above bit works fine and it shows all of the users that are part of groups that do knitting, but I also want it to show all the users that are knitting independently. This is what I have attempted to add:
SELECT
users.name, users.phone, user_groups.activity
FROM users
INNER JOIN group_membership ON group_membership.userID = users.userID
INNER JOIN user_groups ON user_groups.groupID = group_membership.groupID
INNER JOIN independent_activity ON independent_activity.userID = users.userID
WHERE user_groups.activity = 'Knitting' OR independent_activity.activity = 'Knitting';
The problem here is the syntax, I understand the algorithm that I'm trying to do but I don't know how to transfer it into sql and so any help is appreciated.
You could use a UNION in this case
SELECT users.NAME
,users.phone
,user_groups.activity
FROM users
INNER JOIN group_membership ON group_membership.userID = users.userID
INNER JOIN user_groups ON user_groups.groupID = group_membership.groupID
WHERE user_groups.activity = 'Knitting'
UNION
SELECT users.NAME
,users.phone
,independent_activity.activity
FROM users
INNER JOIN independent_activity ON independent_activity.userID = users.userID
WHERE independent_activity.activity = 'Knitting';
You also might want to lookup the differences between a UNION and a UNION ALL and decide the one that suites your requirement.
You've got a working answer from SoulTrain. However, for completeness sake I'd like to mention that you don't have to join all those tables. (You could use outer joins here and remove duplicate matches with DISTINCT, but that's not necessary. You don't have to query the users table twice either. And you don't need UNION for doing the distinct job.)
Simply select from the one table you want to display data from, i.e. the users table, and then use EXISTS or IN to get only those users that are either in one set or another.
select name, phone
from users
where userid in
(
select userid
from independent_actives
where activity = 'Knitting'
)
or userid
(
select userid
from group_membership
where groupid in (select groupid from user_groups where activity = 'Knitting')
)