MySQL - Recursion with parent child relationship sql query

时间:2019-01-18 19:07:14

标签: mysql sql recursion

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.

3 个答案:

答案 0 :(得分:0)

您使用的数据库模型存在两个主要问题:

  • 具有不规则的层次结构规则。第一行与表的其余部分遵循不同的规则。
  • 层次结构是硬编码的,最多具有6个级别。如果某些社区需要更多级别,会发生什么?

尽管如此,尽管使用了难看的SQL查询,仍然可以检索到您拥有的信息。在这里:

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

注意:此查询不使用CTE(公用表表达式),因此适用于MySQL 5.x和MySQL 8.x。

答案 1 :(得分:0)

您可以使用case when表达式:

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

答案 2 :(得分:0)

不更改表格? 在所有定义关系的字段上将位置本身结合起来。

示例:

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'

但是此模型并未真正归一化。重复的名称太多。
使用外键指向表的pk可能是值得的。
然后,更改名称将不是一个小问题。

例如在此test

或者您可以切换到嵌套集模型。
不需要像邻接表模型那样进行递归。
有关这两个模型here

的更多信息