MySQL outer join substitute - mysql

I am working on a game inventory management system and would like to display the owner's restock wish list and a count of customer buy reservations for each game in a single table. I wrote a query that I thought was working, but then I noticed that it actually omits any games for which there are reservations but that aren't initially in the restock wish list. The query is below:
SELECT rwl.*, g.gameName, coalesce(payYes, 0) payYes, coalesce(payNo, 0) payNo FROM RestockWishList AS rwl, Games AS g
LEFT JOIN
(SELECT gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes, COUNT(if(prepaid='No', 1, NULL)) payNo FROM ReservationsBuy GROUP BY gameID) AS res
ON res.gameID = g.gameID
WHERE rwl.gameID = g.gameID;
Query results:
gameID,
quantity,
gameName,
payYes,
payNo
1,
4,
A Castle for all Seasons,
0,
0
2,
2,
A Few Acres of Snow,
0,
0
18,
4,
Alhambra,
0,
0
54,
2,
Big Boggle,
2,
0
Apparently the solution to this problem is to use FULL OUTER JOIN instead of LEFT JOIN, but MySQL doesn't support that function. I have spent hours trying to translate it to a UNION structure, but can't quite get it to work correctly. This is as close as I've got:
SELECT rwl.*, res.gameID, res.payYes, res.payNo FROM RestockWishList rwl
LEFT JOIN
(SELECT gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes, COUNT(if(prepaid='No', 1, NULL)) payNo FROM ReservationsBuy GROUP BY gameID) AS res
ON res.gameID = rwl.gameID
UNION
SELECT rwl.*, res.gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes, COUNT(if(prepaid='No', 1, NULL)) payNo FROM ReservationsBuy res
LEFT JOIN RestockWishList rwl ON rwl.gameID = res.gameID;
Query results:
gameID, quantity, gameID, payYes, payNo
1, 4, NULL, NULL, NULL
2, 2, NULL, NULL, NULL
18, 4, NULL, NULL, NULL
54, 2, 54, 2, 0
NULL, NULL, 30, 3, 1
(Sorry, I don't know how to nicely format query table results in StackOverflow.)
I want the query to display as I originally wrote it, just with the missing values from ReservationsBuy. Specific help please?
Tables:
CREATE TABLE IF NOT EXISTS RestockWishList (
gameID INT(6),
quantity INT(3) NOT NULL,
PRIMARY KEY (gameID),
FOREIGN KEY (gameID) REFERENCES Games(gameID) ON UPDATE CASCADE ON DELETE CASCADE);
CREATE TABLE IF NOT EXISTS ReservationsBuy (
gameID INT(6),
customerEmail VARCHAR(25) NOT NULL,
customerName VARCHAR(25) NOT NULL,
dateReserved DATETIME NOT NULL, #date customer files game reservation
datePurchased DATETIME, #date Board and Brew restocks game
dateClaimed DATETIME, #date customer physically claims game
prepaid ENUM('Yes', 'No') NOT NULL,
PRIMARY KEY (gameID, customerEmail),
FOREIGN KEY (gameID) REFERENCES Games (gameID) ON UPDATE CASCADE ON DELETE CASCADE);
Sample data:
RestockWishList:
gameID,
quantity
1,
4
2,
2
18,
4
54,
2
ReservationsBuy:
gameID,
customerEmail,
customerName,
dateReserved,
datePurchased,
dateClaimed,
prepaid
30,
wonder#woman.com,
Diana,
2015-04-24 14:46:05,
NULL,
NULL,
Yes
54,
boggie#marsh.com,
boggie,
2015-04-24 14:43:32,
NULL,
NULL,
Yes
54,
manny#second.com,
manny,
2015-04-27 19:48:22,
NULL,
NULL,
Yes
43,
old#mom.com,
grandma,
2015-04-23 22:32:03,
NULL,
NULL,
No
Expected output:
gameID, quantity, gameName, payYes, payNo
1, 4, A Castle for all Seasons, 0, 0
2, 2, A Few Acres of Snow, 0, 0
18, 4, Alhambra, 0, 0
30, 0, Arkham Horror, 1, 0
43, 0, Bananagrams, 0, 1
54, 2, Big Boggle, 2, 0
(Games table not particularly important for this query. Only relevance is that both ReservationsBuy and RestockWishList are connected to Games by gameID)

I think maybe you want a query like this - not a full outer join:
select q.id, q.name, q.reservations, ifnull(q2.wishcount, 0) wishcount, q.payYes, q.payNo
from (
select g.*, count(rb.gameid) reservations, count(case when prepaid = 'Yes' then 1 end) payYes, count(case when prepaid = 'No' then 1 end) payNo
from games g
left join reservationsbuy rb
on g.id = rb.gameid
group by g.id
) q
left join (
select g.id, sum(quantity) wishcount
from games g
left join restockwishlist rwl
on g.id = rwl.gameid
group by g.id
) q2
on q.id = q2.id;
There's a demo here, but the gist of it is, for each game in the game table, it will give you the total number of reservations, the quantity from the wish list, and we use a conditional count to provide the count ofprepaid = yes, or prepaid = no. Effectively it is just joining together two small queries on the shared gameid.
If you want this to include filtering by date etc, you might need to be a bit more explicit about how you want the results to work, or display

You're on the right track with using a FULL OUTER JOIN, you just have the implementation incorrect.
A FULL OUTER JOIN in MySQL can be thought of as the UNION of a LEFT JOIN and a RIGHT JOIN. In your query, you're trying to approximate this by treating the RIGHT JOIN part of the logic as an inverse LEFT JOIN of the two tables, but your first part doesn't work because it's not a subselect with the same GROUP BY sequence as your first LEFT JOIN.
Simplest thing to do is simply take your first LEFT JOIN query stanza, copy it to the second stanza, and replace LEFT JOIN with RIGHT JOIN, then link the results to your games table, like so:
SELECT g.gameID, IFNULL(q.quantity, 0) AS quantity, g.gameName,
IFNULL(q.payYes, 0) AS payYes, IFNULL(q.payNo, 0) AS payNo
FROM games g
INNER JOIN (
SELECT IFNULL(rwl.gameID, res.gameID) AS gameID, rwl.quantity,
res.payYes, res.payNo
FROM RestockWishList rwl
LEFT JOIN (
SELECT gameID, COUNT(if(prepaid='Yes', 1, NULL)) payYes,
COUNT(if(prepaid='No', 1, NULL)) payNo
FROM ReservationsBuy
GROUP BY gameID
) AS res ON res.gameID = rwl.gameID
UNION
SELECT IFNULL(rwl.gameID, res.gameID) AS gameID, rwl.quantity,
res.payYes, res.payNo
FROM RestockWishList rwl
RIGHT JOIN (
SELECT gameID, COUNT(IF(prepaid='Yes', 1, NULL)) payYes,
COUNT(IF(prepaid='No', 1, NULL)) payNo
FROM ReservationsBuy
GROUP BY gameId
) AS res ON res.gameID = rwl.gameID
) AS q ON g.gameID = q.gameID
SQL Fiddle Results

ok. So we now that always exists a record in game table, right?
Then start your FROM with this table, then you just have to do a LEFT JOIN for each table as follow:
SELECT
rwl.*
, g.gameName
, coalesce(payYes, 0) payYes
, coalesce(payNo, 0) payNo
FROM
Games AS g LEFT JOIN
RestockWishList AS rwl ON rwl.gameID = g.gameID LEFT JOIN
(
SELECT
gameID
, COUNT(if(prepaid='Yes', 1, NULL)) payYes
, COUNT(if(prepaid='No', 1, NULL)) payNo
FROM
ReservationsBuy
GROUP BY gameID) AS res ON res.gameID = g.gameID
;
As you can say, the only changes were: Start the FROM with Games table and use LEFT JOIN, also remove the condition from WHERE and put it in the LEFT JOIN

Related

SQL - How can I use UNION to join three INNER JOIN results into a single results table?

For the table:
assistants_rating(
id INT PRIMARY KEY auto_increment,
assistant_id INT(11),
rating INT(1)
)
And another table of assistants which contains a name. My query goes as follows:
(SELECT assistants.name AS assist_name , count(rating) AS OneStar FROM assistants_rating
INNER JOIN assistants on assistants.assistant_id = assistants_rating.assistant_id WHERE rating = 1)
UNION
(SELECT assistants.name AS assist_name , count(rating) AS TwoStar FROM assistants_rating
INNER JOIN assistants on assistants.assistant_id = assistants_rating.assistant_id WHERE rating = 2)
UNION
(SELECT assistants.name AS assist_name , count(rating) AS ThreeStar FROM assistants_rating
INNER JOIN assistants on assistants.assistant_id = assistants_rating.assistant_id WHERE rating = 3)
GROUP BY assistants.name,OneStar,TwoStar,ThreeStar;
Let's say I have the assistants named Bob and Sophie, the query should return:
assist_name OneStar TwoStar ThreeStar
Bob 9 18 52
Sophie 15 8 61
But instead I'm getting a SQL syntax error, which is weird because when I do each of them alone , they work properly and that leads me to believe that my syntax is good. Not only that, but my queries with the UNION work if I only do TwoStar and ThreeStar together but not with OneStar and TwoStar.
I'm so confused; why doesn't this work?
UNION doesn't help here anyway. It does a different thing.
The query you need is something along the lines of:
SELECT
assistants.name AS assist_name,
SUM(IF(rating = 1, 1, 0)) AS OneStar,
SUM(IF(rating = 2, 1, 0)) AS TwoStar,
SUM(IF(rating = 3, 1, 0)) AS ThreeStar
FROM assistants_rating
INNER JOIN assistants ON assistants.assistant_id = assistants_rating.assistant_id
GROUP BY assistants.name
You can use CASE instead of Union
SELECT assistants.name AS assist_name,
SUM(CASE WHEN rating =1 THEN 1 ELSE 0 END) AS OneStar,
SUM(CASE WHEN rating =2 THEN 1 ELSE 0 END) AS TwoStar,
SUM(CASE WHEN rating =3 THEN 1 ELSE 0 END) AS ThreeStar
FROM assistants_rating
INNER JOIN assistants on assistants.assistant_id = assistants_rating.assistant_id
GROUP BY assistants.name

Order by joined table through table

my mySQL (pun intended) is a bit rusty. I am trying to join a table through another table.
carparks has many clients
clients has many cars
This is the query
select `carparks`.* from `carparks`
left join `clients` on `carparks`.`carpark_id` = `clients`.`carpark_id`
left join `cars` on `clients`.`client_id` = `cars`.`client_id`
where `carparks`.`carpark_id` in (1, 3, 8, 33, 34, 38, 39)
order by `cars`.`created_at` desc
As you can see I am trying to order by the created_at column of cars, the above query though returns duplicated carparks for each of the cars within the carpark.
What I am looking at is to return only those carparks with the ids in the WHERE IN clause, simply ordered by the created_at column of the cars table.
Thanks
You can use aggregation in your order by clause on max created date from cars table
SELECT cp.*
FROM `carparks` cp
LEFT JOIN `clients` cl ON cp.`carpark_id` = cl.`carpark_id`
LEFT JOIN `cars` c ON cl.`client_id` = c.`client_id`
WHERE cp.`carpark_id` IN (1, 3, 8, 33, 34, 38, 39)
GROUP BY cp.`carpark_id`
ORDER BY MAX(c.`created_at`) DESC
Reduce the wanted dates to one per carpark before joining back to carparks. Note if a carpark does have no cars than a left join is logical, however I expect every carpark (that is open for business) will have cars, so that left join might not be needed.
SELECT `carparks`.*
FROM `carparks`
LEFT JOIN (
SELECT
`carparks`.`carpark_id`
, max(`cars`.`created_at`) max_car_created
FROM `clients`
INNER JOIN `cars` ON `clients`.`client_id` = `cars`.`client_id`
GROUP BY
`carparks`.`carpark_id`
) d ON `carparks`.`carpark_id` = d.`carpark_id`
WHERE `carparks`.`carpark_id` IN (1, 3, 8, 33, 34, 38, 39)
ORDER BY max_car_created DESC
Reduce the number of carparks and clients before doing the joins, this will reduce the execution time of the query.
SELECT A.* FROM (SELECT * FROM `carparks` WHERE `carpark_id` in
(1, 3, 8, 33, 34, 38, 39)) A LEFT JOIN
(SELECT `carpark_id`, `client_id` FROM `clients` WHERE `carpark_id`
in (1, 3, 8, 33, 34, 38, 39)) B ON A.`carpark_id`=B.`carpark_id` LEFT JOIN
`cars` C ON B.`client_id` = C.`client_id`
GROUP BY A.`carpark_id`
ORDER BY MAX(C.`created_at`) DESC

optimizing a query using union left joins and inner joins

I have a query that I want to optimize. Both unions executed separatley run fine, however as soon as I include inner join it takes up to 57 seconds. How do I solve this. My query is as follows
SELECT
p.PROJID,
p.StartDate,
o.ORDERNO,
p.PROJCODE,
p.PROJECT,
cat.type AS CATEGORY,
p.AREA,
p.STATE,
p.COUNTRY,
p.VALUE,
p.PROCESSOR,
p.PROJINFO,
p.NES,
p.SPECSALE,
p.OFFICE,
p.DEPTCODE,
p.INTERNLCHG,
p.INTERCOCHG,
p.LORM,
p.PERCENT,
d.COMPANY,
CONCAT(
d.LASTNAME,
", ",
d.FIRSTNAME
) AS Contact
FROM
(
(
SELECT
*
FROM
(
SELECT
`clients`.`CLIENTID` AS `CLIENTIDA`,
`clients`.`COMPANY` AS `COMPANY`
FROM
`hdb`.`clients`
UNION
SELECT
`accounts`.`id` AS `CLIENTIDA`,
`accounts`.`name` AS `COMPANY`
FROM
`sugarcrm`.`accounts`
) AS a
INNER JOIN (
SELECT
`hdb`.`contacts`.`CONTACTID` AS `CONTACTID`,
`hdb`.`contacts`.`CLIENTID` AS `CLIENTIDC`,
`hdb`.`contacts`.`FIRSTNAME` AS `FIRSTNAME`,
`hdb`.`contacts`.`LASTNAME` AS `LASTNAME`
FROM
`hdb`.`contacts`
UNION
SELECT
`sugarcrm`.`contacts`.`id` AS `CONTACTID`,
`sugarcrm`.`accounts_contacts`.`account_id` AS `CLIENTIDC`,
`sugarcrm`.`contacts`.`first_name` AS `FIRSTNAME`,
`sugarcrm`.`contacts`.`last_name` AS `LASTNAME`
FROM
`sugarcrm`.`contacts`
LEFT JOIN `sugarcrm`.`accounts_contacts` ON `sugarcrm`.`contacts`.`id` = `sugarcrm`.`accounts_contacts`.`contact_id`
) AS c ON a.CLIENTIDA = c.CLIENTIDC
) AS d
)
INNER JOIN (
(
projects AS p
INNER JOIN orders AS o ON p.ORDERNO = o.ORDERNO
)
INNER JOIN category AS cat ON p.category_id = cat.category_id
) ON d.CONTACTID = o.CONTACTID
Explain on this provides the following:
1, PRIMARY, cat, ALL, PRIMARY, , , , 10,
1, PRIMARY, p, ref, FK_orders_projects,FK_category_projects_idx, FK_category_projects_idx, 5, hdb.cat.category_id, 400, Using where
1, PRIMARY, o, eq_ref, PRIMARY, PRIMARY, 4, hdb.p.ORDERNO, 1,
1, PRIMARY, <derived2>, ALL, , , , , 18878, Using where
2, DERIVED, <derived3>, ALL, , , , , 7087,
2, DERIVED, <derived5>, ALL, , , , , 18879, Using where
5, DERIVED, contacts, ALL, , , , , 8261,
6, UNION, contacts, ALL, , , , , 10251,
6, UNION, accounts_contacts, ref, idx_contid_del_accid, idx_contid_del_accid, 111, sugarcrm.contacts.id, 1, Using index
, UNION RESULT, <union5,6>, ALL, , , , , ,
3, DERIVED, clients, ALL, , , , , 2296,
4, UNION, accounts, ALL, , , , , 4548,
, UNION RESULT, <union3,4>, ALL, , , , , ,
The original query without the union takes 0.125 seconds
SELECT p.PROJID, p.StartDate, o.ORDERNO, p.PROJCODE, p.PROJECT, cat.type AS CATEGORY, p.AREA, p.STATE, p.COUNTRY,
p.VALUE, p.PROCESSOR, p.PROJINFO, p.NES, p.SPECSALE, p.OFFICE, p.DEPTCODE, p.INTERNLCHG, p.INTERCOCHG, p.LORM,
p.PERCENT, a.COMPANY, CONCAT(c.LASTNAME, ", ", c.FIRSTNAME) AS Contact
FROM (clients AS a
INNER JOIN contacts AS c ON a.CLIENTID =c.CLIENTID)
INNER JOIN ((projects AS p INNER JOIN orders AS o ON p.ORDERNO = o.ORDERNO)
INNER JOIN category AS cat ON p.category_id = cat.category_id) ON c.CONTACTID = o.CONTACTID
ORDER BY p.PROJID, a.COMPANY;
explain on this provides following:
1, SIMPLE, cat, ALL, PRIMARY, , , , 10, Using temporary; Using filesort
1, SIMPLE, p, ref, FK_orders_projects,FK_category_projects_idx, FK_category_projects_idx, 5, hdb.cat.category_id, 400, Using where
1, SIMPLE, o, eq_ref, PRIMARY,FK_contacts_orders, PRIMARY, 4, hdb.p.ORDERNO, 1,
1, SIMPLE, c, eq_ref, PRIMARY,FK_clients_contacts, PRIMARY, 52, hdb.o.CONTACTID, 1,
1, SIMPLE, a, eq_ref, PRIMARY, PRIMARY, 52, hdb.c.CLIENTID, 1,
Query with view:
SELECT
p.PROJID,
p.StartDate,
o.ORDERNO,
p.PROJCODE,
p.PROJECT,
cat.type AS CATEGORY,
p.AREA,
p.STATE,
p.COUNTRY,
p.VALUE,
p.PROCESSOR,
p.PROJINFO,
p.NES,
p.SPECSALE,
p.OFFICE,
p.DEPTCODE,
p.INTERNLCHG,
p.INTERCOCHG,
p.LORM,
p.PERCENT,
a.COMPANY,
CONCAT(
c.LASTNAME,
", ",
c.FIRSTNAME
) AS Contact
FROM
(
view_accounts_sugar_hdb AS a
INNER JOIN view_contacts_sugar_hdb AS c ON a.CLIENTID = c.CLIENTID
)
INNER JOIN (
(
projects AS p
INNER JOIN orders AS o ON p.ORDERNO = o.ORDERNO
)
INNER JOIN category AS cat ON p.category_id = cat.category_id
) ON c.CONTACTID = o.CONTACTID
ORDER BY
p.PROJID,
a.COMPANY;
This takes over 340 secs.
This one was definitely uglier than the last one I helped you on :)... Anyhow, the same principles apply. For future, please do try to understand what I'm doing here. Write the JOIN relationships FIRST to know where your data is coming from. Also, take a look at my indentations... I am showing at each level for readability.
Orders -> Projects -> Categories...
then, the path for those in the normal clients table via
Orders -> Contacts -> Clients
finally to the SugarCRM contacts...
Orders -> Accounts_Contacts -> Accounts
So, now that you have the relationships set (and aliased), this follows similar of last answer implementing LEFT-JOIN to normal contact/client vs CRM Contacts/Accounts.
The field list is simple on the Order, Products and Category tables as those are pretty direct. That leaves just the "who/client" information where the LEFT-JOIN comes in. If the Normal client is null, use the CRM version fields, otherwise use the normal client fields...
SELECT
P.PROJID,
P.StartDate,
O.ORDERNO,
P.PROJCODE,
P.PROJECT,
cat.`type` AS CATEGORY,
P.AREA,
P.STATE,
P.COUNTRY,
P.VALUE,
P.PROCESSOR,
P.PROJINFO,
P.NES,
P.SPECSALE,
P.OFFICE,
P.DEPTCODE,
P.INTERNLCHG,
P.INTERCOCHG,
P.LORM,
P.PERCENT,
CASE when HCLIENT.ClientID IS NULL
then SCLIENT.`name`
ELSE HCLIENT.Company end as Company,
CASE when HCLIENT.ClientID IS NULL
then CONCAT( SCT.LAST_NAME, ", ", SCT.FIRST_NAME )
ELSE CONCAT( HCT.LASTNAME, ", ", HCT.FIRSTNAME ) end as Contact
FROM
orders O
JOIN projects P
ON O.OrderNo = P.OrderNo
JOIN category AS cat
ON p.category_id = cat.category_id
LEFT JOIN hdb.contacts HCT
ON O.ContactID = HCT.ContactID
LEFT JOIN hdb.clients HCLIENT
ON HCT.ClientID = HCLIENT.ClientID
LEFT JOIN sugarcrm.contacts SCT
ON O.ContactID = SCT.ID
LEFT JOIN sugarcrm.accounts_contacts SAC
ON SCT.ID = SAC.contact_id
LEFT JOIN sugarcrm.accounts SCLIENT
ON SCT.account_id = SCLIENT.ID
I'd be interested in the performance improvement too.

