我可以在多个表中使用“标识”(唯一,非重复)列吗? 例如,假设我有两个表:书籍和作者。
Authors
AuthorID
AuthorName
Books
BookID
BookTitle
BookID列和AuthorID列是标识列。 我希望标识部分跨越两列。 因此,如果存在值为123的AuthorID,则不能存在值为123的BookID。反之亦然。
我希望这是有道理的。
这可能吗?
感谢。
为什么我要这样做?我正在写一个APS.NET MVC应用程序。我正在创建一个评论部分。作者可以发表评论。书籍可以有评论。我希望能够将实体ID(书籍ID或作者ID)传递给某个操作,并让该操作提取所有相应的注释。如果它是书籍或作者或其他什么,该行动将无关紧要。听起来合理吗?
答案 0 :(得分:5)
即使您可以将标识序列放在多个表中,您的注释表也无法在单个外键中引用这两列。
就关系数据库设计理论而言,最好的方法是创建两个注释表。但显然,您可能希望避免这种情况,可能是出于代码重用的原因。
最直接的实用方法是在注释表上放置两个外键列,只为每个注释创建一个null,另一个不为null。
另一种方法可能是最好的妥协,就是这样。您在问题中提到“实体ID”。所以制作实体表!然后作者,书籍和评论都可以参考那个表。
已编辑添加:
Philip Kelley,Ray和(我认为)Artic都建议通过添加entity_id
修改评论表,该book_id
可以引用author_id
或char(1)
,以及某种标志(分别为tinyint
,boolean
和comment.entity_id
),表示其中的哪一个被引用。
由于许多原因(包括数据完整性,报告,效率)和理论上的原因,这不是一个好的解决方案。
第一个也是最明显的问题是数据完整性问题。关系数据库系统应始终负责维护其自身数据的完整性,并且DB有自然和首选的方法来实现此目的。这些机制中最重要的一个是外键系统。如果book.book_id
列要同时引用author.author_id
和comment
,则无法为此列创建外键。
当然,你可以检查你的DML(插入,更新,删除)存储过程以验证引用,但这很快就会变成一团糟,因为所有三个表上的所有DML操作都会被涉及。 / p>
这导致了我们的效率问题。每当针对author
表运行查询时,它都需要连接到book
或event_id
表或两者。查询计划生成系统将不具有可用于优化的外键,因此其性能可能会降低。
然后在报告中存在此方案的问题。任何报告生成系统都会遇到这种系统的问题。对于专业程序员来说,这不会是一个问题,但是当comment (comment_id int, comment_type char(1), entity_id int,
user_id int, comment_text nvarchar(max), comment_date datetime)
/* comment_id identifies a comment (comment_text) that a user (user_id)
has made about a book (entity_id if comment_type = 'B') or author
(entity_id if comment_type = 'A') at a particular date and
time (comment_date).*/
表示这个或那个时,任何用户临时报告都必须模拟背后的逻辑,这可能是一个非常糟糕的交易。也许你永远不会在这个数据库上使用报告生成工具。但话说回来,没有人知道最终会使用数据库的位置。为什么不与系统一起工作以允许任何事情?
这导致我们理论上的问题。
在关系数据库理论中,每个表中的每一行(a.k.a。“tuple”)(“关系变量”)代表关于现实世界的命题。设计表格是决定该命题的形式。让我们看几个如何运作的例子。
entity_id
很明显,名为comment (comment_id int, book_id int, author_id int, user_id int,
comment_text nvarchar(max), comment_date datetime)
/* comment_id identifies a comment (comment_text) that a user (user_id)
has made about a book (book_id if not null) or author (author_id if
not null) at a particular date and time (comment_date). */
的列(或“属性”)正在执行双重任务。除了引用另一列之外,它并不真正代表任何东西。这是可行的,但不能令人满意。
book_comment (book_comment_id int, book_id int, user_id int,
comment_text nvarchar(max), comment_date datetime)
/* book_comment_id identifies a comment (comment_text) that a
user (user_id) has made about a book (book_id) at a particular
date and time (comment_date). */
author_comment (author_comment_id int, author_id int, user_id int,
comment_text nvarchar(max), comment_date datetime)
/* author_comment_id identifies a comment (comment_text) that a
user (user_id) has made about an author (author_id) at a particular
date and time (comment_date). */
这给我们买了第一个版本中最大遗漏的外键。但这仍然不是非常令人满意,除非一个评论可以同时引用一本书和一本作者(这可能是合理的)。可空列是一个警告标志,表明设计出了问题,这也可能就是这种情况。如果不允许,则可能需要检查约束以避免引用任何内容的注释,或者引用书籍和作者。
从理论角度(因此,我的观点:))有一个明确的最佳选择:
create view comments as
select
book_comment_id as comment_id,
book_id as entity_id,
comment_text,
'B' as comment_type
from book_comment
union
select
author_comment_id as comment_id,
author_id as entity_id,
comment_text,
'A' as comment_type
from author_comment
最后一个选项将提供最佳效率,数据完整性和易于报告。并且唯一的费用是DML存储过程需要将注释放入正确的表中,这不是什么大问题,因为他们必须知道评论所指的是什么。
如果您的计划是一次性检索书籍或作者的所有评论,那么您可以轻松地在这些表格之上创建一个视图,以再现其他设计,如果这是您想要做的。
{{1}}
答案 1 :(得分:2)
简短的回答是:不,你不能这样做(至少在MS SQL Server到2008年)。
您可以创建一个新表“CommentableEntity”,在其中插入您的标识列,然后在作者和书籍中定义外键以将其作为父表引用,然后执行一些技巧以确保给定的ID值没有分配给两个表......但是这个想法很糟糕,因为你构建的数据模型意味着作者和书籍是相关的数据类型,而实际上并非如此。
您可以拥有一个单独的表,评论,其中包含标识列,并在作者和图书中保留一条CommentId列。但是,这会限制每本书和作者只有一条评论。
我,我可能会在评论表中添加一个类似“CommentorType”的列,并在其中放置一个标志,指示评论来源(作者为“A”,书为“B”)。在“CommentorId + CommentorType”上构建一个主键,它应该运行得足够好 - 在系统扩展时添加更多类型的注释器是微不足道的。
答案 2 :(得分:2)
实际上,Joe Celko建议在this博客上使用数据库中的自定义序列,然后,对于所需表的任何主键,指定其默认值以从自定义序列中获取下一个数字。
以下是他博客中的代码示例:
CREATE SEQUENCE Service_Ticket_Seq
AS INTEGER
START WITH 1
INCREMENT BY 1
MINVALUE 1
MAXVALUE 100
CYCLE;
CREATE TABLE Meats
(ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq
PRIMARY KEY,
meat_type VARCHAR(15) NOT NULL);
CREATE TABLE Fish
(ticket_seq INTEGER DEFAULT NEXT VALUE FOR Service_Ticket_Seq
PRIMARY KEY,
fish_type VARCHAR(15) NOT NULL);
INSERT INTO Meats (meat_type) VALUES ('pig');
INSERT INTO Fish (fish_type) VALUES ('squid');
select * from Meats
select * from Fish
这就是说,在MS SQL中可以使用跨越多个表的标识字段。
答案 3 :(得分:0)
作为建议 - 尝试使用ComentId,EntityId,isBook,评论等评论表。 isBook是布尔类型,并没有太多的地方可以获得。从关系的角度来看,你的概念并不好。
答案 4 :(得分:0)
SQL服务器不支持此功能。你可以使用id表滚动你自己,但那将是更多的工作,而不是它的价值。
我建议你的评论表如下:
comment_id int identity
comment_type tinyint
entity_id int
comment_type指定评论是否属于您将来添加的书籍,作者或其他内容。 entity_id是书,作者的id,无论如何。在这个方案中,书籍或作者ID是否重叠并不重要。
或者,如果您可以切换到oracle,请使用序列:)