3 mysql table join but not getting expected result that i want - mysql

Here my table:
account
ac name
120 Tom
130 Jony
140 Jone
bread_sale
ac pcs amount date
120 12 60 2018-01-03
120 10 50 2018-01-04
140 8 40 2018-01-04
130 5 25 2018-01-05
water_sale
ac pcs amount date
130 2 30 2018-01-03
130 5 75 2018-01-04
140 3 45 2018-01-04
130 4 60 2018-01-05
120 5 75 2018-01-07
Here's the query that I have tried:
select account.ac,
account.name,
bread_sale.amount as BSAmount,
bread_sale.date as BSDate,
water_sale.amount as WSAmount,
water_sale.date as WSDate
from account left outer join bread_sale on account.ac = bread_sale.ac
left outer join water_sale on water_sale.ac = account.ac
order by account.ac
This is the result:
ac name BSAmount BSdate WSAmount WSdate
120 Tom 30 2018-01-03 75 2018-01-07
120 Tom 75 2018-01-04 75 2018-01-07
130 Jony 45 2018-01-05 30 2018-01-03
130 Jony 60 2018-01-05 75 2018-01-04
130 Jony 75 2018-01-05 60 2018-01-05
140 Jone 75 2018-01-04 45 2018-01-04
But I want to obtain something like this:
ac name BSAmount BSdate WSAmount WSdate
120 Tom 60 2018-01-03 75 2018-01-07
120 Tom 50 2018-01-04 0 2018-01-07
130 Jony 25 2018-01-05 30 2018-01-03
130 Jony 0 2018-01-05 75 2018-01-04
130 Jony 0 2018-01-05 60 2018-01-05
140 Jone 40 2018-01-04 45 2018-01-04
In 2018-01-07 Tom did not sale water but I get 75 amount.
Someone help me, please

