цикл xquery по столбцам

#sql-server-2005 #xquery #xquery-sql

#sql-server-2005 #xquery #xquery-sql

Вопрос:

Мне было поручено создать service broker, использующий Xquery для обработки отслеживания изменений в коллекции таблиц. Я выяснил, как передавать сообщения (xml имен столбцов и обновленных и удаленных таблиц для инструкций). Цель состоит в том, чтобы получить список имен столбцов, а затем сравнить аналогичный столбец для каждой обновленной / удаленной строки, а не для изменения.

Вот пример XML-:

 <Update xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <TableName>
    <ID>2414</ID>
    <fkEvent>2664</fkEvent>
    <fkType xsi:nil="true" />
    <Description>Phil Test 3</Description>
    <DTS>2011-04-04T14:01:36.533</DTS>
    <uID>192204FA-612F-46F4-A6CB-1B4D53769A81</uID>
    <VersionID xsi:nil="true" />
    <UpdateDateTime>2011-04-04T14:04:31.013</UpdateDateTime>
    <DeleteFlag>0</DeleteFlag>
    <Updated>0</Updated>
    <Owner>42</Owner>
    <CreatedBy>42</CreatedBy>
  </TableName>
</Update>
  

Генерируется:

 SET @xml1 = (SELECT * FROM TableName ORDER BY ID DESC FOR XML AUTO, ELEMENTS XSINIL, ROOT('MsgEnv'))
  

У меня есть следующий код:

 WHILE @cnt <= @totCnt BEGIN
SELECT @child = @ColNames.query('/Columns/name[position()=sql:variable("@cnt")]')
SET @CurrentCol = REPLACE(REPLACE(CAST(@child AS VARCHAR(500)), '<name>', ''), '</name>', '')
PRINT @CurrentCol
WHILE @updateCnt <= @updateCntTotal BEGIN
    SELECT @childUpdate = @xml1.query('/Update/TableName/sql:variable("@CurrentCol")')
    PRINT CAST(@childUpdate AS VARCHAR(MAX))

    WHILE @deleteCnt <= @deleteCntTotal BEGIN
        SELECT @deleteCnt = @deleteCnt   1
    END

    SET @deleteCnt = 1
    SELECT @updateCnt = @updateCnt   1
END

SET @updateCnt = 1
SELECT @cnt = @cnt   1
END
  

Проблема, с которой я сталкиваюсь, заключается в динамической настройке имени столбца для этого оператора:

 SELECT @childUpdate = @xml1.query('/Update/TableName/sql:variable("@CurrentCol")')
  

Я попробовал несколько разных вариантов с использованием переменной sql:. Разве это невозможно сделать? Я хотел бы иметь возможность выполнять это динамически, поскольку существует множество таблиц, в которых нам нужно «проверять» изменения.

Правка 1:

 SELECT @childUpdate = @xml1.query('/Update/TableName/*[name() = sql:variable("@CurrentCol")]')
  

