Как мне составить таблицу отношений для дружбы?

33

Если Aдруг друга B, то я должен хранить оба значения ABи BA, или одного достаточно? Каковы преимущества и недостатки обоих методов.

Вот мое наблюдение:

  • Если я сохраню оба, я должен обновить оба, когда получу запрос от друга.
  • Если я не сохраню оба, то мне будет трудно, когда приходится делать несколько JOINс этой таблицей.

В настоящее время я поддерживаю отношения в одном направлении.

введите описание изображения здесь

Так что мне делать в этом случае? Любой совет?

Chan
источник
Вы привержены платформе или это теоретический вопрос?
Ник Чаммас
А как насчет гибридного подхода: моделируйте обязательные и безответственные дружеские отношения соответственно в отдельных таблицах, гарантируйте, что дружеская связь вставлена ​​точно в одну из этих таблиц, чего нельзя добиться с помощью сегодняшних продуктов SQL :(
onedaywhen
@onedaywhen - Да, звучит более подходящим для графической базы данных .
Ник Чаммас
@NickChammas: Это не теоретический вопрос. Я работаю над тем, mysqlчто хранится в облаке Amazon.
Чан
1
@Chan - Ах, это означает, что вы не можете использовать проверочные ограничения для обеспечения соблюдения отношений, которые хранятся только одним способом (MySQL не применяет их)
Martin Smith

Ответы:

30

Я хотел бы хранить AB и BA. Дружба - это двусторонние отношения, каждая сущность связана с другой. Хотя интуитивно мы думаем о «дружбе» как об одной связи между двумя людьми, с точки зрения отношений это больше похоже на «А имеет друга Б» и «Б имеет друга А». Два отношения, две записи.

datagod
источник
3
Большое спасибо. Мне действительно нужно тщательно продумать вашу идею! Причина, по которой я избегаю хранения AB и BA, заключается в хранении, поскольку каждый раз, когда у меня возникает дружба, мой стол хранит в два раза больше.
Чан
1
Вы правы насчет хранения, но помните, что если хранить в виде целых чисел, каждое отношение друг-друг займет около 30 байтов (2 записи x 3 столбца x 4 байта на целое число = 24 байта плюс некоторое заполнение). 1 миллион человек с 10 друзьями каждый будет иметь только около 300 МБ данных.
Датагод
1
Датагод: это верно!
Чан
Так я и разработал свои столы, AB & BA.
kabuto178
2
Кроме того, в ситуациях, когда есть только AB, а не BA, это может представлять «ожидающий запрос на добавление в друзья».
Грег
13

Если дружба предназначена для того, чтобы быть симметричной (то есть A дружить с ней Bневозможно, но не наоборот), я бы просто сохранил односторонние отношения с проверочным ограничением, гарантирующим, что каждое отношение может быть представлено только одним способом.

Также я отказался бы от суррогатного идентификатора и вместо этого использовал бы составной PK (и, возможно, составной уникальный индекс также на обращенных столбцах).

CREATE TABLE Friends
  (
     UserID1 INT NOT NULL REFERENCES Users(UserID),
     UserID2 INT NOT NULL REFERENCES Users(UserID),
     CONSTRAINT CheckOneWay CHECK (UserID1 < UserID2),
     CONSTRAINT PK_Friends_UserID1_UserID2 PRIMARY KEY (UserID1, UserID2),
     CONSTRAINT UQ_Friends_UserID2_UserID1 UNIQUE (UserID2, UserID1)
  ) 

Вы не говорите запросы, которые это затрудняет, но вы всегда можете создать представление

CREATE VIEW Foo
AS
SELECT UserID1,UserID2 
FROM Friends
UNION ALL
SELECT UserID2,UserID1 
FROM Friends
Мартин Смит
источник
Я знаю, что это довольно старый, извините за то, что выкопал это. Не лучше ли НЕ определять обратный индекс дружбы UNIQUE, чтобы не создавать ненужного и избыточного дополнительного бремени для INSERTs? Так как у нас PRIMARY KEY (a,b)и так как PK является UNIQUE, обращенная KEY (b,a)не также UNIQUEнезависимо от того , что.
от
1
@tf Думаю, это зависит от оптимизатора запросов. Как вы указали, необходимо проверить только в одну сторону, чтобы план вставки мог сделать это в любом случае. Вопрос помечен MySQL - понятия не имею, как это ведет себя.
Мартин Смит
Я знаю, что это старый ответ, но я просто хочу указать всем, кто спотыкается об этом, что MySQL полностью игнорирует ограничения CHECK (хотя он будет успешно "анализировать" их), поэтому такой подход, вероятно, не подходит для этой технологии.
Михей
@ Мика правда. Я не знал об этом в 2012 году. Все еще буду работать в других СУБД ...
Мартин Смит
+1 за реализацию View для этого. Хранение AB & BA приводит к несогласованности (если отношения не являются двунаправленными), тогда как этот метод является лучшим подходом
imans77
7

Предполагая, что «дружба» всегда двусторонняя / взаимная, я бы, вероятно, справился с этим примерно так.

CREATE TABLE person (
    person_id int IDENTITY(1,1) PRIMARY KEY,
    ...other columns...
)

CREATE TABLE friendship (
    friendship_id int IDENTITY(1,1) PRIMARY KEY,
    ...other columns, if any...
)

CREATE TABLE person_friendship (
    person_id int NOT NULL,
    friendship_id int NOT NULL
    PRIMARY KEY (person_id, friendship_id)
)

В результате вы измените его с соединения «многие ко многим» с «человека» на «человека» на соединение «многие ко многим» с «человека» на «дружбу». Это упростит объединения и ограничения, но побочным эффектом будет использование более двух человек в одной «дружбе» (хотя, возможно, дополнительная гибкость будет потенциальным преимуществом).

db2
источник
Это в основном шаблон группы / членства. Интересная идея, хотя.
einSelbst
4

Возможно, вам потребуется определить индексы для дружеских отношений, а не удваивать количество строк:

CREATE TABLE person
(
    person_id INT NOT NULL AUTO_INCREMENT,
    ...
    PRIMARY KEY (person_id)
);
CREATE TABLE friendship
(
    friend_of INT NOT NULL,
    friend_to INT NOT NULL,
    PRIMARY KEY (friend_of,friend_to),
    UNIQUE KEY friend_to (friend_to,friend_of)
);

Таким образом, вы удваиваете хранилище для индексов, но не для табличных данных. В результате это должно быть 25% экономии дискового пространства. MySQL Query Optimizer выберет только выполнять сканирование диапазона индекса, поэтому концепция покрытия индексов здесь работает хорошо.

Вот несколько хороших ссылок по индексам покрытия:

ПРЕДОСТЕРЕЖЕНИЕ

Если дружба не взаимна, у вас есть основание для другого типа отношений: FOLLOWER

Если friend_to не является другом friend_of, вы можете просто оставить эти отношения вне таблицы.

Если вы хотите определить отношения для всех типов, независимо от того, являются ли они взаимными или нет, вы, вероятно, можете использовать следующую схему таблиц:

CREATE TABLE person
(
    person_id INT NOT NULL AUTO_INCREMENT,
    ...
    PRIMARY KEY (person_id)
);
CREATE TABLE relationship
(
    rel_id INT NOT NULL AUTO_INCREMENT,
    person_id1 INT NOT NULL,
    person_id2 INT NOT NULL,
    reltype_id TINYINT,
    PRIMARY KEY (rel_id),
    UNIQUE KEY outer_affinity (reltype_id,person_id1,person_id2),
    UNIQUE KEY inner_affinity (reltype_id,person_id2,person_id1),
    KEY has_relationship_to (person1_id,reltype_id),
    KEY has_relationship_by (person2_id,reltype_id)
);
CREATE TABLE relation
(
    reltype_id TINYINT NOT NULL AUTO_INCREMENT,
    rel_name VARCHAR(20),
    PRIMARY KEY (reltype_id),
    UNIQUE KEY (rel_name)
);
INSERT INTO relation (relation_name) VALUES
('friend'),('follower'),('foe'),
('forgotabout'),('forsaken'),('fixed');

Из таблицы отношений вы можете расположить отношения так, чтобы они включали следующее:

  • Друзья должны быть взаимными
  • Враги могут быть взаимными или нет
  • Последователи могут быть взаимными или нет
  • Другие отношения будут подлежать интерпретации (забытым или оставленным или получателем мести (фиксированный))
  • Возможные отношения могут быть расширены

Это должно быть более надежным для всех отношений, независимо от того, являются ли эти отношения взаимными или нет.

RolandoMySQLDBA
источник
привет @rolandomysqldba, я большой поклонник ваших ответов. это действительно полезно для меня (в данном случае 1-й пример). Теперь вот одна оговорка для меня, я хочу уникальных отношений. (Например, если пользователь A дружит с B, то B дружит с A недопустимо.) мне делать с триггером? а как насчет производительности? потому что у меня очень большая таблица (около 1 миллиона записей), и если я ищу друзей пользователя A (A хранится в обоих полях (friend_of, friend_to), а mysql использует только один индекс, то он работает очень медленно. Я должен хранить повторяющиеся записи в моей таблице (например, A-> B, B-> A). Любой лучший вариант?
Маниш Сапкал
1

Если вы можете контролировать в приложении, что идентификатор A всегда ниже, чем идентификатор B (предварительный порядок идентификаторов элементов A, B), вы можете использовать запрос без ИЛИ (выберите где id_A = a И id_B = b вместо запроса (id_A = a AND id_B = b) ИЛИ (id_A = b AND id_B = a)), а также ведите половину записей, которые вам понадобятся, с приближениями других. Затем вы должны использовать другое поле, чтобы поддерживать состояние отношений («друзья», «a-solicited-to-b», «b-solicited-to-a», «exfriends-a», «exfriends-b»), и все готово.

Так я управлял своей системой дружбы, и это упрощает систему и использует половину строк, которые вам понадобятся в других системах, только говоря, что A равно нижнему значению id в коде.

appartisan
источник