Использование рекурсивного CTE с Ecto
Как мне использовать результат рекурсивного CTE в запросе, который я планирую выполнить с Ecto? Например, допустим, у меня есть таблица узлов, структурированная так:
-- nodes table example --
id parent_id
1 NULL
2 1
3 1
4 1
5 2
6 2
7 3
8 5
и у меня также есть еще одна таблица node_users, структурированная так:
-- nodes_users table example --
node_id user_id
1 1
2 2
3 3
5 4
Теперь я хочу захватить всех пользователей с узлом в или выше определенного узла, для примера давайте выберем узел с идентификатором 8.
Я мог бы использовать следующий рекурсивный запрос postgresql для этого:
WITH RECURSIVE nodes_tree AS (
SELECT *
FROM nodes
WHERE nodes.id = 8
UNION ALL
SELECT n.*
FROM nodes n
INNER JOIN nodes_tree nt ON nt.parent_id = n.id
)
SELECT u.* FROM users u
INNER JOIN users_nodes un ON un.user_id = u.id
INNER JOIN nodes_tree nt ON nt.id = un.node_id
Это должно вернуть пользователей.* Для пользователей с идентификатором 1, 2 и 4.
Я не уверен, как бы я мог выполнить этот же запрос, используя ecto, в идеале таким образом, чтобы он возвращал цепочку вывода. Я понимаю, что могу вставить необработанный SQL в свой запрос с помощью макроса фрагмента, но я не совсем уверен, куда это пойдет для этого использования или даже если это будет наиболее подходящий путь.
Помощь и / или предложения будут оценены!
2 ответа
Я смог сделать это с помощью фрагмента. Вот пример кода, который я использовал. Я, вероятно, перенесу этот метод в хранимую процедуру.
Repo.all(MyProj.User,
from u in MyProj.User,
join: un in MyProj.UserNode, on: u.id == un.user_id,
join: nt in fragment("""
(
WITH RECURSIVE node_tree AS (
SELECT *
FROM nodes
WHERE nodes.id = ?
UNION ALL
SELECT n.*
FROM nodes n
INNER JOIN node_tree nt ON nt.parent_id == n.id
)
) SELECT * FROM node_tree
""", ^node_id), on: un.node_id == nt.id
)
Сегодня, спустя почти 7 лет с тех пор, как был задан вопрос, в документации Ecto есть раздел о том, как использовать рекурсивные CTE . Вот как это работает.
Сначала создайте базовый вариант и рекурсивный шаг вашего запроса. Затем используйте объединение этого и передайте егоEcto.Query.with_cte
. Вам также необходимо установитьrecursive_ctes(true)
:
base_case =
from(row in "nodes", where: row.id == ^node_id)
recursive_step =
from(n in "nodes", join: nt in "node_tree", on: nt.parent_id == n.id)
node_tree = base_case |> union(^recursive_step)
MyProj.User
|> recursive_ctes(true)
|> with_cte("node_tree", as: ^node_tree)
|> join(:inner, [u], un in MyProj.UserNode, on: u.id == un.user_id)
|> join(:inner, [u, un], nt in "node"tree", on: un.node_id == nt.id)