Получить все значения из столбца XML - PullRequest
0 голосов
/ 15 декабря 2018

У меня есть приложение COTS, в котором есть таблица аудита со столбцом в формате XML.Я пытаюсь проанализировать все данные, чтобы я мог написать отчет SSRS, который будет ориентирован на клиента.При сохранении системы приложение записывает исходные данные и измененное значение в столбец XML.Это означает, что столбец может содержать одно или несколько значений и может быть одним из многих типов измененных данных.

Я хочу закончить запросом, который покажет, какие данные были изменены и на какие.

  1. Могу ли я написать запрос, который бы возвратил все значения, существующие в столбце, чтобы в худшем случае я мог явно выписать все значения, которые произошли до сих пор?

  2. Возможно ли, чтобы он просто динамически извлекал данные, идентифицируя их?

Пример 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'

Затем я собирался попытаться выполнить какую-то логику конкатенации, когда значение NULL не равно нулю, с использованием TSQL.

Я использовал этот фрагмент кода, который возвращал все значения, но поскольку он был удаленвсю информацию тега, которую я не смог определить, что было изменено

XML.value('(LogMessage/Fields)[1]','varchar(max)') AS 'Raw_Data'

Приведенная выше строка в примере вернула бы это (все значения, без пробелов, без указания того, что представляет значение): ContractorEmployeeTRUEFALSE10 / 31 /201812/31/2019

Если есть способ изменить приведенный выше код так, чтобы он возвращал что-то вроде этого, это было бы здорово, но это должно быть динамически.

PersonnelType Old: Подрядчик Новый: Сотрудник

Отключено Старый: Истинный Новый: Ложный

Срок действия: Старый: 31.10.2008 НОВЫЙ: 12/31/2019

Даже это было бы здорово:

PersonnelType Подрядчик, Сотрудник

Отключено Истина, Да

Срок действия 31.10.2008, 12/31/2019

Ответы [ 3 ]

0 голосов
/ 15 декабря 2018

Я публикую этот ответ только для одной части, т. Е. Для обновления в таблице.Вы можете написать триггер, который будет динамически отслеживать обновление / изменение таблицы.Ниже приведен код триггера SQL-сервера.

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]

Как только данные будут изменены в таблице, выводбудет храниться, как показано ниже

Update Tracking

0 голосов
/ 17 декабря 2018

Вы не четко указали ожидаемый результат, но это кажется довольно простым.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)
0 голосов
/ 15 декабря 2018

Я использовал 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

RexTesterDEMO

...