簡體   English   中英

分層表-如何獲取項目的路徑[MySQL中的鏈接列表]

[英]Hierarchical table - how to get paths of the items [linked lists in MySQL]

我在MySQL中有一個分層表:每個項目的parent字段都指向其父項目的id字段。 對於每一項,我都可以使用此處描述查詢獲取所有父項的列表(無論深度如何)。 使用GROUP_CONCAT我將完整路徑作為單個字符串獲取:

SELECT GROUP_CONCAT(_id SEPARATOR ' > ') FROM (
SELECT  @r AS _id,
         (
         SELECT  @r := parent
         FROM    t_hierarchy
         WHERE   id = _id
         ) AS parent,
         @l := @l + 1 AS lvl
 FROM    (
         SELECT  @r := 200,
                 @l := 0
         ) vars,
         t_hierarchy h
WHERE    @r <> 0
ORDER BY lvl DESC
) x

僅當該項目的id是固定的[在這種情況下為200 ]時,我才能使這項工作有效。

我想對所有行執行相同的操作:使用一個附加字段( path )檢索整個表,該字段將顯示完整路徑。 我想到的唯一解決方案是將此查詢包裝在另一個選擇中,設置一個臨時變量@id並在子查詢中使用它。 但這是行不通的。 我在path字段中得到NULL

SELECT @id := id, parent, (
    SELECT GROUP_CONCAT(_id SEPARATOR ' > ') FROM (
    SELECT  @r AS _id,
             (
             SELECT  @r := parent
             FROM    t_hierarchy
             WHERE   id = _id
             ) AS parent,
             @l := @l + 1 AS lvl
     FROM    (
             SELECT  @r := @id,
                     @l := 0
             ) vars,
             t_hierarchy h
    WHERE    @r <> 0
    ORDER BY lvl DESC
    ) x
) as path
 FROM t_hierarchy

PS我知道我可以將路徑存儲在單獨的字段中,並在進行插入/更新時對其進行更新,但是我需要基於鏈表技術的解決方案。

更新:我希望看到一個不會使用遞歸或forwhile構造的解決方案。 上面的查找路徑的方法不使用任何循環或函數。 我想以相同的邏輯找到解決方案。 或者,如果不可能,請嘗試解釋原因!

請考慮以下兩個查詢之間的區別:

SELECT @id := id as id, parent, (
    SELECT concat(id, ': ', @id)
) as path
FROM t_hierarchy;

SELECT @id := id as id, parent, (
    SELECT concat(id, ': ', _id)
    FROM (SELECT @id as _id) as x
) as path
FROM t_hierarchy;

它們看起來幾乎相同,但給出的結果卻截然不同。 在我的MySQL版本上,第二個查詢中的_id對於其結果集中的每一行都是相同的,並且等於最后一行的id 但是,最后一點是正確的,因為我按照給定的順序執行了兩個查詢。 SET @id := 1 ,例如,我可以看到_id始終等於SET語句中的值。

那么這是怎么回事? 一個EXPLAIN產量線索:

mysql>     explain SELECT @id := id as id, parent, (
    ->         SELECT concat(id, ': ', _id)
    ->         FROM (SELECT @id as _id) as x
    ->     ) as path
    ->     FROM t_hierarchy;
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
| id | select_type        | table       | type   | possible_keys | key              | key_len | ref  | rows | Extra          |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
|  1 | PRIMARY            | t_hierarchy | index  | NULL          | hierarchy_parent | 9       | NULL | 1398 | Using index    |
|  2 | DEPENDENT SUBQUERY | <derived3>  | system | NULL          | NULL             | NULL    | NULL |    1 |                |
|  3 | DERIVED            | NULL        | NULL   | NULL          | NULL             | NULL    | NULL | NULL | No tables used |
+----+--------------------+-------------+--------+---------------+------------------+---------+------+------+----------------+
3 rows in set (0.00 sec)

第三行,即不使用任何表的DERIVED表,向MySQL指示可以隨時精確地計算一次。 服務器沒有注意到派生表使用查詢中其他位置定義的變量,並且不知道您希望它每行運行一次。 您被MySQL文檔中有關用戶定義變量的行為所困擾:

通常,永遠不要為用戶變量分配值,並且不要在同一條語句中讀取該值。 您可能會得到預期的結果,但這不能保證。 涉及用戶變量的表達式的求值順序是不確定的,並且可能根據給定語句中包含的元素而改變; 此外,在MySQL服務器的發行版之間,不能保證此順序相同。

就我而言,它選擇先計算表,然后再由外部SELECT (重新)定義@id 實際上,這就是為什么原始分層數據查詢有效的原因。 @r定義是由MySQL在查詢中的其他任何內容之前計算的,正是因為它是這種派生表。 但是,我們這里需要一種方法來對每個表行重置@r ,而不僅僅是對整個查詢重置一次。 為此,我們需要一個看起來像原始查詢的查詢,並手動重置@r

SELECT  @r := if(
          @c = th1.id,
          if(
            @r is null,
            null,
            (
              SELECT  parent
              FROM    t_hierarchy
              WHERE   id = @r
            )
          ),
          th1.id
        ) AS parent,
        @l := if(@c = th1.id, @l + 1, 0) AS lvl,
        @c := th1.id as _id
FROM    (
        SELECT  @c := 0,
                @r := 0,
                @l := 0
        ) vars
        left join t_hierarchy as th1 on 1
        left join t_hierarchy as th2 on 1
HAVING  parent is not null

此查詢以與原始查詢相同的方式使用第二個t_hierarchy ,以確保結果中有足夠的行供父子查詢循環。 它還為每個_id添加一行,並將其自身作為父級。 否則,所有根對象(父字段中為NULL )將根本不會出現在結果中。

奇怪的是,通過GROUP_CONCAT運行結果似乎干擾了排序。 幸運的是,該函數具有自己的ORDER BY子句:

SELECT  _id,
        GROUP_CONCAT(parent ORDER BY lvl desc SEPARATOR ' > ') as path,
        max(lvl) as depth
FROM    (
  SELECT  @r := if(
            @c = th1.id,
            if(
              @r is null,
              null,
              (
                SELECT  parent
                FROM    t_hierarchy
                WHERE   id = @r
              )
            ),
            th1.id
          ) AS parent,
          @l := if(@c = th1.id, @l + 1, 0) AS lvl,
          @c := th1.id as _id
  FROM    (
          SELECT  @c := 0,
                  @r := 0,
                  @l := 0
          ) vars
          left join t_hierarchy as th1 on 1
          left join t_hierarchy as th2 on 1
  HAVING  parent is not null
  ORDER BY th1.id
) as x
GROUP BY _id;

合理的警告:這些查詢隱式依賴於@c更新之前發生的@r@l更新。 MySQL不能保證該順序,並且可能隨服務器的任何版本而變化。

定義getPath函數並運行以下查詢:

select id, parent, dbo.getPath(id) as path from t_hierarchy 

定義getPath函數:

create function dbo.getPath( @id int)
returns varchar(400)
as
begin
declare @path varchar(400)
declare @term int
declare @parent varchar(100)
set @path = ''
set @term = 0
while ( @term <> 1 )
begin
   select @parent = parent from t_hierarchy where id = @id
   if ( @parent is null or @parent = '' or  @parent = @id )
        set @term = 1
   else
        set @path = @path + @parent   
   set @id = @parent     
end
return @path
end

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM