Як я повинен створити таблицю стосунків для дружби?


33

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

Ось моє спостереження:

  • Якщо я зберігаю обоє, мені доведеться поновлювати обидва, коли отримую запит від друга.
  • Якщо я не дотримуюсь обох, то мені було складно, коли мені доведеться робити кілька разів JOINз цією таблицею.

В даний час я підтримую відносини в один бік.

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

То що мені робити в цьому випадку? Будь-яка порада?


Ви прихильні до платформи, чи це теоретичне питання?
Нік Чаммас

Що стосується гібридного підходу: модель, яка вимагає та нерозподілених дружби відповідно в окремих таблицях, гарантує дружбу, вставлену саме в одну з таких таблиць, що не приємно досягти, використовуючи сьогоднішні продукти SQL :(
onedaywhen

@onedaywhen - Так, це здається більш підходящим для бази даних графіків .
Нік Чаммас

@ NickChammas: Це не теоретичне питання. Я працюю над тим, mysqlщо зберігається в хмарі Amazon.
Чан

1
@Chan - А це означає, що ви не можете використовувати обмеження перевірки для забезпечення відносин, зберігається лише один спосіб (MySQL не застосовує їх)
Мартін Сміт

Відповіді:


30

Я б зберігав AB та BA. Дружба - це дійсно двосторонні стосунки, кожна сутність пов'язана з іншою. Хоча інтуїтивно ми думаємо про "дружбу" як про один зв'язок між двома людьми, з реляційної точки зору це більше схоже на "А має друга Б" і "В має друга А". Два стосунки, два записи.


3
Велике дякую. Мені справді потрібно ретельно продумати твою ідею! Причиною того, що я уникаю зберігання AB та BA, є те, що вона зберігається, оскільки кожен раз, коли я веду дружбу, мій стіл зберігав би вдвічі більше.
Чан

1
Ви маєте рацію щодо пам’яті, але пам’ятайте, що якщо їх зберігати як цілі числа, кожне співвідношення друг-друг займало б близько 30 бай (2 записи x 3 стовпчики x 4 байти на ціле число = 24 байти плюс трохи прокладки). На 1 мільйон людей з 10 друзями все одно залишиться лише близько 300 МБ даних.
datagod

1
datagod: саме так!
Чан

Ось так я спроектував свої столи, також AB та BA.
kabuto178

2
Крім того, у ситуаціях, коли є лише AB, а не BA, це може означати "очікує на запит друзів".
Грег

13

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

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

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)і оскільки ПК є UNIQUE, то зворотнийKEY (b,a) значення теж UNIQUEне має значення.
tfrommen

1
@tf Вгадайте, що залежить від оптимізатора querŷ. Як ви зазначаєте, потрібно лише перевірити один бік, щоб план вставки все-таки міг зробити це. Питання позначене MySQL - поняття не має, як це поводиться.
Мартін Сміт

Я знаю, що це стара відповідь, але я просто хочу зазначити будь-кому, хто натрапляє на це, що MySQL повністю ігнорує обмеження CHECK (хоча це буде успішно "розбирати їх"), тому такий підхід, мабуть, не є таким шляхом.
Міхе

@Micah правда. Я про це не знав у 2012 році. Ще працюватиму в інших СУБД ...
Мартін Сміт

+1 для реалізації Погляду для цього. Зберігання 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)
)

Результат полягає в тому, що ви змінюєте його на приєднання багатьох до багатьох з "людини" на "особу", на приєднання багатьох до багатьох людей з "людини" на "дружбу". Це спростить приєднання та обмеження, але має побічний ефект, що дозволяє отримати більше двох людей в одній «дружбі» (хоча, можливо, додаткова гнучкість буде потенційною перевагою).


Це, в основному, структура групи / членства. Цікава ідея, хоча.
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 вибере лише виконувати сканування діапазону індексів, тому тут добре працює концепція покриття індексів.

Ось кілька приємних посилань на покриття індексів:

КАВАТИ

Якщо дружба не є взаємною, у вас є підстава для іншого типу відносин: НАЙКРАЩИЙ

Якщо 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, я великий фанат ваших відповідей. це дуже корисно для мене (в даному випадку 1-й приклад). Тепер ось один застереження для мене, я хочу унікальних стосунків. (напр., якщо користувач A, дружив з B, B, товариш з A, неприйнятний.) чи варто робити тригер? а як щодо продуктивності? тому що у мене дуже величезна таблиця (близько 1 мільйона записів), і якщо я шукаю друзів користувача A (A зберігається в обох (friend_of, friend_to) полях і mysql, використовуючи лише один індекс, то він працює дуже повільно. Ось так Я повинен мати зберігати повторювані записи в моїй таблиці (eg.A-> B, B-> A) Будь-який кращий варіант.?
Manish Sapkal

1

Якщо ви можете в програмі контролювати, що ідентифікатор A завжди нижчий, ніж id B (попереднє замовлення ідентифікаторів елементів A, B), ви можете використовувати запит без АБО (виберіть, де id_A = a AND id_B = b, а не запитувати (id_A = a AND id_B = b) АБО (id_A = b AND id_B = a)), а також підтримувати половину записів, які вам знадобляться, з наближеннями інших. Тоді вам слід використовувати інше поле для підтримки стану відносин (are-friends, a-zaproy-to-b, b-solicited-to-a, exfriends-a, exfriends-b), і ви закінчили.

Це те, як я керував своєю системою дружби, і це спрощує систему і використовує половину рядків, які вам знадобляться для інших систем, лише кажучи, що A дорівнює нижчому значенню id у коді.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.