I have two tables, GAMES and USERS set up with the following fields:
tblGAMES-->
GAME_ID (int) primary key,
P1_ID (int),
P2_ID (int),
P3_ID (int),
P4_ID (int)
tblUSERS-->
USER_ID (int) primary key,
FIRST_NAME (text),
LAST_NAME (text)
I am querying my database to print out the details of every game row. Example:
GAME_ID:1, P1:1, P2:2, P3:3, P4:4
but instead of printing out the id which is stored in tblGAMES I would like to print out the first name of each player as it appears in the corresponding row in tblUSERS. Is this doable using a single query as opposed to multiple queries?
end result-->GAME_ID:1, P1:David, P2:Paul, P3:John, P4:Bobby
You'll need to join the users table 4 times...
select g.GAME_ID,
u1.FIRST_NAME as ulname,
u2.FIRST_NAME as u2name,
u3.FIRST_NAME as u3name,
u4.FIRST_NAME as u4name
from tblGAMES as g
left join tblUSERS as u1 on g.P1_ID =u1.USER_ID
left join tblUSERS as u2 on g.P2_ID =u2.USER_ID
left join tblUSERS as u3 on g.P3_ID =u3.USER_ID
left join tblUSERS as u4 on g.P4_ID =u4.USER_ID
(untested)
In the query you're going to want to reference tblUSERS once per JOIN. Something like this:
SELECT
tblGAMES.GAME_ID,
tblUSER1.FIRST_NAME AS P1,
tblUSER2.FIRST_NAME AS P2,
tblUSER3.FIRST_NAME AS P3,
tblUSER4.FIRST_NAME AS P4
FROM tblGAMES
INNER JOIN tblUSERS AS tblUSER1 ON tblGAMES.P1_ID = tblUSER1.USER_ID
INNER JOIN tblUSERS AS tblUSER2 ON tblGAMES.P2_ID = tblUSER2.USER_ID
INNER JOIN tblUSERS AS tblUSER3 ON tblGAMES.P3_ID = tblUSER3.USER_ID
INNER JOIN tblUSERS AS tblUSER4 ON tblGAMES.P4_ID = tblUSER4.USER_ID
Note that my syntax may need a little tweaking, I haven't written enough MySQL recently to free-hand it 100% reliably. But you get the idea. Each join gets added to the overall product independently.
You may also change the JOIN types depending on how required each player is. For example, if some games are 2-player then you'd want these to be LEFT OUTER JOINs instead so as to better handle null values, etc.
Related
I have a products table where I include 3 columns, created_user_id, updated_user_id and in_charge_user_id, all of which are related to my user table, where I store the id and name of the users.
I want to build an efficient query to obtain the names of the corresponding user_id's.
The query that I build so far is the following:
SELECT products.*,
(SELECT name FROM user WHERE user_id = products.created_user_id) as created_user,
(SELECT name FROM user WHERE user_id = products.updated_user_id) as updated_user,
(SELECT name FROM user WHERE user_id = products.in_charge_user_id) as in_charge_user
FROM products
The problem with this query is that if I have 30,000 records, I am executing 3 more queries per row.
What would be a more efficient way of achieving this? I am using mysql.
For each type of user id (created, updated, in_charge) you would JOIN the users table once:
SELECT
products.*,
u1.username AS created_username,
u2.username AS updated_username,,
u3.username AS in_charge_username,
FROM products
JOIN user u1 ON products.created_user_id = u1.user_id
JOIN user u2 ON products.updated_user_id = u2.user_id
LEFT JOIN user u3 ON products.in_charge_user_id = u3.user_id
This is the best practice method to obtain the data.
It is similiar to your query with sub-selects but a more modern approach which I think the database can optimize and utilize better.
Important:
You need foreign key index on all the user_id fields in both tables!
Then the query will be very fast no matter how many rows are in the table. This requires an engine which supports foreign keys, like InnoDB.
LEFT JOIN or INNER JOIN ?
As the other answers suggest a LEFT JOIN, I would not do a left join.
If you have an user id in the products table, there MUST be a linked user_id in the user table, except for the in_charge_user which is only present some times. If not, the data would be semantically corrupt. The foreign keys assure that you always have a linked user_id and a user_id can only be deleted when there is no linked product left.
JOIN is equivalent to INNER JOIN.
You can use LEFT JOIN instead of subselects.
Your query should be like:
SELECT
P.*,
[CU].[name],
[UU].[name],
[CU].[name]
FROM products AS [P]
LEFT JOIN user AS [CU] ON [CU].[user_id] = [P].[created_user_id]
LEFT JOIN user AS [UU] ON [UU].[user_id] = [P].[updated_user_id]
LEFT JOIN user AS [CU] ON [CU].[user_id] = [P].[in_charge_user_id]
First, your query should be fine. You only need an index on user(user_id) or better yet user(user_id, name) for performance. I imagine that the first exists.
Second, you can write this using LEFT JOIN:
SELECT p.*, uc.name as created_user,
uu.name as updated_user, uin.name as in_charge_user
FROM products p LEFT JOIN
user uc
ON uc.user_id = p.created_user_id LEFT JOIN
user uu
ON uu.user_id = p.updated_user_id LEFT JOIN
user uin
ON uin.user_id = p.in_charge_user_id;
With one of the above indexes, the two methods should have very similar performance.
Also note the use of LEFT JOIN. This handles the case where one or more of the user ids is missing.
Try this below query
SELECT products.*, c.name as created_user,u.name as updated_user,i.name as in_charge_user
FROM products left join user c on(products.created_user_id=c.user_id ) left join user u on(products.updated_user_id=u.user_id ) left join user u on(products.in_charge_user_id=i.user_id )
Also as Gordon Linoff mentioned create index on user table will fetch your data faster.
So I always have trouble with this type of query. I have 2 tables and I need to combine them as 3 but I need the query to see if a record does not exist in one connection.
The design is this:
Table 1 connects to Table 2 looking for existing records. Table 1 then needs to connect to table 2 again on a different record id but looking for not existing.
My current code looks like this:
select p.ProfileId, p.OrgInd, p.OrgName, p1.OrgInd, p1.ProfileId
from profile as p inner join
ProfileRelations as pa on p.ProfileId = pa.ProfileID inner join
profile as p1 on p1.ProfileId = pa.RelProfileID
I need profile(p) to connect to PRofileRelations(pa) as it states but I then need to connect ProfileRelations(pa) back on Profile(p1) where p1.profileid does not exist in ProfileRelations(pa) looking specifically at the column RelProfileID.
This is an association table to look at related records. The Profile table holds all of my customers and I can relate them as connections. What I am looking for is show me any profile that has no related records. The code given shows me those who do have related records, I just now want to see those who have none.
Thanks.
Jayson
Visual understanding of Joins: https://blog.codinghorror.com/a-visual-explanation-of-sql-joins/
Use a left join:
All profiles without a related relation. In this case we join the tables but we know the values for p1 and pa will be null becuase the left join will include all profiles and only keep those with a related record in the relations table.
SELECT p.ProfileId, p.OrgInd, p.OrgName, p1.OrgInd, p1.ProfileId
FROM profile as p
LEFT join ProfileRelations as pa
on p.ProfileId = pa.ProfileID
LEFT join profile as p1
on p1.ProfileId = pa.RelProfileID
WHERE PA.ProfileID is null
In the above P1 values will always be null because of the where clause.
OR uses not exists and correlated subquery
All profiles w/o a related profile relation. since we know there is no relation there is no data we need from the other tables; thus we can exclude them from the select simplifying the query and gaining performance.
SELECT p.ProfileId, p.OrgInd, p.OrgName
FROM profile as p
WHERE NOT EXISTS (SELECT *
FROM ProfileRelations as pa
WHERE p.ProfileId = pa.ProfileID)
In this we don't select the P1 table at all since there is no relation record.
First of all: You want to select profiles. So do this; select from the profiles table. You want to apply criteria (missing relations); so put this in the WHERE clause. Criteria belongs in the WHERE clause. There is no need to join.
All profiles that have no related profiles:
select *
from profile
where ProfileId not in (select ProfileID from ProfileRelations)
and ProfileId not in (select RelProfileID from ProfileRelations);
or
select *
from profile p
where not exists
(
select *
from ProfileRelations pr
where p.ProfileId in (pr.ProfileId, pr.RelProfileID)
);
All profile pairs that are not related:
select *
from profile p1
join profile p2 on p2.ProfileID <> p1.ProfileID
where not exists
(
select *
from ProfileRelations pr
where (pr.ProfileId = p1.ProfileId and pr.RelProfileID = p2.ProfileId)
or (pr.ProfileId = p2.ProfileId and pr.RelProfileID = p1.ProfileId)
);
Per your problem statement, please try this:
select p.ProfileId, p.OrgInd, p.OrgName, p1.OrgInd, p1.ProfileId
from profile as p
inner join
ProfileRelations as pa
on p.ProfileId = pa.ProfileID
where pa.RelProfileID not in (select ProfileId from profile);
So, the two tables in question:
userinfo: id(PK), users_id(FK to users table), name, surname
doctorpatient: id(PK), doctor_id(FK to users table), patient_id(FK to users table)
The idea is each doctor is assigned a few patients via the doctorpatient table. What I want to do is return an array of arrays, where each of the inner arrays contains this:
users_id(doctor), name(doctor), surname(doctor), users_id(patient), name(patient), surname(patient)
Can this even be done using purely SQL? I tried this:
SELECT userinfo.users_id,
userinfo.name,
userinfo.surname,
u2.users_id,
u2.name,
u2.surname
FROM doctorpatient
RIGHT OUTER JOIN userinfo
ON doctorpatient.doctor_id = userinfo.users_id
LEFT OUTER JOIN userinfo AS u2
ON doctorpatient.patient_id = u2.users_id
but no matter what combination of joins I try, it never comes out right. I tried getting the data in three separate queries and then somehow get the result I need using PHP, but I got nowhere with that.
Edit: What I want is this:
array(
subarray1(patient_id1,
patient_name1,
patient_surname1,
doctor_id1,
doctor_name1,
doctor_surname1)
subarray2(patient_id2,
patient_name2,
patient_surname2,
doctor_id1,
doctor_name1,
doctor_surname1)
etc...
where one doctor can have multiple patients. What my query gets me looks something like this:
array(
subarray1(patient_id1,
patient_name1,
patient_surname1,
)
subarray2(patient_id2,
patient_name2,
patient_surname2,
)
etc...
But most of the data is null.
I think a simple JOIN may be sufficient. The OUTER JOINs appear to be causing the null values because it tries to treat the doctors as patients.
SELECT u1.users_id AS doctor_id,
u1.name AS doctor_name,
u1.surname AS doctor_surname,
u2.users_id AS patient_id,
u2.name AS patient_name,
u2.surname AS patient_surname
FROM doctorpatient AS d JOIN userinfo AS u1 ON d.doctor_id = u1.users_id
JOIN userinfo AS u2 ON d.patient_id = u2.users_id
Try this:
SELECT
u.id as user_id,
u.name as user_name
u.surname as user_usrname
d.id as doc_id,
d.name as doc_name,
d.surname as doc_surname
FROM doctorpatient as dp
LEFT JOIN userinfo as u ON (dp.pacient_id = u.id)
LEFT JOIN userinfo as d ON (dp.doctor_id = d.id)
I have table called users and table called events.
Every user sets is own preferred "area codes".
Every event is set to some area codes, and this information is saved in a table:
events_areas:
area_id BIGINT
event_id BIGINT
I am trying to find a good way to let the user select is own area codes... and then to match it in a select statement with the event area codes.
i tought about doing the same as events_areas and do: users_areas:
area_id BIGINT
user_id BIGINT
But then I dont know how to match them in select statement... ?
Thanks
Assuming the tables you listed, the following would select all events in the users selected area codes.
SELECT u.*, e.*
FROM users u
JOIN users_areas ua
ON u.id = ua.user_id
JOIN events_areas ea
ON ea.area_id = ua.area_id
JOIN events e
ON ea.event_id = e.id
The select statement would look something like this:
SELECT DISTINCT users.*, events.*
FROM users
JOIN users_areas ON users.user_id = users_areas.user_id
JOIN events_areas ON users_areas.area_id = event_areas.area_id
JOIN events ON events_areas.event_id = events.event_id
It's useful to start listing the relations between the tables (objects) in plain English. For example:
A user belongs to an area code and an area code has many users.
An event happens in an area code and an area code has many events.
Both statements describe many-to-many relations, so you do need those two tables (sometimes called cross-reference tables).
Writing those statements always helps to understand the problem and ask questions about it. Assuming the statements above are correct, then having those two tables users_areas and events_areas is correct too. To build the select statement, note that there's only one column that both tables have in common: area_id (which by the way, would fit in a int, using bigint is a huge overkill, and I think the same goes for user_id and event_id). So area_id is the column you need to use to match them (the correct term is to join them).
Here's your select statement:
SELECT ea.event_id, ea.area_id, ua.user_id
FROM events_areas ea
INNER JOIN user_areas ua ON ea.area_id = ua.area_id
Or, assuming you also have an event table and a user table, which is likely the case, we expand the select to look like this:
SELECT e.name, u.name
FROM events_areas ea
INNER JOIN user_areas ua ON ea.area_id = ua.area_id
INNER JOIN users u ON u.user_id = ua.user_id
INNER JOIN events e ON e.event_id = ue.event_id
Okay I changed this based on your comment...
Create these tables:
Table: user
id
name
Table: user_area
id
user_id
area_id
Table: event
id
name
Table: event_area
id
event_id
area_id
Table: area
id
area_code
Then run this query:
SELECT event.name FROM event, event_area WHERE event.id = event_area.event_id AND event_area.area_id IN (SELECT area_id FROM user_area WHERE user_id = <CURRENTUSERSID>)
SELECT *
FROM users u
INNER JOIN users_areas ua
ON u.user_id = ua.user_id
INNER JOIN events_areas ea
ON ua.area_id = ea.area_id
INNER JOIN events e
ON ea.event_id = e.event_id
I have a table called bans where I have the follow fields:
room_id, banned_user_id, banned_by_id, reason, ts_start, ts_end
The users data come from the table called users, now I wanted to query the bans to retrive the name of who was banned and by who along with reason, time the ban was placed and time it ends.
So I have this query:
SELECT u.username, us.username, b.reason, b.ts_start, b.ts_end
FROM `bans` b
LEFT JOIN users us ON b.banned_by_uid = us.uid
LEFT JOIN users u ON b.banned_uid = u.uid
WHERE room_id = 3
My question here is wether my query is ok by using the LEFT JOIN for the 2 data I have to grab from the table users or there is a different approach for this kinda of scenario ?
Your query is perfectly acceptable. Each join to users is on a specific ID, which translates into a simple lookup, with minimal overhead.