2014-11-26 3 views
3

Это вопрос, который, как мне кажется, я должен знать, но у меня было время, столь же выразительное, как хотелось бы быть в SQL.Transitive Property в SQL

Мой вопрос простой: что такое идиома для выражения транзитивных отношений в SQL? Конкретный пример:

Скажем, у меня есть следующая схема:

user(email, name) 
friends(friend1_email, friend2_email) 

У меня проблема, выражающая следующий запрос:

Найти пользователей A, B и C, такие, что А друзья с B, B дружит с C, но C не дружит с A.

Я признаю, что это домашнее задание, но у меня возникли концептуальные проблемы с выражением запросов. Любая помощь будет оценена. Благодарю.

+2

Вы пытаетесь перемещаться по направленному графику? Если это так, это будет очень сложно сделать с чистым SQL, поскольку этот процесс часто является глубоко рекурсивным. – tadman

+2

@tadman: глубокая рекурсивность не является проблемой SQL как таковой, потому что рекурсивные запросы уже были в стандарте SQL уже более десяти лет. Только MySQL все еще не может их выполнять. –

+0

@a_horse_with_no_name Предоставлено SQL * standard * позволяет такие вещи, но это MySQL. – tadman

ответ

2

Мой совет со сложными запросами всегда начать с простого:

# Find friends A and B 
select A.email as A_email, A.name as A_name, B.email as B_email, B.name as B_name 
from user A 
join friends 
on A.email = friends.friend1_email 
join user B 
on B.email = friends.friend2.email 

достаточно просто, давайте делать это снова для B и C:

# Find friends B and C 
select B.email as B_email, B.name as B_name, C.email as C_email, C.name as C_name 
from user B 
join friends 
on B.email = friends.friend1_email 
join user C 
on C.email = friends.friend2.email 

Теперь давайте объединимся, чтобы получить A, B и C в одном запросе

# Find friends A, B, and C 
select A.email as A_email, A.name as A_name, B.email as B_email, B.name as B_name, C.email as C_email, C.name as C_name 
from user A 
join friends f1 
on A.email = f1.friend1_email 
join user B 
on f1.friend2_email = B.email 
join friends f2 
on B.email = f2.friend1_email 
join user C 
on f2.friend2_email = C.email 

Вышеприведенный запрос даст нам всех пользователей A, которые являются друзьями с пользователями B, которые являются друзьями с пользователями C, но не ограничивают набор результатов теми отчетами, в которых A и C не являются друзьями. Чтобы получить этот набор результатов, нам придется немного изменить наш запрос.

# Find friends A, B, and C 
select A.email as A_email, A.name as A_name, B.email as B_email, B.name as B_name, C.email as C_email, C.name as C_name 
from user A 
join friends f1 
on A.email = f1.friend1_email 
join user B 
on f1.friend2_email = B.email 
join friends f2 
on B.email = f2.friend1_email 
join user C 
on f2.friend2_email = C.email 
left join friends f3 
on A.email = f3.friend1_email 
and C.email = f3.friend2_email 
where 
    f3.friend1_email is null 
+0

Я не прав, говоря, что это решение не сработает, если дружба не будет симметричной? То есть, если пользователь A дружит с B, но пользователь B не дружит с A? –

+0

Решение, которое я изложил, предполагает, что дружба взаимная и без направления, так что A {дружит с} B подразумевает, что B {дружит с} A. Если это не так, тогда да, запрос должен быть изменен на учтите это. –

0

Что-то вроде:

select * 
from friends a 

--If this joins, a and b are friends 
left join friends b 
on (a.friend1_email = b.friend1_email or 
    a.friend1_email = b.friend2_email or 
    a.friend2_email = b.friend1_email or 
    a.friend2_email = b.friend2_email) 

--If this joins, b and c are friends 
left join friends c 
on (b.friend1_email = c.friend1_email or 
    b.friend1_email = c.friend2_email or 
    b.friend2_email = c.friend1_email or 
    b.friend2_email = c.friend2_email) 

--If this joins, c and a are friends. 
--Also making sure that a is same person as a2 
left join friends a2 
on (c.friend1_email = a2.friend1_email or 
    c.friend1_email = a2.friend2_email or 
    c.friend2_email = a2.friend1_email or 
    c.friend2_email = a2.friend2_email) 
and (a.friend1_email = a2.friend1_email or 
    a.friend1_email = a2.friend2_email or 
    a.friend2_email = a2.friend1_email or 
    a.friend2_email = a2.friend2_email) 

where b.friend1_email is not null --join was made, a and b are friends 
and c.friend1_email is not null --join was made, b and c are friends 
and a2.friend1_email is null --join was NOT made, a and c are NOT friends 
Смежные вопросы