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

|id |parent_id|is_category|level|
|:-:|:   -   :|:    -    :|: - :|
|0  |   -1    |    true   |  0  |
|1  |    0    |    true   |  1  |
|2  |    0    |    true   |  1  |
|3  |    1    |    true   |  2  |
|4  |    2    |   false   |  2  |
|5  |    3    |    true   |  3  |
|6  |    5    |   false   |  4  |
|7  |    5    |   false   |  4  |
|8  |    5    |    true   |  4  |
|9  |    5    |   false   |  4  |

Задача :
Получить все подэлементы levels <= 3 в папке id == 1.
Идентификаторы результатов должны быть [1,3,5]

Моя текущая реализация - это рекурсивные запросы, что означает, что для приведенного выше примера моя программа сначала получит id == 1, а затем найдет все элементы с is_categorh == true и level <= 3.

Это не похоже на эффективный способ. Любой совет будет оценен.

sql
0
JsW 9 Июл 2019 в 18:33

2 ответа

Лучший ответ

Вы не упоминаете базу данных, которую используете, поэтому я буду использовать PostgreSQL.

Вы можете получить нужные вам строки, используя один запрос, который использует «Рекурсивный CTE». Рекурсивные CTE реализуются несколькими механизмами баз данных, такими как Oracle, DB2, PostgreSQL, SQL Server, MariaDB, MySQL, HyperSQL, H2, Teradata и т. Д.

Запрос должен быть похож на:

with recursive x as (
  select * from t where id = 1
  union all
  select t.*
  from x
  join t on t.parent_id = x.id and t.level <= 3
)
select id from x

Для записи, сценарий данных, который я использовал, чтобы проверить это:

create table t (
  id int,
  parent_id int,
  level int
);

insert into t (id, parent_id, level) values (0, -1, 0);
insert into t (id, parent_id, level) values (1, 0, 1);
insert into t (id, parent_id, level) values (2, 0, 1);
insert into t (id, parent_id, level) values (3, 1, 2);
insert into t (id, parent_id, level) values (4, 2, 2);
insert into t (id, parent_id, level) values (5, 3, 3);
insert into t (id, parent_id, level) values (6, 5, 4);
insert into t (id, parent_id, level) values (7, 5, 4);
insert into t (id, parent_id, level) values (8, 5, 4);
insert into t (id, parent_id, level) values (9, 5, 4);
3
The Impaler 9 Июл 2019 в 19:25

Как уже говорили другие, рекурсивные CTE - это быстрый и, как правило, эффективный метод извлечения данных, которые вы ищете. Если вы хотите избежать рекурсивных CTE, поскольку они не являются бесконечно масштабируемыми и, следовательно, подвержены ошибочному поведению в определенных случаях использования, вы также можете использовать более прямой подход, реализуя рекурсивный поиск с помощью цикла WHILE. Обратите внимание, что это не более эффективно, чем рекурсивный CTE, но это то, что дает вам больший контроль над тем, что происходит в рекурсии. В моем примере я использую Transact-SQL.

Во-первых, установочный код, такой как @The Impaler:

drop table if exists
    dbo.folder_tree;

create table dbo.folder_tree 
    (
    id int not null constraint [PK_folder_tree] primary key clustered,
    parent_id int not null,
    fs_level int not null,
    is_category bit not null constraint [DF_folder_tree_is_category] default(0),
    constraint [UQ_folder_tree_parent_id] unique(parent_id, id)
    );

insert into dbo.folder_tree 
    (id, parent_id, fs_level, is_category)
values 
    (0, -1, 0, 1),  --|0  |   -1    |    true   |  0  |
    (1, 0, 1, 1),   --|1  |    0    |    true   |  1  |
    (2, 0, 1, 1),   --|2  |    0    |    true   |  1  |
    (3, 1, 2, 1),   --|3  |    1    |    true   |  2  |
    (4, 2, 2, 0),   --|4  |    2    |   false   |  2  |
    (5, 3, 3, 1),   --|5  |    3    |    true   |  3  |
    (6, 5, 4, 0),   --|6  |    5    |   false   |  4  |
    (7, 5, 4, 0),   --|7  |    5    |   false   |  4  |
    (8, 5, 4, 1),   --|8  |    5    |    true   |  4  |
    (9, 5, 4, 0);   --|9  |    5    |   false   |  4  |

А затем код для реализации рекурсивного поиска в таблице через цикл WHILE:

drop function if exists
    dbo.folder_traverse;
go

create function dbo.folder_traverse
    (
    @start_id int,
    @max_level int = null
    )
returns @result table
    (
    id int not null primary key,
    parent_id int not null,
    fs_level int not null,
    is_category bit not null
    )
as
    begin
        insert into 
            @result
        select
            id,
            parent_id,
            fs_level,
            is_category
        from
            dbo.folder_tree
        where
            id = @start_id;

        while @@ROWCOUNT > 0
            begin
                insert into 
                    @result
                select
                    f.id,
                    f.parent_id,
                    f.fs_level,
                    f.is_category
                from
                    @result r
                    inner join dbo.folder_tree f on
                        r.id = f.parent_id
                where
                    f.is_category = 1 and
                    (
                        @max_level is null or
                        f.fs_level <= @max_level
                    )
                    except
                select
                    id,
                    parent_id,
                    fs_level,
                    is_category
                from
                    @result;
            end;

        return;
    end;
go

В заключение, единственная причина, по которой я бы порекомендовал этот подход, - это если у вас большое количество рекурсивных членов или вам нужно добавить ведение журнала или какой-то другой процесс между действиями. Этот подход в большинстве случаев медленнее и добавляет сложности к коду, но является альтернативой рекурсивному CTE и соответствует вашим требуемым критериям.

1
Solonotix 11 Июл 2019 в 15:40