SQLite рекурсивный запрос для получения первой ветви дерева - PullRequest
2 голосов
/ 15 января 2020

Я создаю приложение Xamarin.Forms. У меня есть таблица в моей базе данных SQLite, которая содержит иерархические данные в форме дерева. Давайте назовем его TreeNode:

public class TreeNode
{
    [PrimaryKey]
    public int Id { get; set; }

    [Indexed]
    public int? ParentId {get; set;}
    public string ParentPath { get; set; }

    [Indexed]
    public int Sort { get; set; }
    public string Data { get; set; }
}

Эта древовидная структура содержит огромное меню (+ 100K узлов). У братьев и сестер в дереве есть значение Sort, чтобы установить порядок. Цели этого свойства:

  1. Чтобы я мог показывать элементы меню в произвольном порядке.
  2. Чтобы я мог вычислить выбор поддерева по умолчанию, когда выбран узел.

Давайте сосредоточимся на пункте 2. Я хочу выбирать первую ветвь до первого листа, учитывая свойство Sort.

В качестве примера давайте рассмотрим следующее дерево, представляющее menu (извлечено из здесь ):

                               1,0
                                |
    ------------------------------------------------------------------
    2,1                        93,2                 4,3            5,4       
     |                          |                    |              |
------------------------  ----------------------  ----------------  ----------  
6,5  7,6  8,7  9,8  10,9  11,3  12,5  13,7  14,9  15,1  16,5  17,9  18,2  19,8
      |                                |                       |     |
  ----------                        ----------           ----------  ----------
  27,8  26,9                        25,7  24,8           23,6  22,7  21,5  20,6

Для каждого узла первое число - Id, второе - сортировка для каждого брата на том же уровне. Таким образом, для узла с Id = 2, имеет Sort = 1, Id = 93 имеет Sort = 2, et c.

Я исследовал рекурсивные запросы за последние дни, и я могу пройти по вершине дерева. снизу (в ширину и в глубину), также я знаю, как получить всех предков данного узла. Есть множество примеров для этого.

Как я уже говорил в пункте 2, я хочу иметь рекурсивный запрос, который возвращает мне первую ветвь до первого листа, или, возможно, какое-то другое условие в будущем, но давайте придерживаться того, что я хочу только первая ветвь до первого листа. В приведенном выше примере узлы, которые я хочу получить из дерева:

1,0
2,1
6,5

Я пытался выполнить запрос, подобный следующему:

WITH RECURSIVE depth_first_traversal
( 
    level,
    lineage,
    Id,
    ParentId,
    Sort,
    leaf 
)
AS 
( 
    SELECT CAST ( 1 AS INTEGER )    AS level,
        '00' || node.Sort        AS lineage,
        node.Id                  AS Id,
        node.ParentId            AS ParentId,
        node.Sort                AS Sort,
        node.leaf                AS leaf
    FROM node    
    WHERE node.ParentId = 1 --Is NULL 
UNION ALL
    SELECT depth_first_traversal.level + 1,
           depth_first_traversal.lineage || '-' || '00' || node.sibling_number,
           node.node_id,
           node.parent_id,
           node.sibling_number,
           node.leaf
   FROM depth_first_traversal
   INNER JOIN node
   ON node.parent_id = depth_first_traversal.node_id
   --WHERE node.leaf = 1 I was trying to play with a leaf flag to reduce the CTE size
   ORDER BY lineage
)
SELECT 
    node_id,
    level,
    lineage,
    leaf
FROM depth_first_traversal;

Но этот запрос возвращает мне все ветви от данного узла в начальном выборе. Таким образом, для текущего запроса он возвращает меня:

NodeId    Leaf
2         0
6         1 <- I would like to stop recursivity here, but how?
7         0
27        1
28        1
8         1
9         1
10        1
93        0
11        1
12        1
13        0
25        1
24        1
14        1
4         0
15        1
16        1
17        0
23        1
22        1
5         0
18        0
21        1
20        1
19        1

Я надеюсь, что изложил свой случай ясно, но в заключение,

мой вопрос : как я могу сделать рекурсивный запрос, чтобы получить первую ветвь дерева, учитывая, что братья и сестры на том же уровне отсортированы?

Единственное, что можно обойти эту проблему, о которой я могу подумать, - это опубликовать обработанный результат и использовать leaf флаг, чтобы прекратить обходить результат запроса. Но это далеко не желательно, так как близко к root дерева запрашиваемого узла, чем дольше будет выполняться запрос и тем больше будет набор результатов.

Спасибо за вашу помощь!

ОБНОВЛЕНИЕ: я добавляю операторы создания таблицы и вставки:

CREATE TABLE node ( 
   Id          INTEGER,
   ParentId    INTEGER REFERENCES node ( node_id ),
   Sort        INTEGER,
   leaf        BOOL,
PRIMARY KEY ( Id ) );

INSERT INTO node VALUES (  1,  NULL,             9, 0 );
INSERT INTO node VALUES (      2,  1,             1, 0 );
INSERT INTO node VALUES (          6,  2,             5, 1 );
INSERT INTO node VALUES (          7,  2,             6, 0 );
INSERT INTO node VALUES (             27,  7,             8, 1 );
INSERT INTO node VALUES (             26,  7,             9, 1 );
INSERT INTO node VALUES (          8,  2,             7, 1 );
INSERT INTO node VALUES (          9,  2,             8, 1 );
INSERT INTO node VALUES (         10,  2,             9, 1 );
INSERT INTO node VALUES (     93,  1,             2, 0 );
INSERT INTO node VALUES (         11, 93,             3, 1 );
INSERT INTO node VALUES (         12, 93,             5, 1 );
INSERT INTO node VALUES (         13, 93,             7, 0 );
INSERT INTO node VALUES (             25, 13,             7, 1 );
INSERT INTO node VALUES (             24, 13,             8, 1 );
INSERT INTO node VALUES (         14, 93,             9, 1 );
INSERT INTO node VALUES (      4,  1,             3, 0 );
INSERT INTO node VALUES (         15,  4,             1, 1 );
INSERT INTO node VALUES (         16,  4,             5, 1 );
INSERT INTO node VALUES (         17,  4,             9, 0 );
INSERT INTO node VALUES (             23, 17,             6, 1 );
INSERT INTO node VALUES (             22, 17,             7, 1 );
INSERT INTO node VALUES (      5, 1,              4, 0 );
INSERT INTO node VALUES (         18,  5,             2, 0 );
INSERT INTO node VALUES (             21, 18,             5, 1 );
INSERT INTO node VALUES (             20, 18,             6, 1 );
INSERT INTO node VALUES (         19,  5,             8, 1 );

1 Ответ

2 голосов
/ 15 января 2020

Хитрость заключается в том, чтобы рекурсивно выбрать только строку родного брата с минимальным значением сортировки, которое является самым левым дочерним элементом текущей родительской строки:

WITH cte AS
 (SELECT Id, ParentId, Sort, 0 as Depth FROM node WHERE ParentId IS NULL
 UNION ALL
  SELECT c.Id, c.ParentId, c.Sort, p.Depth + 1
  FROM node AS c
  JOIN cte AS p ON c.ParentId = p.Id
  WHERE c.Sort = (SELECT min(Sort) FROM node AS t WHERE t.ParentId = p.Id))
SELECT * FROM cte ORDER BY Depth;
Id          ParentId    Sort        Depth     
----------  ----------  ----------  ----------
1                       9           0         
2           1           1           1         
6           2           5           2         
...