Выдает эту ошибку (включая . в () имеет аналогичный эффект.

 Msg 2395, Level 16, State 1, Line 34
XQuery [query()]: There is no function '{http://www.w3.org/2004/07/xpath-functions}:name()'
  

Ответ №1:

Ваше выражение XQuery:

 /Update/TableName/sql:variable("@CurrentCol")
  

Он вызовет sql:variable() функцию расширения для каждого /Update/TableName элемента.

Если вы хотите выбрать TableName дочерний элемент с тем же именем, что и строковый результат вашей функции расширения, затем используйте:

 /Update/TableName/*[name(.) = sql:variable("@CurrentCol")]
  

Комментарии:

1. @bdwakefield: Вы правы, видите msdn.microsoft.com/en-us/library /… . Мы не можем говорить о движке XQuery, который не поддерживает функцию XPath 1.0 name()

Ответ №2:

Предыдущий ответ вообще не помог, но вот то, что я нашел, чтобы работать в этой ситуации. Триггер будет передаваться в виде 4 XML-строк. Первый содержит информацию о столбце, следующие два представляют собой XML-содержимое ВСТАВЛЕННЫХ и УДАЛЕННЫХ временных таблиц, а последний представляет собой мета-строку (имя схемы, название таблицы, обновленное пользователем, временная метка и т.д.).

Вот как выглядит XML-код столбца:

 DECLARE @ColNames XML
DECLARE @ColumnTypeInfo TABLE (
column_name varchar(100),
data_type varchar(100))

INSERT INTO @ColumnTypeInfo (column_name,data_type)
(
SELECT column_name 'column_name',
    CASE WHEN
        DATA_TYPE = 'datetime' OR DATA_TYPE = 'int' OR DATA_TYPE = 'bit' OR 
        DATA_TYPE = 'uniqueidentifier' OR DATA_TYPE = 'sql_variant'
    THEN DATA_TYPE ELSE
        CASE WHEN CHARACTER_MAXIMUM_LENGTH IS NOT NULL THEN
            data_type   '('   
                CAST(CHARACTER_MAXIMUM_LENGTH AS VARCHAR(10))
              ')'
        ELSE
            CASE WHEN NUMERIC_PRECISION IS NOT NULL AND NUMERIC_SCALE IS NOT NULL THEN
                data_type   '('  
                    CAST(NUMERIC_PRECISION AS VARCHAR(10))
                          ','   
                    CAST(NUMERIC_SCALE AS VARCHAR(10))
                  ')'
            ELSE
            DATA_TYPE
            END
        END
    END 'data_type'
FROM information_schema.columns WHERE table_name = 'tbl_ActivityPart'
)

SET @ColNames = (
    SELECT * FROM @ColumnTypeInfo 
    FOR XML PATH ('Column'), ROOT('ColumnDef')
)
  

@ColNames передается в очередь сообщений.

Это основа процедуры, которая обрабатывает сообщения, помещенные в очередь:

 WHILE @cnt <= @totCnt BEGIN
    SET @CurrentCol = CAST(@ColNames.query('for $b in /ColumnDef/Column[position()=sql:variable("@cnt")]/column_name return ($b)') AS VARCHAR(MAX))
    SET @CurrentCol = REPLACE(REPLACE(@CurrentCol, '<column_name>', ''), '</column_name>', '')

    SET @DataType = CAST(@ColNames.query('for $b in /ColumnDef/Column[position()=sql:variable("@cnt")]/data_type return ($b)') AS VARCHAR(MAX))
    SET @DataType = REPLACE(REPLACE(@DataType, '<data_type>', ''), '</data_type>', '')  

    SET @updateQuery = '/Update/Scheme.TableName/' @CurrentCol
    SET @SQL = 'SELECT @TmpXML = @UpdatedXML.query('''   @updateQuery   ''')'
    EXEC sp_executesql @SQL, N'@UpdatedXML xml, @TmpXML XML output', @UpdatedXML, @TmpXML output
    SET @childUpdate = @TmpXML

    SET @NewValue = REPLACE(REPLACE(CAST(@childUpdate AS VARCHAR(8000)), '<' @CurrentCol '>', ''), '</' @CurrentCol '>', '')
        IF (CHARINDEX('xsi:nil="true"', CONVERT(VARCHAR(8000), @NewValue)) <> 0) BEGIN
            SET @NewValue = NULL
        END

    SET @deleteQuery = '/Delete/Scheme.TableName/' @CurrentCol
    SET @SQL = 'SELECT @TmpXML = @DeletedXML.query('''   @deleteQuery   ''')'
    EXEC sp_executesql @SQL, N'@DeletedXML xml, @TmpXML XML output', @DeletedXML, @TmpXML output
    SET @childDelete = @TmpXML

    SET @OldValue = REPLACE(REPLACE(CAST(@childDelete AS VARCHAR(8000)), '<' @CurrentCol '>', ''), '</' @CurrentCol '>', '')
        IF (CHARINDEX('xsi:nil="true"', CONVERT(VARCHAR(8000), @OldValue)) <> 0) BEGIN
            SET @OldValue = NULL
        END

    IF @NewValue <> @OldValue BEGIN
        INSERT INTO @Changes (SchemaName, TableName, FieldName, DTS, 
            [uID], OldValue, NewValue, ValueDataType, [User]) 
            SELECT @Schema, @TableName, @CurrentCol, @TimeStamp, 
                CONVERT(UNIQUEIDENTIFIER, @CurrentUID), @OldValue, @NewValue, @DataType, @UpdateUserID  
    END
    -- **********************************************************************************************************

    SELECT @cnt = @cnt   1
END
  

Затем содержимое @Changes вставляется в постоянную таблицу (которая теперь находится на отдельном томе диска от остальных таблиц в этой базе данных).