من الممكن القيام بمفتاح MySQL الخارجية لواحد من جدولين محتملين؟

StackOverflow https://stackoverflow.com/questions/441001

  •  22-07-2019
  •  | 
  •  

سؤال

حسنًا ، هذه مشكلتي لدي ثلاثة طاولات ؛ المناطق والدول والدول. يمكن أن تكون البلدان داخل المناطق ، ويمكن أن تكون الدول داخل المناطق. المناطق هي الجزء العلوي من السلسلة الغذائية.

الآن أقوم بإضافة جدول _areas popular مع عمودين ؛ region_id و popular_place_id. هل من الممكن جعل popular_place_id مفتاحًا خارجيًا لأي من البلدان أو تنص على. من المحتمل أن أضطر إلى إضافة عمود popular_place_type لتحديد ما إذا كان المعرف يصف دولة أو دولة في أي من الاتجاهين.

هل كانت مفيدة؟

المحلول

ما تصفه يسمى الجمعيات متعددة الأشكال. أي أن عمود "المفتاح الأجنبي" يحتوي على قيمة معرف يجب أن توجد في واحدة من مجموعة من الجداول المستهدفة. عادةً ما ترتبط الجداول المستهدفة بطريقة ما ، مثل حالات بعض الفئة الشائعة للبيانات. ستحتاج أيضًا إلى عمود آخر جنبًا إلى جنب مع عمود المفاتيح الخارجية ، بحيث يمكنك تحديد الجدول المستهدف في كل صف.

CREATE TABLE popular_places (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  place_type VARCHAR(10) -- either 'states' or 'countries'
  -- foreign key is not possible
);

لا توجد طريقة لنمذجة الجمعيات المتعددة الأشكال باستخدام قيود SQL. يشير القيد الرئيسي الخارجي دائمًا واحد الجدول المستهدف.

يتم دعم الجمعيات المتعددة الأشكال بواسطة أطر مثل القضبان والإسبات. لكنهم يقولون صراحة أنه يجب عليك تعطيل قيود SQL لاستخدام هذه الميزة. بدلاً من ذلك ، يجب أن يقوم التطبيق أو الإطار بعمل مكافئ لضمان استيفاء المرجع. أي أن القيمة في المفتاح الخارجي موجودة في أحد الجداول المستهدفة المحتملة.

الجمعيات المتعددة الأشكال ضعيفة فيما يتعلق بتطبيق اتساق قاعدة البيانات. تعتمد سلامة البيانات على جميع العملاء الذين يصلون إلى قاعدة البيانات بنفس منطق النزاهة المرجعية المطبق ، وكذلك يجب أن يكون التطبيق خاليًا من الأخطاء.

فيما يلي بعض الحلول البديلة التي تستفيد من النزاهة المرجعية المدعومة من قاعدة البيانات:

إنشاء جدول واحد إضافي لكل هدف. فمثلا popular_states و popular_countries, أي إشارة states و countries على التوالى. يشير كل من هذه الجداول "الشائعة" أيضًا إلى ملف تعريف المستخدم.

