Selecting multiple tables - sql

This is a hypothetical (maybe even a naive) example without using joins. Consider you have a users table and you have to list all users assigned to a particular role. A SQL statement could be expressed as:
SELECT u.username
FROM users u, roles r
WHERE (u.roleid = r.id AND r.id = 5);
If you assume that a relation exists, can the following mean the same thing or a join of some sort is required?:
SELECT u.username
FROM users u, roles r
WHERE r.id = 5;

The second query is an example of an implicit cross join (aka Cartesian join) - every record from users will be joined to every record from roles with id=5, since all these combinations will have the where clause evaluate as true.

A join will be required to have correct data returned
SELECT u.username
FROM users u
JOIN roles r
ON u.roleid = r.id
WHERE r.id = 5;
I think is better to use explicit join with ON to dtermine which columns have relationship rather than using realtionship in WHERE clause

You need two columns of the same type one for each table to JOIN .You need the join-predicate ON u.roleid = r.id to get the correct data .
Inner join creates a new result table by combining column values of two tables (A and B) based upon the join-predicate. The query compares each row of A with each row of B to find all pairs of rows which satisfy the join-predicate. When the join-predicate is satisfied, column values for each matched pair of rows of A and B are combined into a result row. The result of the join can be defined as the outcome of first taking the Cartesian product (or Cross join) of all records in the tables (combining every record in table A with every record in table B)—then return all records which satisfy the join predicate.

No. In the second version you have no relationship between the tables. The , operator in the from clause means cross join. The second example will either return all users at least once (depending on the number of matched in the second table). Or it will return no rows (if there are no matches in the second table).
If the second example were:
SELECT u.username
FROM users u, roles r
WHERE r.id = 5 and u.id = 5
Then they would mean the same thing. The clearer and better way to write this is:
SELECT u.username
FROM users u cross join roles r
WHERE r.id = 5 and u.id = 5
Or using proper inner join syntax:
SELECT u.username
FROM users u join
roles r
on r.id = u.id
WHERE r.id = 5 /* this could also be in the `on` clause */

Related

How create SQL pagination difficult join query with duplicate data?

I have several tables in the database.
Users, profiles and user roles.
The relationship of profiles and users one to one.
The relationship of roles and users many to many.
To select all users, I send the following request:
SELECT A.role_id, A.role_name, A.user_id,B.user_username, B.user_password, B.profile_color_text, B.profile_color_menu, B.profile_color_bg FROM
(SELECT Roles.role_id, Roles.role_name, UserRoles.user_id
FROM Roles INNER JOIN UserRoles ON Roles.role_id = UserRoles.role_id) AS A
LEFT JOIN
(SELECT Users.user_username, Users.user_password, Profiles.profile_color_text, Profiles.profile_color_menu, Profiles.profile_color_bg, Profiles.profile_id
FROM Users INNER JOIN Profiles ON Users.user_id = Profiles.profile_id) AS B
ON A.user_id = B.profile_id;
The question is how do I select a pagination?
I would get the 10 users first, then perform the joins. Two reasons for this:
Since you don't want specifically 10 results but just the results of 10 users, which could contain any number of rows, you can't get all the data then limit it, otherwise you could be getting 10 rows containing data for 5 users;
Even if point 1 were irrelevant because there was always a 1-1 relationship, and especially if the number of results is small like 10, it's faster to get those results first and then join on that smaller "table", rather than doing all your joins on all the data and then limiting it.
.
SELECT
u.user_id,
u.user_username,
u.user_password,
r.role_id,
r.role_name,
p.profile_id,
p.profile_color_text,
p.profile_color_menu,
p.profile_color_bg
FROM (
SELECT user_id, user_username, user_password
FROM users
ORDER BY ???
OFFSET 10
LIMIT 10
) AS u
LEFT JOIN profiles AS p
ON u.user_id = p.profile_id
LEFT JOIN userroles AS ur
ON u.user_id = ur.user_id
LEFT JOIN roles AS r
ON ur.role_id = r.role_id
I assume you'll want some order, so I've put an ORDER BY in there - to be completed.
OFFSET added to get the second page of results; first page wouldn't require it, or would be OFFSET 0. Then a LIMIT of course to limit the page size.
I've also restructured the joins in a way that made more sense to me.

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

Create table using SQL combining column data from two or more other tables

I was trying to figure out how to insert data into a new join table using the column data from two other tables. This seemed at first simple, but wasn't immediately obvious now could I find the solution anyplace.
Example:
I have tProfiles table:
select prof_id from tProfiles
prof_id
1
2
3
4
5
...
and table containing roles tRole
select roleid from tRole
roleid
1
2
3
4
5
6
7
...
and new tRoleByProfile needs all roles from tRole and all prof_id from tProfiles using an insert like this:
insert into tRoleByProfile(RoleId, ProfileId)
Now I'm doing several joins here to get just the profiles with active users in them like this.
insert into tRoleByProfile(RoleId, ProfileId)
select distinct r.RoleId, p.prof_id from tRole r, tProfiles p
inner join tUserRoles ur on p.PROF_ID = ur.prof_id
inner join tUsers u on u.user_id = ur.user_id
inner join tUserLogins ul on ul.user_id = u.user_id
where u.user_inactive_flg = 'N'
notice in the select I must use distinct on the Full Join. Without using Distinct would create lot's of duplicate data. See http://www.informit.com/articles/article.aspx?p=30875&seqNum=5 for more on Full Join.
Now my new join table has the exact data needed from the combined two tables.
You possibly could have done...
INSERT INTO tRoleByProfile
SELECT r.RoleId, p.prof_id
FROM tRole r
JOIN tProfiles p;
Joining two tables without any condition will yield a result set that incorporates every combination of the records from both tables. In other words, every profile would receive every role.
To filter what rows are included in the Cartesian join (that is a join with no relationship), filter either side of the JOIN above by making them a separate select, as in...
INSERT INTO tRoleByProfile
SELECT r.RoleId, p.prof_id
FROM tRole r
JOIN (SELECT prof_id
FROM tProfiles
WHERE blah...)

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.

How to make this join with a TSQL query?

I have a table called USERS that has a foreign key to the table GROUPS (a user can pertain to one or none GROUPS). The table USERS also contains a column ISDELETED (a char column with T or F).
I need a query to retrieve all the GROUPS and all the USERS that are not deleted, if all the users in a GROUP are deleted or no users are defined I need the query to return NULL for that GROUP.
I tried with the following query:
SELECT GROUPS.*, USERS.*
FROM GROUPS INNER JOIN
USERS ON GROUPS.ID = USERS.GROUPID
WHERE USERS.ISDELETED = 'F'
But this query does not returns the groups that are empty. SQL and me are not the best friends in world, some help will be great, thanks.
If you want all the groups, regardless of a match in the users table, you should use a left outer join:
SELECT GROUPS.*, USERS.*
FROM GROUPS
LEFT OUTER JOIN
USERS
ON GROUPS.ID = USERS.GROUPID AND USERS.ISDELETED = 'F'
You should just need to do a left outer join -
SELECT GROUPS.*, USERS.*
FROM GROUPS LEFT OUTER JOIN
USERS ON GROUPS.ID = USERS.GROUPID
WHERE USERS.ISDELETED = 'F'
Here's a reference I like to use to remind myself of the differences in sql joins.
You need to use the LEFT OUTER JOIN operator instead of the INNER JOIN.