MySQL: 4 Table "has-many-through" Join? - php

Let's say I have the following 4 tables (for examples' sake): Owners, Trucks, Boxes, Apples.
An owner can have many trucks, a truck can have many boxes and a box can have many apples.
Owners have an id. Trucks have an id and owner_id. Boxes have an id and truck_id. Apples have an id and box_id.
Let's say I want to get all the apples "owned" by an owner with id = 34. So I want to get all the apples that are in boxes that are in trucks that owner 34 owns.
There is a "hierarchy" if you will of 4 tables that each only has reference to its direct "parent". How can I quickly filter boxes while satisfying conditions across the other 3 tables?
I hope that made sense somewhat.
Thanks.

select a.*
from Trucks t
inner join Boxes b on t.id = b.truck_id
inner join Apples a on b.id = a.box_id
where t.owner_id = 34

You just start at the "top" (owners) and keep joining until you get where you want:
SELECT a.*
FROM Owners o
INNER JOIN Trucks t ON t.owner_id = o.id
INNER JOIN Boxes b on b.truck_id = t.id
INNER JOIN Apples a on a.box_id = b.id
WHERE o.id = ?
If queries like that are needed often and you are working with very large data sets, sometimes it makes sense to denormalize the data a bit as well. For example by adding the owner_id to the apples table. It makes inserting/updating the data a bit more difficult, but can make queries easier.

SELECT a.*
FROM Apples a
INNER JOIN Boxes b ON b.id = a.box_id
INNER JOIN Trucks t ON t.id = b.truck_id
INNER JOIN Owners o ON o.id = t.owner_id
WHERE o.id = 34
You can simplify this somewhat by leaving out the join to owners and just selecting where t.owner_id = 34 if you don't need any information about the owner later.

Related

How to join hierarchical table in MySQL (table joins other table that joins other table that joins other table)?

I have 3 tables, tb_item, tb_branch, tb_department, tb_division.
As you can see:
tb_division is under tb_department
tb_department is under branch
tb_branch has no superior
for example if I want to retrieve item_id 1 or 2 or 3 then this should be the result
If I want to retrieve item_id 2 then it will show the department and branch.
My problem is that I can nested join them if if I know the item is related to a specific table but in this case since tb_item can be related to tb_division or tb_department and tb_branch I don't know what query should I formulate.
Changing the database design is not an option since this design is already implemented.
SELECT column-names
FROM table-name1 JOIN table-name2
ON column-name1 = column-name2
WHERE condition
Never Mind guys. I managed to solved it myself. been struggling since I thought I need to do tons of subqueries. Thanks for the answers tho.
SELECT
i.item_id,
dv.division_name,
COALESCE(dept.department_name, dept2.department_name) AS department_name,
COALESCE(br.branch_name, br2.branch_name, br3.branch_name) AS branch_name
FROM
tb_item AS i
LEFT JOIN
tb_division AS dv
ON i.division_id = dv.division_id
LEFT JOIN
tb_department AS dept2
ON dv.department_id = dept2.department_id
LEFT JOIN
tb_branch AS br2
ON dept2.branch_id = br2.branch_id
LEFT JOIN
tb_department AS dept
ON i.department_id = dept.department_id
LEFT JOIN
tb_branch AS br3
ON dept.branch_id = br3.branch_id
LEFT JOIN
tb_branch AS br
ON i.branch_id = br.branch_id

MySQL returning same data different rows

I am trying to get data from reviews table then, join the same table again but find different data (likes) based on the id of the first table (reviews) and find the corresponding description from yet another joined table (descriptions).
I know this might be hard to visualize but maybe someone will know why the likes from the second reviews table return the same data for different rows:
SELECT r.title, co.likes, d.description
FROM reviews r
INNER JOIN reviews co
INNER JOIN reviews_descriptions d
ON co.id = d.review_id
WHERE co.parent = 52
AND r.id = 52;
The result is two rows in which title and likes have the same data while the description field grabs different data (the correct way). Likes should have different data for each row.
Help please.
An alternative for writing this and I believe this is what you are looking for:
SELECT r.title, co.likes, d.description
FROM reviews r, reviews co, review_descriptions d
WHERE co.parent = r.id AND d.review_id = co.id AND r.id = 52;
Since you are matching co.parent to 52 and r.id to 52, then you should have a join on co.parent = r.id, however, you should rarely have to do a join on the same table. I think your tables are poorly formatted or you don't need to do the join in the first place and should be able to use r.likes instead of co.likes.

Wrapping my head around what I assume is a complicated MySQL query

