MySQL Left Join Many to One Row - mysql

To simplify my problem: Let's say I have 3 tables.
Rooms People Things
-------- -------- --------
id| name id | name | fk_rooms id | name | fk_rooms
----------- --------------------- ---------------------
1 | kitchen 1 | John | 1 1 | TV | 2
2 | bedroom 2 | Mary | 2 2 | bed | 2
3 | Andy | 1 3 | sink | 1
4 | Laura| 1
Now I'm doing something like:
SELECT r.name AS room_name, p.name AS name, t.name AS thing FROM Rooms r
LEFT JOIN People p ON p.fk_rooms = r.id
LEFT JOIN Things t ON t.fk_rooms = r.id
which in my case works perfectly except for a few that have many to one relationship with the "Rooms" table. So instead of new rows in the result set holding the different names for "People" and "Things" in relation to the "Rooms" table, I would like to receive only two rows:
1. kitchen, John, Andy, Laura, sink
2. bedroom, Mary, TV, bed
A GROUP BY on r.id will only select one row from each table. Any help is highly appreciated!

Here it is what you're looking for:
SELECT r.name AS room_name,
GROUP_CONCAT(p.name separator ',') AS people_name,
GROUP_CONCAT(t.name separator ',') AS things
FROM Rooms r
LEFT JOIN People p ON p.fk_rooms = r.id
LEFT JOIN Things t ON t.fk_rooms = r.id
GROUP BY r.id

Yes, you need the group by clause, and you need to use the GROUP_CONCAT function. You should group your results by People.fk_rooms and Thing.fk_rooms.
Maybe you could use two different queries: The first will result the join of Rooms and People, grouped by fk_rooms, having selected three columns, they are being RoomsID, RoomName, People, while the second will result the join of Rooms and Thing, grouped by fk_rooms, having selected three columns, they are being RoomID, RoomName, Things. In your query you name these selections as t1 and t2 and join t1 and t2 by RoomsID, select t1.RoomName, t1.People, t2.Things.
Good luck.

Related

Sql query with 2 join and count numbers and group by

I want to use an join to list the car colors count, car type, and users name.
I have 3 table
Table 1 Useres
id|username|fullname
1 | test0 | xy xy
2 | test1 | yx yx
Table 2 Car Type
id|car_type|user_id
1 | Ford | 1
2 | BMW | 2
3 | Ford | 1
4 | Skoda | 1
5 | BMW | 2
Table 3 Car Color
id| Color |user_id|car_id
1 | Red | 1 |1
2 | Blue | 2 |2
3 | Red | 2 |5
4 | Red | 1 |3
5 | Red | 1 |4
6 | Green | 1 |4
One car has 2 color
The result should be:
countType | CountColor | UserName
3 | 4 | test0
2 | 2 | test1
I tryed this:
SELECT
test as BlogsPost,
test2 as CommenstPost,
u.name
FROM users u
LEFT JOIN (
select COUNT(blogs.user_id) as test FROM blogs GROUP by blogs.user_id) blogs
on blogs.user_id=u.id
LEFT JOIN (
select COUNT(comments.user_id) as test2 FROM comments GROUP by comments.user_id) comments
on comments.user_id=u.id
GROUP by users.id
If I understand your question correctly with reference to your actual code section what you want is a list of users with how many blogs they have and how many comments they have. Now if you were wanting to count one matching table you could just do this:
SELECT
U.NAME
,COUNT(1) AS BLOG_COUNT
FROM USERS U
LEFT JOIN BLOGS B
ON B.USER_ID = U.ID
GROUP BY U.NAME
But since you are wanting to count two tables you have to do it slightly differently. There's a few ways of doing it but the way I like is like this:
SELECT
U.NAME
,B.BB_COUNT AS BLOG_COUNT
,C.CC_COUNT AS COMMENT_COUNT
FROM USERS U
LEFT JOIN
(
SELECT
BB.USER_ID
,COUNT(1) AS BB_COUNT
FROM BLOGS BB
GROUP BY BB.USER_ID
) B
ON B.USER_ID = U.ID
LEFT JOIN
(
SELECT
CC.USER_ID
,COUNT(1) AS CC_COUNT
FROM COMMENTS CC
GROUP BY CC.USER_ID
) C
ON C.USER_ID = U.ID
That may or may not be the most efficient way but in my experience it works pretty well and it's simple to understand. It all depends a lot on the number of rows in the tables and indexes etc. Usually the idea is to narrow down rows returned as fast as possible. In this case you'll have two sub queries but they'll end up with only as many rows as you have users basically.
Another thing to note, this will return all users, period. That may not be what you want. You might want only a subset of users. If so this inner select may not be the most efficient because you're doing calculations on users that may not be in the final result, wasting time. However I may be getting off topic.
I agree with the comment that states the table design is not really well constructed yet for you to achieve the counts you want you will need to do subqueries like this:
SELECT
(SELECT count(1) from CarType where user_id=username) as countType,
(SELECT count(1) from CarColor where user_id=username) as countColor,
username from (
SELECT username from Users
) a
As a suggestion for design:
Table Users
Table Cars
Table Colors
Then you have a Relationship table where you have user_id, car_id, color_id
This would be the proper table design for this structure

