【问题标题】:Audited table and foreign key审计表和外键
【发布时间】:2021-08-18 11:05:47
【问题描述】:

我有一个包含多个必须审核的表的数据库。

例如,我有一个对象表,其中定义了唯一的 ID、名称和描述。

名称将始终相同。无法更新它。 “ObjectA”永远是“ObjectA”。

如您所见,名称在数据库中不是唯一的,而只是在逻辑中。

“from”、“to”和“creator_id”行用于审核更改。 “from”是更改的日期,“to”是添加新行的日期,当它是最新行时为空。 “creator_id”是进行更改的用户的 ID。

+----+----------+--------------+----------------------+----------------------+------------+
| id |   name   | description  |         from         |          to          | creator_id |
+----+----------+--------------+----------------------+----------------------+------------+
|  1 | ObjectA  | My object    | 2021-05-30T00:05:00Z | 2021-05-31T05:04:36Z |         18 |
|  2 | ObjectB  | My desc      | 2021-05-30T02:07:25Z | null                 |         15 |
|  3 | ObjectA  | Super object | 2021-05-31T05:04:36Z | null                 |         20 |
+----+----------+--------------+----------------------+----------------------+------------+

现在我有另一个表,它必须有一个基于“唯一”对象名称的对象表的外键。

+----+---------+-------------+
| id |   foo   | object_name |
+----+---------+-------------+
|  1 | blabla  | ObjectA     |
|  2 | wawawa  | ObjectB     |
+----+---------+-------------+

如何在这两个表之间创建此链接?

我已经尝试使用 uuid 创建另一个表,并在对象表中添加一列“unique_identifier”。然后外键将链接到这个 uuid 表而不是对象表。问题是我有多个表存在这个问题,我将不得不创建双倍数的表。

也可以使用对象 ID 作为 FK 而不是名称,但这意味着我必须在更新对象时使用新 ID 更新具有该 FK 的每个表。

【问题讨论】:

  • 你没有清楚地说明你想要完成什么。解释“在这两个表之间创建此链接”是什么意思。您没有说对有效数据库状态的限制是什么。您说“外键”,但不清楚您的意思是什么 FK,或者为什么您不只是声明 FK 并且似乎您甚至可能不是在谈论 FK,而是在说“FK”时您的意思是其他意思让你想起FK。 minimal reproducible exampleHow to AskHelp centerPS“在逻辑上”不清楚。 PS 允许 NULL 的子行不太可能适合作为 SQL 中的唯一标识符,尤其是这样的子行不能匹配 FK。

标签: sql foreign-keys azure-sql-database audit


【解决方案1】:

根据 SQL 标准,外键必须引用父表的主键或唯一键。如果主键有多个列,则外键必须具有相同的列数和顺序。因此外键引用父表中的唯一行;不能有重复。

另一种解决方案是使用触发器,您可以在插入另一个表之前检查对象表中的对象是否存在。

更新:添加代码

准备表并创建触发器:(为简单起见,我只在 Objects 表中包含 3 列。在触发器中,我只是在 else 部分打印错误,您可以使用 RAISEERROR 函数将错误返回给客户端)

  Create table AuditObjects(id int identity (1,1),ObjectName varchar(20), ObjectDescription varchar(100) )
    Insert into AuditObjects values('ObjectA','description ObjectA Test')
    Insert into AuditObjects values('ObjectB','description ObjectB Test')
    Insert into AuditObjects values('ObjectC','description ObjectC Test')
    Insert into AuditObjects values('ObjectB','description ObjectB Test')
    Insert into AuditObjects values('ObjectB','description ObjectB Test')
    Insert into AuditObjects values('ObjectA','description ObjectA Test')
    
    Create table ObjectTab2 (id int identity (1,1),foo varchar(200), ObjectName varchar(20))
    go
    
    CREATE TRIGGER t_CheckObject ON ObjectTab2 INSTEAD OF INSERT
    AS BEGIN
        
        Declare @errormsg varchar(200), @ObjectName varchar(20)
        select @ObjectName = objectname from INSERTED
        if exists(select 1 from AuditObjects where objectname = @ObjectName)
        Begin
            INSERT INTO ObjectTab2 (foo, Objectname)
            Select foo, Objectname
            from INSERTED
        End
        Else
        Begin
            Select @errormsg = 'Object '+objectname+ ' does not exists in AuditObjects table'
            from Inserted
    
            print(@errormsg)
        End
        
    END;