MySql return multiple rows from different columns of the same row

Given two tables:
The 'people' table contains the following columns:
name
favorite_walking_shoe
favorite_running_shoe
favorite_dress_shoe
favorite_house_shoe
favorite_other_shoe
The 'shoes' table contains the following columns:
shoe
name
description
I want to create a result set that contains:
people.name, people.favorite_shoe_type, shoes.name, shoes.description
I know I can get the desired results using something like:
select p.name, p.favorite_shoe_type, s.name, s.description
from (select name, favorite_walking_shoe as shoe, 'walking' as favorite_shoe_type
from people where favorite_walking_shoe is not null
union all
select name, favorite_running_shoe, 'running'
from people where favorite_running_shoe is not null
union all
select name, favorite_dress_shoe, 'dress'
from people where favorite_dress_shoe not is null
union all
select name, favorite_house_shoe, 'house'
from people where favorite_house_shoe not is null
union all
select name, favorite_other_shoe, 'other'
from people where favorite_other_shoe not is null
) p
join shoes s on s.shoe = p.shoe
order by 1,2
but this would require 5 passes of the 'people' table. Is there a way to accomplish the UNION ALLs without requiring multiple passes?
I should point out that the structures are part of a vendor product which I cannot modify. :(
You can get around the five scans by doing a cross join:
select p.name, p.favorite_shoe_type, s.name, s.description
from (select p.*,
(case when favorite_shoetype = 'walking' then p.favore_walking_shoe
when favorite_shoetype = 'running' then p.favorite_running_shoe
when favorite_shoetype = 'dress' then p.favorite_dress_shoe
when favorite_shoetype = 'house' then p.favorite_house_shoe
when favorite_shoetype = 'other' then p.favorite_other_shoe
end) as shoe
from people p cross join
(select 'walking' as favorite_shoe_type union all
select 'running' union all
select 'dress' union all
select 'house' union all
select 'other'
) shoetypes join
shoes s
) p
on s.shoe = p.shoe
I'm not sure this will be more efficient. If you have indexes on shoe, this even more complicated version might be more efficient:
select p.name, p.favorite_shoe_type, s.name, s.description
from (select p.name, favorite_shoe_types,
(case when favorite_shoetype = 'walking' then ws.name
when favorite_shoetype = 'running' then rs.name
when favorite_shoetype = 'dress' then ds.name
when favorite_shoetype = 'house' then hs.name
when favorite_shoetype = 'other' then os.name
end) as name,
(case when favorite_shoetype = 'walking' then ws.description
when favorite_shoetype = 'running' then rs.description
when favorite_shoetype = 'dress' then ds.description
when favorite_shoetype = 'house' then hs.description
when favorite_shoetype = 'other' then os.name
end) as description
from people p left outer join
shoes ws
on ws.shoe = favorite_walking_shoe left outer join
shoes rs
on rs.shoe = favorite_running_shoe left outer join
shoes ds
on ds.shoe = favorite_dress_shoe left outer join
shoes hs
on hs.shoe = favorite_house_shoe left outer join
shoes os
on os.shoe = favorite_other_shoe cross join
(select 'walking' as favorite_shoe_type union all
select 'running' union all
select 'dress' union all
select 'house' union all
select 'other'
) shoetypes
) p
on s.shoe = p.shoe
where s.name is not null
This should do the five joins using indexes -- quite fast, one scan of the people table, and feed this to the cross join. The logic then returns the values that you want.
Note: both of these are untested so they might have syntax errors.
Unfortunately, the way your current table is structured you will have multiple passes to get each value. If it is possible, I would suggest changing your table structure to the include a shoe_type table and then a join table between the people and shoes and in this table, you can include a flag that will show if the shoe is the favorite.
So it will be similar to this:
create table people_shoe
(
people_id int,
shoe_id int,
IsFavorite int
);
You could also have a shoe_type table to store each of the different show types:
create table shoe_type
(
id int,
name varchar(10)
);
insert into shoe_type
values('Walking'), ('Running'), ('Dress'), ('House'), ('Other');
The shoe_type.id would be added to your shoe table and you would join the tables.
Edit #1, if you can remodel the database, you could use the following (mock-up model):
create table people
(
id int, 
name varchar(10)
);
insert into people values (1, 'Jim'), (2, 'Jane');
create table shoe_type
(
id int,
name varchar(10)
);
insert into shoe_type
values(1, 'Walking'), (2, 'Running'), (3, 'Dress'), (4, 'House'), (5, 'Other');
create table shoes
(
id int,
name varchar(10),
description varchar(50),
shoe_type_id int
);
insert into shoes 
values(1, 'Nike', 'test', 2), (2, 'Cole Haan', 'blah', 3);
create table people_shoe
(
    people_id int,
    shoe_id int,
    IsFavorite int
);
insert into people_shoe
values (1, 1, 1),
(1, 2, 0),
(2, 1, 1);
Then when you query, your code will be similar to this:
select p.name PersonName,
s.name ShoeName,
st.name ShoeType,
ps.isFavorite
from people p
inner join people_shoe ps
on p.id = ps.people_id
inner join shoes s
on ps.shoe_id = s.id
inner join shoe_type st
on s.shoe_type_id = st.id
See SQL Fiddle with Demo

How to combine these two queries into one? (multiple joins against the same table)

Given two tables, one for workers and one for tasks completed by workers,
CREATE TABLE IF NOT EXISTS `workers` (
`id` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `workers` (`id`) VALUES
(1);
CREATE TABLE IF NOT EXISTS `tasks` (
`id` int(11) NOT NULL,
`worker_id` int(11) NOT NULL,
`status` int(11) NOT NULL,
PRIMARY KEY (`id`)
);
INSERT INTO `tasks` (`id`, `worker_id`, `status`) VALUES
(1, 1, 1),
(2, 1, 1),
(3, 1, 2),
(4, 1, 2),
(5, 1, 2);
I'm trying to get the number of tasks each worker has with each status code.
I can say either
SELECT w.*
,COUNT(t1.worker_id) as status_1_count
FROM workers w
LEFT JOIN tasks t1 ON w.id = t1.worker_id AND t1.status = 1
WHERE 1
GROUP BY
t1.worker_id
ORDER BY w.id
or
SELECT w.*
,COUNT(t2.worker_id) as status_2_count
FROM workers w
LEFT JOIN tasks t2 ON w.id = t2.worker_id AND t2.status = 2
WHERE 1
GROUP BY
t2.worker_id
ORDER BY w.id
and get the number of tasks with a single given status code, but when I try to get the counts for multiple task statuses in a single query, it doesn't work!
SELECT w.*
,COUNT(t1.worker_id) as status_1_count
,COUNT(t2.worker_id) as status_2_count
FROM workers w
LEFT JOIN tasks t1 ON w.id = t1.worker_id AND t1.status = 1
LEFT JOIN tasks t2 ON w.id = t2.worker_id AND t2.status = 2
WHERE 1
GROUP BY t1.worker_id
,t2.worker_id
ORDER BY w.id
The tasks table is cross-joining against itself when I would rather it wouldn't!
Is there any way to combine these two queries into one such that we can retrieve the counts for multiple task statuses in a single query?
Thanks!
SELECT w.*,
SUM(t1.status = 1) AS status_1_count,
SUM(t1.status = 2) AS status_2_count
FROM workers w
LEFT JOIN tasks t1 ON w.id = t1.worker_id AND t1.status IN (1, 2)
GROUP BY w.id
ORDER BY w.id;
I'm trying to get the number of tasks each worker has with each status code.
SELECT worker_id, status, COUNT(*)
FROM tasks
GROUP BY worker_id, status;
That's all.
I don't have an instance of MySQL here, but I tested this on a t-sql box and it worked.
select distinct(worker_id),
(select count(*) from tasks where status = 1) as Status1,
(select count(*) from tasks where status = 2) as Status2
from tasks;