ฉันจะออกแบบตารางความสัมพันธ์สำหรับมิตรภาพได้อย่างไร


33

ถ้าAเป็นเพื่อนBแล้วฉันควรเก็บค่าทั้งสองABและBAหรือหนึ่งก็พอ? ข้อดีและข้อเสียของทั้งสองวิธีคืออะไร

นี่คือการสังเกตของฉัน:

  • ถ้าฉันเก็บทั้งคู่ไว้ฉันต้องอัปเดตทั้งคู่เมื่อได้รับคำขอจากเพื่อน
  • หากฉันไม่เก็บทั้งสองไว้ฉันก็พบว่ามันยากเมื่อต้องทำหลายอย่างJOINกับตารางนี้

ปัจจุบันฉันรักษาความสัมพันธ์ไว้ทางเดียว

ป้อนคำอธิบายรูปภาพที่นี่

แล้วฉันควรทำอย่างไรในกรณีนี้? คำแนะนำใด ๆ?


คุณมุ่งมั่นกับแพลตฟอร์มหรือเป็นคำถามเชิงทฤษฎีหรือไม่?
Nick Chammas

สิ่งที่เกี่ยวกับวิธีการแบบผสมผสาน: แบบจำลองที่ร้องขอและมิตรภาพที่ไม่ได้รับการตอบรับตามลำดับในตารางแยกต่างหากให้แน่ใจว่ามีการแทรกมิตรภาพเข้าไปในหนึ่งในตารางเหล่านั้นไม่เหมาะที่จะใช้ผลิตภัณฑ์ SQL ของวันนี้ :(
onedaywhen

@onedaywhen - ใช่เสียงมากขึ้นเหมาะสมสำหรับฐานข้อมูลกราฟ
Nick Chammas

@NickChammas: ไม่ใช่คำถามเชิงทฤษฎี ฉันกำลังทำงานmysqlซึ่งถูกเก็บไว้ในคลาวด์ของ Amazon
จัน

1
@Chan - อานั่นหมายความว่าคุณไม่สามารถใช้การตรวจสอบข้อ จำกัด ในการบังคับใช้ความสัมพันธ์จะถูกเก็บไว้เพียงหนึ่งวิธีที่แล้ว (MySQL ไม่บังคับเหล่านี้)
มาร์ตินสมิ ธ

คำตอบ:


30

ฉันจะเก็บ AB และ BA มิตรภาพคือความสัมพันธ์แบบสองทางแต่ละเอนทิตีเชื่อมโยงกับสิ่งอื่น แม้ว่าเราจะคิดว่า "มิตรภาพ" อย่างสังหรณ์ใจเป็นลิงก์เดียวระหว่างคนสองคนจากมุมมองเชิงสัมพันธ์มันเป็นเหมือน "A มีเพื่อน B" และ "B มีเพื่อน A" สองความสัมพันธ์สองบันทึก


3
ขอบคุณมาก. ฉันต้องคิดถึงความคิดของคุณอย่างรอบคอบจริงๆ! เหตุผลที่ฉันหลีกเลี่ยงการเก็บ AB และ BA เป็นเพราะการเก็บข้อมูลเนื่องจากทุกครั้งที่ฉันมีมิตรภาพตารางของฉันจะเก็บสองเท่า
จัน

1
คุณถูกต้องเกี่ยวกับที่เก็บข้อมูล แต่โปรดจำไว้ว่าหากเก็บเป็นจำนวนเต็มความสัมพันธ์ระหว่างเพื่อนกับเพื่อนแต่ละคนจะใช้เวลาประมาณ 30 ไบต์ (2 ระเบียน x 3 คอลัมน์ x 4 ไบต์ต่อจำนวนเต็ม = 24 ไบต์บวกกับการขยายบางส่วน) 1 ล้านคนที่มีเพื่อน 10 คนแต่ละคนจะยังคงมีข้อมูลอยู่ที่ประมาณ 300MB เท่านั้น
datagod

1
datagod: ถูกต้อง!
จัน

นี่คือวิธีที่ฉันออกแบบตารางของฉันเช่นกัน AB & BA
kabuto178

2
นอกจากนี้ในสถานการณ์ที่มีเพียง AB และไม่ใช่ BA นี่สามารถแสดง 'คำขอเป็นเพื่อนที่รอดำเนินการ'
เกร็ก

13

หากมิตรภาพนั้นมีความสมมาตร (เช่นเป็นไปไม่ได้ที่A จะเป็นเพื่อนกับBแต่ไม่ใช่ในทางกลับกัน) จากนั้นฉันจะเก็บความสัมพันธ์แบบทางเดียวกับข้อ จำกัด การตรวจสอบเพื่อให้แน่ใจว่าแต่ละความสัมพันธ์สามารถเป็นตัวแทนได้เพียงทางเดียว

นอกจากนี้ฉันจะทิ้ง id ตัวแทนและมีคอมโพสิต 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เพื่อไม่ให้มีภาระเพิ่มเติมที่ไม่จำเป็นและซ้ำซ้อนINSERT? เนื่องจากเรามีPRIMARY KEY (a,b)และเนื่องจาก PK เป็นUNIQUEสิ่งที่ตรงกันข้ามKEY (b,a)ก็UNIQUEไม่ว่าอะไร
58

1
@tf เดาว่าขึ้นอยู่กับเครื่องมือเพิ่มประสิทธิภาพข้อความค้นหา ในขณะที่คุณชี้ให้เห็นว่าจำเป็นต้องตรวจสอบรอบเดียวดังนั้นแผนการแทรกอาจทำเช่นนี้ต่อไป คำถามถูกติดแท็ก MySQL - ไม่รู้ว่ามันทำงานอย่างไร
Martin Smith

ฉันรู้ว่านี่เป็นคำตอบที่เก่า แต่ฉันต้องการชี้ให้เห็นว่าใครก็ตามที่สะดุดกับสิ่งนี้ที่ MySQL เพิกเฉยต่อข้อ จำกัด การตรวจสอบอย่างสมบูรณ์ (แม้ว่ามันจะ "แยกวิเคราะห์" สำเร็จ) ดังนั้นวิธีการนี้อาจไม่ใช่วิธีที่จะไปกับเทคโนโลยีนั้น
คา

@Micah จริง ผมไม่ทราบว่าในปี 2012 จะยังคงทำงานในอื่น ๆ DBMSs ...
มาร์ตินสมิ ธ

+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 จะเลือกทำการสแกนช่วงดัชนีเท่านั้นซึ่งเป็นสาเหตุที่แนวคิดของการครอบคลุมดัชนีทำงานได้ดีที่นี่

นี่คือลิงค์ที่ดีบางส่วนในดัชนีการครอบคลุม:

ข้อแม้

หากมิตรภาพไม่ได้เป็นแบบร่วมกันคุณมีพื้นฐานสำหรับความสัมพันธ์ประเภทอื่น: 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');

จากตารางความสัมพันธ์คุณสามารถจัดเรียงความสัมพันธ์เพื่อรวมสิ่งต่อไปนี้:

  • เพื่อนควรอยู่ด้วยกัน
  • อาจเป็นศัตรูกันหรือไม่
  • ผู้ติดตามอาจเป็นผู้ร่วมกันหรือไม่
  • ความสัมพันธ์อื่น ๆ จะถูกตีความ (โดยลืมหรือถูกทอดทิ้งหรือผู้รับการแก้แค้น (แก้ไข))
  • ความสัมพันธ์ Possibie สามารถขยายเพิ่มเติมได้

สิ่งนี้ควรมีความแข็งแกร่งสำหรับความสัมพันธ์ทั้งหมดไม่ว่าจะเป็นความสัมพันธ์ซึ่งกันและกันหรือไม่ก็ตาม


สวัสดี @rolandomysqldba ฉันเป็นแฟนตัวยงของคำตอบของคุณ มันมีประโยชน์กับฉันจริงๆ (ในกรณีนี้คือตัวอย่างที่ 1) ตอนนี้ที่นี่เป็นข้อแม้หนึ่งสำหรับฉันฉันต้องการความสัมพันธ์ที่ไม่ซ้ำกัน (เช่นหากผู้ใช้ A เพื่อนที่มี B ดังนั้น B เพื่อนที่ไม่ได้รับการยอมรับ) ฉันควรทำอย่างไรกับทริกเกอร์? แล้วประสิทธิภาพการทำงานล่ะ เพราะฉันมีตารางขนาดใหญ่มาก (ประมาณ 1 ล้านบันทึก) และถ้าฉันค้นหาเพื่อนของผู้ใช้ A (เก็บไว้ในทั้งสอง (friend_of, friend_to) และ mysql ใช้เพียงดัชนีเดียวแล้วมันก็ทำงานได้ช้ามาก ฉันต้องเก็บรายการที่ซ้ำกันในตารางของฉัน (เช่น A-> B, B-> A) มีตัวเลือกที่ดีกว่านี้อีกไหม
Manish Sapkal

1

หากคุณสามารถควบคุมได้ภายในแอปพลิเคชันที่ id ของ A นั้นต่ำกว่า id ของ 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 ต่ำกว่าในรหัส

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.