返回CTE乘以记录

时间:2014-11-02 07:51:11

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

我使用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。动作可以具有插入,更新或删除的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显示问题。

我在这里错过了什么吗?

2 个答案:

答案 0 :(得分: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(在您的小提琴示例中,插入和更新)通过实时表PK链接回相同的live表参考数据,没有您引用的版本的上下文。

意思是随时添加另一个parent update(正如您通过向客户添加新的'U'所做的那样),您的递归CTE将再次拉动(〜交叉连接)所有链接的子审计记录,而不管'当'他们被改变 - 如上所述,这似乎是审计模型和CTE的设计/意图,因为在数据模型的当前版本中,您无法重建父/子/孙子图的实际版本它被修改的时间。

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

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

答案 1 :(得分:0)

我不确定,但我认为在这种情况下您不需要UNION ALL运算符。 <{1}}运算符剂量不会删除重复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.

您将获得一个表,其中存储您已完成的最后一个插入的信息。 你只需要创建触发器&amp;从插入的表中获取值&amp;插入审计表。

On Insert of new Row : 

当某人删除您表格中的任何记录时,您就可以访问已删除表格。该表存储了上次删除的记录的信息。

 On DElete of new Row : 

您可以访问On Update of table : 和&amp;在这种情况下,inserted表。

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

这是清除所有疑虑的最佳链接。关于触发器的基本概念&amp;审核数据。

Click Here For Triggers Information