I have a table called categories and a table called business_categories_coupling. In Categories, you have the usual id, name, parent. In the Coupling table, you have business_id and category_id. Each business can have multiple categories, so I store them in that table. It kinda looks like this:
business_id category_id
73 80
73 81
73 90
74 4
74 10
Right now, my query is just selecting all the categories, doing a foreach and doing a db query in each loop to find how many businesses are in that category. Obviously not the right way to go about it.
Is there a way to do a SQL query that basically selects all the categories, gets the number of times it comes up in the coupling table, and add a count to each category?
SELECT
C.*
FROM
CATEGORIES AS C
LEFT JOIN
BUSINESS_CATEGORIES_COUPLING AS B
ON
C.id = B.category_id;
Kinda like that, but with a count somewhere. I've tried various setups but nothing works like I want. Any suggestions?
EDIT 1
Solution as provided by #phani-rahul, but I added a WHERE clause:
SELECT cat.id AS id, cat.name AS name, cat.slug AS slug, COUNT(cat.id) AS business_count
FROM categories AS cat
LEFT JOIN business_categories_coupling AS coupling ON cat.id=coupling.category_id
WHERE coupling.category_id IS NOT NULL
GROUP BY cat.id
Yes, there is.
you can use Group by clause:
select a.id as category, count(a.id) as count_of_category
from categories a
left join business_categories_coupling b on a.id=b.category_id
group by a.id
your result would be something like:
category count_of_category
80 2
81 5
90 1
. .
. .
. .
You also need to GROUP by the fields of C table.
SELECT C.id, C.field1, C.field2, COUNT(*)
FROM CATEGORIES AS C
LEFT JOIN BUSINESS_CATEGORIES_COUPLING AS B
ON (C.id = B.category_id)
GROUP BY C.id, C.field1, ...
(In MySQL you can GROUP BY the single value C.id; in other SQL dialects you can express the concept of "grouping by rows of C table" by grouping by "C.*"; in some others you need to specify all non-aggregate columns of your query, in this case all columns you select from C, one by one).
What you're looking for is a GROUP BY clause.
SELECT
C.*, count(C.id)
FROM
CATEGORIES AS C
LEFT JOIN
BUSINESS_CATEGORIES_COUPLING AS B
ON
C.id = B.category_id
GROUP BY B.category_id;

how to do this complex fetch in 1 query?

I have an application with tutors and courses and subscribers and ratings. These are the tables I am using:
tbl_tutors:
id
name
tbl_subscribers:
id
user_id
course_id
tbl_courses:
id
name
tutor_id
tbl_ratings:
id
user_id
course_id
rating
I need to get 1 tutor with the number of courses he has, the number of total subscribers for those courses and the average course rating for all his courses. This is a lot of data; can it be done in 1 sql query or do I need to code foreach statements in php to get the average ratings and the total subscribers for those courses?
Well do you need totals per tutor-course combination or a total (and average) at the tutor level?
And what is the rating table adding over the subscriber table? Aren't they both unique user-course combinations?
If one user attends multiple courses by the same tutor, how many subscribers do they count as?
The SQL provided by #alfasin is easily extended to all tutors. The syntax below is for SQL server, you may need to change for MySQL
Select t.name, count(distinct c.id) courseCount, count(s.id) subscribers, avg(r.rating) subRating
From tbl_tutors t
Inner join tbl_courses c on c.tutorid = t.id
Inner join tbl_subscribers s on s.courseid = c.id
Inner join tbl_ratings r on r.userid = s.userid and r.courseid = c.id
Group by t.name
Note that when trying to build queries like this it's usually best to do them without grouping so you can inspect which rows are contributing to the counts and ensure you're including everything you expect and that you're not duplicating results
select t.name "Tutor", count(c.id) "# courses", count(s.id) "# subscribers"
from tbl_tutors t, tbl_subscribers s, tbl_courses c, tbl_ratings r
where t.id = XXX
and c.tutor_id = t.id
and s.course_id = c.id
and r.user_id = s.user_id
group by t.name
this sql will get you all you need besides the courses average (substitute the XXX with the tutor-id you want to find). for courses average you can run a separate select.

Category post count

I am building a blog with Codeigniter and MySQL. The question I have is this, I have a table with posts and one with categories. I also have a cross reference table with post_categories. What I am trying to do is get all the categories with their names and the number of posts they have under their name.
Example output would be: Hello World(1) Test(0) etc.
What I am having a hard time finding is a SQL query that will join the three tables and get me the counts, and I am also having a hard time wrapping my head around how to make that query.
Here is my table schema:
blgpost
====
id
*Other schema unimportant
blgpostcategories
=================
postid
categoryid
blgcategories
==========
id
name
*Other schema unimportant
This should give you the output you want....
SELECT c.name, COUNT(p.id) FROM
blgcategories c
INNER JOIN blgpostcategories pc ON c.id = pc.categoryid
INNER JOIN blgpost p ON pc.postid = p.id
GROUP BY c.id
You don't need to join the three tables - the blgpost table doesn't have any information in it that you need.
SELECT COUNT(*), blgcategories.name
FROM blgcategories INNER JOIN blgpostcategories
ON blgcategories.id=blgpostcategories.categoryid
GROUP BY blgcategories.id;
SELECT name, COUNT(pc.id)
FROM blgcategories c
LEFT JOIN
blgpostcategories pc
ON pc.categoryid = c.id
GROUP BY
c.id
Using LEFT JOIN will show 0 for empty categories (those without posts linked to them) rather than omitting them.

Categories