Retrieve a value through three tables in a single Query - php

I have been trying to understand joins for a while now, I've noticed quite a few examples showing how to retrieve a value based on a two-table query using a left / inner join. I will try to draw out my example for you all, hopefully it will aid in the explanation of my question.
Table 1:
project_id | project_name
1 super-project
Table 2:
project_id | course_id ---> project_id and course_id are foreign keys here.
1 3
Table 3:
course_id | course_name ---> course_id is a primary key here
3 | Writing 101
My Goal:
I would like to be able to return "Writing 101" by using the project_id. So, if I know the project_id is 1, I would query the db to find that the course_id associated with the current project is 3. I would then use the course_id of 3 to query table 3 and find that the related course_name is writing 101.
Is there a way to do this in a single query using join of some sort?

select course_name from Table3
inner join Table2
on Table2.course_id = Table3.course_id
where Table2.project_id = 1
Notice there is only 1 join needed!
Also note that if the project_id = 1 is present in more than one course all of those courses will appear.
Can you explain why only one join is needed as opposed to the answer below which requires two joins?
The other answer does not require two joins. As the project_id is present in the "middle" table there is no need to look for it in Table1. Now, if you require the name of the project (which is not the case because you didn't mention that in the question) then you would have to join the Table1

Here you go:
SELECT c.course_name
FROM Table3 c
INNER JOIN Table2 pc ON pc.course_id = c.course_id
INNER JOIN Table1 p ON p.project_id = pc.project_id
WHERE project_id = 1

Related

mySQL INNER JOIN very large tables with same column names [duplicate]

This question already has answers here:
How to resolve ambiguous column names when retrieving results?
(11 answers)
Closed 2 years ago.
I have some big tables which I need to combine into a single very large table, to form a single-page data export for a statistical package.
This is easy with INNER JOIN but the some of the tables have the same column names and these are being overwritten by each other when I fetch them as an array in PHP.
There are 4 tables being joined with 30-200 columns in each so there are far too many field names to manually include in the query with aliases, as would be the norm in this situation.
Here's the query:
SELECT * FROM logs
INNER JOIN logdetail ON logdetail.logID = logs.id
INNER JOIN clients ON clients.id = logs.clientID
INNER JOIN records ON records.id = logdetail.id
WHERE logs.userID=1
Is there any way around this? I don't actually mind what the column names are as long as I have the data so if I could prepend the table name to each field, that would do the trick.
I would create a view, your view would be comprised of your long query with aliases
Here is an example taken from the manual
mysql> CREATE TABLE t (qty INT, price INT);
mysql> INSERT INTO t VALUES(3, 50);
mysql> CREATE VIEW v AS SELECT qty, price, qty*price AS value FROM t;
mysql> SELECT * FROM v;
+------+-------+-------+
| qty | price | value |
+------+-------+-------+
| 3 | 50 | 150 |
+------+-------+-------+
This has always worked for me, unless you have one to many or some other relationship among these tables, which will duplicate records.
SELECT * FROM logs l
INNER JOIN logdetail ld ON ld.logID = l.id
INNER JOIN clients c ON c.id = l.clientID
INNER JOIN records r ON r.id = ld.id
WHERE l.userID=1
As andrew says you can also use a View to get this thing working which is much cooler.
I found a solution for this. Simply, fetch each duplicate column a second time, this time using an alias. This way, the overwritten values are selected again and aliased:
SELECT * FROM logs,
clients.name as clientName,
logs.name as logName,
etc...
INNER JOIN logdetail ON logdetail.logID = logs.id
INNER JOIN clients ON clients.id = logs.clientID
INNER JOIN records ON records.id = logdetail.id
WHERE logs.userID=1
Note: There is no need to do this for the final instance of the duplicate, because this column will not have been overwritten. So, in the example above, there is no need to include a line like records.name as recordName because, since there are no columns after it which have the same name, the record.name field was never overwritten and is already available in the name column.

GROUP_CONCAT with ordering and missing fields

I have a series of tables that I want to get rows returned from in the following format:
Student ID | Last Name | First Name | Quiz Scores
-------------------------------------------------
xxxxxxx | Snow | Jon | 0,0,0,0,0,0,0,0
There's 3 relevant tables (changing any existing DB structure is not an option):
person - table of all people in the organization
enrollment - table of student and faculty enrollment data
tilt.quiz - table of quiz scores, with each row storing an individual score
The tricky part of this is the Quiz Scores. A row for the quiz score only exists if the student has taken a the quiz. Each quiz row has a module, 1 - 8. So possible quiz data for a student could be (each of these being a separate row):
person_id | module | score
---------------------------
223355 | 1 | 100
223355 | 2 | 95
223355 | 4 | 80
223355 | 7 | 100
I need the quiz scores returned in proper order with 8 comma separated values, regardless if any or all of the quizzes are missing.
I currently have the following query:
SELECT
person.id,
first_name,
last_name,
GROUP_CONCAT(tilt.quiz.score) AS scores
FROM person
LEFT JOIN enrollment ON person.id = enrollment.person_id
LEFT JOIN tilt.quiz ON person.id = tilt.quiz.person_id
WHERE
enrollment.course_id = '$num' AND enrollment_status_id = 1
GROUP BY person.id
ORDER BY last_name
The problems with this are:
It does not order the quizzes by module
If any of the quizzes are missing it simply returns fewer values
So I need the GROUP_CONCAT scores to at least include commas for missing quiz values, and have them ordered correctly.
The one solution I considered was creating a temporary table of the quiz scores, but I'm not sure this is the most efficient method or exactly how to go about it.
EDIT: Another solution would be to execute a query to check for the existence of each quiz individually but this seems clunky (a total of 9 queries instead of 1); I was hoping there was a more elegant way.
How would this be accomplished?
There are some assumptions here about your data structure, but this should be pretty close to what you're after. Take a look at the documentation for GROUP_CONCAT and COALESCE.
SELECT `person`.`id`, `person`.`first_name`, `person`.`last_name`,
GROUP_CONCAT(
COALESCE(`tilt`.`quiz`.`score`, 'N/A')
ORDER BY `tilt`.`quiz`.`module_id`
) AS `scores`
FROM `person`
CROSS JOIN `modules`
LEFT JOIN `enrollment` USING (`person_id`)
LEFT JOIN `tilt`.`quiz` USING (`person_id`, `module_id`)
WHERE (`enrollment`.`course_id` = '$num')
AND (`enrollment`.`enrollment_status_id` = 1)
GROUP BY `person`.`id`
ORDER BY `person`.`last_name`
First thing to do is use the IFNULL() function on the score
Then, use ORDER BY inside the GROUP_CONCAT
Here is my proposed query
SELECT
person.id,
first_name,
last_name,
GROUP_CONCAT(IFNULL(tilt.quiz.score,0) ORDER BY tilt.quiz.module) AS scores
FROM person
LEFT JOIN enrollment ON person.id = enrollment.person_id
LEFT JOIN tilt.quiz ON person.id = tilt.quiz.person_id
WHERE
enrollment.course_id = '$num' AND enrollment_status_id = 1
GROUP BY person.id
ORDER BY last_name

Joining multiple tables with NULL values to avoid duplication

I am building a news feed from multiple tables status, events and tracks. The data retrieved from these tables should correspond to the user-id of all the users that I follow. On the face of it I thought this seemed simple enough and I could do this with a few joins.
Every row in each of the status, events and tracks table has unique ID and they are also unique from each other, this should make matters easier later. I have done this using a unique_id table with a primary key to retrieve ID's before inserting.
My trouble is upon joining everything together the values duplicate.
Example
If I have this data.
----------
**Status**
user-id = 1
id = 1
status = Hello Universe!
----------
**Events**
user-id = 1
id = 2
event-name = The Big Bang
----------
**Tracks**
user-id = 1
id = 3
track-name = Boom
----------
Assuming I follow user 1 I would want to retrieve this.
user-id ---- id ---- status ---- event-name ---- track-name
1 1 Hello NULL NULL
Universe
1 2 NULL The Big Bang NULL
1 3 NULL NULL Boom
But in reality what I would get is something like this.
user-id ---- status.id ---- events.id ---- tracks.id ---- status ---- event-name ---- track-name
1 1 2 3 Hello The Big Bang Boom
Universe
And that row would be repeated 3 times.
Most of the queries I have tried will get something along those lines.
SELECT * FROM users
INNER JOIN followers ON users.id = followers.`user-id`
LEFT JOIN status ON followers.`follows-id` = status.`user-id`
LEFT JOIN events ON followers.`follows-id` = events.`user-id`
LEFT JOIN tracks ON followers.`follows-id` = tracks.`user-id`
WHERE users.`id` = 2
I am using laravel, so eventually this query will be put into Eloquent format. If there is a simpler and a not performance degrading way of doing this in Eloquent please let me know.
Edit
I cannot use a UNION as there is a different number of values in each table. The example is simplified for ease of reading.
Thanks to Frazz for pointing out I could use UNIONS. I have researched into them and come up with this query.
SELECT stream.*, users.id AS me FROM users
INNER JOIN followers ON users.id = followers.`user-id`
LEFT JOIN (
SELECT `id`,`user-id`,`created_at`, `name`, NULL as status
FROM events
UNION ALL
SELECT `id`,`user-id`, `created_at`,NULL AS name, `status`
FROM status
) AS stream ON stream.`user-id` = `followers`.`follows-id`
WHERE users.id = 2
Now comes the process of converting it to an eloquent model...

Left Joining three MySQL tables

tbl_teams: team_id | team_name
tbl_players: player_id | player_fname | player_sname | player_bplace | player_bdate
tbl_players_stats: player_id | season_id | player_squad_no | team_id | player_apps | player_goals
Sorry if this is a basic question, but from all the MySQL tables and columns above I'd like to join the tables and then display the results by which season_id and team_id is selected. I need using PHP like this:
player_squad_no | player_sname, player_fname | team_name | player_apps | player_goals
I've looked at examples on here but still can't figure out how to write the MySQL query to do it with three separate tables and how to specify the table name before the column name. I've seen some examples with only the initial. tt.teams for instance. Is Left Join the way to do it?
Any help would be much appreciated.
With three separate tables, you simply write the join like this:
SELECT *
FROM Table_A AS A
LEFT JOIN Table_B AS B USING(ID)
LEFT JOIN Table_C AS C USING(ID)
Note that USING(column) is a syntactic alternative to ON A.column = B.column that you can use when the columns you want to join on have the same name in both tables.
In the above example, the tables are aliased with AS so that you can refer to them by the alias instead of the full table name. (AS is actually optional; you can just give the alias immediately after the table, if you're paying by the character.) Try to choose an alias that makes sense when you look at it; often times people will alias like this:
SELECT a.Name, b.State
FROM Customers AS a
LEFT JOIN Orders AS b
...etc.
But if you have a longer query, how are you supposed to remember what tables a and b refer to? At the very least, it would make sense to alias Customers AS C and Orders AS O; in some cases, I would go a step further: Registration AS REG, for instance. This gets more and more important as you JOIN more and more tables together.
Here's one way to write your query:
SELECT
Stats.player_squad_no,
CONCAT_WS(', ', Players.player_sname, Players.player_fname) AS player_full_name,
Teams.team_name,
Stats.player_apps,
Stats.player_goals
FROM tbl_players AS Players
LEFT JOIN tbl_players_stats AS Stats USING(player_id)
LEFT JOIN tbl_teams AS Teams USING(team_id)
The CONCAT_WS() function is included to assemble the player's full name the way you indicated you wanted it to be displayed. Since this function will output a column with a messy name, I also gave it an alias.
This should work
SELECT tbl_players_stats.player_squad_no,
tbl_players.player_sname,
tbl_players.player_fname,
tbl_teams.team_name,
tbl_players_stats.player_apps,
tbl_players_stats.player_goals
FROM tbl_players
JOIN tbl_players_stats ON tbl_players.player_id = tbl_players_stats.player_id
JOIN tbl_teams ON tbl_teams.team_id = tbl_players_stats.team_id
SELECT player_squad_no , player_sname, player_fname,team_name, player_apps, player_goals
FROM tbl_players_stats as s
JOIN tbl_players as p ON s.player_id=p.player_id
JOIN tbl_teams as t ON s.team_id=t.team_id
Nothing Joining is simple concept. But we should use proper columns for tables. While selecting the list of columns to select we should be little careful by using table aliasing. Try the below code
select c.player_squad_no,b.player_sname,b.player_fname,a.team_name,c.player_apps,c.player_goals
from tbl_teams a,tbl_players b,tbl_players_stats c
where a.team_id=c.team_id
and b.player_id=c.player_id

What's wrong with my MySQL query?

I'm not getting any errors as such just a minor performance issue.
EXPLAIN
SELECT
a.nid,
a.title,
a.uid,
b.parent,
b.weight,
c.name,
d.value
FROM table1 AS a INNER JOIN table2 AS b ON a.vid = b.vid AND a.status = 1
INNER JOIN table3 AS c ON c.uid = a.uid
INNER JOIN table4 AS d ON d.content_id = a.nid AND d.value_type = 'percent' AND d.function = 'average'
When I look at which tables are being referenced, everything is fine, but from table4 where it should only be selecting the "value" field, I'm getting an ALL being called...
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE a ref PRIMARY,vid,status,uid,node_status_type,nid status 4 const 1
1 SIMPLE b eq_ref PRIMARY PRIMARY 4 databasename.a.vid 1
1 SIMPLE c eq_ref PRIMARY PRIMARY 4 databasename.a.uid 1 Using where
1 SIMPLE d ALL NULL NULL NULL NULL 2 Using where
As you can see, it's selecting * from the final table (d). Why is it doing this when I only need ONE field selected from it? Can anyone help me out?
ALL means all rows, not all columns. Since it says there are no possible keys, I'd guess that you don't have an index on d.content_id or d.value_type or d.function.
If you wanted to be fancy, you could put an index across all 3 of those columns.
Are d.value_type and d.function indexed fields? That would be initial instinct as to the cause.
Add a multi-column index to table4 based on the content_type, value_type and function columns.
Your query isn't selecting all the columns from table4, it's selecting all the rows; this isn't much of a problem when there's only two.
Note that a MySQL query execution plan might not give the give the answer you expect when you're working with a small number of records; it can be faster for the database to do a full table scan in those circumstances.

Categories