Update UDF names stored in table to add parameter value - sql

I have thousands of UDF names stored in table and executed dynamically where it is required. The problem is I have added one new parameter unit to the function dbo.GetStockPrice(6544,1) so I need to send one more parameter value for now 1 bue it can be any and the data should be changed to dbo.GetStockPrice(6544,1,1) for all the rows where dbo.GetStockPrice is exist. So I am seeking for the query to update these all at once.
Sample Data
DECLARE #table AS TABLE(id INT, UDF VARCHAR(1000))
INSERT INTO #table VALUES
(7774,'dbo.GetStockPrice(1211,1)*dbo.GetStockPrice(1211,1)'),
(7775,'dbo.GetStockPrice(232,1)'),
(7778,'dbo.GetStockPrice(6456,1)'),
(7780,'dbo.GetStockPrice(34,1)'),
(7784,'dbo.FNACondition(dbo.FNAMargin(1,NULL,0), 0, dbo.GetStockPrice(654,1)+1)'),
(7786,'dbo.GetStockPrice(9876,1)'),
(7906,'dbo.GetStockPrice(5565,1)'),
(7911,'dbo.GetStockPrice(7886,1)'),
(7912,'dbo.GetStockPrice(87,1)'),
(8403,'dbo.PriceValue(479,NULL,NULL)*dbo.GetStockPrice(6544,1)+dbo.FNAMargin(1,NULL,0)')
Expected Output:
7774 dbo.GetStockPrice(1211,1,1)*dbo.GetStockPrice(1211,1,1)
7775 dbo.GetStockPrice(232,1,1)
so on......
I am still trying with REPLACE, SUBSTRING but unable to come out with any solution. Getting difficulties with it's different length and position in the row.
Seeking Help !! Thank you in Advance :)

Try it with this approach:
DECLARE #table AS TABLE(id INT, UDF VARCHAR(1000))
INSERT INTO #table VALUES
(7774,'dbo.GetStockPrice(1211,1)*dbo.GetStockPrice(1211,1)'),
(7775,'dbo.GetStockPrice(232,1)'),
(7778,'dbo.GetStockPrice(6456,1)'),
(7780,'dbo.GetStockPrice(34,1)'),
(7784,'dbo.FNACondition(dbo.FNAMargin(1,NULL,0), 0, dbo.GetStockPrice(654,1)+1)'),
(7786,'dbo.GetStockPrice(9876,1)'),
(7906,'dbo.GetStockPrice(5565,1)'),
(7911,'dbo.GetStockPrice(7886,1)'),
(7912,'dbo.GetStockPrice(87,1)'),
(7913,'dbo.Blah(87,1)'),
(8403,'dbo.PriceValue(479,NULL,NULL)*dbo.GetStockPrice(6544,1)+dbo.FNAMargin(1,NULL,0)');
--The query
WITH SplitToParts AS
(
SELECT t.*
,A.parted
,ROW_NUMBER() OVER(ORDER BY (SELECT NULL)) AS PartNr
,B.part.value('text()[1]','nvarchar(max)') AS Part
FROM #table AS t
CROSS APPLY(SELECT CAST('<x>' + REPLACE((SELECT t.UDF AS [*] FOR XML PATH('')),'dbo.GetStockPrice(','</x><x>$$$FoundIt$$$</x><x>') + '</x>' AS XML)) AS A(parted)
CROSS APPLY parted.nodes(N'/x') AS B(part)
WHERE UDF LIKE '%dbo.GetStockPrice(%'
)
,modified AS
(
SELECT *
,CASE WHEN LAG(stp.Part) OVER(PARTITION BY id ORDER BY PartNr)='$$$FoundIt$$$' THEN STUFF(stp.Part,CHARINDEX(')',stp.Part),0,',1') ELSE stp.Part END AS Added
FROM SplitToParts AS stp
)
SELECT t2.*
,(
SELECT REPLACE(m.added,'$$$FoundIt$$$','dbo.GetStockPrice(')
FROM modified AS m
WHERE m.id=t2.id
ORDER BY m.PartNr
FOR XML PATH(''),TYPE
).value('.','nvarchar(max)')
FROM #table AS t2
WHERE UDF LIKE '%dbo.GetStockPrice(%';
The result
7774 dbo.GetStockPrice(1211,1,1)*dbo.GetStockPrice(1211,1,1)
7775 dbo.GetStockPrice(232,1,1)
7778 dbo.GetStockPrice(6456,1,1)
7780 dbo.GetStockPrice(34,1,1)
7784 dbo.FNACondition(dbo.FNAMargin(1,NULL,0), 0, dbo.GetStockPrice(654,1,1)+1)
7786 dbo.GetStockPrice(9876,1,1)
7906 dbo.GetStockPrice(5565,1,1)
7911 dbo.GetStockPrice(7886,1,1)
7912 dbo.GetStockPrice(87,1,1)
8403 dbo.PriceValue(479,NULL,NULL)*dbo.GetStockPrice(6544,1,1)+dbo.FNAMargin(1,NULL,0)
Some explanation: The string will be cut in parts using your function's name as splitter. Gladfully you tagged this with [sql-server-2012] so you can use LAG(). This will test the previous element, if it is $$$FoundIt$$$. In this case the first closing bracket will get an additional ,1. The rest is reconcatenation.
Attention: If your call might include a computed value such as
dbo.GetStockPrice(1211,(1+2))
or
dbo.GetStockPrice(dbo.SomeOtherFunc(1),1)
...the first closing bracket is the wrong place to insert the ,1. But this would get really tricky... You'd have to run through it, char by char, and count the opening brackets to find the related closing one.

