简体   繁体   中英

MySQL - Recursion with parent child relationship sql query

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?

Eg 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 eg

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.

The database model you are using suffers from two main issues:

  • Has irregular hierarchy rules. The first row follows different rules than the rest of the table.
  • The hierarchy is hard coded, having at most 6 levels. What happens if some communities need more levels?

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.

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

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM