需要复杂的分层SQL查询帮助!

时间:2011-04-12 00:09:29

标签: mysql sql

召集所有mySQL专家!

我需要对mySQL进行复杂的查询,但我无法理解它。有2个表格有问题:

地点
(列:location_id,父级,位置)
数据以分层方式分为国家,地区,县和城镇,因此:
1,0,英格兰( country
2,1,西南(地区
3,1,东南(地区
4,2,多塞特(
5,4,伯恩茅斯( town
6,4,普尔( town
7,4,Wimborne( town
等多达400多行的位置数据

简档
(列:profile_id,title,location_id)
每行都有一个位置ID,它始终是一个城镇(即最后一个孩子)。例如:
1,'此配置文件的位置设置为伯恩茅斯',5
2,'此配置文件的位置设置为普尔',6

我需要实现的是从Locations表中返回所有ID,其中或其子项具有与之关联的条目。因此,在上面的示例中,我需要返回以下位置ID:1,2,4,5,6

原因:
1 - 是的,英格兰是西南,多塞特郡和伯恩茅斯的母公司,其中有一个条目
2 - 是的,西南是多塞特和伯恩茅斯的母公司,其中有一个条目
3 - 否,东南没有任何条目或其任何子女 4 - 是的,多塞特是伯恩茅斯的父母,其中有一个参赛作品 5 - 是的,伯恩茅斯有一个条目
6 - 是的,普尔有一个条目
7 - 不,Wimborne没有参赛作品

那么,这实际上是可能的吗?我试图在PHP中使用嵌套的SQL查询,但脚本超时,所以必须有一种方法只在SQL查询中执行此操作?

提前感谢你! :)

===========

更新

在阅读并播放所有这些解决方案后,我意识到我完全以错误的方式解决问题。不是遍历所有位置并返回那些具有条目的位置,而是更有意义,并且更有效地获取所有条目并返回相应的位置,然后上层级以使每个位置成为父级,直到根被击中。 / p>

非常感谢你的帮助,至少让我意识到我的尝试是不必要的。

4 个答案:

答案 0 :(得分:0)

事实上,你的脚本超时会在某处显示无限循环。

考虑到你是根据子区域引用位置表,再加上对父区域的另一个引用,你需要使用PHP和& amp;组合。 Mysql滚动浏览所有这些 - 一个简单的JOIN语句在这种情况下不起作用,我不认为。

此外,您需要更改表格,以便如果它是顶级页面,则其parent_id为NULL,而不是0.完成后...

$sql = "SELECT * FROM locations WHERE parent =''";
$result = mysql_query($sql);
while($country = mysql_fetch_array($result)) {

$subsql = "SELECT * FROM locations WHERE parent='".$country['id']."'";
$subresult = mysql_query($subsql);
while($subregion = mysql_fetch_array($subresult)) {

$profilesql = "SELECT * FROM profiles WHERE location_id='".$subregion['id']."'";
$profileresult = mysql_query($profilesql); 

echo mysql_num_rows($profileresult).' rows under '.$subregion['location'].'.<br />';
}

}

基本代码在那里......有没有人有一个聪明的想法让它适用于各种子级别?但老实说,如果这是我的项目,我会为Country,然后是Regions,然后是City / Town制作单独的表。 3个表将使数据导航更容易。

答案 1 :(得分:0)

如果你的php代码很好,你可能在[location - &gt;中有一个嵌套循环。父母] fd。我会先从那里开始,然后使用PHP。我认为SQL没有递归函数。

如果你需要一个嵌套的父循环,你应该写一个合并算法的变异来解决这个问题。

在PHP中找到嵌套循环

$ids = array();
function nestedLoopFinder($parent)
{
    global $ids;
    $result = mysql_query("SELECT location_id FROM locations WHERE parent=$parent");
    while($row = mysql_fetch_object($result))
    {
        if(in_array($row->location_id, $ids)) {
            die("duplicate found: $row->location_id");
        }
        $ids[] = $row->location_id;

        //recurse
        nestedLoopFinder($row->location_id);
    }
}

答案 2 :(得分:0)

我处理这个问题的方法是只进行一次SQL加载,然后将引用放在父对象中。

    $locations = array();

        $obj_query = "SELECT * from locations";
        $result_resource = mysql_query($obj_query);
        while ($row = mysql_fetch_assoc($result_resource) {
            $locations[$row['location_id'] = (object) $row;
        }

        foreach ($locations as $location) {
            if (isset($location->parent) {
                $locations[$location->parent]->children[] = $location;
            }
        }

您的对象需要一个这样的方法来确定某个位置是否是后代:


function IsAnscestorOF ($location) {
    if (empty($children)) { return false; }
    if (in_array($location, keys($this->children) {
        return true;
    } else {
        foreach ($children as $child) {
             if ($child->isAnscestor) {
                 return true;
             }
         }
     }
     return false;
}

答案 3 :(得分:0)

不确定我是否完全理解您的要求,但以下存储过程示例可能是您的良好起点:

示例调用(请注意包含的列)

mysql> call location_hier(1);
+-------------+---------------------+--------------------+---------------------+-------+----------+
| location_id | location            | parent_location_id | parent_location     | depth | included |
+-------------+---------------------+--------------------+---------------------+-------+----------+
|           1 | England (country)   |               NULL | NULL                |     0 |        1 |
|           2 | South West (region) |                  1 | England (country)   |     1 |        1 |
|           3 | South East (region) |                  1 | England (country)   |     1 |        0 |
|           4 | Dorset (county)     |                  2 | South West (region) |     2 |        1 |
|           5 | Bournemouth (town)  |                  4 | Dorset (county)     |     3 |        1 |
|           6 | Poole (town)        |                  4 | Dorset (county)     |     3 |        1 |
|           7 | Wimborne (town)     |                  4 | Dorset (county)     |     3 |        0 |
+-------------+---------------------+--------------------+---------------------+-------+----------+
7 rows in set (0.00 sec)

您将从php调用存储过程,如下所示:

$startLocationID = 1;
$result = $conn->query(sprintf("call location_hier(%d)", $startLocationID));

完整脚本:

http://pastie.org/1785995

drop table if exists profiles;
create table profiles
(
profile_id smallint unsigned not null auto_increment primary key,
location_id smallint unsigned null,
key (location_id)
)
engine = innodb;

insert into profiles (location_id) values (5),(6);

drop table if exists locations;
create table locations
(
location_id smallint unsigned not null auto_increment primary key,
location varchar(255) not null,
parent_location_id smallint unsigned null,
key (parent_location_id)
)
engine = innodb;

insert into locations (location, parent_location_id) values
('England (country)',null), 
    ('South West (region)',1),
    ('South East (region)',1),
        ('Dorset (county)',2),
            ('Bournemouth (town)',4),
            ('Poole (town)',4),
            ('Wimborne (town)',4);


drop procedure if exists location_hier;
delimiter #

create procedure location_hier
(
in p_location_id smallint unsigned
)
begin

declare v_done tinyint unsigned default 0;
declare v_depth smallint unsigned default 0;

create temporary table hier(
 parent_location_id smallint unsigned, 
 location_id smallint unsigned, 
 depth smallint unsigned default 0,
 included tinyint unsigned default 0,
 primary key (location_id),
 key (parent_location_id)
)engine = memory;

insert into hier select parent_location_id, location_id, v_depth, 0 from locations where location_id = p_location_id;
create temporary table tmp engine=memory select * from hier;

/* http://dev.mysql.com/doc/refman/5.0/en/temporary-table-problems.html */

while not v_done do

    if exists( select 1 from locations c
        inner join tmp on c.parent_location_id = tmp.location_id and tmp.depth = v_depth) then

        insert into hier select c.parent_location_id, c.location_id, v_depth + 1, 0 from locations c
            inner join tmp on c.parent_location_id = tmp.location_id and tmp.depth = v_depth;

    update hier inner join tmp on hier.location_id = tmp.parent_location_id
    set hier.included = 1;

        set v_depth = v_depth + 1;          

        truncate table tmp;
        insert into tmp select * from hier where depth = v_depth;

    else
        set v_done = 1;
    end if;

end while;

update hier inner join tmp on hier.location_id = tmp.parent_location_id
set hier.included = 1;

-- include any locations that have profiles ???

update hier inner join profiles on hier.location_id = profiles.location_id
set hier.included = 1;

-- output the results

select 
 c.location_id,
 c.location as location,
 p.location_id as parent_location_id,
 p.location as parent_location,
 hier.depth,
 hier.included
from 
 hier
inner join locations c on hier.location_id = c.location_id
left outer join locations p on hier.parent_location_id = p.location_id
-- where included = 1 -- filter in your php or here up to you !
order by
 hier.depth;

-- clean up

drop temporary table if exists hier;
drop temporary table if exists tmp;

end #

delimiter ;

call location_hier(1);

希望这会有所帮助:)