Related

In SQL, in XML datatype field, how do I query the data like a simple sql record?

I have one XML field in table.
It stores data as
'<NewDataSet>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>367.25</LCAmount>
<FCCurr>USD</FCCurr>
<FCAmount>100</FCAmount>
<ExchangeRate>3.67252</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-004</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate>5%</VatRate>
<VatBaseAmount>349.76</VatBaseAmount>
<VatAmount>17.49</VatAmount>
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>90.00</LCAmount>
<FCCurr>AED</FCCurr>
<FCAmount>90</FCAmount>
<ExchangeRate>1</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-005</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate />
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
</NewDataSet>'
Now I want to simply query it like an sql record how do I do that?
I tried with conversion and everything but no luck with that. I just want it be simply like any sql record which can be selected, inserted deleted.
Tired but no luck with this:
SELECT
Tbl.Col.value('GLAccount[0]', 'varchar')
FROM #xml.nodes('/NewDataSet/ClaimExpense/GLAccount') Tbl(Col)
Check it out how to do it for your XML.
SQL Fiddle
SQL
CREATE table tbl (ID INT IDENTITY PRIMARY KEY, xmldata XML);
insert into tbl (xmldata) values
(
N'<NewDataSet>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>367.25</LCAmount>
<FCCurr>USD</FCCurr>
<FCAmount>100</FCAmount>
<ExchangeRate>3.67252</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-004</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate>5%</VatRate>
<VatBaseAmount>349.76</VatBaseAmount>
<VatAmount>17.49</VatAmount>
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
<ClaimExpense>
<ClaimNo>3003-LOB-0003</ClaimNo>
<Office>3003</Office>
<BranchId>1</BranchId>
<CostCenterId>35</CostCenterId>
<ServiceLineId>14</ServiceLineId>
<ProjectId>62</ProjectId>
<LCAmountCurr>AED</LCAmountCurr>
<LCAmount>90.00</LCAmount>
<FCCurr>AED</FCCurr>
<FCAmount>90</FCAmount>
<ExchangeRate>1</ExchangeRate>
<ExpenseDate>2020-11-03T00:00:00+04:00</ExpenseDate>
<ClaimItemNo>ITM-005</ClaimItemNo>
<GLAccount>10000000</GLAccount>
<VatRate/>
<ClaimType>LOB</ClaimType>
<ForPayment>357.25</ForPayment>
<ForDeduction>0.00</ForDeduction>
<EmpCode>2019-1194</EmpCode>
</ClaimExpense>
</NewDataSet>');
select c.value('(ClaimNo/text())[1]', 'VARCHAR(20)') as ClaimNo
-- everything in between with proper data types
, c.value('(EmpCode/text())[1]', 'VARCHAR(10)') as EmpCode
from tbl cross apply xmldata.nodes('/NewDataSet/ClaimExpense') t(c);
As I mention in the comments, you need to use the names of your nodes; your XML has no node "row" so of course SQL Server isn't going to find any data. I also recommend use the text() function, as it is far more efficient. For example:
SELECT NDS.CE.value('(ClaimNo/text())[1]','int') AS ClaimNo
FROM #XML.nodes('NewDataSet/ClaimExpense') NDS(CE);

SQL to XML not able to create proper XMLNAMESPACE due to quotation marks (I think)

I have the following query:
WITH XMLNAMESPACES ('CommonImport StudentRecordCount="1"
xsi:schemaLocation="http://collegeboard.org/CommonImport CommonImport.xsd"
xmlns="http://collegeboard.org/CommonImport"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"' AS CommonImport)
SELECT B.award_year_token AS [StudentID/AwardYearToken]
,A.student_ssn AS [StudentID/SSN]
,A.last_name AS [StudentName/LastName]
,A.first_name AS [StudentName/FirstName]
,A.alternate_id AS [StudentName/AlternateID]
,'2807' AS [CustomStrings/CustomString/FieldID]
,C.processed_status AS [CustomStrings/CustomString/Value]
,'2506' AS [CustomDates/CustomDate/FieldID]
,CAST (C.date_processed AS DATE) AS [CustomDates/CustomDate/Value]
FROM [dbo].[student] A INNER JOIN [stu_award_year] B ON A.[student_token] = B.[student_token]
LEFT OUTER JOIN [dbo].[isir_convert_data] C ON A.[student_ssn] = C.[ssn] AND B.award_year_token = C.award_year_token
--LEFT OUTER JOIN [user_string] E ON B.[stu_award_year_token] = E.[stu_award_year_token]
--WHERE B.AWARD_YEAR_TOKEN = 2018 --For 18-19 year.
WHERE B.AWARD_YEAR_TOKEN = 2017 --For 17-18 year.
AND C.processed_status ='B'
AND C.date_processed = (SELECT MAX (X.date_processed)
FROM isir_convert_data X
WHERE C.ssn = X.ssn)
FOR XML PATH('Student'), ROOT('CommonImport')
The output is unusable due to the mishandling of the quotation marks. It looks like the following:
<CommonImport xmlns:CommonImport="CommonImport StudentRecordCount="1" xsi:schemaLocation="http://collegeboard.org/CommonImport CommonImport.xsd" xmlns="http://collegeboard.org/CommonImport" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"">
I am generating this via SQL Server. Can you offer any advice on how to properly create the XML Tag? And if I'm not properly using the XMLNAMESPACE function, please let me know. Thank you for considering.
You must distinguish between
the declaration of a namespace and
the usage of a namespace
It seems to me, that StudentRecordCount should be an attribute in the <CommonImport> node, same with schemaLocation. The second attribute is living within the xmlns:xsi-namespace.
You did not state the expected output, but my magic crystal ball showed me, that you might need this:
WITH XMLNAMESPACES (DEFAULT 'http://collegeboard.org/CommonImport'
,'http://www.w3.org/2001/XMLSchema-instance' AS xsi)
SELECT 1 AS [#StudentRecordCount]
,'http://collegeboard.org/CommonImport CommonImport.xsd' AS [#xsi:schemaLocation]
,'SomeOtherData' AS [Student/SomeElement]
FOR XML PATH('CommonImport');
the result
<CommonImport xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://collegeboard.org/CommonImport"
StudentRecordCount="1"
xsi:schemaLocation="http://collegeboard.org/CommonImport CommonImport.xsd">
<Student>
<SomeElement>SomeOtherData</SomeElement>
</Student>
</CommonImport>
If this does not help enough, please read about how to create a MCVE and provide sample data and expected output.
UPDATE 1
This is - roughly - what you need, but the namespaces are repeated. This is a known and annoying issue. Not wrong, the result is perfectly okay, but bloated.
WITH XMLNAMESPACES (DEFAULT 'http://collegeboard.org/CommonImport'
,'http://www.w3.org/2001/XMLSchema-instance' AS xsi)
,cte AS
(
SELECT object_id,name FROM sys.objects
)
SELECT COUNT(*) AS [#RecordCount]
,'http://collegeboard.org/CommonImport CommonImport.xsd' AS [#xsi:schemaLocation]
,(
SELECT *
FROM cte
FOR XML PATH('Object'),TYPE
)
FROM cte
FOR XML PATH('CommonImport');
UPDATE 2
An ugly workaround
WITH cte AS
(
SELECT object_id,name FROM sys.objects
)
SELECT
CAST(REPLACE(REPLACE(REPLACE(CAST(
(
SELECT COUNT(*) AS [#RecordCount]
,'http://collegeboard.org/CommonImport CommonImport.xsd' AS [#xsi_schemaLocation] --<-- "xsi:" is replaced with "xsi_"
,'http://collegeboard.org/CommonImport' AS [#_xmlns_] --<-- "xmlns" is not allowed
,'http://www.w3.org/2001/XMLSchema-instance' AS [#_xmlns_xsi] --<-- Same with "xmlns:xsi"
,(
SELECT *
FROM cte
FOR XML PATH('Object'),TYPE
)
FROM cte
FOR XML PATH('CommonImport'),TYPE) AS nvarchar(MAX)),'xsi_','xsi:'),'_xmlns_',' xmlns'),'xmlnsxsi','xmlns:xsi') AS XML);
Alternatively you might create the whole thing without namespaces at all and add the namespace declaration with string methods at the end.

Querying XML colum for values

I have a SQL Server table with an XML column, and it contains data something like this:
<Query>
<QueryGroup>
<QueryRule>
<Attribute>Integration</Attribute>
<RuleOperator>8</RuleOperator>
<Value />
<Grouping>OrOperator</Grouping>
</QueryRule>
<QueryRule>
<Attribute>Integration</Attribute>
<RuleOperator>5</RuleOperator>
<Value>None</Value>
<Grouping>AndOperator</Grouping>
</QueryRule>
</QueryGroup>
</Query>
Each QueryRule will only have one Attribute, but each QueryGroup can have many QueryRules. Each Query can also have many QueryGroups.
I need to be able to pull all records that have one or more QueryRule with a certain attribute and value.
SELECT *
FROM QueryBuilderQueries
WHERE [the xml contains any value=X where the attribute is either Y or Z]
I've worked out how to check a specific QueryRule, but not "any".
SELECT
Query
FROM
QueryBuilderQueries
WHERE
Query.value('(/Query/QueryGroup/QueryRule/Value)[1]', 'varchar(max)') like 'UserToFind'
AND Query.value('(/Query/QueryGroup/QueryRule/Attribute)[1]', 'varchar(max)') in ('FirstName', 'LastName')
You can use two exist(). One to check the value and one to check Attribute.
select Q.Query
from dbo.QueryBuilderQueries as Q
where Q.Query.exist('/Query/QueryGroup/QueryRule/Value/text()[. = "UserToFind"]') = 1 and
Q.Query.exist('/Query/QueryGroup/QueryRule/Attribute/text()[. = ("FirstName", "LastName")]') = 1
If you really want the like equivalence when you search for a Value you can use contains().
select Q.Query
from dbo.QueryBuilderQueries as Q
where Q.Query.exist('/Query/QueryGroup/QueryRule/Value/text()[contains(., "UserToFind")]') = 1 and
Q.Query.exist('/Query/QueryGroup/QueryRule/Attribute/text()[. = ("FirstName", "LastName")]') = 1
According to http://technet.microsoft.com/pl-pl/library/ms178030%28v=sql.110%29.aspx
"The XQuery must return at most one value"
If you are quite certain that for example your XML has let's say maximum 10 QueryRules you could maybe use WHILE to loop everything while droping your results into temporary table?
maybe below can help you anyway
CREATE TABLE #temp(
Query type)
DECLARE #i INT
SET #i = 1
WHILE #i >= 10
BEGIN
INSERT INTO #temp
SELECT
Query
FROM QueryBuilderQueries
WHERE Query.value('(/Query/QueryGroup/QueryRule/Value)[#i]', 'varchar(max)') LIKE 'UserToFind'
AND Query.value('(/Query/QueryGroup/QueryRule/Attribute)[#i]', 'varchar(max)') IN ('FirstName', 'LastName')
#i = #i + 1
END
SELECT
*
FROM #temp
It's a pity that the SQL Server (I'm using 2008) does not support some XQuery functions related to string such as fn:matches, ... If it supported such functions, we could query right inside XQuery expression to determine if there is any. However we still have another approach. That is by turning all the possible values into the corresponding SQL row to use the WHERE and LIKE features of SQL for searching/filtering. After some experiementing with the nodes() method (used on an XML data), I think it's the best choice to go:
select *
from QueryBuilderQueries
where exists( select *
from Query.nodes('//QueryRule') as v(x)
where LOWER(v.x.value('(Attribute)[1]','varchar(max)'))
in ('firstname','lastname')
and v.x.value('(Value)[1]','varchar(max)') like 'UserToFind')

Get multiple xml nodes (delimited)

I have a table with a xml that is formatted something like this (simplified for readability)
<parentItem xmlns:i="http://tempuri.org/1" xmlns="http://tempuri.org/2">
<ItemA></ItemA>
<ItemB></ItemB>
<ItemC xmlns:d2p1="http://tempuri.org/3">
<d2p1:string>value1</d2p1:string>
<d2p1:string>value2</d2p1:string>
<d2p1:string>value3</d2p1:string>
<!-- .... (0 to many strings here) -->
</ItemC>
</parentItem>
The only think I care about are the values in parentItem > ItemC > string
I would like to get those values delimited by something, such as a comma
Desired Result: "value1,value2,value3"
currently I can get one value by doing this:
SELECT CAST([QueryXml] as xml).value('(/*:parentItem/*:ItemC/node())[1]','nvarchar(max)')
FROM [opendb].[dbo].[MyTable]
Result: "value1"
I can also get all the values like this:
SELECT CAST([QueryXml] as xml).value('(/*:ConflictsSearchTermQuery/*:TermItems)[1]','nvarchar(max)')
FROM [opendb].[dbo].[ConflictsSearchTerms]
Result: "value1value2value3"
but I'm looking to get a delimited set of values
Desired Result: "value1,value2,value3"
To get multiple values out of XML you need to use the nodes() method of the XML data type.
However, since this method does not return a single, scalar value (but a rowset), you need to call it through CROSS APPLY.
WITH MyTable AS (
SELECT 1 AS ID, CAST('<parentItem xmlns:i="http://tempuri.org/1" xmlns="http://tempuri.org/2">
<ItemA></ItemA>
<ItemB></ItemB>
<ItemC xmlns:d2p1="http://tempuri.org/3">
<d2p1:string>value1</d2p1:string>
<d2p1:string>value2</d2p1:string>
<d2p1:string>value3</d2p1:string>
<!-- .... (0 to many strings here) -->
</ItemC>
</parentItem>' AS XML) AS QueryXml
)
SELECT
t.ID,
x.node.value('.', 'varchar(100)') AS nodeValue
FROM
MyTable t
CROSS APPLY QueryXml.nodes('
declare namespace i="http://tempuri.org/1";
declare namespace def="http://tempuri.org/2";
declare namespace d2p1="http://tempuri.org/3";
/def:parentItem/def:ItemC/d2p1:string'
) x(node)
gives you
ID nodeValue
----------- ------------------
1 value1
1 value2
1 value3
After that, if you really must, standard techniques for concatenating values in SQL Server apply.
Note that I have properly declared the namespaces in the XQuery instead of using *. Namespaces are important, don't ignore them.

Combine 2 XML Output Statements into one XML file

I've been working on outputting from SQL to XML and have finally come up with 2 queries that work fabulously for what I need with one problem. I did these independently as I am a novice with XML and now I need to combine them into one file for output. What I have is pretty complicated using dynamic SQL so I'm just going to post the dynamic part and exclude all of the variable and un-pivoting stuff I'm doing at the beginning. If I could just get result set 1 to be at the top and result set 2 just below it would suffice. I would like a wrapper tag (not sure if that's the term) as the very first and very last lines in this file. I'm not sure if anyone can tell me what I'm doing wrong just by looking at the dynamic code but I thought it would be worth a try. As I said, both of these work great but produce 2 separate files. Thanks in advance!
EXEC ('
SELECT
(SELECT GETDATE() AS STARTDATE,
GETDATE() AS ENDDATE
FOR XML PATH(''RECDATE''),ELEMENTS, TYPE),
(SELECT COUNT(*) AS SURVEY_COUNT
FROM PM_TEMP.dbo.tmpKansasCancerCenterExtract FOR XML PATH(''''), ELEMENTS, TYPE),
(SELECT ''PEPM'' as SERVICE,
DataCol as VARNAME,
Question as QUESTION_TEXT,
AnswerValue as ANSWER_TEXT
from PM_TEMP.dbo.tmpKansasCancerCenterCodeSheetExtract FOR XML PATH(''QUESTION''), ROOT(''QUESTION_MAP''), ELEMENTS, TYPE)
FOR XML PATH(''HEADER'')
SELECT PATID,CLIENTID,SERVICE,PATVISITDT,DATE,
(
SELECT VarName,Value
FROM PM_TEMP.dbo.tmpKansasCancerCenterExtract_AnalysisPivot P
INNER JOIN PM_TEMP.dbo.tmpKansasCancerCenterExtract E ON E.PatVisitID = P.PatVisitID
FOR XML PATH(''Response''), TYPE, ROOT(''Analysis'')
),
(
SELECT VarName,Value
FROM PM_TEMP.dbo.tmpKansasCancerCenterExtract_DemoPivot P
INNER JOIN PM_TEMP.dbo.tmpKansasCancerCenterExtract E ON E.PatVisitID = P.PatVisitID
FOR XML PATH(''Response''), TYPE, ROOT(''Demographics'')
)
FROM PM_TEMP.dbo.tmpKansasCancerCenterExtract
FOR XML PATH(''PatientLevelData''),TYPE, ROOT(''PatientLevelData'')
')