我有一个COTS应用程序,该应用程序具有一个审核表,其中包含XML格式的列。我正在尝试解析所有数据,以便编写SSRS报告以面对客户。系统保存后,应用程序会将原始数据和更改后的值写入XML列。这意味着该列可能包含一个或多个值,并且可能是修改后的多种数据之一。
我想结束一个查询,该查询将显示更改了哪些数据以及更改了哪些数据。
我可以编写查询以返回列中存在的所有值的查询吗,因此在最坏的情况下,我可以显式地写出到目前为止已发生的每个值?
是否有可能仅在确定数据是什么的同时动态提取数据?
示例XML条目:
<LogMessage>
<Fields>
<TransactionCount />
<PersonnelType>
<OldValue> Contractor </OldValue>
<NewValue> Employee </NewValue>
</PersonnelType>
<Disabled>
<OldValue> TRUE </OldValue>
<NewValue> FALSE </NewValue>
</Disabled>
<Expiration>
<OldValue> 10/31/2018</OldValue>
<NewValue> 12/31/2019 </NewValue>
</Expiration>
</Fields>
</LogMessage>
上面的事务计数只是表明它已被更改但未被跟踪。所以我一直在做一个CASE语句,如果存在,则返回一个值,如果不存在则返回NULL。在我查看的所有条目中,旧值始终在新值之前。
每个单元格可以有一个或多个条目,无法提前告知它们将会是什么。
我沿着查找单元格的路径开始,只是调用每个可能的实例来提取数据。
XML.value('(LogMessage/Fields/Disabled/OldValue)[1]','varchar(5)') AS 'Old_Disabled'
XML.value('(LogMessage/Fields/Disabled/NewValue)[1]','varchar(5)') AS 'New_Disabled'
然后,当我使用TSQL不为null时,我将尝试执行某种串联逻辑。
我使用这段代码返回了所有值,但是由于它剥夺了所有标签信息,所以我无法确定更改了什么
XML.value('(LogMessage/Fields)[1]','varchar(max)') AS 'Raw_Data'
上面的示例字符串将返回此值(所有值,无空格,不表示该值代表什么):ContractorEmployeeTRUEFALSE10 / 31/201812/31/2019
如果有一种方法可以修改上面的代码以返回类似的代码,那将是很好的,但是它必须是动态的。
人员类型旧:承包商新:员工
已禁用旧版本:正确新版本:错误
有效期届满:10/31/2018新:12/31/2019
即使那样太棒了:
人员类型承包商,员工
禁用True,是
到期10/31/2018,12/31/2019
答案 0 :(得分:2)
我使用过OPENXML,请检查是否合适。
DECLARE @InputXml xml;
set @InputXml ='<LogMessage>
<Fields>
<TransactionCount />
<PersonnelType>
<OldValue> Contractor </OldValue>
<NewValue> Employee </NewValue>
</PersonnelType>
<Disabled>
<OldValue> TRUE </OldValue>
<NewValue> FALSE </NewValue>
</Disabled>
<Expiration>
<OldValue> 10/31/2018</OldValue>
<NewValue> 12/31/2019 </NewValue>
</Expiration>
</Fields>
</LogMessage>'
select @InputXml
declare @idoc int
exec sp_xml_preparedocument @idoc out, @InputXml
select FieldName,
replace(FieldValue, ' ', ', ') as value
from openxml(@idoc, '/LogMessage/Fields/*',2)
with (
FieldName varchar(50) '@mp:localname',
FieldValue varchar(50) '.'
)
exec sp_xml_removedocument @idoc
答案 1 :(得分:1)
您没有明确说明预期的输出,但这似乎很容易。 XML可以很好地处理通用结构:
DECLARE @xml XML=
'<LogMessage>
<Fields>
<TransactionCount />
<PersonnelType>
<OldValue> Contractor </OldValue>
<NewValue> Employee </NewValue>
</PersonnelType>
<Disabled>
<OldValue> TRUE </OldValue>
<NewValue> FALSE </NewValue>
</Disabled>
<Expiration>
<OldValue> 10/31/2018</OldValue>
<NewValue> 12/31/2019 </NewValue>
</Expiration>
</Fields>
</LogMessage>';
-查询将使用.nodes()
和指向/*
的路径。
-这将返回<Fields>
下的所有元素,但是它们被命名为
-查询将返回元素名称(local-name(.)
)以及两个嵌套元素,分别包含新旧值:
SELECT fld.value('local-name(.)','nvarchar(max)') AS FieldName
,fld.value('(OldValue/text())[1]','nvarchar(max)') AS OldValue
,fld.value('(NewValue/text())[1]','nvarchar(max)') AS NewValue
FROM @xml.nodes('/LogMessage/Fields/*') A(fld);
结果
FieldName OldValue NewValue
-----------------------------------------
TransactionCount NULL NULL
PersonnelType Contractor Employee
Disabled TRUE FALSE
Expiration 10/31/2018 12/31/2019
与表的列相同:
DECLARE @mockup TABLE(ID INT IDENTITY,YourXml XML)
INSERT INTO @mockup VALUES
('<LogMessage>
<Fields>
<TransactionCount />
<PersonnelType>
<OldValue> Contractor </OldValue>
<NewValue> Employee </NewValue>
</PersonnelType>
<Disabled>
<OldValue> TRUE </OldValue>
<NewValue> FALSE </NewValue>
</Disabled>
<Expiration>
<OldValue> 10/31/2018</OldValue>
<NewValue> 12/31/2019 </NewValue>
</Expiration>
</Fields>
</LogMessage>');
SELECT fld.value('local-name(.)','nvarchar(max)') AS FieldName
,fld.value('(OldValue/text())[1]','nvarchar(max)') AS OldValue
,fld.value('(NewValue/text())[1]','nvarchar(max)') AS NewValue
FROM @mockup m
OUTER APPLY m.YourXml.nodes('/LogMessage/Fields/*') A(fld)
答案 2 :(得分:0)
我仅将这个答案发布给您一部分,即在表格中添加更新。您可以编写触发器来动态跟踪表中的更新/更改。下面是触发器的SQL Server代码。
CREATE TRIGGER [dbo].[TR_Employee_AUDIT] ON [dbo].[Employee_mstr] FOR UPDATE
AS
DECLARE @bit INT ,
@field INT ,
@maxfield INT ,
@char INT ,
@fieldname VARCHAR(128) ,
@TableName VARCHAR(128) ,
@PKCols VARCHAR(1000) ,
@sql VARCHAR(2000),
@UpdateDate VARCHAR(21) ,
@UserName VARCHAR(128) ,
@Type CHAR(1) ,
@PKSelect VARCHAR(1000),
@empcode VARCHAR(20)
--You will need to change @TableName to match the table to be audited.
-- Here we made GUESTS for your example.
SELECT @TableName = 'Employee_Mstr'
-- date and user
SELECT @UserName = SYSTEM_USER ,
@UpdateDate = CONVERT (NVARCHAR(30),GETDATE(),126)
-- Action
IF EXISTS (SELECT * FROM inserted)
IF EXISTS (SELECT * FROM deleted)
SELECT @Type = 'U'
ELSE
SELECT @Type = 'I'
ELSE
SELECT @Type = 'D'
-- get list of columns
SELECT * INTO #ins FROM inserted
SELECT * INTO #del FROM deleted
select @UserName = EMP_ModifiedBy, @empcode = emp_cd from #ins
if isnull(@UserName,'') = ''
select @UserName = EMP_ModifiedBy, @empcode = emp_cd from #del
-- Get primary key columns for full outer join
SELECT @PKCols = COALESCE(@PKCols + ' and', ' on')
+ ' i.' + c.COLUMN_NAME + ' = d.' + c.COLUMN_NAME
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
-- Get primary key select for insert
SELECT @PKSelect = COALESCE(@PKSelect+'+','')
+ '''<' + COLUMN_NAME
+ '=''+convert(varchar(100),
coalesce(i.' + COLUMN_NAME +',d.' + COLUMN_NAME + '))+''>'''
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS pk ,
INFORMATION_SCHEMA.KEY_COLUMN_USAGE c
WHERE pk.TABLE_NAME = @TableName
AND CONSTRAINT_TYPE = 'PRIMARY KEY'
AND c.TABLE_NAME = pk.TABLE_NAME
AND c.CONSTRAINT_NAME = pk.CONSTRAINT_NAME
IF @PKCols IS NULL
BEGIN
RAISERROR('no PK on table %s', 16, -1, @TableName)
RETURN
END
SELECT @field = 0,
@maxfield = MAX(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS WHERE TABLE_NAME = @TableName
AND COLUMN_NAME NOT IN ('EMP_ModifiedOn','EMP_ModifiedBy')
WHILE @field < @maxfield
BEGIN
SELECT @field = MIN(ORDINAL_POSITION)
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND ORDINAL_POSITION > @field
AND COLUMN_NAME NOT IN ('EMP_ModifiedOn','EMP_ModifiedBy')
SELECT @bit = (@field - 1 )% 8 + 1
SELECT @bit = POWER(2,@bit - 1)
SELECT @char = ((@field - 1) / 8) + 1
IF SUBSTRING(COLUMNS_UPDATED(),@char, 1) & @bit > 0
OR @Type IN ('I','D')
BEGIN
SELECT @fieldname = COLUMN_NAME
FROM INFORMATION_SCHEMA.COLUMNS
WHERE TABLE_NAME = @TableName
AND ORDINAL_POSITION = @field
AND COLUMN_NAME NOT IN ('EMP_ModifiedOn','EMP_ModifiedBy')
SELECT @sql = '
insert NewAuditLog ( Type,
TableName,
PK,
FieldName,
OldValue,
NewValue,
UpdateDate,
UserName)
select ''' + @Type + ''','''
+ @TableName + ''',''' + @empcode + ''',''' + @fieldname + ''''
+ ',convert(varchar(1000),d.' + @fieldname + ')'
+ ',convert(varchar(1000),i.' + @fieldname + ')'
+ ',''' + @UpdateDate + ''''
+ ',''' + @UserName + ''''
+ ' from #ins i full outer join #del d'
+ @PKCols
+ ' where i.' + @fieldname + ' <> d.' + @fieldname
+ ' or (i.' + @fieldname + ' is null and d.'
+ @fieldname
+ ' is not null)'
+ ' or (i.' + @fieldname + ' is not null and d.'
+ @fieldname
+ ' is null)'
EXEC (@sql)
END
END
您需要创建一个表来存储更改的值,如下所示。
CREATE TABLE [dbo].[NewAuditLog](
[Type] [char](1) NULL,
[TableName] [varchar](128) NULL,
[PK] [varchar](1000) NULL,
[FieldName] [varchar](128) NULL,
[OldValue] [varchar](1000) NULL,
[NewValue] [varchar](1000) NULL,
[UpdateDate] [datetime] NULL,
[UserName] [varchar](128) NULL
) ON [PRIMARY]
表中的数据一旦更改,输出将存储如下所示