It is achievable but messy and probably not very efficient. There is a relationship between the bread water and account on ac. It is possible to establish the maximum number of row numbers spanning bread and water and then rejoining on row number. Put another way bread and water are joined on a position basis (ie the order rows appear in the tables) The resulting query is horrible and parses the data more frequently than I would be comfortable with personally.
so
select * from
(
select c.ac cac,t.name tname,d.amount bsamount, d.dt ddt,
e.amount wsamount ,e.dt edt
,
case when d.dt is not null and d.dt < e.dt then d.dt
when d.dt is not null and e.dt is null then d.dt
else e.dt
end as sortorder
from
(
select *
from
(
select bs.ac,
if(bs.ac <> #p,#rn:=1,#rn:=#rn+1) rn,
#p:=bs.ac p
from bs
cross join (select #rn:=0,#p:=0) r
order by bs.ac,bs.dt
) a
union
(
select ac2,rn1,p1
from
(
select ws.ac ac2,
if(ws.ac <> #p1,#rn1:=1,#rn1:=#rn1+1) rn1,
#p1:=ws.ac p1
from ws
cross join (select #rn1:=0,#p1:=0) r
order by ws.ac,ws.dt
) b
)
) c
left join
(
select bs.ac,pcs,amount,dt,
if(bs.ac <> #p3,#rn3:=1,#rn3:=#rn3+1) rn3,
#p3:=bs.ac p
from bs
cross join (select #rn3:=0,#p3:=0) r
order by bs.ac,bs.dt
) d
on d.ac = c.ac and d.rn3 = c.rn
left join
(
select ws.ac,pcs,amount,dt,
if(ws.ac <> #p4,#rn4:=1,#rn4:=#rn4+1) rn4,
#p4:=ws.ac p
from ws
cross join (select #rn4:=0,#p4:=0) r
order by ws.ac,ws.dt
) e
on e.ac = c.ac and e.rn4 = c.rn
join t on t.ac = c.ac
) f
order by cac , sortorder;
+------+-------+----------+------------+----------+------------+------------+
| cac | tname | bsamount | ddt | wsamount | edt | sortorder |
+------+-------+----------+------------+----------+------------+------------+
| 120 | Tom | 60 | 2018-01-03 | 75 | 2018-01-07 | 2018-01-03 |
| 120 | Tom | 50 | 2018-01-04 | NULL | NULL | 2018-01-04 |
| 130 | Jony | 25 | 2018-01-05 | 30 | 2018-01-03 | 2018-01-03 |
| 130 | Jony | NULL | NULL | 75 | 2018-01-04 | 2018-01-04 |
| 130 | Jony | NULL | NULL | 60 | 2018-01-05 | 2018-01-05 |
| 140 | Jone | 40 | 2018-01-04 | 45 | 2018-01-04 | 2018-01-04 |
+------+-------+----------+------------+----------+------------+------------+
6 rows in set (0.00 sec)

select account.ac,
account.name,
bread_sale.amount as BSAmount,
bread_sale.date as BSDate,
water_sale.amount as WSAmount,
water_sale.date as WSDate
from account left outer join bread_sale on account.ac = bread_sale.ac
left outer join water_sale on water_sale.ac = bread_sale.ac
order by account.ac
This Query would produce this result. Your zeros will be replaced by correct value. I hope may be you would need this reuslt.
ac name BSAmount BSdate WSAmount WSdate
120 Tom 60 2018-01-03 75 2018-01-07
120 Tom 50 2018-01-04 75 2018-01-07
130 Jony 25 2018-01-05 30 2018-01-03
130 Jony 25 2018-01-05 75 2018-01-04
130 Jony 25 2018-01-05 60 2018-01-05
140 Jone 40 2018-01-04 45 2018-01-04

Related

MYSQL Update rows with duplicate value but oldest date

Tried many suggestions to get this to work, difficult to explain so below is the data I have and the result I want to achieve.
I want to update the 'Active' Column to 0 if its not the MAX Ldate.
ID | SNumber | Ldate | Active
4804 188 2015-11-17 1
4806 189 2015-11-25 1
4807 190 2015-11-25 1
4808 191 2015-11-19 1
4809 192 2015-11-19 1
4820 193 2015-11-17 1
4821 193 2016-06-08 1
4830 194 2015-11-17 1
4831 194 2016-06-08 1
4828 195 2015-11-17 1
4829 195 2016-06-08 1
ID SNumber Ldate Active
4804 188 2015-11-17 1
4806 189 2015-11-25 1
4807 190 2015-11-25 1
4808 191 2015-11-19 1
4809 192 2015-11-19 1
4820 193 2015-11-17 0
4821 193 2016-06-08 1
4830 194 2015-11-17 0
4831 194 2016-06-08 1
4828 195 2015-11-17 0
4829 195 2016-06-08 1
I can get all rows with the MAX Ldate by "select ID, SNumber, Ldate from (select * from tbl order by SNumber, Ldate desc) x group by SNumber"
Thanks for taking the time to look!
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(id SERIAL PRIMARY KEY
,SNumber INT NOT NULL
,Ldate DATE NOT NULL
);
INSERT INTO my_table VALUES
(4804,188,'2015-11-17'),
(4806,189,'2015-11-25'),
(4807,190,'2015-11-25'),
(4808,191,'2015-11-19'),
(4809,192,'2015-11-19'),
(4820,193,'2015-11-17'),
(4821,193,'2016-06-08'),
(4830,194,'2015-11-17'),
(4831,194,'2016-06-08'),
(4828,195,'2015-11-17'),
(4829,195,'2016-06-08');
SELECT x.*
, COALESCE(x.ldate = y.ldate,0) active
FROM my_table x
LEFT
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate;
+------+---------+------------+--------+
| id | SNumber | Ldate | active |
+------+---------+------------+--------+
| 4804 | 188 | 2015-11-17 | 1 |
| 4806 | 189 | 2015-11-25 | 1 |
| 4807 | 190 | 2015-11-25 | 1 |
| 4808 | 191 | 2015-11-19 | 1 |
| 4809 | 192 | 2015-11-19 | 1 |
| 4820 | 193 | 2015-11-17 | 0 |
| 4821 | 193 | 2016-06-08 | 1 |
| 4830 | 194 | 2015-11-17 | 0 |
| 4831 | 194 | 2016-06-08 | 1 |
| 4828 | 195 | 2015-11-17 | 0 |
| 4829 | 195 | 2016-06-08 | 1 |
+------+---------+------------+--------+
I can't think why you'd store this, but it's easy enough to change the above to an UPDATE. It might look something like this (obviously, you'd need to alter the table design above first)...
UPDATE my_table x
LEFT
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate
SET x.active = 0
WHERE y.snumber IS NULL;
But I think I would normally use an INNER JOIN for an UPDATE, in which case it might look like this (perhaps bound up inside a transaction)...
UPDATE my_table SET active = 0;
UPDATE my_table x
JOIN
( SELECT snumber
, MAX(ldate) ldate
FROM my_table
GROUP
BY snumber
) y
ON y.snumber = x.snumber
AND y.ldate = x.ldate
SET x.active = 1;

MySQL matching row values sets

I am relatively new with mysql and php. I have developed a hockey stat db. Until now, I have been doing pretty basic queries and reporting of the stats.
I want to do a little more advanced query now.
I have a table that records which players were on the ice (shows as a "fk_pp1_id" - "fk_pp5_id") when a goal is scored. here is the table:
pt_id | fk_gf_id | fk_pp1_id | fk_pp2_id | fk_pp3_id | fk_pp4_id | fk_pp5_id
1 | 1 | 19 | 20 | 68 | 90 | 97
2 | 2 | 1 | 19 | 20 | 56 | 91
3 | 3 | 1 | 56 | 88 | 91 | 93
4 | 4 | 1 | 19 | 64 | 88 | NULL
5 | 5 | 19 | 62 | 68 | 88 | 97
6 | 6 | 55 | 19 | 20 | 45 | 62
7 | 7 | 1 | 19 | 20 | 56 | 61
8 | 8 | 65 | 68 | 90 | 93 | 97
9 | 9 | 19 | 20 | 45 | 55 | 62
10 | 10 | 1 | 19 | 20 | 56 | 61
11 | 11 | 1 | 19 | 20 | 56 | 61
12 | 12 | 19 | 20 | 68 | 90 | 97
13 | 13 | 19 | 20 | 68 | 90 | 97
14 | 14 | 19 | 20 | 55 | 62 | 91
15 | 15 | 1 | 56 | 61 | 64 | 88
16 | 16 | 1 | 56 | 61 | 64 | 88
17 | 17 | 1 | 19 | 20 | 56 | 61
18 | 18 | 1 | 19 | 20 | 56 | 61
19 | 19 | 1 | 65 | 68 | 93 | 97
I want to do several queries:
Show which of the five players were together on the ice most often
when a goal was scored.
Select say 2 players and show which other players were on the ice most often with them when a goal was scored.
I was able to write a query which partially accomplishes query #1 above.
SELECT
fk_pp1_id,
fk_pp2_id,
fk_pp3_id,
fk_pp4_id,
fk_pp5_id,
count(*)
FROM TABLE1
group by
fk_pp1_id,
fk_pp2_id,
fk_pp3_id,
fk_pp4_id,
fk_pp5_id
Here are the results:
fk_pp1_id fk_pp2_id fk_pp3_id fk_pp4_id fk_pp5_id count(*)
1 19 20 56 61 4
1 19 20 56 91 1
1 19 64 88 (null) 1
1 56 61 64 88 2
1 56 88 91 93 1
1 65 68 93 97 1
19 1 20 56 61 1
19 20 45 55 62 1
19 20 55 62 91 1
19 20 68 90 97 3
19 62 68 88 97 1
55 19 20 45 62 1
65 68 90 93 97 1 4
See this sqlfiddle:
http://sqlfiddle.com/#!9/e3f5f/1
This seems to work at first, but I realized this query, as written, is sensitive to the order in which the players are listed. That is to say a row with:
1, 19, 20, 68, 90
will not match
19, 1, 20, 68, 90
So to fix this problem, I feel like I have a couple options:
Ensure the data is input into the table in numerical order
Re-write the query so the order of the data in the table doesn't matter
Make the resulting query a sub-query to another query that first
orders the column (left to right) in numerical order.
Change the schema to record/store the data in a better way
1, I can do, but would prefer to have the query be fool-proof.
2 or 3 I prefer, but don't know how to do either.
4, I don't know how to do and is least desirable as I already have some complex queries against this table that would need to be totally re-written.
Am i going about this in the wrong way or is there a solution??
Thanks for your help
UPDATE -
OK I (hopefully) better normalized the data in the table. Thanks #strawberry. Now my table has a column for the goal_id (foreign key) and a column for the player_id (another foreign key) that was on the ice at the time the goal was scored.
Here is the new fiddle:
http://sqlfiddle.com/#!9/39e5a
I can easily get the one player who was on the ice most when goals are scored, but I can't get my mind around how to find the occurrences of a group of players who were on the ice together. For example, how many times were a group of 5 players on the ice together. Then from there, how often a group of 2 players were on the ice together with the 3 other players.
Any other clues???
I find a similar problem here and based on that i come up with this solution.
For the first part of your problem to select how many time same five player were on the ice when the goal is scored your query could look like this:
SELECT GROUP_CONCAT(t1.fk_gf_id) AS MinOfGoal,
t1.players AS playersNumber,
COUNT(t1.fk_gf_id) AS numOfTimes
FROM (SELECT fk_gf_id, GROUP_CONCAT(fk_plyr_id ORDER BY fk_plyr_id) AS players
FROM Table1
GROUP BY fk_gf_id) AS t1
GROUP BY t1.players
ORDER BY numOfTimes DESC;
And for your second part of the question where you want to select two players and find three more player which were on the ice when goal were scored you should extend previous query whit WERE clause like this
SELECT GROUP_CONCAT(t1.fk_gf_id) AS MinOfGoal,
t1.players AS playersNumber,
COUNT(t1.fk_gf_id) AS numOfTimes
FROM (SELECT fk_gf_id, GROUP_CONCAT(fk_plyr_id ORDER BY fk_plyr_id) AS players
FROM Table1
WHERE fk_gf_id IN (SELECT fk_gf_id
FROM Table1
WHERE fk_plyr_id = 19)
AND fk_gf_id IN (SELECT fk_gf_id
FROM Table1
WHERE fk_plyr_id = 56)
GROUP BY fk_gf_id) AS t1
GROUP BY t1.players
ORDER BY numOfTimes DESC;
You can see how it's work here in SQL Fiddle...
Note: I added some data in Table1 (don't be confused with more date counted).
GL!

I would like to find the difference of each row, from the first row As Alias

To clarify my Title
I would like to tabulate how far behind the leader, each successive finisher is from 1st place as shown in my table below.
Finish | Points | Points Behind
1 | 102 |
2 | 92 | 10
3 | 82 | 20
4 | 71 | 31
5 | 61 | 41
6 | 50 | 52
7 | 40 | 62
8 | 30 | 72
9 | 20 | 82
10 | 10 | 92
Select
snpc_stats.gamedetail.Finish,
snpc_stats.gamedetail.Points,
some code I don't know As 'Points Behind'
From
snpc_stats.gamedetail
Where
snpc_stats.gamedetail.GamesID = 113
You can get the points from first finish and do a cross join with rest of the table.
SQL Fiddle
select gd.Finish, gd.Points,
t.Points-gd.Points as PointsBehind
from gamedetail gd
cross join ( select max(Points) from gamedetail where Finish =1) t

date Subquery PHP and Mysql

Following is my sql query with included subquery. Essentially I'm looking to obtain the most recent meter readings for our equipment for a given month and year. I know it's in my subquery where the issue is coming up but I don't know how to properly fix it.
Following is result if I delete the subquery and just select equipment with meter readings for that month.
eid eqid name pid hours date
70 C1 KOMATSU WA250 3YD BUCKET 27 1176 2013-10-07
70 C1 KOMATSU WA250 3YD BUCKET 27 1195 2013-10-28
70 C1 KOMATSU WA250 3YD BUCKET 27 1178 2013-10-14
73 C11 KOMATSU PC200 EXCAVATOR 27 1080 2013-10-14
73 C11 KOMATSU PC200 EXCAVATOR 27 1099 2013-10-28
73 C11 KOMATSU PC200 EXCAVATOR 27 1078 2013-10-07
92 C4 CATERPILLAR 304D MINI EX 27 646 2013-10-14
92 C4 CATERPILLAR 304D MINI EX 27 645 2013-10-07
92 C4 CATERPILLAR 304D MINI EX 27 649 2013-10-28
58 E14 BOBCAT-ATV 2300 Utility 8 522 2013-10-31
61 E17 SKYTRAK FORKLIFT 40 943 2013-10-10
62 E18 SKYTRAK FORKLIFT 5 1790 2013-10-30
62 E18 SKYTRAK FORKLIFT 5 1789 2013-10-29
62 E18 SKYTRAK FORKLIFT 5 1777 2013-10-13
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-07
62 E18 SKYTRAK FORKLIFT 5 1777 2013-10-13
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-04
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-07
62 E18 SKYTRAK FORKLIFT 5 1772 2013-10-04
67 E23 BOBCAT SKID STEER 27 1178 2013-10-28
Following is result of full query including subquery.
92 C4 CATERPILLAR 304D MINI EX 27 649 2013-10-28
61 E17 SKYTRAK FORKLIFT 40 943 2013-10-10
62 E18 SKYTRAK FORKLIFT 5 1790 2013-10-30
Following is query I am using.
SELECT e.eid, e.eqid, e.name, m.pid, m.hours, m.date FROM meter m
JOIN equipment e ON m.eid = e.eid
WHERE MONTH(date) = $month
AND YEAR(date) = $year
AND m.date = (SELECT MAX(m2.date) FROM meter m2 WHERE m2.eid = m.eid)
ORDER BY e.eqid ASC
Any help is greatly appreciated.
EDIT***
I would have never gotten there Sebas. I had to change one thing and it worked perfectly.
SELECT DISTINCT e.eid, e.eqid, e.name, m.pid, m.hours, m.date
FROM equipment e
JOIN (
SELECT eid, MAX(date) as date
FROM meter
WHERE MONTH(date) = $month
AND YEAR(date) = $year
GROUP BY eid
) maxdate ON maxdate.eid = e.eid
JOIN meter m ON m.eid = e.eid AND m.date = maxdate.date
ORDER BY e.eqid ASC
Guessing out the fields of each table, I figured the following query:
SELECT DISTINCT e.eid, e.eqid, e.name, m.pid, m.hours, m.date
FROM
equipment e
JOIN (
SELECT eid, MAX(date) date
FROM meter
WHERE
MONTH(date) = $month
AND YEAR(date) = $year
GROUP BY eid
) maxdate ON maxdate.eid = e.eid
JOIN meter m ON m.eid = e.eid AND m.date = maxdate.date
ORDER BY e.eqid ASC
Try
SELECT e.eid, e.eqid, e.name, m.pid, m.hours, m.date
FROM
(
SELECT eid, MAX(date) date
FROM meter
WHERE date BETWEEN '2013-10-01' AND LAST_DAY('2013-10-01')
GROUP BY eid
) q JOIN meter m
ON q.eid = m.eid
AND q.date = m.date JOIN equipment e
ON q.eid = e.eid
ORDER BY e.eid
Note: make sure you have an index on date. Don't apply any functions (MONTH() and such) to this columns in WHERE clause because it prevents from using any index you might have on it effectively causing full table scan.
Sample output:
| EID | EQID | NAME | PID | HOURS | DATE |
|-----|------|--------------------------|-----|-------|------------|
| 58 | E14 | BOBCAT-ATV 2300 Utility | 8 | 522 | 2013-10-31 |
| 61 | E17 | SKYTRAK FORKLIFT | 40 | 943 | 2013-10-10 |
| 62 | E18 | SKYTRAK FORKLIFT | 5 | 1790 | 2013-10-30 |
| 67 | E23 | BOBCAT SKID STEER | 27 | 1178 | 2013-10-28 |
| 70 | C1 | KOMATSU WA250 3YD BUCKET | 27 | 1195 | 2013-10-28 |
| 73 | C11 | KOMATSU PC200 EXCAVATOR | 27 | 1099 | 2013-10-28 |
| 92 | C4 | CATERPILLAR 304D MINI EX | 27 | 649 | 2013-10-28 |
Here is SQLFiddle demo

MySQL inner join to sort parent, child and grand-child from the same table

MySQL database contains Countries, Town and Areas within the towns, all in the "mailshot" table. I want to return the whole set in order of descending granularity, using and inner join. I actually want to present the user with a drop down list for them to choose a Country or Town or Area within a town.
The data looks like this:
mailshot_id mailshot_parent mailshot_name mailshot_level
49 0 England 0
56 0 Scotland 0
140 49 London 1
149 49 York 1
191 56 Glasgow 1
300 140 Wimbledon 2
310 140 Westminster 2
493 56 Edinburgh 1
and I want it output like this:
mailshot_id mailshot_parent mailshot_name mailshot_level
49 0 England 0
149 49 York 1
140 49 London 1
300 140 Wimbledon 2
310 140 Westminster 2
56 0 Scotland 0
191 56 Glasgow 1
493 56 Edinburgh 1
I've almost got it with this:
SELECT
p.mailshot_id as p_id,
p.mailshot_name as p_name,
p.mailshot_level as p_level,
p.mailshot_parent as p_parent,
c.mailshot_id as c_id,
c.mailshot_parent as c_parent,
c.mailshot_level as c_level,
c.mailshot_name as c_name,
case
WHEN p.mailshot_parent = 0 THEN
p.mailshot_id
ELSE
p.mailshot_parent
END AS calcOrder
FROM
mailshot p LEFT JOIN mailshot c
ON p.mailshot_id = c.mailshot_parent
ORDER BY calcOrder , p_id "
but it's not grouping the grandchildren records (level 2) close to the child records (level 1) I think the "case" part must be wrong, and I need to have some relationship between mailshot_id and parent_id dependent on level. But I can't think if it.
Any suggestions? Thanks in advance.
Unfortunately MySQL does not support hierarchicaly queries (no START WITH...CONNECT BY or CTE equivalents). Becasue of that you need to do this the hard and ugly way.
The following will work for your 3 levels, buts gets pretty cumbersome if you need a lot more depth in the tree. Here is the Fiddle
SELECT C.MAILSHOT_ID
,C.MAILSHOT_PARENT
,C.MAILSHOT_NAME
,C.MAILSHOT_LEVEL
,CASE WHEN C.MAILSHOT_LEVEL = 0
THEN CAST(C.MAILSHOT_ID AS CHAR(4))
WHEN C.MAILSHOT_LEVEL = 1
THEN CONCAT(CAST(C.MAILSHOT_PARENT AS CHAR(4)),"..",CAST(C.MAILSHOT_ID AS CHAR(4)))
ELSE CONCAT(CAST(P.MAILSHOT_PARENT AS CHAR(4)),"..",CAST(C.MAILSHOT_PARENT AS CHAR(4)),"..",CAST(C.MAILSHOT_ID AS CHAR(4)))
END AS SORT_ORDER
FROM MAILSHOT C
LEFT OUTER JOIN
MAILSHOT P
ON P.MAILSHOT_ID = C.MAILSHOT_PARENT
ORDER BY CASE WHEN C.MAILSHOT_LEVEL = 0
THEN CAST(C.MAILSHOT_ID AS CHAR(4))
WHEN C.MAILSHOT_LEVEL = 1
THEN CONCAT(CAST(C.MAILSHOT_PARENT AS CHAR(4)),"..",CAST(C.MAILSHOT_ID AS CHAR(4)))
ELSE CONCAT(CAST(P.MAILSHOT_PARENT AS CHAR(4)),"..",CAST(C.MAILSHOT_PARENT AS CHAR(4)),"..",CAST(C.MAILSHOT_ID AS CHAR(4)))
END
This is a typical example for a hierarchical table, which is easier to query in oracle, but that's beside the point. #Declan_K gave you a good answer to achieve what you want. If you were looking for an alternative that gives you a slightly different by still well organized output, you could try this approach:
SELECT m1.mailshot_name AS lev1n ,
m1.mailshot_id AS lev1,
m1.mailshot_parent AS lev1p,
m2.mailshot_name AS lev2n,
m2.mailshot_id AS lev2,
m2.mailshot_parent AS lev2p,
m3.mailshot_name lev3n,
m3.mailshot_id lev3,
m3.mailshot_parent AS lev3p
FROM mailshot m1
LEFT JOIN mailshot m2 ON m2.mailshot_parent = m1.mailshot_id
LEFT JOIN mailshot m3 ON m3.mailshot_parent = m2.mailshot_id
WHERE m1.mailshot_parent = 0;
Gives output:
+----------+------+-------+-----------+------+-------+-------------+------+-------+
| lev1n | lev1 | lev1p | lev2n | lev2 | lev2p | lev3n | lev3 | lev3p |
+----------+------+-------+-----------+------+-------+-------------+------+-------+
| England | 49 | 0 | London | 140 | 49 | Wimbledon | 300 | 140 |
| England | 49 | 0 | London | 140 | 49 | Westminster | 310 | 140 |
| England | 49 | 0 | York | 149 | 49 | NULL | NULL | NULL |
| Scotland | 56 | 0 | Glasgow | 191 | 56 | NULL | NULL | NULL |
| Scotland | 56 | 0 | Edinburgh | 493 | 56 | NULL | NULL | NULL |
+----------+------+-------+-----------+------+-------+-------------+------+-------+
Good summaries on how to deal with hierarchical data in MySQL can be found here:
http://mikehillyer.com/articles/managing-hierarchical-data-in-mysql/
http://explainextended.com/2009/03/17/hierarchical-queries-in-mysql/