【问题标题】:CTE multiplying records returned返回 CTE 乘法记录
【发布时间】:2014-11-03 12:24:48
【问题描述】:

我使用 CTE 检索数据库中任何表的审计记录。例如,我有以下表格:

CREATE TABLE [customers]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [name] [varchar](50) NULL
);

CREATE TABLE [customers_orders]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [customer_id] [int] NOT NULL,
    [date_time] [datetime] NULL DEFAULT (getdate())
);

CREATE TABLE [customers_orders_lines]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [order_id] [int] NOT NULL,
    [quantity] [int] NULL,
);

我的审计表是这样的:

CREATE TABLE [audit]
(
    [id] [int] IDENTITY(1,1) NOT NULL,
    [user_name] [varchar](50) NULL,
    [date_time] [datetime] NULL DEFAULT (getdate()),
    [parent_table] [varchar](50) NULL,
    [parent_id] [int] NULL,
    [table_name] [varchar](50) NULL,
    [table_id] [int] NULL,
    [action] [varchar](1) NULL
)

customers 表的条目还将将 parent_tableparent_id 列分别设置为 table_nametable_id。 Action 可以具有 I、U、D 值,用于插入、更新或删除。

我的 CTE 是这样的:

DECLARE @tablename varchar(100)
SET @tablename = 'customers'

DECLARE @tableid int
SET @tableid = 100

;WITH cteAudit AS
(
    SELECT id, [user_name], date_time, table_name, table_id, action, 1 AS audit_level
    FROM audit
    WHERE 
        table_name = @tablename AND
        table_id = @tableid
    UNION ALL
    SELECT a.id, a.[user_name], a.date_time, a.table_name, a.table_id, a.action, cteAudit.audit_level + 1
    FROM audit a
        INNER JOIN cteAudit
            ON a.parent_id = cteAudit.table_id
            AND a.parent_table = cteAudit.table_name
    WHERE 
        a.parent_table <> a.table_name AND
        a.parent_id <> a.table_id   
)
SELECT * FROM cteAudit ORDER BY date_time DESC, id_no, audit_level

所以,我添加了一个客户、他的订单和订单行。行动是我的所有记录。 Audit cte 正确检索记录。当我只为具有操作 U 的客户添加 1 条审计记录时,返回的记录会加倍。

cte 应返回客户和相关表的所有审计记录。

这个SQLFiddle 显示了问题。

我错过了什么吗?

【问题讨论】:

  • 您能否分享与此 cte 相关的其他表结构?

标签: sql sql-server common-table-expression


【解决方案1】:

嗯,你有一个递归 CTE,seems to work as designed,它在获取锚行之后,然后递归到链接到更新行的所有子节点。

由于 cte 已经通过 audit_level 跟踪其深度,如果您需要将递归限制为仅指定没有子表的表,只需过滤 audit_level

SELECT * FROM cteAudit
WHERE AUDIT_LEVEL = 1
ORDER BY date_time DESC, audit_level

SqlFiddle here

编辑
您审核信息的方式的问题是,您有多个 audit rows(在您的 Fiddle 示例中,一个插入和更新)通过实时表 PK 链接回相同的 live 表引用数据,而没有上下文您引用的是哪个版本。

意味着无论何时您添加另一个 parent update(就像您通过向客户添加新的“U”所做的那样),您的递归 CTE 将再次提取(~交叉连接)所有链接的子审计记录,而不管“何时”它们已更改-如上所述,这似乎是审计模型和 CTE 的设计/意图,因为在数据模型的当前化身中,您当时无法重建父/子/孙图的实际版本它被修改了。

要限制 CTE 仅提取在更改时实际修改的数据的聚合根(customer 及其子 customers_orders 和孙 customers_orders_lines),您需要更改您的审计模式以包含更改数据的version or timestamp

然后需要将此版本作为新字段包含在审计表中,并且此版本需要包含在 cte 的连接键中(即 table_nametable_idrecord_version)。

【讨论】:

  • 我用 SqlFiddle 示例编辑了我的问题。它显示了问题。
  • 不可能添加一个版本,因为我们有可以追溯到 10 年前的审计表。审计表已经有一个 date_time 列,但是我不知道它是否会起作用,因为插入记录时的秒数/毫秒数可能会略有不同。
【解决方案2】:

我不确定,但我认为在这种情况下您不需要 UNION ALL 运算符。 UNION ALL 运算符不删除重复的 Union 将不允许在您的查询中。

DECLARE @tablename varchar(100);
DECLARE @tableid int;

SELECT @tablename = 'customers', @tableid = 100;

;WITH cteAudit AS
(
    SELECT id, [user_name], date_time, table_name, table_id, action, 1 AS audit_level
    FROM audit    
)
SELECT * FROM cteAudit
ORDER BY date_time DESC, audit_level;

但我认为这不是为您的应用程序存储审计数据的最佳标准方式。

最好的方法是使用Triggers to Audit your data.

On Insert of new Row : 

您将获得一个表,该表存储您完成的最后一次插入的信息。 您只需创建触发器并从该插入的表中获取值并插入到您的审计表中。

 On DElete of new Row : 

当有人从您的表中删除任何记录时,您将可以访问 deleted 表。该表存储上次删除记录的信息。

On Update of table :

在这种情况下,您将可以访问 inserteddeleted 表。

删除的表包含更新前的旧数据 插入的表由更新后的新数据组成

这是清除您对触发器的所有疑问和基本概念以及审核数据的最佳链接。

Click Here For Triggers Information

【讨论】:

  • 我已经在使用 CLR 触发器,以便相应地更新审计表。此 cte 用于检索审计记录。
  • 好的,你检查过我上面的查询了吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-09-25
  • 1970-01-01
  • 1970-01-01
  • 2011-04-25
相关资源
最近更新 更多