Reputation: 10078
I have the following location hierarchy. Jobs are assigned to locations. If I have just the location name how can I return all jobs in that location and in any place that comes under that location?
E.g if I select Leeds
or Oakwood
then only jobs 1 and 2 should be returned. If I select Yorkshire
or England
or Uk
or Europe
then all 3 jobs would be returned.
Locations:
id | name | continent | country | admin1 | admin2 | city
-------------------------------------------------------------------------------------
1 | Europe | | | | |
2 | UK | Europe | | | |
3 | England | Europe | UK | | |
4 | Yorkshire | Europe | UK | England | |
5 | Leeds | Europe | UK | England | Yorkshire |
6 | Oakwood | Europe | UK | England | Yorkshire | Leeds
Jobs:
id | location_id
--------------------
1 | 6
2 | 6
3 | 4
This is straight forward when you know which column to filter by e.g
Select jobs.*
from jobs
INNER JOIN locations on locations.id = jobs.location_id
where locations.name = 'Europe' OR location.continent = 'Europe'
Select jobs.*
from jobs
INNER JOIN locations on locations.id = jobs.location_id
where locations.name = 'UK' OR location.country = 'UK'
But how can you achieve the same when you don't know which column to filter in.
Upvotes: 0
Views: 382
Reputation: 29677
Without changing the tables? Join the locations to itself on all the fields that define the relation.
Example:
select loc.name, job.*
from Locations as loc
join Locations as parent_loc
on parent_loc.name in (loc.name, loc.continent, loc.country, loc.admin1, loc.admin2, loc.city)
join Jobs as job on job.location_id = loc.id
where parent_loc.name = 'UK'
But this model isn't really normalized. Too many duplicated names.
It might be worth it to use foreign keys to the table's pk.
Then changing some name wouldn't become an small issue.
For example like in this test
Or you could switch to a Nested Set Model.
That wouldn't require recursion, like the Adjacency List Model would.
More info about those 2 models here
Upvotes: 0
Reputation: 48865
The database model you are using suffers from two main issues:
Anyway, it's possible to retrieve the information you have, albeit using an ugly SQL query. Here it is:
select
j.*
from (
select -- select the initial location
from locations l
where name = 'Yorkshire'
union
select -- select all children locations
from locations l
join locations r
on (l.continent is null and l.name = r.continent)
or (l.continent is not null and l.country is null and l.name = r.country)
or (l.continent is not null and l.country is not null and l.admin1 is null and l.name = r.admin1)
or (l.continent is not null and l.country is not null and l.admin1 is not null and l.admin2 is null and l.name = r.admin2)
or (l.continent is not null and l.country is not null and l.admin1 is not null and l.admin2 is not null and l.city is null and l.name = r.city)
where l.name = 'Yorkshire'
) x
join jobs j on j.location_id = x.id
Note: This query does not use CTEs (Common Table Expressions) so it's suitable for MySQL 5.x as well as MySQL 8.x.
Upvotes: 0
Reputation: 351328
You could use a case when
expression:
select jobs.*
from (
select id
from locations
where name = "Europe"
union all
select child.id
from locations main
inner join locations child
on main.name = case when main.continent is null then child.continent
when main.country is null then child.country
when main.admin1 is null then child.admin1
when main.admin2 is null then child.admin2
else child.city
end
where main.name = "Europe"
) sub
inner join jobs
on jobs.location_id = sub.id
Upvotes: 1