我需要构建一个审计报告来识别两个表之间以及其中一个表中连续行之间的字段更改。第一个表(数据)保存当前数据:
id data_id field1 field2 data_dttm
1 100 data3 data3 2017-05-03 00:00:00.000
CREATE TABLE [dbo].[data](
[id] [bigint] NULL,
[data_id] [nchar](10) NULL,
[field1] [nchar](10) NULL,
[field2] [nchar](10) NULL,
[adt_dttm] [datetime] NULL
)
Insert INTO data
Values (1, 100, 'data3', 'data3', '2017-05-03 00:00:00')
第二个表 (data_hst) 在数据发生变化(触发类型操作)之前保存数据。
data_hst_id data_id field1 field2 chng_fld_txt data_dttm
1 100 data1 data2 field1|field2 2017-05-01 00:00:00.000
2 100 data2 data3 field1 2017-05-02 00:00:00.000
CREATE TABLE [dbo].[data_hst](
[data_hst_id] [bigint] NULL,
[data_id] [bigint] NULL,
[field1] [nvarchar](200) NULL,
[field2] [nvarchar](200) NULL,
[chng_fld_txt] [nvarchar](200) NULL,
[data_dttm] [datetime] NULL
)
Insert INTO data_hst
Values (1, 100, 'data1', 'data2', 'field1|field2', '2017-05-01 00:00:00'),
(2, 100, 'data2', 'data3', 'field1', '2017-05-02 00:00:00')
“chng_fld_txt”字段包含已修改的字段列表,以竖线分隔。我需要确定数据表行和最近的 data_hst 表行之间发生了什么变化,以及 data_hst 表中连续行之间的变化。跟踪每个更改的审计报告,在它们发生时识别旧值和新值。
结果是这样的:
table_name db_field old_value new_value data_dttm
data field1 data1 data2 5/1/2017
data field2 data2 data3 5/1/2017
data field1 data2 data3 5/2/2017
我有一些复杂的动态 sql,其中有一个游标适用于第一个条件,但不能同时适用于这两个条件。希望有一种更清洁的方法来满足这两个条件。
DROP TABLE #changed
CREATE TABLE #changed(
[tbl_hst_id] [bigint] NULL,
[change_field] [nvarchar](200) NULL
) ON [PRIMARY]
DECLARE @db VARCHAR(200)
SET @db = 'data'
DECLARE @change_date DATETIME
DECLARE @change_date_varchar NVARCHAR(200)
SET @change_date = GETDATE()
SET @change_date_varchar = LEFT(CONVERT(VARCHAR, @change_date, 120), 10)
DECLARE @changed_table nvarchar(max)
SELECT @changed_table =
'SELECT TOP 2 t1.' + @db + '_hst_id as tbl_hst_id, t1.change_field
FROM (
SELECT A.' + @db + '_hst_id
,Split.a.value(''.'', ''VARCHAR(100)'') AS change_field
FROM (
SELECT ' + @db + '_hst_id
,CAST(''<M>'' + REPLACE(upsrt_chng_fld_txt, '','', ''</M><M>'') + ''</M>'' AS XML) AS String
,adt_dttm
FROM ' + @db + '_hst
) AS A
CROSS APPLY String.nodes(''/M'') AS Split(a)
WHERE adt_dttm >= DATEADD(D,-4,''' + @change_date_varchar + ''')
) T1
WHERE T1.change_field NOT IN (
''upsrt_dttm''
,''upsrt_trnsctn_nmbr''
)'
exec ('insert #changed ' + @changed_table)
DECLARE @delta_field VARCHAR(max)
DECLARE @db_sql_all NVARCHAR(max) = ''
DECLARE @getDeltaField CURSOR SET @getDeltaField = CURSOR
FOR
SELECT change_field
FROM #changed
OPEN @getDeltaField
FETCH NEXT
FROM @getDeltaField
INTO @delta_field
WHILE @@FETCH_STATUS = 0
BEGIN
DECLARE @db_sql NVARCHAR(MAX)
SELECT @db_sql = 'select ''' + @db + ''' as table_name
, ''1'' as id
,''' + @delta_field + ''' as db_field
,CAST(hst.' + @delta_field + ' AS NVARCHAR(200)) as old_value
,CAST(c.' + @delta_field + ' AS NVARCHAR(200)) as new_value
--,c.upsrt_usr_id as change_by
--,c.upsrt_dttm as change_time
from ' + @db + ' c
inner join ' + @db + '_hst hst
on c.' + @db + '_id = hst.' + @db + '_id
join #changed ch on hst.' + @db + '_hst_id = ch.tbl_hst_id
UNION '
SET @db_sql_all = @db_sql_all + @db_sql
FETCH NEXT
FROM @getDeltaField
INTO @delta_field
END
CLOSE @getDeltaField
DEALLOCATE @getDeltaField
IF len(@db_sql_all) > 0
BEGIN
SET @db_sql_all = SUBSTRING(@db_sql_all, 1, len(@db_sql_all) - 6)
END
PRINT(@db_sql_all);
EXEC (@db_sql_all);
如果您有旧行和新行,则可以忽略有关更改内容的信息,而只查看数据。
重要提示:此解决方案需要 SQL Server 2012,因为那
LEAD
是添加该功能的时间。所以。
hist_and_curr
CTE 是一个简单的 UNION ALL of the和data
rowsdata_hst
,因为它们似乎具有相同的布局,除了`chng_fld_txt,我们将忽略它。在
find_changes
CTE 中,我们将每一列的 sUNION ALL
分开SELECT
。为此,我们使用该LEAD
函数从下一行访问列的值data_id
(行按列排序data_dttm
),以获取old_value
和new_value
。old_value
然后,我们将行保持在new_value
不同的位置。假设:
SELECT
可能有几十个表和数百列的 s 合并到一个 CTE 中。find_changes
,否则它们无法合并到一个虚拟表中。我确实浏览了您的代码,但由于它涉及对我们没有的列数据的引用,因此我决定专注于所述问题,该问题相对清楚。希望这种方法能为您提供一些可以使用的东西。