SQL - can I do repeated inner joins again on the same column? - mysql

I have two simple tables, one called itineraries that holds details of holiday itineraries and one called users, that holds details of users. Other users create itineraries, and users can copy their itineraries and add travel agents, so the copied_from_id is the ID of the original creating user from users.id.
I've joined itineraries.user_id to users.id using the below query which works perfectly:
SELECT
itineraries.travel_agent_id,
itineraries.copied_from_id,
itineraries.user_id,
users.full_name,
users.username
FROM `gadabouting_gadabouting_production`.`itineraries`
INNER JOIN `gadabouting_gadabouting_production`.`users` ON itineraries.user_id=users.id
WHERE itineraries.travel_agent_id='253'
Giving me the following output:
+-----------------+------------------+---------+-------------+-------------+
| travel_agent_id | original_creator | user_id | full_name | username |
| 253 | 501 | 1465 | John Smithy | j.smithy |
| 253 | 501 | 1465 | John Smithy | j.smithy |
| 253 | 501 | 1474 | Ben Stockes | ben.stockes |
+-----------------+------------------+---------+-------------+-------------+
(The travel_agent_id and original_creator columns are the same as users.id).
What I want to do now is map the itineraries.travel_agent_id and itineraries.original creator to the users.full_name and users.username columns (so have the full_name and username columns printed next to each of the travel_agent_id and original_creator columns, but I just can't work out how to do it. I've spent hours on it now and can't get my head round it. Do I need to do more joins?
I've looked at several other SO questions about multiple joins but as far as I can see, none of them cover the process of 'going back' again and again on the same column as I want to do here.
Is this possible? Would greatly appreciate any help!
Thanks

You can join in the same table over and over, but you need to use an alias for each one so that you can specify which one you want to use. If you access the result by name, you also need alases for some of the field names.
(It's conventient to use aliases on other tables also, to make the query less verbose.)
select
i.travel_agent_id,
i.copied_from_id,
i.user_id,
u.full_name,
u.username,
ut.full_name as travel_agent_full_name,
ut.username as travel_agent_username,
uc.full_name as creator_full_name,
uc.username as creator_username
from
gadabouting_gadabouting_production.itineraries as i
inner join gadabouting_gadabouting_production.users as u on u.id = i.user_id
inner join gadabouting_gadabouting_production.users as ut on u.id = i.travel_agent_id
inner join gadabouting_gadabouting_production.users as uc on u.id = i.original_creator
where
i.travel_agent_id = '253'

Related

MySQL Select Left Join multiple columns from the same table

I have objects in the Main Project Workflow table that I need to relate to one object in the User table. The issue I am facing is when I do a LEFT JOIN, I can only relate one object at a time.
The relation I need to do is:
workflowUID = user_id
assignedTo = user_id
I believe the problem is being caused by my LEFT JOIN, however, I don't know which join statement I need to use to do this relation.
User Table
user_id | user_firstName | user_lastName
1 | Joe | Smith
2 | John | Doe
Main Project Table
projectID | projectTitle | projectDesc | projectDueDate | projectAssignedTo
1 | Test Title | Desc for Proj | 11-06-2018 | 2
Main Project Workflow Table EDITED
projectID | CID | workflowUID | assignedTo
1 | 1 | 1 | 2
The projectID is releated to another table called mainProjects, which list more info about the project such as the project title, created/due date, created by, effort in hours, project description.
The CID is stored in the Main Project Workflow Table. It is the Commit ID. Which will be used later for stuff like editing/deleting comments.
Output
Workflow Created By | Workflow Assigned To
Joe Smith | John Doe
SQL:
SELECT *
FROM mainprojectworkflow
LEFT JOIN user ON mainprojectworkflow.workflowUID = user.user_id
WHERE projectID = $projectID
ORDER BY cid DESC
The second I try setting a second LEFT JOIN user ON mainprojectworkflow.workflowUID = user.user_id but instead, as a mainprojectworkflow.assignedTo I get a not unique table/alias user. I believe this is because I am already setting the user table to mainprojectworkflow.
EDIT
I'm sorry, I should have been more clear on what's going on.
END RESULT: My plan is to use the SQL to select the data and display it in PHP on a website. It's a project management website. I want to be able to have PHP pull the variables from SQL so I can use them however I feel fit.
You will need to join two times to the table user, like this:
SELECT
mpw.workflowUID,
CONCAT(cu.user_firstName, " ", cu.user_lastName) AS "Workflow Created By",
mpw.assginedTo,
CONCAT(au.user_firstName, " ", au.user_lastName) AS "Workflow Assigned To"
FROM
mainprojectworkflow AS mpw
INNER JOIN
user AS cu ON cu.user_id = mpw.workflowUID
INNER JOIN
user AS au ON au.user_id = mpw.assignedTo
WHERE
projectID = $projectID
ORDER BY
cid DESC
Try this type Of query :
SELECT
CONCAT_WS(' ', uc.user_firstName, uc.user_lastName) AS Workflow_Created_By,
CONCAT_WS(' ', ua.user_firstName, ua.user_lastName) AS Workflow_Assigned_To
FROM mainprojectworkflow
LEFT JOIN User uc ON mainprojectworkflow.workflowUID = uc.user_id
LEFT JOIN User ua ON mainprojectworkflow.assignedTo = ua.user_id;

MySQL Multiple Join with delimiting via FINDINSET

I am attempting to JOIN onto two different columns in the first table below from columns in the second and third tables.
I wish to JOIN users.id to job_listings.id to return users.username, and to also JOIN and delimit job_listings.categories to job_categories.id to return job_categories.description via FIND_IN_SET
job_listings
id | employer_id | categories
1 | 1 | 1,2
2 | 1 | 2
users
id | username | type
1 | foo | employer
2 | wat | employer
job_categories
id | description
1 | fun
2 | hak
I desire output that is of the following format:
output
username | type | category | description
foo | employer | 1 | fun
foo | employer | 2 | hak
foo | employer | 2 | hak
I have tried using various permutations of the following code:
SELECT users.username, users.type, job_listings.categories FROM users
JOIN job_listings ON users.id
JOIN job_listings AS category ON FIND_IN_SET(category.categories, job_categories.id)
ORDER BY users.username, category.categories
I know from other answers that I need to use an alias in order to use multiple JOIN operations with the same table, but despite adapting other answers I keep receiving errors related to declaring an alias, or returning output that has a column with the alias but no data returned in that column.
First, you should normalize your design. You should not store integer values in strings. You should not have foreign key references that you cannot declare as such. You should not store lists in strings. Is that enough reasons? You want a junction table for JobCategories with one row per job and one row per category.
Sometimes, we are stuck with other peoples lousy decisions and cannot readily change them. In that case, you want a query like:
SELECT u.username, u.type, jc.id, jc.category
FROM users u JOIN
job_listings jl
ON u.id = jl.employer_id and u.type = 'employer' join
job_categories jc
ON FIND_IN_SET(jc.id, j.categories) > 0
ORDER BY u.username, jc.category;
This query cannot take advantage of indexes for the category joins. That means that it will be slow. The proper data structure -- a junction table -- would fix this performance problem.

Mysql select between two table without limiting if record appear on the joined table

I have been trying to figure out how to select data related to one id between to tables without limit it to the joined table. I tried using UNION, Inner join, JOIN, but it limit me to show records that are only in both tables. By example:
Table 1 (users)
id | name | register
1 | John | 2014-03-01
2 | Kate | 2014-03-02
etc..
Table 2 (birthdays by example)
id | user | birthday
1 | 1 | 1989-09-09
Note that kate dont have a record on the birthdays table, if i do:
SELECT U.id, name, register, B.birthday FROM users as U INNER JOIN birthday as B ON B.user = U.id
it will only shows JOHN data, i would like to select all my users and if the record do not exist on the joined table, still be able to select all my users, sort of:
id | name | register | birthday
1 | John | 2014-03-01 | 1989-09-09
2 | kate | 2014-03-02 | null or ''
3
4
etc.
Sorry if its a stupid question but i dont find the light on this one. I would appreciate the help.
Regards
You need a LEFT OUTER JOIN instead of the plain JOIN (also known as INNER JOIN), like this:
SELECT U.id, name, register, B.birthday
FROM users as U
LEFT JOIN birthday as B
ON B.user = U.id
A LEFT JOIN between users and birthday tables will contain all records of the "left" table (users), even if the join-condition does not find any matching record in the "right" table (birthday).
This excellent article on The Code Project will help you a lot: Visual Representation of SQL Joins.
Summary of all JOIN types:
Note: Mysql does not support FULL OUTER JOIN but it can be emulated. Useful articles:
https://stackoverflow.com/a/4796911
http://www.sql-tutorial.ru/en/book_full_join_and_mysql.html
http://www.xaprb.com/blog/2006/05/26/how-to-write-full-outer-join-in-mysql/
Use left outer join instead of inner join..
SELECT U.id, name, register, B.birthday
FROM users as U left join birthday as B ON B.user = U.id

MySQL join multiple rows from one table while selecting only a single row from the others

Maybe a bit of a strange title description, but i basically want to achieve something the GROUP_CONCAT() function does, only then keep the double entries.
I have four tables i want to join, client, doctor, physio and records
Depending on the variable $client i want to get the client details, attending doctor and therapist (one single row from three tables) and join all records for that user.
Say that in this case the $client = 1. The records table has five records where the column r_client_id = 1. If i run a query like below i only get one record from the records table, namely the first occurrence where r_client_id = 1 (which makes sense of course):
SELECT
client.c_id, client.c_name
doctor.d_name,
physio.p_name,
records.r_record
FROM
adm_clients AS client
INNER JOIN
norm_client_doctor AS ncd ON ncd.ncd_client_id = client.c_id
INNER JOIN
adm_doctor AS doctor ON doctor.d_id = ncd.ncd_doctor_id
INNER JOIN
norm_client_physio AS ncp ON ncp.ncp_client_id = client.c_id
INNER JOIN
adm_physio AS physio ON physio.p_id = ncp.ncp_physio_id
LEFT JOIN
adm_doctor_records AS records ON records.r_client_id = client.c_id
WHERE
client.c_id = '".$client."'
Now assume the five records where r_client_id = 1 are like so:
+------+-------------+-------------------+----------+
| r_id | r_client_id | r_record | r_date |
+------+-------------+-------------------+----------+
| 1 | 1 | regular visit | 10/10/12 |
+------+-------------+-------------------+----------+
| 3 | 1 | emergency control | 24/10/12 |
+------+-------------+-------------------+----------+
| 7 | 1 | regular visit | 08/09/12 |
+------+-------------+-------------------+----------+
| 18 | 1 | delivery | 03/01/12 |
+------+-------------+-------------------+----------+
| 20 | 1 | health checkup | 10/12/11 |
+------+-------------+-------------------+----------+
I want my output to be in an array like so:
Client 1
- Name Doctor
- Name Physio
Records
- Emergency control, 24/10/12
- Regular visit, 10/10/12
- Regular visit, 08/09/12
- Delivery, 03/01/12
- Health checkup, 10/12/11
The closest one i can image is a to add a GROUP_CONCAT() on the records, but that, of course, groups the 'regular visit', so i'll get 4 rows instead of 5
GROUP_CONCAT(DISTINCT records.r_record SEPARATOR '|')
[..]
echo(str_replace("|","<br>",$show->r_record));
Anybody an idea how to display all the matching records? I have the feeling i'm close, but i'm out of options by now..
Edit:
I forgot to mention that when i remove the DISTINCT, it displays all the records twice..
SOLVED:
Got it working like so:
GROUP_CONCAT(DISTINCT
CONCAT (records.r_date, '~', records.r_record, '~', records.r_paraph)
SEPARATOR '|') AS clientDoctorRecords,
Try:
SELECT
client.c_id, client.c_name
doctor.d_name,
physio.p_name,
GROUP_CONCAT(records.r_record)
FROM
adm_clients AS client
INNER JOIN
norm_client_doctor AS ncd ON ncd.ncd_client_id = client.c_id
INNER JOIN
adm_doctor AS doctor ON doctor.d_id = ncd.ncd_doctor_id
INNER JOIN
norm_client_physio AS ncp ON ncp.ncp_client_id = client.c_id
INNER JOIN
adm_physio AS physio ON physio.p_id = ncp.ncp_physio_id
LEFT JOIN
adm_doctor_records AS records ON records.r_client_id = client.c_id
WHERE
client.c_id = '".$client."'
GROUP BY
client.c_id
If you want r_date to come along with record in one column, then you can use plain CONCAT first and then do a GROUP_CONCAT on it.

Problem with one of my LEFT JOIN and SUM the result of it

So I got a question about LEFT JOIN, this code returns different values for totalPoints depending on if the user got the group or not. (if user don't got group or event it returns the correct value)
I just want to grasp how to get the LEFT JOIN flow_has_vote ON flow_has_vote.flow_id=flows.id to work every time. I did a solution before with three query's, one that gets the group and event rule, one that checks if the user got the group or event considering the security and one to get the flow...
And I guess I could solve this by having two query's, one that gets the group and event rules and also check if the user got the group and event and then one that gets the flow depending on the user should have access to it.
Right now I'm getting every information needed in ONE query and then checking with IF statements if it should be printed or not...
So, my question is, is it possible to get the SUM(flow_has_vote.points) AS totalPoints to work this way? And do you know how?
And also I'm a bit curios, is one query the best way to work with this? Would it be justified to use two when you take into account performance?
SELECT
flows.id AS flowId,
flows.security,
SUM(flow__has__vote.points) AS totalPoints,
users.id AS userId,
users.alias,
flows.event_id AS eventId,
events.group_id AS groupId,
events.membershipRules AS eMR,
groups.membershipRules AS gMR,
user__has__group.permission AS userHasGroup,
user__has__event.permission AS userHasEvent
FROM
users,
events LEFT JOIN user__has__event ON user__has__event.user_id = '.$userId.',
groups LEFT JOIN user__has__group ON user__has__group.user_id = '.$userId.',
flows LEFT JOIN flow__has__vote ON flow__has__vote.flow_id=flows.id
WHERE
flows.user_id = users.id AND
events.id = flows.event_id AND
groups.id = events.group_id AND
flows.id='.$flowId
And if you wonder what the SQL-statement is doing, getting the information for the flow(post), the information about the event and group that the flow is in, checking the user access to the group and event and also getting all the votes for the flow...
This is how the tables looks like...
FLOWS id,security,event_id,user_id
USERS id, alias
EVENTS id, name group_id, membershipRules
GROUPS id, name, membershipRules
USER__HAS__GROUP user_id,group_id,permission
USER__HAS__EVENT user_id,event_id,permission
FLOW__HAS__VOTE flow_id,user_id,points
This is the result I wish for...
+--------+----------+-------------+--------+--------+---------+---------+-----+-----+--------------+--------------+
| flowId | security | totalPoints | userId | alias | eventId | groupId | eMR | gMR | userHasGroup | userHasEvent |
+--------+----------+-------------+--------+--------+---------+---------+-----+-----+--------------+--------------+
| 1 | 2 | 1337 | 5 | Pontus | 15 | 2 | 2 | 2 | 4 | 4 |
+--------+----------+-------------+--------+--------+---------+---------+-----+-----+--------------+--------------+
and one more example...
+--------+----------+-------------+--------+--------+---------+---------+-----+-----+--------------+--------------+
| flowId | security | totalPoints | userId | alias | eventId | groupId | eMR | gMR | userHasGroup | userHasEvent |
+--------+----------+-------------+--------+--------+---------+---------+-----+-----+--------------+--------------+
| 1 | 2 | 1337 | 6 | Kezia | 15 | 2 | 2 | 2 | null | null |
+--------+----------+-------------+--------+--------+---------+---------+-----+-----+--------------+--------------+
Enjoy your life ~ Pontus
So, basically the main point (IMHO) is not to include conditions on tables you LEFT JOINed in the WHERE clause, since this makes the LEFT JOIN behave like an INNER JOIN.
Start with trying this query (although I am sure you will have to make adjustments as I am not sure exactly what you want as a result, more about this later):
SELECT
flows.id AS flowId,
flows.security,
SUM(flow__has__vote.points) AS totalPoints,
users.id AS userId,
users.alias,
flows.event_id AS eventId,
events.group_id AS groupId,
events.membershipRules AS eMR,
groups.membershipRules AS gMR,
user__has__group.permission AS userHasGroup,
user__has__event.permission AS userHasEvent
FROM users,
LEFT JOIN user__has__event
ON user__has__event.user_id = users.id,
LEFT JOIN events
ON user__has__event.event_id = events.id
LEFT JOIN user__has__group
ON user__has__group.user_id = users.id,
LEFT JOIN groups
ON user__has__group.group_id = groups.id
AND groups.id = events.group_id
LEFT JOIN flows
ON flows.user_id = users.id
AND events.id = flows.event_id
AND flows.id='.$flowId'
LEFT JOIN flow__has__vote
ON flow__has__vote.flow_id = flows.id
WHERE users.id = '.$userId.'
GROUP BY users.id
Here, I LEFT JOINed everything to the user, and also grouped by the user. I have a feeling you will want to add columns to the group by (flows.id?, events.id?)
Also, you may want to turn some of the LEFT JOINs to JOIN, so you will get only users who have a 'flow', for example.