Я потратил некоторое время, чтобы это произошло, и оно наконец готово:)
Чтобы использовать его, вы должны изменить main_data на исходную таблицу.
Чтобы рассказать, в основном, что он делает, сначала он находит записи верхнего уровня. Затем добавляет своих детей первого уровня. После завершения первого уровня, затем переходит на второй уровень и добавляет их, et c.
Я использовал al oop, чтобы сделать его рекурсивным. На всякий случай, если я допустил ошибку в коде, я добавил переменную безопасности max_loops. Таким образом, в этом случае, если он повторяется 10 раз, он останавливается. Вы можете изменить этот параметр с самого начала.
Если код не понятен, пожалуйста, дайте мне знать, чтобы я мог помочь.
DECLARE nth_loop INT64 DEFAULT 0;
DECLARE max_loops INT64 DEFAULT 10;
DECLARE unique_masters INT64;
CREATE TEMP TABLE main_data AS
(
SELECT 'user1' as name, 'user2' as handler union all
SELECT 'user2' as name, 'user3' as handler union all
SELECT 'user3' as name, 'user4' as handler union all
SELECT 'user5' as name, 'user1' as handler union all
SELECT 'user6' as name, 'user1' as handler union all
SELECT 'user7' as name, 'user8' as handler union all
SELECT 'user8' as name, 'user9' as handler
);
SET unique_masters = (SELECT COUNT(DISTINCT name) FROM main_data);
CREATE TEMP TABLE output (
name STRING,
handlers ARRAY<STRING>,
masters ARRAY<STRING>,
level INT64
);
WHILE (SELECT COUNT(*) FROM output) < unique_masters AND nth_loop < max_loops
DO
SET nth_loop = nth_loop + 1;
CREATE OR REPLACE TEMP TABLE output AS
WITH
remaining_items AS
(
SELECT name, handler
FROM main_data m
LEFT JOIN output o
USING(name)
WHERE o.name IS NULL
),
new_level AS
(
SELECT
r1.name,
r1.handler
FROM remaining_items r1
LEFT JOIN remaining_items r2
ON r1.name = r2.handler
WHERE r2.handler IS NULL
)
select
nl.name,
[nl.handler] as handlers,
ARRAY_AGG(master IGNORE NULLS) as masters,
COALESCE((SELECT MAX(level) FROM output ), 0) + 1 as level
from new_level nl
left join (
SELECT handler as name, name as master
FROM output
JOIN UNNEST(handlers) handler
)
USING(name)
GROUP BY nl.name, nl.handler
union all
select
o.name,
ANY_VALUE(o.handlers) || COALESCE(ARRAY_AGG(distinct nl.handler IGNORE NULLS), array<string>[]) as handlers,
ANY_VALUE(masters) as masters,
o.level as level
from output as o
left join unnest(o.handlers) h
left join new_level nl
on nl.name = h
group by o.name, o.level
order by level, name;
END WHILE;
INSERT INTO output (name, handlers, masters, level)
WITH
bottom_level AS (
SELECT m1.name as master, m1.handler as name
FROM main_data m1
LEFT JOIN main_data m2
ON m1.handler = m2.name
WHERE m2.name IS NULL
)
select
bl.name,
ARRAY<STRING>[] as handlers,
output.masters || [bl.master] as masters,
output.level + 1 as level
from bottom_level bl
join output
ON output.name = bl.master
;
SELECT *
FROM output
ORDER BY name;