Filtering data in left join - mysql

Here is some data:
record
-------------------------------------------------
| id | name |
-------------------------------------------------
| 1 | Round Cookie |
| 2 | Square Cookie |
| 3 | Oval Cookie |
| 4 | Hexagon Cookie |
-------------------------------------------------
record_field_data
----------------------------------------------
| id | record_id | data | type_id |
----------------------------------------------
| 1 | 1 | White | 1 |
| 2 | 1 | Round | 2 |
| 3 | 2 | Green | 1 |
| 4 | 2 | Square | 2 |
| 5 | 3 | Blue | 1 |
| 6 | 3 | Oval | 2 |
| 7 | 4 | Hexagon | 2 |
----------------------------------------------
record_type_field
-------------------------------------------------
| id | data_type |
-------------------------------------------------
| 1 | Color |
| 2 | Shape |
-------------------------------------------------
I am trying to get a list of all records left joined with the record_field_data of type "Color". This needs to be a left join because there may not be record_field_data of a given type, and I still want the record if the case.
This is the query I have come up with but it is returning a left join with ALL record_field_data and not just the specific ones I want.
SELECT record.id AS id, recordfield.data, recordtype.field_name
FROM record
LEFT JOIN record_field_data AS recordfield ON (record.id = recordfield.record_id)
LEFT JOIN record_type_field AS recordtype ON (recordfield.type_id = recordtype.id AND recordtype.data_type = 'Color');
I could do this with a subquery in the JOIN but I can't use a subquery. I have to translate this to HQL and subqueries are not supported in HQL for joins.
The result I am looking for is records ordered by the record_field_data where record_type_field.data_type is 'Color'. Note that "Hexagon cookie" doesn't have a color defined, I don't know if it should be at the top or bottom at this point. Either way will work.
-------------------------------------------------
| id | name |
-------------------------------------------------
| 3 | Oval Cookie |
| 2 | Square Cookie |
| 1 | Round Cookie |
| 4 | Hexagon Cookie |
-------------------------------------------------

SELECT r.id, r.name
FROM record r
JOIN record_type_field rf
ON rf.data_type = 'Color'
LEFT JOIN
record_type_data rd
ON rd.record_id = r.id
AND rd.type_id = rf.id
ORDER BY
rd.data

Have you tried using the SQL 'IN' clause.
select record.id as id, recordfield.data, recordtype.field_name
from record
left join record_field_data recordfield ON record.id = recordfield.record_id
WHERE id IN (SELECT id FROM record_type_field where data_type='Color');
The IN clause allows you to specify a list condition. So the subquery gets all of the ids where the type is "Color", and you are then doing the join and selecting all records from that join that have an id in the list of ids corresponding to the type "Color".

You have to change the second join to a INNER JOIN.
If you use two LEFT JOINs you are selecting all the records from record AND from record_field_data.

Related

How can I get a total count of items?

