Enrich XML document via stored procedure - sql

Our middleware software receives an XML document and forwards it to another software. However, there is one field in the target software (ext_text_10), that I do NOT want to overwrite.
Upon receiving the XML, I would like to call a stored proc, that should query the target database table to find the current ext_text_10 value, and insert a new element in the incoming XML with the resulting value, and then pass it on. This way we will simply pass the current value for that field.
The procedure below is reading the XML, building it again by preserving everything from it, and then inserting one element.
However, it does not return any result. Below is my procedure, and my source XML.
I hope somebody can tell me what is wrong. This is SQL Server 2016.
CREATE PROCEDURE [dbo].[z_ION_GetCurrentCostCenterData] (#xmlData xml)
AS
BEGIN
-- Prevent extra result sets from interfering with SELECT statements
SET NOCOUNT ON;
-- Parse XML
DECLARE #parsedXmlData int;
EXEC sp_xml_preparedocument #parsedXmlData OUTPUT, #xmlData;
DECLARE
#tenantID varchar(50),
#acc_entity varchar(3),
#doc_id varchar(50),
#anl_code varchar(15),
#anl_dim_id varchar(2),
#lookup_code varchar(15),
#anl_code_name varchar(50),
#prohibit_posting varchar(1),
#statusCode varchar(1),
#anl_cat_id varchar(15),
#ext_text_6 varchar(50),
#ext_text_7 varchar(50),
#ext_text_8 varchar(50),
#ext_text_9 varchar(50),
#ext_num_1 varchar(50),
#ext_num_2 varchar(50),
#ext_num_3 varchar(50),
#ext_num_4 varchar(50),
#ext_num_5 varchar(50),
#ext_date_1 varchar(50),
#ext_date_2 varchar(50),
#ext_fixed_1 varchar(50),
#ext_fixed_2 varchar(50),
#ext_fixed_3 varchar(50),
#TableName NVarchar(255)
--Get data from XML
select
#tenantID = tenantID,
#acc_entity = acc_entity,
#doc_id = doc_id,
#anl_code = anl_code,
#anl_dim_id = anl_dim_id,
#lookup_code = lookup_code,
#anl_code_name = anl_code_name,
#prohibit_posting = prohibit_posting,
#statusCode = statusCode,
#anl_cat_id = anl_cat_id,
#ext_text_6 = ext_text_6,
#ext_text_7 = ext_text_7,
#ext_text_8 = ext_text_8,
#ext_text_9 = ext_text_9,
#ext_num_1 = ext_num_1,
#ext_num_2 = ext_num_2,
#ext_num_3 = ext_num_3,
#ext_num_4 = ext_num_4,
#ext_num_5 = ext_num_5,
#ext_date_1 = ext_date_1,
#ext_date_2 = ext_date_2,
#ext_fixed_1 = ext_fixed_1,
#ext_fixed_2 = ext_fixed_2,
#ext_fixed_3 = ext_fixed_3
from OPENXML (#parsedXmlData,'DataArea',2)
with
(tenantID varchar(50) 'Sync/TenantID',
acc_entity varchar(3) 'Sync/AccountingEntityID',
doc_id varchar(50) 'SunSystemsAnalysisCodes/DocumentID/ID',
anl_code varchar(15) 'SunSystemsAnalysisCodes/AnalysisCode',
anl_dim_id varchar(2) 'SunSystemsAnalysisCodes/AnalysisDimensionId',
lookup_code varchar(15) 'SunSystemsAnalysisCodes/LookupCode',
anl_code_name varchar(50) 'SunSystemsAnalysisCodes/Name',
prohibit_posting varchar(1) 'SunSystemsAnalysisCodes/ProhibitPosting',
statusCode varchar(1) 'SunSystemsAnalysisCodes/Status',
anl_cat_id varchar(15) 'SunSystemsAnalysisCodes/AnalysisCatID/AnlCat_SHead',
ext_text_6 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText6',
ext_text_7 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText7',
ext_text_8 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText8',
ext_text_9 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText9',
ext_num_1 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber1',
ext_num_2 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber2',
ext_num_3 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber3',
ext_num_4 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber4',
ext_num_5 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber5',
ext_date_1 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionDate1',
ext_date_2 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionDate2',
ext_fixed_1 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionFixed1',
ext_fixed_2 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionFixed2',
ext_fixed_3 varchar(50) 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionFixed3'
);
--Create XML to return
select
[EXT_TEXT_10] as 'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText10'
from [dbo].[IMT_ANL_CODE_EXT]
where [ANL_CAT_ID] = #anl_cat_id and [ANL_CODE] = #anl_code
for XML PATH ('SunSystemsAnalysisCodes'), root('DataArea')
EXEC sp_xml_removedocument #parsedXmlData
END
Source XML:
<DataArea xmlns:my="http://schema.infor.com/InforOAGIS/2">
<Sync>
<TenantID>INFRA_TRN</TenantID>
<AccountingEntityID>IMT</AccountingEntityID>
<ActionCriteria>
<ActionExpression actionCode="Change" />
</ActionCriteria>
</Sync>
<SunSystemsAnalysisCodes>
<DocumentID>
<ID variationID="1495808583000">PRO 00002</ID>
</DocumentID>
<IONStatus>
<Code listID="GenericStatus">Open</Code>
</IONStatus>
<AnalysisCode>PRO17</AnalysisCode>
<AnalysisDimensionId>01</AnalysisDimensionId>
<LookupCode>Informatiesyste</LookupCode>
<Name>Informatiesysteem Relatics 10</Name>
<ProhibitPosting>0</ProhibitPosting>
<Status>0</Status>
<AnalysisCatID>
<AnlCat_SHead>COST CENTRE</AnlCat_SHead>
</AnalysisCatID>
<ExtendedAnalysis>
<ExtensionText6>SVE</ExtensionText6>
<ExtensionText7>Open</ExtensionText7>
<ExtensionText8 />
<ExtensionNumber1>81700</ExtensionNumber1>
<ExtensionNumber2 />
<ExtensionNumber3 />
<ExtensionNumber4 />
<ExtensionNumber5 />
<ExtensionDate1>01012016</ExtensionDate1>
<ExtensionDate2>31122016</ExtensionDate2>
<ExtensionFixed1>1</ExtensionFixed1>
<ExtensionFixed2>2</ExtensionFixed2>
<ExtensionFixed3>2</ExtensionFixed3>
</ExtendedAnalysis>
</SunSystemsAnalysisCodes>
</DataArea>

If this doesn't help you, please answer the questions from my comment below your question!
My magic crystall ball tells me, that you might be looking for something like this (shortened for brevity):
DECLARE #xml XML=
N'<DataArea xmlns:my="http://schema.infor.com/InforOAGIS/2">
<Sync>
<TenantID>INFRA_TRN</TenantID>
<!-- more elements -->
</Sync>
<SunSystemsAnalysisCodes>
<DocumentID>
<ID variationID="1495808583000">PRO 00002</ID>
</DocumentID>
<!-- more elements -->
<ExtendedAnalysis>
<ExtensionText6>SVE</ExtensionText6>
<ExtensionText7>Open</ExtensionText7>
<ExtensionText8 />
<ExtensionNumber1>81700</ExtensionNumber1>
<ExtensionNumber2 />
<ExtensionNumber3 />
<ExtensionNumber4 />
<ExtensionNumber5 />
<ExtensionDate1>01012016</ExtensionDate1>
<ExtensionDate2>31122016</ExtensionDate2>
<ExtensionFixed1>1</ExtensionFixed1>
<ExtensionFixed2>2</ExtensionFixed2>
<ExtensionFixed3>2</ExtensionFixed3>
</ExtendedAnalysis>
</SunSystemsAnalysisCodes>
</DataArea>';
--This is the content you want to introduce
DECLARE #ContentToAdd NVARCHAR(MAX)=N'SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText10';
--This XML_DML-statement will insert your content with the given name before the <ExtensionNumber1> (which must exist! Other positions are possible of course)
SET #xml.modify(N'insert <ExtensionText10>{sql:variable("#ContentToAdd")}</ExtensionText10>
before (/DataArea/SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionNumber1)[1]');
--Check the output:
SELECT #xml;
<DataArea xmlns:my="http://schema.infor.com/InforOAGIS/2">
<Sync>
<TenantID>INFRA_TRN</TenantID>
<!-- more elements -->
</Sync>
<SunSystemsAnalysisCodes>
<DocumentID>
<ID variationID="1495808583000">PRO 00002</ID>
</DocumentID>
<!-- more elements -->
<ExtendedAnalysis>
<ExtensionText6>SVE</ExtensionText6>
<ExtensionText7>Open</ExtensionText7>
<ExtensionText8 />
<ExtensionText10>SunSystemsAnalysisCodes/ExtendedAnalysis/ExtensionText10</ExtensionText10>
<ExtensionNumber1>81700</ExtensionNumber1>
<ExtensionNumber2 />
<ExtensionNumber3 />
<ExtensionNumber4 />
<ExtensionNumber5 />
<ExtensionDate1>01012016</ExtensionDate1>
<ExtensionDate2>31122016</ExtensionDate2>
<ExtensionFixed1>1</ExtensionFixed1>
<ExtensionFixed2>2</ExtensionFixed2>
<ExtensionFixed3>2</ExtensionFixed3>
</ExtendedAnalysis>
</SunSystemsAnalysisCodes>
</DataArea>

Related

How to send Array of objects to stored procedure from nodejs express?

thanks in advance. Here i want to send a array of objects to the stored procedure via mssql connection to the ms sql-server from NodeJs backend. i have submitted my Stored Procedure and Backend code block.
--- Stored Procedure ---
CREATE PROCEDURE [dbo].[AddProductVariant]
#ProductId BIGINT ,
#Name NVARCHAR(200) ,
#InputTypeId TINYINT ,
#Attributes dbo.UT_ProductVariant READONLY
AS
BEGIN
BEGIN TRY
SET NOCOUNT ON;
DECLARE #Id BIGINT
INSERT INTO dbo.ProductVariants
( ProductId, Name, InputTypeId )
VALUES ( #ProductId -- ProductId - bigint
, #Name -- Name - nvarchar(50)
, #InputTypeId -- InputTypeId - tinyint
)
SET #Id = SCOPE_IDENTITY()
EXECUTE MergeProductVariantAttributes #Id, #Attributes
SELECT *
FROM SellEasyDb.dbo.ProductVariants
WHERE Id = #Id
--SELECT * FROM SellEasyDb.dbo.UserGroups WHERE SellEasyDb.dbo.UserGroups.Id = #Id
RETURN 0
END TRY
BEGIN CATCH
RETURN ##ERROR
END CATCH
END
--- NODE TS CODE ---
const pool = dbConnect.connect();
const request = dbConnect.request();
request.input("ProductId", req.body.productId);
request.input("Name", req.body.name);
request.input("InputTypeId", req.body.inputTypeId);
request.input("Attributes", req.body.attributes); // this is the array i want to send to SP
await request.execute("AddProductVariant", async (err: any, result: any, resultvalue: any) => {
debugger;
}
});
--- ERROR ---
class:16
code:'EREQUEST'
lineNumber:0
message:'Operand type clash: nvarchar is incompatible with UT_ProductVariant'
name:'RequestError'
number:206
state:2
--- User Define Type ---
CREATE TYPE UT_ProductVariant AS TABLE
(
[Id] [BIGINT],
[Title] [VARCHAR](200) NOT NULL,
[PriceAdjustment] [DECIMAL](18, 2) NOT NULL,
[IsDefault] [BIT] NOT NULL,
[SortOrder] [INT] NOT NULL,
[VarientId] [BIGINT],
[ProductId] [BIGINT]
)

How to update XML elements with new values in SQL script

I have XML in one of the column in the XYZ table, and I need to update the Amount element with a new value instead of 0.00, and the PolicyReference and AccountReference elements with two different values instead of blank.
For example:
<PolicyReference>7657576567</PolicyReference>
<AccountReference>7657576875</AccountReference>
This is my XML in the column :
<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>0.00</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference />
<AccountReference />
<AccountId>2034001793</AccountId>
</document>
This is what I have tried:
UPDATE XYZ
SET XmlPayload.modify('replace value of (//document/PolicyReference/)[1] with "<PolicyReference>275654</PolicyReference>"')
WHERE PaymentSearchId = 18785
I am getting an error:
Msg 9341, Level 16, State 1, Line 4
XQuery [XYZ.XmlPayload.modify()]: Syntax error near ')', expected a step expression
I think this is a good question as it presents an interesting challenge of having an existing element without a text value. This is handled differently than simply adding a new element or replacing the contents of an existing one.
First, though, your provided XML was broken. If that is the XML you're receiving, you have other issues. For instance, in your original question, you had </AccountReference> which is invalid syntax by itself. I corrected this to <AccountReference /> both in your question as well as in my example.
With empty XML elements, you need to call the insert text DML of the XML.modify method.
DECLARE #xml XML =
'<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>0.00</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference />
<AccountReference />
<AccountId>2034001793</AccountId>
</document>';
DECLARE
#Amount DECIMAL(18,2) = 99.95,
#AccountReference VARCHAR(20) = '7657576875',
#PolicyReference VARCHAR(20) = '7657576567';
/* Update Amount */
SET #xml.modify('
replace value of (/document/Amount/text())[1]
with sql:variable("#Amount")
');
/* Insert the AccountReference text */
SET #xml.modify('
insert text {sql:variable("#AccountReference")} into (/document/AccountReference[1])[1]
');
/* Insert the PolicyReference text */
SET #xml.modify('
insert text {sql:variable("#PolicyReference")} into (/document/PolicyReference[1])[1]
');
/* Show updated XML */
SELECT #xml;
The updated XML is now:
<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>99.95</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference>7657576567</PolicyReference>
<AccountReference>7657576875</AccountReference>
<AccountId>2034001793</AccountId>
</document>
An example of updating a table:
DECLARE #xyz TABLE ( PaymentSearchId INT, XmlPayload XML );
INSERT INTO #xyz VALUES ( 18785,
'<document>
<StatusCode>ACV</StatusCode>
<PaymentMethodDetail>
<EFT>
<AccountNumber>123456789</AccountNumber>
<AccountName>ABCDEFGHIJK</AccountName>
</EFT>
</PaymentMethodDetail>
<PaymentExtendedData>
<CHECK>
<Source>System</Source>
<SourceType>ACH</SourceType>
</CHECK>
</PaymentExtendedData>
<PostMarkDate />
<EntryUserId>1</EntryUserId>
<Amount>0.00</Amount>
<EntryDate />
<PolicyLineOfBusiness>LOL</PolicyLineOfBusiness>
<PolicyReference />
<AccountReference />
<AccountId>2034001793</AccountId>
</document>' );
DECLARE
#PaymentSearchId INT = 18785,
#Amount DECIMAL(18,2) = 99.95,
#AccountReference VARCHAR(20) = '7657576875',
#PolicyReference VARCHAR(20) = '7657576567';
/* Update Amount */
UPDATE #xyz
SET XmlPayload.modify('
replace value of (/document/Amount/text())[1]
with sql:variable("#Amount")
')
WHERE PaymentSearchId = #PaymentSearchId;
/* Insert the AccountReference text */
UPDATE #xyz
SET XmlPayload.modify('
insert text {sql:variable("#AccountReference")} into (/document/AccountReference[1])[1]
')
WHERE PaymentSearchId = #PaymentSearchId;
/* Insert the PolicyReference text */
UPDATE #xyz
SET XmlPayload.modify('
insert text {sql:variable("#PolicyReference")} into (/document/PolicyReference[1])[1]
')
WHERE PaymentSearchId = #PaymentSearchId;
/* Show updated XML */
SELECT XmlPayload FROM #xyz WHERE PaymentSearchId = #PaymentSearchId;

make a variable default if something on sql server insert stament

i want to insert the default values, if the values i gave him are 0 or ''
ALTER procedure [dbo].[altaemp]
#id_emp int,
#nom_emp varchar(100),
#lnom_emp varchar(100),
#l2nom_emp varchar(100),
#id_type int,
#id_supervisor int,
#correo varchar(50),
#id_area int
as
begin
if #id_supervisor = 0 (select #id_supervisor = ?? )
if #correo = '' (select #correo = ??)
INSERT INTO [dbo].[empleados]
([id_emp]
,[nom_emp]
,[lnom_emp]
,[l2nom_emp]
,[id_type]
,[id_supervisor]
,[correo]
,[id_area])
VALUES
(#id_emp,
#nom_emp,
#lnom_emp,
#l2nom_emp,
#id_type,
#id_supervisor,
#correo,
#id_area)
return
end
i have tried equals the variables to null or default without success
To handle these 2 cases you can add the following decision statements, replacing ?? with your replacement value:
IF LEN(#correo) = 0 SET #correo = ??
IF #id_supervisor = 0 SET #id_supervisor = ??
I like to use a COALESCE with a NULLIF inside it to achieve this.
SELECT #id_supervisor = COALESCE(NULLIF(#id_supervisor,0),[DEFAULT VALUE])
,#correo = COALESCE(NULLIF(#correo ,' '),[DEFAULT VALUE])
There are multiple options for handling this scenario. If they are truly default values, you can simply pass null into the stored procedure and define the procedure like this:
ALTER procedure [dbo].[altaemp]
#id_emp int,
#nom_emp varchar(100),
#lnom_emp varchar(100),
#l2nom_emp varchar(100),
#id_type int,
#id_supervisor int = <default supervisor id>,
#correo varchar(50) = <default correo>,
#id_area int
as
begin
INSERT INTO [dbo].[empleados]
([id_emp]
,[nom_emp]
,[lnom_emp]
,[l2nom_emp]
,[id_type]
,[id_supervisor]
,[correo]
,[id_area])
VALUES
(#id_emp,
#nom_emp,
#lnom_emp,
#l2nom_emp,
#id_type,
#id_supervisor,
#correo,
#id_area)
return
end
Alternatively, if there is a decision that has to be made, you can replace your logic with case logic which would look similar to this:
ALTER procedure [dbo].[altaemp]
#id_emp int,
#nom_emp varchar(100),
#lnom_emp varchar(100),
#l2nom_emp varchar(100),
#id_type int,
#id_supervisor int = <default supervisor id>,
#correo varchar(50) = <default correo>,
#id_area int
as
begin
INSERT INTO [dbo].[empleados]
([id_emp]
,[nom_emp]
,[lnom_emp]
,[l2nom_emp]
,[id_type]
,[id_supervisor]
,[correo]
,[id_area])
Select #id_emp,
#nom_emp,
#lnom_emp,
#l2nom_emp,
#id_type,
CASE WHEN #id_supervisor = 0 THEN <Default SupervisorId> ELSE #id_supervisor END,
CASE WHEN #correo= '' THEN <Default correo> ELSE #correo END,
#id_area
return
end
Since you can pass null instead of 0 and '', then you can try this:
ALTER procedure [dbo].[altaemp]
#id_emp int,
#nom_emp varchar(100),
#lnom_emp varchar(100),
#l2nom_emp varchar(100),
#id_type int,
#id_supervisor int=100, -- Default value=100
#correo varchar(50)='my Default Value', -- Default Value
#id_area int
as
begin
INSERT INTO [dbo].[empleados]
([id_emp]
,[nom_emp]
,[lnom_emp]
,[l2nom_emp]
,[id_type]
,[id_supervisor]
,[correo]
,[id_area])
VALUES
(#id_emp,
#nom_emp,
#lnom_emp,
#l2nom_emp,
#id_type,
#id_supervisor,
#correo,
#id_area)
return
end
So, you can call the procedure like this:
EXEC [dbo].[altaemp]
#id_emp = yourValue,
#nom_emp = yourValue,
#lnom_emp = yourValue,
#l2nom_emp = yourValue,
#id_type = yourValue,
#id_supervisor = null, -- Specify Null or remove parameter
#correo = null, -- Specify Null or remove parameter
#id_area = yourValue

parse xml as table

I have some xml like:
<MyDetails>
<detail key="key1" value="value1" />
<detail key="key2" value="value2" />
<detail key="key3" value="value3" />
<detail key="key4" value="value4" />
</MyDetails>
And I want to be able to parse it in a table format of two columns Key, and Value. How can I create a SQL function to achieve this by specifying a Node path i.e. in this case '/MyDetails/detail' and KeyAttributeName and ValueAttributeName. I created the following function but it gives me the error:
ALTER FUNCTION [dbo].[GetXmlTable] (
#XmlSource XML,
#HierarchyPath NVARCHAR(50) = '',
#SpecificKey NVARCHAR(255) = NULL,
#KeyAttributeName NVARCHAR(50) = 'key',
#ValueAttributeName NVARCHAR(50) = 'value'
)
RETURNS #Table TABLE (
[Key] NVARCHAR(255),
[Value] NVARCHAR(500)
)
AS
BEGIN
DECLARE #KeyAttribute NVARCHAR (50) = '#' + #KeyAttributeName
DECLARE #ValueAttribute NVARCHAR (50) = '#' + #ValueAttributeName
DECLARE #Path NVARCHAR (50) = '/' + #HierarchyPath
INSERT INTO #Table
SELECT XmlElement.Attribute.value(#KeyAttribute, 'nvarchar(255)') AS [Key]
,XmlElement.Attribute.value(#ValueAttribute, 'nvarchar(500)') AS [Value]
FROM #XmlSource.nodes(#Path) AS XmlElement(Attribute)
WHERE #SpecificKey IS NULL OR XmlElement.Attribute.value(#KeyAttribute, 'nvarchar(255)') = #SpecificKey
RETURN
END
GO
Error:
Msg 8172, Level 16, State 1, Procedure GetXmlTable, Line 12 The
argument 1 of the XML data type method "nodes" must be a string
literal.
Looking to call the function like this:
select * from dbo.GetXmlTable(CAST('<MyDetails>
<detail key="key1" value="value1" />
<detail key="key2" value="value2" />
<detail key="key3" value="value3" />
<detail key="key4" value="value4" />
</MyDetails>' as XML), 'MyDetails/detail', default, default, default)
UPDATE---------------
I tried using sql variable syntax but the table returned is blank. Can you please point out what I might be doing wrong:
DECLARE #KeyAttr VARCHAR(50) = N'#' + #KeyAttributeName
DECLARE #ValueAttr VARCHAR(50) = N'#' + #ValueAttributeName
DECLARE #Path VARCHAR(100) = '/' + #HierarchyPath
INSERT INTO #Table
SELECT XmlElement.Attribute.value('(*[local-name() = sql:variable("#KeyAttr")])[1]', 'nvarchar(255)') AS [Key]
,XmlElement.Attribute.value('(*[local-name() = sql:variable("#ValueAttr")][1])', 'nvarchar(500)') AS [Value]
FROM #XmlSource.nodes('(*[local-name() = sql:variable("#Path")])') AS XmlElement(Attribute)
WHERE #SpecificKey IS NULL OR XmlElement.Attribute.value('(*[local-name() = sql:variable("#KeyAttr")])[1]', 'nvarchar(255)') = #SpecificKey
This query will shred your XML into a simple table;
declare #xml xml = '<MyDetails>
<detail key="key1" value="value1" />
<detail key="key2" value="value2" />
<detail key="key3" value="value3" />
<detail key="key4" value="value4" />
</MyDetails>'
select
t.c.value('#key', 'varchar(100)') as [key],
t.c.value('#value', 'varchar(100)') as value
from
#xml.nodes('/MyDetails/detail') as t(c)
The error "The argument 1 of the XML data type method "nodes" must be a string literal." means exactly what it says - you can't pass a variable as the argument to the nodes() method. You can however reference variables in the XQuery passed to Nodes() - see sql:variable() for more info.

Modify several xml attributes, based on a list

From a previous post:
SQL Server XML add attribute if non-existent
What I would like to do is be able to modify multiple tags. Below is the code that shows what I would like to do, but cannot, since I get the error: The argument 1 of the XML data type method "exist" must be a string literal. Is there a way to modify the XML using variables, rather than literals?
ALTER FUNCTION [dbo].[ConvertXmlData](#xmlData XML)
RETURNS XML
AS
BEGIN
DECLARE #tags TABLE (
ID INT IDENTITY(1,1) NOT NULL PRIMARY KEY,
TAG VARCHAR(25)
)
INSERT INTO #tags
SELECT 'xxx' UNION
SELECT 'yyy'
DECLARE #counter INT
DECLARE #count INT
DECLARE #id INT
DECLARE #tag VARCHAR(25)
DECLARE #exist VARCHAR(100)
DECLARE #insert VARCHAR(100)
DECLARE #existX VARCHAR(100)
DECLARE #insertX VARCHAR(100)
SET #exist = 'descendant::{0}[not(#runat)]'
SET #insert = 'insert attribute runat { "server" } into descendant::{0}[not(#runat)][1]'
SET #counter = 1
SELECT #count = COUNT(*) FROM #tags
WHILE #counter <= #count BEGIN
SELECT #tag = TAG FROM #tags WHERE ID = #counter
SET #existX = REPLACE(#existX, '[0]', #tag)
WHILE #xmlData.exist(#existX) = 1 BEGIN
SET #xmlData.modify(REPLACE(#insertX, '[0]', #tag));
END
SET #counter = #counter + 1
END
RETURN #xmlData
END
You can't use a variable as an argument to the xml-functions but you can use variables (and table columns) in the literal expression.
I guess this does what you want. At least it should give you an idea of what you can do.
declare #xmlData xml
set #xmlData =
'<something>
<xxx id="1"/>
<xxx id="2" runat="server" />
<xxx id="3"/>
<yyy id="3" />
<zzz id="1"/>
</something>'
declare #tags table
(
id int identity(1,1) primary key,
tag varchar(25)
)
insert into #tags
select 'xxx' union
select 'yyy'
declare #tag varchar(25)
declare #id int
select top 1
#id = id,
#tag = tag
from #tags
order by id
while ##rowcount > 0
begin
while #xmlData.exist('descendant::*[local-name() = sql:variable("#tag") and not(#runat)]') = 1
begin
set #xmlData.modify('insert attribute runat { "server" } into descendant::*[local-name() = sql:variable("#tag") and not(#runat)][1]');
end
select top 1
#id = id,
#tag = tag
from #tags
where id > #id
order by id
end
select #xmlData
Result:
<something>
<xxx id="1" runat="server" />
<xxx id="2" runat="server" />
<xxx id="3" runat="server" />
<yyy id="3" runat="server" />
<zzz id="1" />
</something>