CREATE TABLE popular_states (
  state_id INT NOT NULL,
  user_id  INT NOT NULL,
  PRIMARY KEY(state_id, user_id),
  FOREIGN KEY (state_id) REFERENCES states(state_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

CREATE TABLE popular_countries (
  country_id INT NOT NULL,
  user_id    INT NOT NULL,
  PRIMARY KEY(country_id, user_id),
  FOREIGN KEY (country_id) REFERENCES countries(country_id),
  FOREIGN KEY (user_id) REFERENCES users(user_id),
);

هذا يعني أنه للحصول على جميع الأماكن المفضلة لدى المستخدم التي تحتاجها للاستعلام عن هذين الجدولين. ولكن هذا يعني أنه يمكنك الاعتماد على قاعدة البيانات لفرض الاتساق.

إنشاء places الجدول باعتباره خارق. كما يذكر Abie ، البديل الثاني هو أن الأماكن الشائعة الخاصة بك تشير إلى جدول مثل places, ، وهو أحد الوالدين لكليهما states و countries. أي أن كل من الدول والبلدان لديها أيضًا مفتاح خارجي places (يمكنك حتى جعل هذا المفتاح الخارجي أيضًا هو المفتاح الأساسي لـ states و countries).

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  PRIMARY KEY (user_id, place_id),
  FOREIGN KEY (place_id) REFERENCES places(place_id)
);

CREATE TABLE states (
  state_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (state_id) REFERENCES places(place_id)
);

CREATE TABLE countries (
  country_id INT NOT NULL PRIMARY KEY,
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

استخدم عمودين. بدلاً من عمود واحد قد يشير إلى أي من الجدولين المستهدفين ، استخدم عمودين. قد يكون هذين العمودين NULL; ؛ في الواقع ، يجب أن يكون واحد منهم فقطNULL.

CREATE TABLE popular_areas (
  place_id SERIAL PRIMARY KEY,
  user_id INT NOT NULL,
  state_id INT,
  country_id INT,
  CONSTRAINT UNIQUE (user_id, state_id, country_id), -- UNIQUE permits NULLs
  CONSTRAINT CHECK (state_id IS NOT NULL OR country_id IS NOT NULL),
  FOREIGN KEY (state_id) REFERENCES places(place_id),
  FOREIGN KEY (country_id) REFERENCES places(place_id)
);

من حيث النظرية العلائقية ، تنتهك الجمعيات المتعددة الأشكال أول شكل عادي, ، بسبب ال popular_place_id هو في الواقع عمود مع معنيين: إنها إما دولة أو بلد. لن تخزن الشخص age وهم phone_number في عمود واحد ، وللسبب نفسه يجب ألا تخزن كليهما state_id و country_id في عمود واحد. حقيقة أن هاتين السمة لهما أنواع بيانات متوافقة مع الصدفة ؛ أنها لا تزال تشير إلى كيانات منطقية مختلفة.

كما تنتهك الجمعيات المتعددة الأشكال الشكل العادي الثالث, ، لأن معنى العمود يعتمد على العمود الإضافي الذي يسمي الجدول الذي يشير إليه المفتاح الخارجي. في النموذج العادي الثالث ، يجب أن تعتمد السمة في الجدول فقط على المفتاح الأساسي لهذا الجدول.


إعادة التعليق من savasvedova:

لست متأكدًا من أنني أتابع وصفك دون رؤية تعريفات الجدول أو استفسار مثال ، لكن يبدو أن لديك ببساطة متعددة Filters الجداول ، كل منها يحتوي على مفتاح خارجي يشير إلى مركزي Products الطاولة.

CREATE TABLE Products (
  product_id INT PRIMARY KEY
);

CREATE TABLE FiltersType1 (
  filter_id INT PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

CREATE TABLE FiltersType2 (
  filter_id INT  PRIMARY KEY,
  product_id INT NOT NULL,
  FOREIGN KEY (product_id) REFERENCES Products(product_id)
);

...and other filter tables...

من السهل الانضمام إلى المنتجات إلى نوع معين من المرشح إذا كنت تعرف النوع الذي تريد الانضمام إليه:

SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)

إذا كنت تريد أن يكون نوع المرشح ديناميكيًا ، فيجب عليك كتابة رمز التطبيق لإنشاء استعلام SQL. يتطلب SQL تحديد الجدول وإصلاحه في الوقت الذي تكتب فيه الاستعلام. لا يمكنك اختيار الجدول المرتبط بالديناميكي بناءً على القيم الموجودة في الصفوف الفردية Products.

الخيار الآخر الوحيد هو الانضمام إلى الكل الجداول مرشح باستخدام الوصلات الخارجية. أولئك الذين ليس لديهم مطابقة Product_ID سيتم إرجاعها كصف واحد من الفهود. لكن لا يزال يتعين عليك الرمز المتشددين الكل الجداول المرتبطة ، وإذا قمت بإضافة جداول مرشح جديدة ، فيجب عليك تحديث الرمز الخاص بك.

SELECT * FROM Products
LEFT OUTER JOIN FiltersType1 USING (product_id)
LEFT OUTER JOIN FiltersType2 USING (product_id)
LEFT OUTER JOIN FiltersType3 USING (product_id)
...

هناك طريقة أخرى للانضمام إلى جميع جداول المرشحات وهي القيام بذلك بشكل تسلسلي:

SELECT * FROM Product
INNER JOIN FiltersType1 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType2 USING (product_id)
UNION ALL
SELECT * FROM Products
INNER JOIN FiltersType3 USING (product_id)
...

لكن هذا التنسيق لا يزال يتطلب منك كتابة إشارات إلى جميع الجداول. لا يوجد هذا حول ذلك.

نصائح أخرى

هذا ليس الحل الأكثر أناقة في العالم ، ولكن يمكنك استخدامه ميراث الجدول الخرساني لجعل هذا العمل.

من الناحية النظرية ، تقترح فكرة عن فئة من "الأشياء التي يمكن أن تكون مجالات شائعة" التي ترث منها أنواع الأماكن الثلاثة. يمكن أن تمثل هذا كجدول يسمى ، على سبيل المثال ، places حيث يكون لكل صف علاقة فردية مع صف في regions, countries, ، أو states. (يمكن دفع السمات المشتركة بين المناطق أو البلدان أو الدول ، إن وجدت ، إلى جدول الأماكن هذا.) popular_place_id هل ستكون بعد ذلك إشارة مفتاح خارجي إلى صف في جدول الأماكن الذي سيقودك بعد ذلك إلى منطقة أو بلد أو دولة.

الحل الذي تقترحه مع عمود ثانٍ لوصف نوع الارتباط هو كيف تتعامل Rails مع الارتباطات المتعددة الأشكال ، لكنني لست من المعجبين بذلك بشكل عام. يشرح بيل بتفاصيل ممتازة لماذا الجمعيات المتعددة الأشكال ليست أصدقائك.

فيما يلي تصحيح لنهج بيل كاروين "Supertable" ، باستخدام مفتاح مركب ( place_type, place_id ) لحل انتهاكات الشكل العادي المتصورة:

CREATE TABLE places (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) NOT NULL
     CHECK ( place_type = 'state', 'country' ),
  UNIQUE ( place_type, place_id )
);

CREATE TABLE states (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'state' NOT NULL
     CHECK ( place_type = 'state' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to states go here
);

CREATE TABLE countries (
  place_id INT NOT NULL UNIQUE,
  place_type VARCHAR(10) DEFAULT 'country' NOT NULL
     CHECK ( place_type = 'country' ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
  -- attributes specific to country go here
);

CREATE TABLE popular_areas (
  user_id INT NOT NULL,
  place_id INT NOT NULL,
  UNIQUE ( user_id, place_id ),
  FOREIGN KEY ( place_type, place_id ) 
     REFERENCES places ( place_type, place_id )
);

ما لا يمكن لهذا التصميم ضمان ذلك لكل صف في places يوجد في صف في states أو countries (لكن ليس كلاهما). هذا هو حدود المفاتيح الأجنبية في SQL. في معايير SQL-92 الكاملة ، يمكنك تحديد قيود متداخلة قابلة للتأجيل والتي من شأنها أن تسمح لك بتحقيق نفس الشيء ولكنها مجمعة ، وتتضمن معاملة ، ولم تصل هذه البيانات إلى تسويقها بعد.

أدرك أن هذا الخيط قديم ، لكنني رأيت هذا وأعاد أن يتبادر إلى الذهن ، وأعتقد أنني سأرميه هناك.

المناطق والدول والدول هي مواقع جغرافية تعيش في التسلسل الهرمي.

يمكنك تجنب مشكلتك تمامًا من خلال إنشاء جدول مجال يسمى Geographical_location_type الذي ستقوم بملؤوها بثلاثة صفوف (المنطقة ، البلد ، الولاية).

بعد ذلك ، بدلاً من جداول الموقع الثلاثة ، قم بإنشاء جدول geographical_location واحد يحتوي على مفتاح خارجي لـ Geographical_location_type_id (حتى تعرف ما إذا كان المثيل منطقة أو بلد أو ولاية).

نموذج التسلسل الهرمي عن طريق جعل هذا الجدول مرجعًا ذاتيًا بحيث يتمسك مثيل الدولة بمحلة FEKEY إلى مثيل بلدها الأم والتي بدورها تحتفظ بمفوك إلى مثيلها في المنطقة الأم. حالات المنطقة ستحتفظ بغيوم في هذا fkey. هذا لا يختلف عن ما كنت ستفعله مع الجداول الثلاثة (سيكون لديك واحد - العديد من العلاقات بين المنطقة والبلد وبين البلد والدولة) باستثناء كل شيء في جدول واحد.

سيكون جدول popular_user_location جدول دقة النطاق بين المستخدم و georgraphical_location (حتى يتمكن الكثير من المستخدمين من مثل العديد من الأماكن).

سووو ...

enter image description here

CREATE TABLE [geographical_location_type] (
    [geographical_location_type_id] INTEGER NOT NULL,
    [name] VARCHAR(25) NOT NULL,
    CONSTRAINT [PK_geographical_location_type] PRIMARY KEY ([geographical_location_type_id])
)

-- Add 'Region', 'Country' and 'State' instances to the above table


CREATE TABLE [geographical_location] (
   [geographical_location_id] BIGINT IDENTITY(0,1) NOT NULL,
    [name] VARCHAR(1024) NOT NULL,
    [geographical_location_type_id] INTEGER NOT NULL,
    [geographical_location_parent] BIGINT,  -- self referencing; can be null for top-level instances
    CONSTRAINT [PK_geographical_location] PRIMARY KEY ([geographical_location_id])
)

CREATE TABLE [user] (
    [user_id] BIGINT NOT NULL,
    [login_id] VARCHAR(30) NOT NULL,
    [password] VARCHAR(512) NOT NULL,
    CONSTRAINT [PK_user] PRIMARY KEY ([user_id])
)


CREATE TABLE [popular_user_location] (
    [popular_user_location_id] BIGINT NOT NULL,
    [user_id] BIGINT NOT NULL,
    [geographical_location_id] BIGINT NOT NULL,
    CONSTRAINT [PK_popular_user_location] PRIMARY KEY ([popular_user_location_id])
)

ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_type_geographical_location] 
    FOREIGN KEY ([geographical_location_type_id]) REFERENCES [geographical_location_type] ([geographical_location_type_id])



ALTER TABLE [geographical_location] ADD CONSTRAINT [geographical_location_geographical_location] 
    FOREIGN KEY ([geographical_location_parent]) REFERENCES [geographical_location] ([geographical_location_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [user_popular_user_location] 
    FOREIGN KEY ([user_id]) REFERENCES [user] ([user_id])



ALTER TABLE [popular_user_location] ADD CONSTRAINT [geographical_location_popular_user_location] 
    FOREIGN KEY ([geographical_location_id]) REFERENCES [geographical_location] ([geographical_location_id])

لم يكن متأكدا ما هو DB الهدف ؛ ما سبق هو خادم MS SQL.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top