BigQuery строит массив иерархии из главного-подчиненного ключа - PullRequest
0 голосов
/ 15 апреля 2020

У меня есть структура таблицы «ведущий-ведомый». Col1 идентифицирует пользователя, Col2 идентифицирует его мастера

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

WITH sample_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
)

SELECT
  level1.name,

  -- handle "nulls" for missing levels
  ARRAY(
    SELECT a 
    FROM UNNEST(([  level1.handler, up_level2.handler, up_level3.handler, up_level4.handler, up_level5.handler ])) AS a 
    WHERE a is not null
  ) AS masters,

  ARRAY(
    SELECT a 
    FROM UNNEST(([ down_level3.handler, down_level4.handler, down_level5.handler ])) AS a 
    WHERE a is not null
  ) AS slaves

FROM sample_data AS level1
  -- Join for handler (hierarchy in up direction)
  LEFT JOIN sample_data AS up_level2 ON level1.handler = up_level2.name
  LEFT JOIN sample_data AS up_level3 ON up_level2.handler = up_level3.name
  LEFT JOIN sample_data AS up_level4 ON up_level3.handler = up_level4.name
  LEFT JOIN sample_data AS up_level5 ON up_level4.handler = up_level5.name

  -- Join for handler (hierarchy in down direction)
  LEFT JOIN sample_data AS down_level2 ON level1.name = down_level2.handler
  LEFT JOIN sample_data AS down_level3 ON down_level2.name = down_level3.handler
  LEFT JOIN sample_data AS down_level4 ON down_level3.name = down_level4.handler
  LEFT JOIN sample_data AS down_level5 ON down_level4.name = down_level5.handler

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

1 Ответ

1 голос
/ 19 апреля 2020

Я потратил некоторое время, чтобы это произошло, и оно наконец готово:)

Чтобы использовать его, вы должны изменить 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;
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...