Set Alias in SELECT with Inner Join

I have a 2 tables:
People table:
id | name | date
1 | Mika | 2013
2 | Rose | 2015
Work table:
id | user_id | work_name | registers
1 | 1 | rugby | 10
2 | 1 | golf | 3
I use this query to join tables:
SELECT * FROM work INNER JOIN people ON work.user_id = people.id WHERE work_name= :work_name
This work it, but I get duplicate column ID and in php when I try to print the camp id, this show the last column id..
I try with this but dont work it:
SELECT *, id AS 'work_id'
FROM work
INNER JOIN people ON work.user_id = people.id
WHERE work_name= :work_name
That is because you are using * in the SELECT that mean will bring all the fields from boths tables instead you can use
SELECT work.id as work_id,
work.name as work_name,
work.date as work_date,
people.id as people_id,
people.name as people_name,
people.date as people_date
Try to manually list all of columns you need to display, for instance
SELECT w.user_id, w.work_name, w.registers, p.name, p.date FROM work as w INNER JOIN people as p ON work.user_id = people.id WHERE work_name= :work_name

SQL - Join 3 tables little confusing

I need a help about a little confusing sql query. I have 3 tables. Names : article, category and category_article table.
In my article table, there are 4 columns which are :
aid -> article id
py -> Year of article
totalPoint -> point of Article
tc -> number of cited of article
In category table, there are 2 columns which are :
cid -> category id
category -> name of category
Lastly, in my category_article table, there are 2 columns which are :
cid -> category id
aid -> article id related with category
In the below, there are sample table inputs.
Article Table
______________________________
| aid | py | totalPoint | tc |
-------------------------------
| 1 | 2014| 30 | 3 |
-------------------------------
| 2 | 2013| 20 | 2 |
-------------------------------
| 3 | 2014| 50 | 10 |
_______________________________
Category Table
__________________
| cid | category |
-------------------
| 1 | Surgery |
-------------------
| 2 | Enginering|
____________________
Category_Article Table
__________________
| cid | aid |
-------------------
| 1 | 3 |
-------------------
| 2 | 5 |
____________________
My purpose is find this output with only one sql query.
Category name, year, totalArticleNumber, totalPoint(desc), id of article(the most cited article at that year and at that category), citedTime (Top 20)
The example output is :
_______________________________________________________________________________________
Category name | year | totalArticleNumber | totalPoint | id of article(best) | citedTime |
________________________________________________________________________________________
Surgery | 2013 | 182 | 5234 | 312 | 22 |
_________________________________________________________________________________________
Engineering | 2014 | 189 | 5000 | 10 | 32 |
I can do this table with java programing language. Like first do some query and after that do it second query.
But i need to do it in one query.
I try to use JOIN , HAVING , GROUP BY , DISTINCT and anaother some sql stuff but i cannot succeed.
Any help is appreciated.
EDIT
For example if there are two article with same time cited value, they are shown together.
Like in first two column, the article 419 and 385 same time cited value so they both in table.
There are two options for me.
First way -> Show only one article and limit 20. So in this way i can see 20 different category-year combination.
Second way -> Show the same value timecited articles but i have to see 20 different category-year combination.
This is a little tricky, because you're trying to do a lot of different things here. I would simply break them down and put them back together. First, if you want to JOIN all the tables together, you can use the following syntax:
SELECT *
FROM article a
JOIN category_article ca ON ca.aid = a.aid
JOIN category c ON c.cid = ca.cid;
Now, this will only show articles that are assigned to a category. In other words, if an article doesn't exist in the category_article table, or a category doesn't exist in the category_article table, it won't appear here.
If you want to get the total number of articles for a category per year, you can use the COUNT(*) aggregate function, and the SUM() function to get the total points for that category, and then group by category and year to get the amounts for that group:
SELECT c.category, a.py, COUNT(*) AS numArticles, SUM(totalPoint) AS totalPoints
FROM article a
JOIN category_article ca ON ca.aid = a.aid
JOIN category c ON c.cid = ca.cid
GROUP BY c.cid, a.py;
Next, getting the best article will have to be done using a subquery. I recommend focusing just on that subquery for a moment. You can write one query to get the MAX tc for each category and year, and then join that with your table to get all the matching conditions, like this:
SELECT c.category, a.*
FROM category c
JOIN category_article ca ON c.cid = ca.cid
JOIN article a ON a.aid = ca.aid
JOIN(
SELECT c.cid, a.py, MAX(a.tc) AS maxCited
FROM category c
JOIN category_article ca ON ca.cid = c.cid
JOIN article a ON a.aid = ca.aid
GROUP BY c.cid, a.py) temp ON temp.cid = c.cid AND temp.py = a.py AND temp.maxCited = a.tc;
Once you have that, you can JOIN that subquery with the query above to show the best article information alongside the other group information.
SELECT temp1.category, temp1.py, temp1.numArticles, temp1.totalPoints, temp2.aid AS bestArticle, temp2.tc AS citedTime
FROM(
SELECT c.category, a.py, COUNT(*) AS numArticles, SUM(totalPoint) AS totalPoints
FROM article a
JOIN category_article ca ON ca.aid = a.aid
JOIN category c ON c.cid = ca.cid
GROUP BY c.cid, a.py) temp1
JOIN(
SELECT c.category, a.*
FROM category c
JOIN category_article ca ON c.cid = ca.cid
JOIN article a ON a.aid = ca.aid
JOIN(
SELECT c.cid, a.py, MAX(a.tc) AS maxCited
FROM category c
JOIN category_article ca ON ca.cid = c.cid
JOIN article a ON a.aid = ca.aid
GROUP BY c.cid, a.py) temp ON temp.cid = c.cid AND temp.py = a.py AND temp.maxCited = a.tc) temp2
ON temp1.category = temp2.category AND temp1.py = temp2.py;
Here is an SQL Fiddle example. I am going to play around with how to use fewer JOINS, but for right now it should give you what you want and since your tables are indexed it shouldn't run too slowly.

