Я не уверен, что вернет ваш второй выбор в запросе, но вот способ получить степени разделения между акторами:
Допустим, у нас есть таблица идентификаторов акторов, Origin.Чтобы собрать всех актеров, которые играли в одном фильме с одним из актеров на нашем столе, нам нужно начать с Origin, присоединиться к Acting, а затем Movie, чтобы получить все фильмы, в которых сыграли наши актеры Origin., а затем присоединитесь к Acting снова и таблице Actor, чтобы получить то, что мы хотим.Обратите внимание, что таблица Acting появляется два раза.Если мы применим это к рекурсивному CTE и вашему вопросу, отметив, что для таблицы Origin в вашем примере будет Cte, мы получим следующее:
WITH RECURSIVE cte(id, distance) AS (
SELECT actor.id, 0
FROM actor
WHERE actor.name = 'Tom Cruise'
UNION
SELECT DISTINCT actor.id, cte.distance + 1
FROM cte
JOIN acting AS acting1 ON (cte.id = acting1.actor_id)
JOIN movie ON (acting1.movie_id = movie.id)
JOIN acting AS acting2 ON (movie.id = acting2.movie_id)
JOIN actor ON (acting2.actor_id = actor.id)
WHERE cte.id <> actor.id AND cte.distance + 1 <= 10
)
После этого таблица cte будет содержать кортежи типа(id, dist), что означает, что существует путь от Тома Круза к актеру с этим идентификатором и расстоянием dist.
DISTINCT по соображениям эффективности.В нашей таблице Cte будет много плохих пар (второе значение будет больше, чем истинное расстояние), особенно если граф актера плотный, но правильный кортеж будет в таблице Cte.Под правильным кортежем я подразумеваю кортеж (актер, расстояние), такой, что расстояние - это кратчайший путь между начинающим актером (например, Томом Крузом) и этим актером.
Редактировать: Мой плохой, UNION уже делает это, поэтому DISTINCT не нужен для дубликатов.
Чтобы получить это расстояние, мы добавляем выбор с предложением group by:
WITH RECURSIVE cte(id, distance) AS (
SELECT actor.id, 0
FROM actor
WHERE actor.name = 'Tom Cruise'
UNION
SELECT actor.id, cte.distance + 1
FROM cte
JOIN acting AS acting1 ON (cte.id = acting1.actor_id)
JOIN movie ON (acting1.movie_id = movie.id)
JOIN acting AS acting2 ON (movie.id = acting2.movie_id)
JOIN actor ON (acting2.actor_id = actor.id)
WHERE cte.id <> actor.id AND cte.distance + 1 <= 10
)
SELECT id, MIN(distance) AS distance
FROM cte
GROUP BY id
ORDER BY 2 ASC;
Если вы хотите увидеть результат для данного второго актера, скажем, Роберта Дауни-младшего, то это даст вам ответ относительно степеней разделения:
WITH RECURSIVE cte(id, distance) AS (
SELECT actor.id, 0
FROM actor
WHERE actor.name = 'Tom Cruise'
UNION
SELECT actor.id, cte.distance + 1
FROM cte
JOIN acting AS acting1 ON (cte.id = acting1.actor_id)
JOIN movie ON (acting1.movie_id = movie.id)
JOIN acting AS acting2 ON (movie.id = acting2.movie_id)
JOIN actor ON (acting2.actor_id = actor.id)
WHERE cte.id <> actor.id AND cte.distance + 1 <= 10
), distance_table (id, distance) AS (
SELECT id, MIN(distance) AS distance
FROM cte
GROUP BY id
)
SELECT 'Tom Cruise and ' || actor.name || ' are separated by ' ||
COALESCE(TO_CHAR(distance_table.distance, '999999'), 'more than 10') || ' degrees of separation'
FROM actor
LEFT JOIN distance_table ON (actor.id = distance_table.id)
WHERE actor.name = 'Robert Downey Jr';
Хотя я неЯ не думаю, что вы, как правило, хотите вычислять такую информацию непосредственно из базы данных, если вы хотите, чтобы сообщение сообщало путь между актерами, подобно тому, который вы предоставили (Том Круз был в «Днях грома» с Робертом Дюваллом ->Роберт Дюваль был в «Удачливом тебе» с Робертом Дауни-младшим), тогда что-то вроде этого могло бы вернуть следующее:
WITH RECURSIVE cte(id, name, distance, message) AS (
SELECT actor.id, actor.name, 0, ''
FROM actor
WHERE actor.name = 'Tom Cruise'
UNION
SELECT actor.id, actor.name, cte.distance + 1,
cte.message || '> ' || cte.name || ' was in ' ||
movie.title || ' with ' || actor.name || ' '
FROM cte
JOIN acting AS acting1 ON (cte.id = acting1.actor_id)
JOIN movie ON (acting1.movie_id = movie.id)
JOIN acting AS acting2 ON (movie.id = acting2.movie_id)
JOIN actor ON (acting2.actor_id = actor.id)
WHERE cte.id <> actor.id AND cte.distance + 1 <= 10
), distance_table (id, distance) AS (
SELECT id, MIN(distance) AS distance
FROM cte
GROUP BY id
)
SELECT id, name, message, distance
FROM cte
WHERE (id, distance) IN (SELECT * FROM distance_table)
ORDER BY distance;