有没有一种简单的方法可以在目标表的一列中引用多个主键?

时间:2019-04-17 16:25:12

标签: sql-server foreign-keys

我对自己的知识不足或术语使用不当表示歉意;我正在上在线DBMS课程,并且大部分是使用Microsoft SQL Server自学的。

我们的任务是创建数据库设计并将数据插入我们感兴趣的内容。我选择创建一个基于龙与地下城的数据库,并对我是否做正确的事有疑问。

我打算创建一个Spell_Source表,该表将多个不同表(类和子类)的主键作为一列,而将拼写名称(在另一表中的主键)作为另一列。但是,当我输入数据时,外键约束正在停止插入。

如果在规范化方面存在问题,或者由于类的自学性而缺少一个简单的修复程序,我已经准备好重新设计数据库本身。

感谢您的帮助!

CREATE TABLE SPELL_SOURCE (
    SpellName       VarChar(50)     NOT NULL,
    SpellSource     Char(25)        NOT NULL,
    CONSTRAINT      SpellSourcePK1  PRIMARY KEY (SpellName, SpellSource),
    CONSTRAINT      SpellSourceFK   FOREIGN KEY (SpellName)
                        REFERENCES SPELLS(SpellName)
                            ON UPDATE NO ACTION
                            ON DELETE NO ACTION,
    CONSTRAINT      SpellSourceFK1  FOREIGN KEY (SpellSource)
                        REFERENCES CLASS(ClassName)
                            ON UPDATE NO ACTION
                            ON DELETE NO ACTION,
    CONSTRAINT      SpellSourceFK2  FOREIGN KEY (SpellSource)
                        REFERENCES SUBCLASS(SubclassName)
                            ON UPDATE NO ACTION
                            ON DELETE NO ACTION
);

我从导入数据工具中得到的确切错误是“ INSERT语句与FOREIGN KEY约束“ SpellSourceFK1”发生冲突。该冲突发生在数据库表“ dbo.CLASS”,“ ClassName”中”

2 个答案:

答案 0 :(得分:1)

问题是您为SpellSource输入的值在表ClassName中没有对应的CLASS。如果列是不同的复合键,则它们可以是不同的外键的一部分。例如。 FK (a, b)FK (b, c),其中b属于2个FK。但是,否则,单列(根据经验)应该只有一个FK。

此外,如果您有类和子类,则只能在此处引用该子类,并在类和子类之间创建1到n的关系。也就是说,子类将具有ClassID的外键

 SPELL_SOURCE                 SPELLS
+-------------  ---+         +---------------+
| PK FK SpellID    | o-----> | PK SpellID    |
| PK FK SubclassID | o--+    |    SpellName  |
+------------------+    |    +---------------+
                        |
                        |     SUBCLASS                    CLASS
                        |    +-----------------+         +---------------+
                        +--> | PK SubclassID   |    +--> | PK ClassID    |
                             |    SubclassName |    |    |    ClassName  |
                             | FK ClassID      | O--+    +---------------+
                             +-----------------+

请勿将名称用作PK。以后将很难更改名称。而是仅引用永不更改的int IDENTITY(1,1)(自动递增)主键,并将名称存储在单独的列中,您可以随时对其进行编辑。参见:CREATE TABLE (Transact-SQL) IDENTITY (Property)

您可以通过以下方式查询组合信息

SELECT
    SS.SpellID, SS.SubclassID,
    S.SpellName,
    C.ClassName,
    SC.SubclassName, SC.ClassID
FROM
    SPELL_SOURCE SS
    INNER JOIN SPELLS S
        ON SS.SpellID = S.SpellID
    INNER JOIN SUBCLASS SC
        ON SS.SubclassID = SC.SubclassID
    INNER JOIN CLASS C
        ON SC.ClassID = C.ClassID
ORDER BY
    C.ClassName, SC.SubclassName, S.SpellName

但是请注意,使用这种设计,同一咒语可能属于不同的类和子类。如果一个咒语只能属于一个子类,则其结构应如下所示。

Class   1 --> n   Subclass   1 --> n   Spell

根据您的评论,一个咒语可能属于一个类别而不是一个子类别(也间接属于一个类别)。然后我建议以下结构

 SPELL_SOURCE (separate PK because of nullables, Unique Constraint UC instead)
+------------------+              SPELLS
| PK SpellSourceID |             +---------------+
| FK UC SpellID    | o---------> | PK SpellID    |
| FK UC SubclassID | o------+    |    SpellName  |
| FK UC ClassID    | o--+   |    +---------------+
+------------------+    |   |
                        |   |     SUBCLASS                       CLASS
                        |   |    +-----------------+    +-----> +---------------+
                        |   +--> | PK SubclassID   |    |  +--> | PK ClassID    |
                        |        |    SubclassName |    |  |    |    ClassName  |
                        |        | FK ClassID      | o--+  |    +---------------+
                        |        +-----------------+       |
                        |                                  |
                        +----------------------------------+

SPELL_SOURCE中,ClassIDSubclassID都是可空的。始终只有两者之一不为空。您可以添加CHECK constraint (ClassID IS NULL AND SubclassID IS NOT NULL) OR (ClassID IS NOT NULL AND SubclassID IS NULL)。并在查询中使用LEFT JOINS。

请参阅:http://www.sqlfiddle.com/#!18/107dd/3/0


另一种方法是保留第一个结构,但在每个类中都有一个主或默认子类条目。例如,具有SubclassName = NULL的子类。该条目将代表该类。

对于下拉列表,您可以选择类似这样的条目

SELECT
    S.SubclassID,
    CASE WHEN S.SubclassName IS NULL THEN
        'CLASS: ' + C.ClassName
    ELSE
        S.SubclassName + ' (' + C.ClassName + ')'
    END AS Name
FROM
    CLASS C
    INNER JOIN SUBCLASS S
        ON C.ClassID = S.ClassID
ORDER BY
    C.ClassName, 
    CASE WHEN S.SubclassName IS NULL THEN 0 ELSE 1 END,
    S.SubclassName

请参阅:http://www.sqlfiddle.com/#!18/d8777/1/0

答案 1 :(得分:1)

只想将我的支持抛在@Olivier答复的答案之后。您将为自己的当前设计带来很多痛苦。

但是,通过在子类和类之间创建1-> N关系,您将始终保持引用完整性,并且可以轻松地通过子类查询您的类