How to combine MySQL number of rows of the joined table, including 0?

I have two tables: 'company' and 'order'. The first one contains company info and the second one holds all orders made with a company. (order.company = company.ID).
I am making a query on the first table, for example all companies in the city of New York. I would like to make a join with the order table, so that it immediately shows how many orders for a company was made. I could do this with a simple JOIN query, however, it does not include 0. For example, if a company has no orders yet, it will not show up at all, while it should be in the list with 0 orders.
Desired end result:
----------------------------------------
| ID | Name | ... | Orders |
----------------------------------------
| 105 | Company A | ... | 14 |
| 115 | Company B | ... | 5 |
| 120 | Company C | ... | 0 |
| 121 | Company D | ... | 0 |
----------------------------------------
Thanks in advance!
This is a left join with aggregation:
SELECT c.ID, c.Name, count(o.company) as total
FROM companies c left outer join
orders o
on c.id = o.company
WHERE c.city = 'New York'
GROUP BY c.ID;
In MySQL, it is best to avoid subqueries in the from clause -- where possible -- because the derived table is actually created.
The COUNT() expression is counting the number of matches by counting the number of non-null values in the id field used for the join.
Try this
SELECT com.id,com.name,od.orders FROM compnay AS com
LEFT JOIN orders AS od ON od.company = com.id;
SELECT companies.ID,companies.Name ,orders.total FROM
(SELECT ID,Name FROM company where county ='NEW YORK') companies
LEFT JOIN (SELECT company,COUNT(*) as total FROM order GROUP BY company) orders
ON orders.company = companies.ID

MySQL: Limit Results related to FROM with Joins that have multiple subelements

I habe a main table that i select from and a table with subelements that i select from in a join. Example:
person skill person_to_skill
id | name id | skill id | p_id | s_id
------------ ------------ ----------------
1 | jim 1 | sewing 1 | 1 | 2
2 | peter 2 | cooking 2 | 2 | 1
3 | susan 3 | singing 3 | 2 | 3
4 | kevin 4 | 3 | 1
5 | 3 | 2
6 | 4 | 3
So now we see, sim has only one skill, peter has two and so forth.
Now if i select from person, koin skill and then also join person_to_skill, but i only want two persons. How do i manage to do so without grouping and thereby not getting all the skills?
Shortly: I want to select two persons from "person" with all their skills.
I tried just using LIMIT but that limits the result rows, not the persons.
If i use GROUP BY i only get one skill per person.
Is this possible without a subselect?
Any ideas anyone?
My Approach so far, changed to work with the example, looks like this:
SELECT p.id,p.name,s.skill
FROM person AS p
LEFT JOIN person_to_skill psk ON (psk.p_id = p.id)
LEFT JOIN skill s ON (s.id = psk.s_id)
ORDER BY p.name
LIMIT 0,2
Limit number of persons at very beginning in subquery then join to them other tables as you've already done:
SELECT p.id,p.name,s.skill
FROM (select * from person ORDER BY name LIMIT 0,2) AS p
LEFT JOIN person_to_skill psk ON (psk.p_id = p.id)
LEFT JOIN skill s ON (s.id = psk.s_id)
Added after comment:
If you really can't use subqueries you can do it using two queries. Firstly select users ids:
select id from person ORDER BY name LIMIT 0,2
and then use those ids in next query:
SELECT p.id,p.name,s.skill
FROM person p
LEFT JOIN person_to_skill psk ON (psk.p_id = p.id)
LEFT JOIN skill s ON (s.id = psk.s_id)
WHERE p.id IN (ids from previous query)
You can do something like
SELECT p.id, p.name, group_concat(s.skill separator ',')
and then group by person and limit the number of rows.