现在,如果您尝试在 ObjectTab2 中插入对象名称为“ObjectC”的行,则插入将被允许,因为审计表中存在“objectC”。

Insert into ObjectTab2 values('blabla', 'ObjectC')
Select * from ObjectTab2

id          foo    ObjectName
----------- ------ --------------------
1           blabla ObjectC

但是,如果您尝试输入“ObjectD”,它将不会进行插入并在输出中给出错误消息。

Insert into ObjectTab2 values('Inserting ObjectD', 'ObjectD')
Object ObjectD does not exists in AuditObjects table

嗯,它不是您要求的,而是为您提供相同的功能和结果。

【讨论】:

  • 那么我会有“name”和“to”有主键吗?但是然后我必须在我的第二个表“object_name”和“object_to”中有 2 列?对吗?
  • @Weedoze ,我在之前的帖子中添加了代码。
【解决方案2】:

您不能继续根据“对象名称”链接两个表吗?唯一的区别是 - 当您加入两个表时,您将从 table1 (您引用的第一个表)中获得多条记录。然后,您可以根据需要添加基于 from 和 to 的过滤条件。

发布编辑 - 我的意思是,在这种情况下,您仍然可以在不引入外键的情况下达到预期的结果-

让我们调用您的表 - Table1 和 Table2

--Below will give you all records from Table1
SELECT T2.*, T1.description, T1.creator_id, T1.from, T1.to 
FROM TABLE2 T2 
INNER JOIN TABLE1 T1 ON T2.OBJECT_NAME = T1.NAME;

--Below will give you ONLY those records from Table1 whose TO is null
SELECT T2.*, T1.description, T1.creator_id, T1.from, T1.to 
FROM TABLE2 T2 
INNER JOIN TABLE1 T1 ON T2.OBJECT_NAME = T1.NAME
WHERE T1.TO IS NULL;

【讨论】:

  • 否,因为外键必须引用主键或唯一键,但情况并非如此
  • 我已经更新了我的答案,假设你仍然可以不用外键
【解决方案3】:

我决定用一张额外的桌子来做这个最终设计:

表“对象”

+-------+--------------------------------------+---------+--------------+----------------------+----------------------+------------+
| id PK |            identifier FK             |  name   | description  |         from         |          to          | creator_id |
+-------+--------------------------------------+---------+--------------+----------------------+----------------------+------------+
|     1 | 123e4567-e89b-12d3-a456-426614174000 | ObjectA | My object    | 2021-05-30T00:05:00Z | 2021-05-31T05:04:36Z |         18 |
|     2 | 123e4567-e89b-12d3-a456-524887451057 | ObjectB | My desc      | 2021-05-30T02:07:25Z | null                 |         15 |
|     3 | 123e4567-e89b-12d3-a456-426614174000 | ObjectA | Super object | 2021-05-31T05:04:36Z | null                 |         20 |
+-------+--------------------------------------+---------+--------------+----------------------+----------------------+------------+

表“Object_identifier”

+--------------------------------------+
|              identifier PK           |
+--------------------------------------+
| 123e4567-e89b-12d3-a456-426614174000 |
| 123e4567-e89b-12d3-a456-524887451057 |
+--------------------------------------+

表“foo”

+-------+--------+--------------------------------------+
| id PK |  foo   |         object_identifier FK         |
+-------+--------+--------------------------------------+
|     1 | blabla | 123e4567-e89b-12d3-a456-426614174000 |
|     2 | wawawa | 123e4567-e89b-12d3-a456-524887451057 |
+-------+--------+--------------------------------------+

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-12-05
    • 2010-11-20
    • 1970-01-01
    • 2015-02-15
    • 1970-01-01
    • 2012-12-19
    • 2013-11-16
    • 1970-01-01
    相关资源
    最近更新 更多