Дизайн таблиц и иерархии классов
Надеемся, что кто-то сможет пролить свет на эту проблему с помощью либо примера, либо, возможно, кто-то из читателей предложил. Мне интересно, каков наилучший подход к моделированию таблиц после их эквивалентности иерархии классов. Лучше всего это можно описать на примере:
abstract class Card{
private $_name = '';
private $_text = '';
}
class MtgCard extends Card{
private $_manaCost = '';
private $_power = 0;
private $_toughness = 0;
private $_loyalty = 0;
}
class PokemonCard extends Card{
private $_energyType = '';
private $_hp = 0;
private $_retreatCost = 0;
}
Теперь, когда моделируем таблицы для синхронизации с этой иерархией классов, я использовал нечто очень похожее:
TABLE Card
id INT, AUTO_INCREMENT, PK
name VARCHAR(255)
text TEXT
TABLE MtgCard
id INT, AUTO_INCREMENT, PK
card_id INT, FK(card.id)
manacost VARCHAR(32)
power INT
toughness INT
loyalty INT
TABLE PokemonCard
id INT, AUTO_INCREMENT, PK
card_id INT, FK(card.id)
hp INT
energytype ENUM(...)
retreatcost INT
Проблема, которую я имею, пытается выяснить, как связать каждый Card
запись с записью, содержащей ее данные из соответствующей таблицы. В частности, как определить, в какой таблице я должен искать.
Должен ли я добавить VARCHAR
столбец к Card
держать имя связанной таблицы? Это единственное решение, к которому я и мои коллеги пришли, но оно кажется слишком "грязным". Ключом к этому является сохранение расширяемости дизайна, что позволяет легко добавлять новые подклассы.
Если бы кто-то мог предоставить пример или ресурсы, показывающие чистый способ зеркального отображения иерархий классов / таблиц, это было бы очень ценно.
2 ответа
Google "Обобщение специализации реляционного моделирования". Вы найдете несколько отличных статей на тему того, как смоделировать паттерн gen-spec, используя реляционные таблицы. Этот же вопрос задавался много раз в SO, с немного другими деталями.
Лучшие из этих статей подтвердят ваше решение иметь одну таблицу для обобщенных данных и отдельные таблицы для специализированных данных. Самым большим отличием будет то, как они рекомендуют использовать первичные и внешние ключи. По сути, они рекомендуют, чтобы специализированные таблицы имели один столбец, который выполняет двойную функцию. Он служит первичным ключом для специализированной таблицы, но также является внешним ключом, который дублирует PK обобщенной таблицы.
Это немного сложно поддерживать, но это очень мило во время соединения.
Также имейте в виду, что DDL требуется при добавлении нового класса в иерархию.
В основном нет.
Забудьте об иерархиях классов, моделях хранения и обо всем, что специфично для вашего приложения и вашего конкретного языка приложения. Если вы не хотите использовать RDb в качестве простого хранилища для ваших файлов, зависимого ведомого.
Если вам нужна мощь и гибкость (в частности, расширяемость) реляционной базы данных, вам нужно моделировать ее независимо от любого приложения и с использованием принципов RDb, а не требований к языку приложения. Оставьте контекст своего приложения на некоторое время и создайте базу данных как базу данных. Узнайте о них. Нормализовать (устранить все дублирование). Узнайте о структурах и правилах и реализуйте их. Когда вы это сделаете, ваши запросы и ваше "отображение", будет легко. Там не будет никакого "импеданса". Используйте правильные типы данных, и несоответствия не будет.
Требуемая вами структура - это обычный подтип-супертип. Это термины, относящиеся к реляционной базе данных, которые существуют в RM более 30 лет и более 23 лет в продуктах реляционной базы данных. Не нужно называть их забавными новыми именами. Вики не является академическим справочником, за исключением, может быть, раковых опухолей.
Учитывая ваши таблицы, которые являются правильными в качестве отправной точки (вы нормализовались автоматически), вам необходимо:
Переименовать Card.Id как Card.CardId
Удалите идентификаторы для подтипов, они на 100% избыточны; CardId - это и PK, и FK.
Добавьте дискриминатор Card.CardType CHAR(1) или TINYINT. Это определит, к какому подтипу присоединиться, когда CardType неизвестен.
Похоже, вы не до конца понимаете концепцию внешних ключей, поэтому было бы неплохо начать сначала. Это реализовано здесь в его простой, обычной форме:
ALTER TABLE MtgCard
ADD CONSTRAINT Card_MtgCard_fk
FOREIGN KEY (CardId)
REFERENCES Card(CardId)
Отношение между Card и MtgCard или PokemonCard всегда равно 1::1. Супертип завершен только тогда, когда есть карта плюс { MtgCard | PokemonCard } с тем же CardId. В вашем случае может быть только один подтип, который легко реализовать с помощью простого ограничения CHECK.
В других случаях более одного подтипа вполне законно.
Подтипы есть человек, учитель или человек, студент
В реляционных базах данных нет концепции объединения "от" или "в" (или вверх / вниз или влево / вправо), эти понятия существуют только для того, чтобы помочь нам, людям; Вы можете начать с любой таблицы / ключа и перейти к любой таблице. Промежуточные таблицы требуются только в отсутствие реляционных идентификаторов (т. Е. Когда дополнительные суррогаты, столбцы идентификаторов используются в качестве PK вместо значимых естественных ключей).
- В этом примере, используя ваши термины, вы можете перейти прямо из Enrollment to Person (например, чтобы получить LastName) или в Course (чтобы получить Name), не посещая промежуточные таблицы; линии связи сплошные.
,
- В этом примере, используя ваши термины, вы можете перейти прямо из Enrollment to Person (например, чтобы получить LastName) или в Course (чтобы получить Name), не посещая промежуточные таблицы; линии связи сплошные.
- Теперь иерархии классов ("Есть" или "Есть") и все остальное просты и не требуют усилий. Если вам это не кажется простым, пожалуйста, опубликуйте предикат класса с одним оператором (на английском языке), и я предоставлю SQL.
Краткий справочник по стандартным диаграммам реляционных баз данных.