I'm trying to figure out a MYSQL string and my noob-ness is getting in my way. I'm trying to count the total number of teams per phase.
Tables to consider:
phases
+----+------------+
| id | phase_name |
+----+------------+
| 1 | start |
| 2 | middle |
| 3 | end |
| 4 | finish |
+----+------------+
teams
+----+-----------+----------+
| id | team_name | phase_id |
+----+-----------+----------+
| 1 | team1 | 2 |
| 2 | team2 | 3 |
| 3 | team3 | 3 |
| 4 | team4 | 4 |
| 4 | team5 | 3 |
+----+-----------+----------+
Desired result
+----------+------------+-----------+
| phase_id | phase_name | tot_teams |
+----------+------------+-----------+
| 1 | start | NULL |
| 2 | middle | 1 |
| 3 | end | 3 |
| 4 | finish | 1 |
+----------+------------+-----------+
I've tried:
SELECT
T.phase_id, P.phase_name, COUNT(*) AS tot_teams
FROM
teams T
LEFT JOIN
phases P ON P.id = T.phase_id
GROUP BY
phase_id;
but that only shows the affected phase_id's...and I'm hoping to get ALL phase_id's in a table. I also tried:
SELECT
P.phase_name, T.phase_id, COUNT(*)
FROM
teams T
RIGHT JOIN
phases P on P.`id` = T.`phase_id`
GROUP BY
P.id
but that shows invalid data. (For example, phase_id has a qty of 1 but doesn't show up in the teams table.
Can you point me in the right direction?
Thanks!
The RIGHT JOIN is correct, but you need to use COUNT(T.phase_id) instead of COUNT(*). Otherwise, you're counting the row containing NULL that's generated for the phase with no teams.
Most people prefer to use LEFT JOIN, putting the master table first.
SELECT P.phase_name, P.phase_name, COUNT(T.phase_id)
FROM phase AS P
LEFT JOIN teams AS T ON P.id = T.phase_id
GROUP BY P.id

mysql request using join and group_concat

I have three tables "names", "groups", "tasks"
"names"
| nid | name |
| 1 | John |
| 2 | Jim |
| 3 | Jerry |
"groups"
| gid | nid |
| 1 | 1 |
| 1 | 2 |
| 2 | 2 |
| 2 | 3 |
"tasks"
| tid | gid |
| 1 | 2 |
| 2 | 1 |
I want to get a list of names, that belong to a task, like this:
| tid | names |
| 1 | Jim,Jerry |
| 2 | John,Jim |
I successfully tried:
SELECT t.tid,n.name
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
This works so far, but the names are in single outputs so I tried to use GROUP_CONCAT(), but this does not work. I just get the first task with the wrong names!?!?
SELECT t.tid,GROUP_CONCAT(n.name)
FROM tasks t
LEFT JOIN groups g ON g.gid=t.gid
LEFT JOIN names n ON n.nid=g.nid
I have never used GROUP_CONCAT() before. It works well, when using it in a single SELECT without joins. A short explanation what I did wrong, would be nice.
When using group_concat you need to group your results.
Add group by t.id at the end of your query.

Sub selecting a dynamic number of rows as columns in a single query

Hey guys I have the following 3 tables:
person event attendance
| id | name | age | | id | name | added | | id | pId | eId | attended |
|-----------------| |---------------------------| |---------------------------|
| 5 | bob | 20 | | 0 | wedding | 01.01.2013 | | 0 | 5 | 0 | YES |
| 6 | ryan | 25 | | 1 | party | 01.01.2013 | | 1 | 5 | 1 | NO |
So "person" and "attendance" are growing dynamically and "event" is a static table to hold the event names. I am trying to achieve the following result:
| id | name | age | wedding | party |
|-----------------------------------|
| 0 | bob | 20 | YES | NO |
| 1 | ryan | 25 | NO | NO |
I should propably mention that my live database is quite big (I have like 11 static event names, so this example would end up with 14 columns) and there is also another table (simulating flags) in between so performance is quite important.
I wonder if this is even possible with one single query and how to exactly achieve it?
SELECT p.id, p.name, p.age,
MAX(IF(e.name='wedding', a.attended, NULL)) AS `wedding`,
MAX(IF(e.name='party', a.attended, NULL)) AS `party`
FROM person p
JOIN attendance a ON a.pId = p.id
JOIN event e ON a.eId = e.id
GROUP BY p.id;
You need the list of event names before you can write this query.
Re your comment, I see what you mean, there are no rows for ryan attending any event, so that person doesn't show up in the result. The JOIN only returns person rows that have one or more matching attendance rows.
The way to solve this is with a LEFT OUTER JOIN. That returns the person row whether there are matching rows or not.
SELECT p.id, p.name, p.age,
COALESCE(MAX(IF(e.name='wedding', a.attended, NULL)), 'NO')AS `wedding`,
COALESCE(MAX(IF(e.name='party', a.attended, NULL)), 'NO') AS `party`
FROM person p
LEFT OUTER JOIN attendance a ON a.pId = p.id
LEFT OUTER JOIN event e ON a.eId = e.id
GROUP BY p.id;
+----+------+------+---------+-------+
| id | name | age | wedding | party |
+----+------+------+---------+-------+
| 5 | bob | 20 | YES | NO |
| 6 | ryan | 25 | NO | NO |
+----+------+------+---------+-------+
PS: Shouldn't the id column in your sample result have values 5 and 6?

How can I join two tables, keeping rows that do not meet the JOIN condition?

ticket
+----------+--------+
| ticketID | assign |
+----------+--------+
| 1015 | NULL |
| 1020 | James |
| 1021 | Nick |
+----------+--------+
staffinfo
+---------+-------+
| staffID | staff |
+---------+-------+
| 1 | Jane |
| 2 | James |
| 3 | Nick |
| 4 | Cole |
+---------+-------+
SELECT staff,COUNT(*) as count FROM staffinfo,ticket
WHERE ticket.assign = staffinfo.staff
GROUP BY staff
result:
+-------+-------+
| staff | count |
+-------+-------+
| James | 1 |
| Nick | 1 |
+-------+-------+
Works fine, but infact i need smthing like:
+-------+-------+
| staff | count |
+-------+-------+
| James | 1 |
| Nick | 1 |
| Jane | 0 |
| Cole | 0 |
+-------+-------+
COUNT doesnt count records that arent in the table, and since i just started learning SQL, i wanna ask if theres a way to count as the above result?
you should be using LEFT JOIN
SELECT a.staff, COUNT(b.assign) as count
FROM staffinfo a
LEFT JOIN ticket b
ON b.assign = a.staff
GROUP BY a.staff
SQLFiddle Demo
To fully gain knowledge about joins, kindly visit the link below:
Visual Representation of SQL Joins
Use LEFT JOIN
The LEFT JOIN keyword returns all rows from the left table (table_name1), even if there are no matches in the right table (table_name2).
SELECT staffinfo.staff, count(ticket.assign)
FROM staffinfo
LEFT JOIN ticket
ON ticket.assign =staffinfo.staff
GROUP BY staffinfo.staff
The LEFT JOIN keyword returns all the rows from the left table (staffinfo), even if there are no matches in the right table (ticket).

MySQL: Using NULL as wildcard in JOIN statement

I have a table containing perfectly defined items and a second table with potentially vague/greedy orders as NULL would require all available values for this parameter.
items
+-----------------------+
| item_id | color | size |
|---------+-------+------|
| 1 | blue | 8 |
| 2 | red | 6 |
| 3 | green | 7 |
| 4 | black | 6 |
+------------------------+
orders
+-------------------------+
| order_id | color | size |
|----------+-------+------|
| 1 | red | 6 |
| 2 | green | 8 |
| 3 | NULL | 6 |
| 4 | blue | NULL |
| 5 | NULL | NULL |
+-------------------------+
Is there an efficient way to generate a complete list of items needed to fill all orders?
+--------------------+
| order_id | item_id |
|----------+---------|
| 1 | 2 |
| 3 | 2 |
| 3 | 4 |
| 4 | 1 |
| 5 | 1 |
| 5 | 2 |
| 5 | 3 |
| 5 | 4 |
+--------------------+
It seems to me like an INNER JOIN should be able to do this, but something like this obviously doesn't consider the possibility of NULL values as greedy wildcards in the orders table:
SELECT order_id, item_id
FROM orders
INNER JOIN items ON orders.color = items.color AND orders.size = items.size
Any ideas?
Try the following:
SELECT order_id, item_id
FROM orders
INNER JOIN items ON (orders.color IS NULL OR orders.color = items.color)
AND (orders.size IS NULL OR orders.size = items.size)
Let me know if that helps, or if I misunderstood the question.
If you rewrite the conditions for the JOIN you can get the desired result:
SELECT order_id, item_id
FROM orders
JOIN items
ON ((orders.color = items.color OR orders.color IS NULL)
AND (orders.size = items.size OR orders.size IS NULL))
However, the orders table should probably look more like the result of this query than the current orders table.
You could use the IFNULL function for this:
SELECT order_id, item_id
FROM orders
JOIN items ON IFNULL(orders.color, items.color) = items.color
AND IFNULL(orders.size, items.size) = items.size
If the value in orders is null, then it'll use the value from items (and thus match).