Concatenating rows in relation to a JOIN - mysql

Suppose I have a cooking show:
cookingepisodes
id | date
---------------
1 | A
2 | B
3 | C
4 | D
…
This show reviews products in these categories (left) and are linked by the table to the right:
tests testitems
id | name id | episodeid | testid | name
------------ ------------------------------------
1 | cutlery 1 | 1 | 1 | Forks
2 | spices 2 | 2 | 1 | Knives
3 | 4 | 1 | Spoons
4 | 4 | 2 | Oregano
My desired output is this:
showid | testid | testname
4 | 1,2 | cutlery, spices
3 | NULL | NULL
2 | 1 | cutlery
1 | 1 | cutlery
I've tried using this query, and it works as long as I don't need to concatenate the results (when there are two tests on the same episode). Then the join will create multiple rows based on the number of
SELECT DISTINCT e.*, i.testid, t.name AS testname
FROM cookingepisodes AS e
LEFT OUTER JOIN testitems AS i ON i.episodeid = e.id
LEFT OUTER JOIN tests AS t ON i.testid = t.id
ORDER BY e.date DESC
I've also tried something like this, but I can't get it to work because of the outer block reference (e.id):
JOIN (
SELECT GROUP_CONCAT(DISTINCT testid)
FROM testitems
WHERE testitems.episodeid = e.id
) AS i
Any tips on how I can solve this without restructuring the database?

Try this one -
SELECT
ce.id showid,
GROUP_CONCAT(te.testid) testid,
GROUP_CONCAT(t.name) testname
FROM cookingepisodes ce
LEFT JOIN testitems te
ON te.episodeid = ce.id
LEFT JOIN tests t
ON t.id = te.testid
GROUP BY
ce.id DESC;

Related

Join Table B to Table A only if entry in Table B equals entry in Table C

