У меня есть асинхронная система аудита, которую я написал для SQL Server, которая использует триггеры для отправки сообщения в очередь компонента Service Broker, который вызывает хранимую процедуру для обработки очереди и регистрации сведений.
Все этоотлично работает, если происходит одно изменение (обновление или удаление), используя следующие фрагменты кода: -
Получить сообщение из очереди:
DECLARE @Message XML;
RECEIVE TOP(1) @Message = CONVERT(NVARCHAR(MAX), message_body) FROM AuditLogReceiveQueue;
Затем я сохраняю имя поляЯ хочу извлечь в переменную @ fieldname
Получить нужный мне узел из XML
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[1]', 'NVARCHAR(Max)'))
Все хорошо, это получает значение, которое я хочу.
Тем не менее, возможно наличие нескольких узлов Inserts, поэтому мне нужно пройти их все.
Доступ к определенному узлу Inserts достаточно прост, следующее работает при наличии трех (или более)
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[1]', 'NVARCHAR(Max)'))
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[2]', 'NVARCHAR(Max)'))
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[3]', 'NVARCHAR(Max)'))
Немного трудно увидеть с узкими кодовыми полями, но если вы прокрутите вправо, вы увидите число в квадратных скобках рядом с концом, это номер узла, [1], [2], [3] и т. д.
Итак, по логике мне просто нужно иметь переменную, содержащую количество узлов Inserts, и пройти через нее: -
DECLARE @nEntries INT = (SELECT @Message.value('count(/Inserts)', 'int'))
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[sql:variable("@nEntries")]', 'NVARCHAR(Max)'))
@ nEntries содержит правильное число, так что часть работает (при независимом тестировании)однако строка SET @InsertedValue = не позволяет даже запустить скрипт ALTER PROCEDURE, он падает с: -
XQuery [value()]: 'value()' requires a singleton (or empty sequence),
found operand of type 'xdt:untypedAtomic *'
на SET @InsertedValue = строка
Я попытался привести переменную как целое число несколькими способами: -
[xs: integer (sql: variable ("@ nEntries"))]
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[xs:integer(sql:variable("@nEntries"))]', 'NVARCHAR(Max)'))
Это дает то же самоеошибка: -
XQuery [value()]: 'value()' requires a singleton (or empty sequence),
found operand of type 'xdt:untypedAtomic *'
[sql: переменная ("@ nEntries") приведена как xs: integer]
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[sql:variable("@nEntries") cast as xs:integer]', 'NVARCHAR(Max)'))
Это дает:
XQuery [value()]: In this version of the server, 'cast as <type>' is not available.
Please use the 'cast as <type> ?' syntax.
[sql: variable ("@ nEntries") приводится как xs: integer?]
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[sql:variable("@nEntries") cast as xs:integer ?]', 'NVARCHAR(Max)'))
Это возвращает меня к
XQuery [value()]: 'value()' requires a singleton (or empty sequence),
found operand of type 'xdt:untypedAtomic *'
[sql: variable ("@ nEntries")]приведено как xs: целое число?
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[sql:variable("@nEntries")] cast as xs:integer ?', 'NVARCHAR(Max)'))
Это в основном мне принадлежит: -
XQuery [value()]: Cannot explicitly convert from 'xdt:untypedAtomic *' to 'xs:integer ?'
Согласно: -
https://docs.microsoft.com/en-us/sql/xquery/type-casting-rules-in-xquery?view=sql-server-2017
вы можете приводить от нетипизированного атома к десятичному, ицелое число является подтипом десятичной дроби.
Итак, не уверен, почему он стонет об этом.
Я пробовал без квадратных скобок: sql: variable ("@ nEntries")
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])sql:variable("@nEntries")', 'NVARCHAR(Max)'))
Это дает мне
XQuery [value()]: No more tokens expected at the end of the XQuery expression. Found 'sql'.
Я пытался использовать дополнительные квадратные скобки: [[sql: variable ("@ nEntries")] приведен как xs: integer?]
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[[sql:variable("@nEntries")] cast as xs:integer ?]', 'NVARCHAR(Max)'))
Это дает
XQuery [value()]: Syntax error near '['
Я знал, что это не сработает, так как я уже обнаружил, что это создает систему, во-первых, но только для того, чтобы охватить все базы, которые я пытался с помощью конкатенации строк
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[' + CONVERT(NVARCHAR(Max),@nEntries) + '] cast as xs:integer ?]', 'NVARCHAR(Max)'))
Как и ожидалось, это дало
The argument 1 of the XML data type method "value" must be a string literal.
Я также попытался использовать переменную: -
DECLARE @Query NVARCHAR(Max) = '(/Inserts/*[local-name()=sql:variable("@fieldname")])[' + CONVERT(NVARCHAR(Max),@nEntries) + '] cast as xs:integer ?]'
SET @InsertedValue = (SELECT @Message.value(@Query, 'NVARCHAR(Max)'))
Снова
The argument 1 of the XML data type method "value" must be a string literal.
Ивот почему я решил попросить помощи у вас, уважаемые ребята!
Учитывая, что я использую sql: variable довольно успешно в том же самом вызове (ниже работает нормально, но получает только первые данные Inserts)...
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[1]', 'NVARCHAR(Max)'))
... у кого-нибудь есть идеи, которые я не пытался разрешить использовать переменную sql: для номера узла?
Edit:
Я также пытался [sql: variable ("@ nEntries") [1]], на случай, если бы он думал, что @nEntries может иметь несколько значений
SET @InsertedValue = (SELECT @Message.value('(/Inserts/*[local-name()=sql:variable("@fieldname")])[sql:variable("@nEntries")[1]]', 'NVARCHAR(Max)'))
Дает нашему старому другу
XQuery [value()]: 'value()' requires a singleton (or empty sequence), found operand of type 'xdt:untypedAtomic *'
Обходной путь Edit: -
Я создал обходной путь, где я создаю временную таблицу и вставляю по одной строке с каждой соответствующей записью Inserts and Deletes, а затем просматриваю эту таблицу.Это позволяет мне использовать [1], как и раньше, что прекрасно работает.
Но я все же хотел бы знать, как использовать переменную, как указано выше, для будущего использования!