【问题标题】:Historize sql data in Parent-Child tables对父子表中的 sql 数据进行历史记录
【发布时间】:2012-06-30 10:27:10
【问题描述】:

1 地图有 N 个图像。当用户更新地图数据时,我不会进行更新,因为那时我会丢失旧数据。因此,我使用相同的自动递增 Id 但新的日期时间戳在 Map 表中执行插入操作。这样,我想在用户界面中记录地图上的所有更改。

表映射:PK 是 Id + CreatedAt

 [Id] [int] IDENTITY(1,1) NOT NULL, 
 [CreatedAt] [datetime] NOT NULL,   
 ...

表格图像:PK 是 Id,FK 是 MapId + CreatedAt

[Id] [int] IDENTITY(1,1) NOT NULL,
[Image] [varbinary](max) NOT NULL,
[VisibleIndex] [int] NOT NULL,
[CreatedAt] [datetime] NOT NULL,
[MapId] [int] NOT NULL,

如果我的 PK 有 2 个字段,我的 FK 也必须有 2 个字段。

但这在我的情况下不起作用,因为表 Images 中的 CreatedAt 日期不能是表 Map 中的 createdAt 日期。

你会如何进行历史“更新”?

【问题讨论】:

    标签: sql sql-server database-design foreign-key-relationship


    【解决方案1】:

    如果您在Map 表上的主键是单列Id,那么您在Images 表中的外键实际上就是MapId 列,它引用了Id 列在父表上。

    如果Map 表上的主键是Id 的单列,那么您将无法插入与Id 具有相同值的另一行。

    可以为IDENTITY列插入具有指定值的行,但是你必须做一个SET IDENTITY_INSERT tablename ON语句,然后你必须记住做一个对应的SET IDENTITY_INSERT tablename OFF语句。


    为了实现 Map 表内容的历史记录,我会考虑创建一个单独的“MapHistory”表来存储更改历史记录,并使用触发器来维护它。

    CREATE TABLE MapHistory (
    [change_date] datetime NOT NULL,
    [Id]          int      NOT NULL,
    [CreatedAt]   datetime NOT NULL,
    ...
    

    要存储更新前Map 中行的“历史记录”,您可以在触发器中引用特殊的逻辑deleted 表。 (为了完整起见,当从 Map 表中删除一行时,我可能还会在历史表中存储一行。)

    CREATE TRIGGER map_update ON Map AFTER UPDATE,DELETE AS
    BEGIN
       SET NOCOUNT ON
       -- store column values as they existed prior to the update or delete
       INSERT MapHistory (change_date, Id, CreatedAt, ... )
       SELECT CURRDATE(), Id, CreatedAt, ... FROM deleted
    END
    

    使用这种方法,您的代码可以简单得多。您只需对Map 表执行UPDATE,数据库将负责维护MapHistory 表。

    这种方法的一个小缺点是现在“当前”值在一个表中,而以前值的历史记录在另一个表中。

    如果您希望将当前值也存储在MapHistory 表中,您可以修改触发器以触发AFTER INSERT, UPDATE,并改为引用特殊的逻辑inserted 表,以便创建一个“将插入的更新行复制到历史表中。

    也可以在历史记录表中同时存储“旧”和“新”行,但这样您实际上会存储冗余数据。在这种情况下,您可能需要包含一个列,以指示该行是来自“已删除”表还是“插入”表。

    CREATE TRIGGER map_update ON Map AFTER INSERT,UPDATE,DELETE AS
    BEGIN
       SET NOCOUNT ON
    
       -- store column values as they existed prior to an update or delete
       INSERT MapHistory (source, change_date, Id, CreatedAt, ... )
       SELECT 'D', CURRDATE(), Id, CreatedAt, ... FROM deleted
    
       -- store column values as they exist after an update or insert
       INSERT MapHistory (source, change_date, Id, CreatedAt, ... )
       SELECT 'I' CURRDATE(), Id, CreatedAt, ... FROM inserted
    
    END
    

    附录:

    此方法允许您将Id IDENTITY 作为Map 表中的简单主键。我认为我在MapHistory 表中添加的change_date 列可能是多余的,它可能符合您在CreatedAt 列中的目的。

    【讨论】:

    • 语法是“SET IDENTITY_INSERT TableName ON;Insert Into ... ;SET IDENTITY_INSERT TableName Off。抱歉我弄错了。Map 的 PK 是 2 个字段:Id 和 CreatedAt。
    【解决方案2】:

    将地图的“常量”和“可演化”方面分开到两个表中:

    这假设您只想对地图字段而不是图像进行版本控制。


    如果您还想对图像进行版本化,您可以这样做:

    创建新地图版本时:

    • 对于未更改的图像:只需复制链接,不要创建新的图像版本。
    • 对于发生变化的图片:创建新的图片版本链接。

    这样,您不必因为地图发生变化而昂贵地复制图像内容。仅当映像本身发生更改时,您才创建一个新的映像版本。

    注意:这个模型有点太笼统了。它允许在多个地图之间共享相同的图像(不仅仅是同一地图的多个版本)。如果你想限制它,请告诉我。


    要使图像对地图“私有”,您可以执行以下操作:

    请注意我们如何引入任何代理键。相反,我们让识别关系产生“自然”键,然后在菱形依赖关系的底部合并:ImageLink.MapId 具有朝向“菱形”的两个“边缘”的外键

    【讨论】:

    • 限制:一张图片只能属于一张地图。1张地图有N张图片。不是 N 个地图有 M 个图像。
    • @Pascal 图片需要版本控制吗?
    • 是的会很好。我从我工作的一家前公司了解到,他们确实对每张桌子进行了历史记录以跟踪所有内容。我仍然知道他们的外键表根本没有 FK,每个数据记录都有一个 ValidFrom 和 ValidUntil 字段。 JFYI 一般。他们的做法与您的建议不同。
    • 抱歉,图片不需要历史化。因为这个功能实际上没有用例;-) 能够检查地图上由谁(CreatedBy)更改的内容对我来说就足够了。没有过度工程;-)
    • @Pascal 那么第一个模型应该没问题;)
    【解决方案3】:

    我建议创建另一个表 History,在其中保留所有最后的值(包括实际值),这样您就可以只使用键来映射表。 [Id] [int] IDENTITY(1,1) NOT NULL 您可以使用触发器或过程来执行此操作,将当前记录插入到历史表中并更改日期时间,然后在您更新记录的普通 Map 表中插入当前记录。

    【讨论】:

      【解决方案4】:

      当您想要保留旧的父子关系时,另一种方法是将旧数据插入新行,然后将旧行更新为新数据。如果您有唯一索引或其他约束会影响在事务期间除了代理键之外的两条类似记录,则可能需要三个步骤。

      【讨论】:

      • 你能解释一下吗
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-10-28
      • 1970-01-01
      • 2016-07-10
      • 1970-01-01
      • 2023-03-28
      相关资源
      最近更新 更多