I have 3 tables. clients, sales and potential_sales.
The basic structure is as follows:
Clients Table:
+-----------+-------+----------------+
| client_id | name | address |
+-----------+-------+----------------+
| 1 | john | 12 blue ave |
| 2 | paul | 34 green lane |
| 3 | peter | 69 yellow road |
+-----------+-------+----------------+
Potential Sales Table:
+----------+------------+---------------------+
|product_id | client_id | received_free_promo |
+-----------+------------+---------------------+
| 3 | 1 | 1 |
| 4 | 2 | 0 |
| 5 | 2 | 1 |
+-----------+------------+---------------------+
Sales:
+----------+-----------+-----------+
| sales_id | client_id | product_id |
+----------+-----------+------------+
| 1 | 2 | 4 |
| 2 | 43 | 4 |
| 3 | 2 | 5 |
| 4 | 18 | 93 |
+----------+-----------+------------+
I want to join clients and potential_sales tables ONLY IF
1) received_promo equals 1 AND
2) they actually bought the promo package (i.e. the product_id for the potential sale has an entry into the sales table ). If they didn't eventually buy the free_promo product then I do not want to join the clients and potential_sales table at all. This is important - I can't simply JOIN to figure it out because this is only a small part of a bigger query and I can't afford to JOIN for no reason.
(Here is how I would like it to work. It's mainly pseudo-code to describe what I want to happen)
SELECT
c.*
FROM
clients c
LEFT JOIN potential_sales ps ON ps.client_id=c.id
LEFT JOIN sales ps ON s.product_id=ps.product_id
IF(s.sales_id) JOIN potential_sales ps ON ps.client_id=c.id
How do I do this in MySQL? I haven't come close to a solution. Please help!
Try this:
SELECT A.*, B.product_id, B.received_free_promo
FROM Clients A JOIN
(SELECT * FROM PotentialSales
WHERE received_free_promo=1) B
ON A.client_id=B.client_id
WHERE EXISTS (SELECT 1 FROM Sales C
WHERE A.client_id=C.client_id
AND B.product_id=C.product_id);
See Demo on SQL Fiddle.
What you are missing is the EXISTS clause:
SELECT
C.*,
P.*
FROM
Clients AS C
INNER JOIN PotentialSales AS P ON C.client_id = P.client_id
WHERE
P.received_free_promo = 1 AND
EXISTS (
SELECT
'the client already sold that product'
FROM
Sales AS S
WHERE
S.client_id = C.client_id AND
S.product_id = P.product_id)
Try this..." select * from client as c natural join potential as p join sales as s on p.product_id = s.product_id where received_promo = 1". select * will mention everything from all the 3 tables. You can choose what you want as the result.

how to perform an outer join in mysql

I have a table A that contains tree columns, id, users ids and vehicle id. And a table B that contains vehicleid, and vehicle name.
Table A
---------------------------
| Id | User_id |Vehicle_id|
---------------------------
| 1 | 1 | 2 |
| 2 | 1 | 3 |
| 3 | 1 | 4 |
| 4 | 2 | 2 |
| 5 | 2 | 3 |
| 6 | 4 | 5 |
---------------------------
Table B
-------------------
| Id |Vehicle_name|
-------------------
| 1 | Car |
| 2 | Bike |
| 3 | Plane |
| 4 | Boat |
| 5 | Rocket |
-------------------
Given a user id, I need to get all vehicle names, that doesn't match with table A. I've tried Outer joins, but I can't manage to do get the info that i need.
For example: Given user id 1, the query should return Car and Rocket.
thanks in advance
This is simple enough using not in or not exists:
select b.*
from b
where not exists (select 1
from a
where a.vehicle_id = b.id and a.user_id = #a_user_id
);
I also thought of using a cross join and was able to get the output in case you are more comfortable with join logic.
SELECT CJOIN.USER_ID, CJOIN.VEHICLE_ID, CJOIN.VEHICLE_NAME
FROM
(SELECT DISTINCT A.USER_ID, B.ID AS VEHICLE_ID, B.VEHICLE_NAME FROM TABLE_A A CROSS JOIN TABLE_B B) CJOIN
LEFT JOIN
TABLE_A D
ON CJOIN.USER_ID = D.USER_ID AND CJOIN.VEHICLE_ID = D.VEHICLE_ID
WHERE D.USER_ID IS NULL AND D.VEHICLE_ID IS NULL;
First, I got all possible combinations of USER_ID x VEHICLE_ID by a cross join and used this table in a left join to pull records for which there is no match.

MySQL count rows in a joint table

I would like to count the number of rows or in this case the number of products which are in the same category and return zero if there are no products in the category.
The tables in my query looking like this:
category category_lang media
------------ ------------- ---------
category_id | published category_id | name | alias media_id | category_id
----------------------- -------------------------- ----------------------
1 | 1 1 | One | one 1 | 1
2 | 1 2 | Two | two 2 | 2
3 | 1 3 | Three| three 3 | 3
media_lang product_category
------------------------------- ----------------
media_id | url | file_name product_id | category_id
------------------------------- ------------------------
1 | /images/ | file1.jpg 1 | 1
2 | /images/ | file2.jpg 2 | 1
3 | /images/ | file3.jpg 3 | 2
and I would like a result like this:
category_id | category_name | alias | media_id | url | file_name | count
1 | One | one | 1 | /images/ | file1.jpg | 2
2 | Two | two | 2 | /images/ | file2.jpg | 1
3 | Three | three | 3 | /images/ | file3.jpg | 0
My query currently looks like this
SELECT
c.`category_id`,
ca.`name`,
ca.`alias`,
m.`media_id`,
ma.`url`,
ma.`file_name`,
COUNT(p.`product_id`) AS `count`
FROM `category` c
LEFT JOIN `category_lang` ca ON (c.`category_id` = ca.`category_id`)
LEFT JOIN `media` m ON (c.`category_id` = m.`category_id`)
LEFT JOIN `media_lang` ma ON (m.`media_id` = ma.`media_id`)
LEFT JOIN `product_category` p ON (c.`category_id` = p.`category_id`)
WHERE c.`published` = 1
ORDER BY ca.`name`
my logic is obviously wrong because this query will return this:
category_id | category_name | alias | media_id | url | file_name | count
1 | One | one | 1 | /images/ | file1.jpg | 3
How can I achieve the desired result?
I think the most painless (and sane) way to approach this would be to just aggregate the product counts for each category from the product_category table in a separate subquery, and then just join this to what you already have:
SELECT
c.category_id,
ca.name,
ca.alias,
m.media_id,
ma.url,
ma.file_name,
COALESCE(t.cat_count, 0) AS cat_count
FROM category c
LEFT JOIN category_lang ca
ON c.category_id = ca.category_id
LEFT JOIN media m
ON c.category_id = m.category_id
LEFT JOIN media_lang ma
ON m.media_id = ma.media_id
LEFT JOIN
(
SELECT category_id, COUNT(*) AS cat_count
FROM product_category
GROUP BY category_id
) t
ON c.category_id = t.category_id
WHERE c.published = 1
ORDER BY ca.name
Note here that your product_category table does not have any category entries with no products. This is not a problem, because in the LEFT JOIN we can simply treat a NULL count as being zero. A NULL value would occur if a given category did not match to anything in the subquery.
Seems to me that it is where you are doing the count and that you doing have the criteria for doing the count. Adding an extra condition where product category id equals category id in your category table should fix the incorrect count.
SELECT
c.`category_id`,
ca.`name`,
ca.`alias`,
m.`media_id`,
ma.`url`,
ma.`file_name`,
COUNT(p.`product_id`) AS `count`
FROM `category` c
LEFT JOIN `category_lang` ca ON (c.`category_id` = ca.`category_id`)
LEFT JOIN `media` m ON (c.`category_id` = m.`category_id`)
LEFT JOIN `media_lang` ma ON (m.`media_id` = ma.`media_id`)
LEFT JOIN `product_category` p ON (c.`category_id` = p.`category_id`)
WHERE (c.`published` = 1) and (c.`category_id` = p.`category_id`)
ORDER BY ca.`name`

How to write this basic sql statement

So I have 4 tables that are connected via foreign keys namely result, position, student, candidates
What i need to achieve is this:
output:
------------------------
s_fname | count(c_id)
-----------------------
Mark | 2 -> President
France| 2 -> President
.. to count as to how many times a c_id have been repeated in the table "result" which is also filtered by pos_id from the "candidates" table
Below is my code which lacks the counting part:
select s_fname
from results, candidates, student, positioning
where results.c_id = candidates.c_id
AND student.sid = results.sid
AND candidates.pos_id = positioning.pos_id
AND positioning.pos_id = 1
Group BY results.sid;
..which I know lacks a lot of things ...
Thanks
it seems very complex to me but i know there are gurus here who can achieve this,
result table
---------------------
| r_id | sid | c_id |
---------------------
1 | 1 | 1
2 | 1 | 2
3 | 1 | 4
4 | 2 | 1
5 | 2 | 2
6 | 2 | 4
7 | 3 | 3
8 | 3 | 2
9 | 5 | 3
10 | 5 | 2
----------------------
student table
----------------
| s_id| s_fname|
----------------
1 | Mark
2 | Jorge
3 | France
4 | James
--------------------
Candidates Table
------------------------
| c_id | sid | pos_id
------------------------
1 | 1 | 1
2 | 2 | 2
3 | 4 | 3
4 | 3 | 1
5 | 5 | 2
----------------------
positioning Table
-----------------------
| pos_id | po_name |
-----------------------
1 | President
2 | Vice President
3 | Secretary
4 | Treasurer
This is untested, but should return your intended result.
What it does is joins all of your tables on the related foreign keys, effectively giving a wide table of all of your columns. Then we limit on the candidates that are running for the President position. Since we need to group because of the count aggregate we group on the name. The count should reflect the number of votes they got, because there is a one to many relationship to the result table.
SELECT s_fname, Count(*)
FROM studentTable st
INNER JOIN Candidates c On c.sid = st.s_ID
INNER JOIN positioning p on c.pos_ID = p.pos_ID
INNER JOIN results r on st.s_ID = r.s_ID
WHERE po_Name = "President"
GROUP BY s_Fname
Due to a misunderstanding of the intended joins, the following query should show the appropriate results.
SELECT s_fname, Count(*)
FROM studentTable st
INNER JOIN Candidates c On c.sid = st.s_ID
INNER JOIN positioning p on c.pos_ID = p.pos_ID
INNER JOIN results r on c.c_ID = r.c_ID
WHERE po_Name = "President"
GROUP BY s_Fname
Code:
SELECT s_fname AS [Student Name], COUNT(A.c_id) AS [Count], po_name AS [Position]
FROM results AS A INNER JOIN candidates AS B ON A.c_id=B.c_id
INNER JOIN student AS C ON A.sid=C.sid
INNER JOIN positioning AS D ON B.pos_id=D.pos_id
WHERE B.pos_id = 1
GROUP BY s_fname
SELECT s.s_fname, COUNT(*), p.po_name
FROM students s
JOIN candidates c ON c.s_id = s.s_id
JOIN positioning p ON c.pos_id = p.pos_id
JOIN results r ON s.s_id = r.s_id
WHERE p.pos_id = 1
GROUP BY s.s_id
http://sqlfiddle.com/#!2/9472a/17

Mysql join on 3 tables output

I am learning joins and have the following tables.
Student
| ID | NAME |
-------------
| 1 | A |
| 2 | B |
| 3 | C |
| 4 | D |
Pass
| ID | MARKS |
--------------
| 2 | 80 |
| 3 | 75 |
Fail
| ID | MARKS |
--------------
| 1 | 25 |
| 4 | 20 |
The output I want is this:
| NAME | MARKS |
----------------
| B | 80 |
| C | 75 |
| A | 25 |
| D | 20 |
I wrote a query like this:
select s.id,s.name,p.marks from student s
left join pass p on s.id=p.id
left join (select f.marks,f.id from fail f ) as nn on s.id=nn.id
order by marks desc;
The output I got is this:
| id | name | Marks|
--------------------
| 1 | B | 80 |
| 2 | C | 75 |
| 3 | A | Null |
| 4 | D | NUll |
Cant figure out why Null is coming. Any pointers?
You can use CASE statement for that:
SELECT Name,
CASE WHEN P.Marks IS NULL THEN f.Marks ELSE P.Marks END AS Marks
FROM Student s
LEFT JOIN Pass p ON s.ID = p.ID
LEFT JOIN Fail f ON s.ID = f.ID
ORDER BY Marks DESC;
Or you can also use IF statement:
SELECT Name,
IF(P.Marks IS NULL, F.Marks, P.Marks) AS Marks
FROM Student s
LEFT JOIN Pass p ON s.ID = p.ID
LEFT JOIN Fail f ON s.ID = f.ID
ORDER BY Marks DESC;
Output
| NAME | MARKS |
----------------
| B | 80 |
| C | 75 |
| A | 25 |
| D | 20 |
See this SQLFiddle
To learn more about JOINs see: A Visual Explanation of SQL Joins
You select only the passed marks, this is the reason of null-s appears near falied results.
If you want to select the failed marks you can use IF condition
select s.id,s.name,IF(p.marks = null, nn.marks, p.marks) as markss
from student s
left join pass p on s.id=p.id
left join fail nn on s.id=nn.id
order by markss desc;
Or you can use union of the passed and failed results.
select s.id,s.name, u.marks
from student s
left join ( (SELECT * FROM pass) UNION (SELECT * FROM fail) ) as n ON n.id = s.id
order by marks desc;
You need to understand how the different joins work to understand why you receive NULL for the marks column.
Take a look here:A Visual Explanation of SQL Joins
The relevant example for you is:
LEFT OUTER JOIN:
SELECT * FROM TableA
LEFT OUTER JOIN TableB
ON TableA.name = TableB.name
id name id name
-- ---- -- ----
1 Pirate 2 Pirate
2 Monkey null null
3 Ninja 4 Ninja
4 Spaghetti null null
The Null values you received for the marks column are rows that have no match in the left joined tables.
(the left part of the Venn diagram) the values that does have a value are the cross section between the tow groups of the Venn Diagram.
specifics for your example:
select s.id,s.name,p.marks
from student s
left join pass p on s.id=p.id
left join (select f.marks,f.id from fail f ) as nn on s.id=nn.id
order by marks desc;
The output i got is this:
id | name | Marks
-------------------
1 | B | 80
2 | C | 75
3 | A | Null
4 | D | NUll
This will return all student rows when.
students that have a passing gtade will display the grade and thous who don't will display null.
Try the below Query, use COALESCE
select s.id,s.name,COALESCE(p.marks , nn.marks) as marks
from student s
left join pass p on s.id=p.id
left join fail nn on s.id=nn.id
order by marks desc;
SQL Fiddle