Updating the xml data nodes by passing the SQL variable - sql-server

I have the following XML and I need to update the specified one based on the parameter:
<DOPremium>
<BasePremium>337500</BasePremium>
<TotalPremium>337500</TotalPremium>
<NettPremium>337500</NettPremium>
<GrossPremium>337500</GrossPremium>
<OptionId>0</OptionId>
</DOPremium>
<DOPremium>
<BasePremium>337500</BasePremium>
<TotalPremium>337500</TotalPremium>
<NettPremium>337500</NettPremium>
<GrossPremium>337500</GrossPremium>
<OptionId>1</OptionId>
</DOPremium>
<DOPremium>
<BasePremium>337500</BasePremium>
<TotalPremium>337500</TotalPremium>
<NettPremium>337500</NettPremium>
<GrossPremium>337500</GrossPremium>
<OptionId>2</OptionId>
</DOPremium>
I'm trying to update the respective nodes based on the selection of the DOPremium object, but I'm not able to do that. Can someone verify where I'm wrong?
SET #NewXmlValue = N' <BasePremium>[sql:variable("#R15_premium")]</BasePremium>'
SET #DataXml.modify('delete /*/Premiums/DOPremium/BasePremium[sql:variable("#OptionID")]')
SET #DataXml.modify('insert sql:variable("#NewXmlValue") into (/*/Premiums/DOPremium[sql:variable("#OptionID")])[1]')
-- Add TotalPremium
SET #NewXmlValue = N' <TotalPremium>[sql:variable("#R15_premium")]</TotalPremium>'
SET #DataXml.modify('delete /*/Premiums/DOPremium/TotalPremium[sql:variable("#OptionID")]')
SET #DataXml.modify('insert sql:variable("#NewXmlValue") into (/*/Premiums/DOPremium[sql:variable("#OptionID")])[1]')

OK, first of all, this can't work:
SET #NewXmlValue = N'<BasePremium>[sql:variable("#R15_premium")]</BasePremium>'
sql:variable() is only interpreted as a function in an XQuery operation. This isn't an XQuery operation, so it will just textually insert sql:variable(...). If you want an actual XML node with the text value of the variable, you have to be a little more roundabout:
SET #NewXmlValue = '';
SET #NewXmlValue = (SELECT #NewXmlValue.query('<BasePremium>{sql:variable("#R15_premium")}</BasePremium>'));
This approach (and others) can be found in the docs. (In this very simple case concatenating the strings in T-SQL also works, of course, but in general that's not a good idea because it doesn't take care of escaping the XML when necessary.)
The syntax for selecting the desired DOPremium node also needs work -- /BasePremium[sql:variable("#OptionID")] is legal, but it means "the BasePremium node that, sequentially numbering from 1, has number #OptionID". If #OptionID is supposed to match what's in OptionID, that's not the way to write it.
If your intent was to write "change the contents of the BasePremium value of the node with OptionID text equal to #OptionID to the value #R15_premium", here's how you do that (well, one way to do that):
SET #DataXml.modify('
replace value of (
/*
/Premiums
/DOPremium[child::OptionId/.=sql:variable("#OptionID")]
/BasePremium
/text()
)[1]
with sql:variable("#R15_premium")')
And something similar for TotalPremium. You can, of course, also replace entire nodes, but that seems unnecessary here.

Related

AT NEW with substring access?

I have a solution that includes a LOOP which I would like to spare. So I wonder, whether you know a better way to do this.
My goal is to loop through an internal, alphabetically sorted standard table. This table has two columns: a name and a table, let's call it subtable. For every subtable I want to do some stuff (open an xml page in my xml framework).
Now, every subtable has a corresponding name. I want to group the subtables according to the first letter of this name (meaning, put the pages of these subtables on one main page -one main page for every character-). By grouping of subtables I mean, while looping through the table, I want to deal with the subtables differently according to the first letter of their name.
So far I came up with the following solution:
TYPES: BEGIN OF l_str_tables_extra,
first_letter(1) TYPE c,
name TYPE string,
subtable TYPE REF TO if_table,
END OF l_str_tables_extra.
DATA: ls_tables_extra TYPE l_str_tables_extra.
DATA: lt_tables_extra TYPE TABLE OF l_str_tables_extra.
FIELD-SYMBOLS: <ls_tables> TYPE str_table."Like LINE OF lt_tables.
FIELD-SYMBOLS: <ls_tables_extra> TYPE l_str_tables_extra.
*"--- PROCESSING LOGIC ------------------------------------------------
SORT lt_tables ASCENDING BY name.
"Add first letter column in order to use 'at new' later on
"This is the loop I would like to spare
LOOP AT lt_tables ASSIGNING <ls_tables>.
ls_tables_extra-first_letter = <ls_tables>-name+0(1). "new column
ls_tables_extra-name = <ls_tables>-name.
ls_tables_extra-subtable = <ls_tables>-subtable.
APPEND ls_tables_extra TO lt_tables_extra.
ENDLOOP.
LOOP AT lt_tables_extra ASSIGNING <ls_tables_extra>.
AT NEW first_letter.
"Do something with subtables with same first_letter.
ENDAT.
ENDLOOP.
I wish I could use
AT NEW name+0(1)
instead of
AT NEW first_letter
, but offsets and lengths are not allowed.
You see, I have to inlcude this first loop to add another column to my table which is kind of unnecessary because there is no new info gained.
In addition, I am interested in other solutions because I get into trouble with the framework later on for different reasons. A different way to do this might help me out there, too.
I am happy to hear any thoughts about this! I could not find anything related to this here on stackoverflow, but I might have used not optimal search terms ;)
Maybe the GROUP BY addition on LOOP could help you in this case:
LOOP AT i_tables
INTO DATA(wa_line)
" group lines by condition
GROUP BY (
" substring() because normal offset would be evaluated immediately
name = substring( val = wa_line-name len = 1 )
) INTO DATA(o_group).
" begin of loop over all tables starting with o_group-name(1)
" loop over group object which contains
LOOP AT GROUP o_group
ASSIGNING FIELD-SYMBOL(<fs_table>).
" <fs_table> contains your table
ENDLOOP.
" end of loop
ENDLOOP.
why not using a IF comparison?
data: lf_prev_first_letter(1) type c.
loop at lt_table assigning <ls_table>.
if <ls_table>-name(1) <> lf_prev_first_letter. "=AT NEW
"do something
lf_prev_first_letter = <ls_table>-name(1).
endif.
endloop.

Power Query M loop table / lookup via a self-join

First of all I'm new to power query, so I'm taking the first steps. But I need to try to deliver sometime at work so I can gain some breathing time to learn.
I have the following table (example):
Orig_Item Alt_Item
5.7 5.10
79.19 79.60
79.60 79.86
10.10
And I need to create a column that will loop the table and display the final Alt_Item. So the result would be the following:
Orig_Item Alt_Item Final_Item
5.7 5.10 5.10
79.19 79.60 79.86
79.60 79.86 79.86
10.10
Many thanks
Actually, this is far too complicated for a first Power Query experience.
If that's what you've got to do, then so be it, but you should be aware that you are starting with a quite difficult task.
Small detail: I would expect the last Final_Item to be 10.10. According to the example, the Final_Item will be null if Alt_Item is null. If that is not correct, well that would be a nice first step for you to adjust the code below accordingly.
You can create a new blank query, copy and paste this code in the Advanced Editor (replacing the default code) and adjust the Source to your table name.
let
Source = Table.Buffer(Table1),
AddedFinal_Item =
Table.AddColumn(
Source,
"Final_Item",
each if [Alt_Item] = null
then null
else List.Last(
List.Generate(
() => [Final_Item = [Alt_Item], Continue = true],
each [Continue],
each [Final_Item =
Table.First(
Table.SelectRows(
Source,
(x) => x[Orig_Item] = [Final_Item]),
[Alt_Item = "not found"]
)[Alt_Item],
Continue = Final_Item <> "not found"],
each [Final_Item])))
in
AddedFinal_Item
This code uses function List.Generate to perform the looping.
For performance reasons, the table should always be buffered in memory (Table.Buffer), before invoking List.Generate.
List.Generate is one of the most complex Power Query functions.
It requires 4 arguments, each of which is a function in itself.
In this case the first argument starts with () and the other 3 with each (it should be clear from the outline above: they are aligned).
Argument 1 defines the initial values: a record with fields Final_Item and Continue.
Argument 2 is the condition to continue: if an item is found.
Argument 3 is the actual transformation in each iteration: the Source table is searched (with Table.SelectRows) for an Orig_Item equal to Alt_Item. This is wrapped in Table.First, which returns the first record (if any found) and accepts a default value if nothing found, in this case a record with field Alt_Item with value "not found", From this result the value of record field [Alt_Item] is returned, which is either the value of the first record, or "not found" from the default value.
If the value is "not found", then Continue becomes false and the iterations will stop.
Argument 4 is the value that will be returned: Final_Item.
List.Generate returns a list of all values from each iteration. Only the last value is required, so List.Generate is wrapped in List.Last.
Final remark: actual looping is rarely required in Power Query and I think it should be avoided as much as possible. In this case, however, it is a feasible solution as you don't know in advance how many Alt_Items will be encountered.
An alternative for List.Generate is using a resursive function.
Also List.Accumulate is close to looping, but that has a fixed number of iterations.
This can be solved simply with a self-join, the open question is how many layers of indirection you'll be expected to support.
Assuming just one level of indirection, no duplicates on Orig_Item, the solution is:
let
Source = #"Input Table",
SelfJoin1 = Table.NestedJoin( Source, {"Alt_Item"}, Source, {"Orig_Item"}, "_tmp_" ),
Expand1 = ExpandTableColumn( SelfJoin1, "_tmp_", {"Alt_Item"}, {"_lkp_"} ),
ChkJoin1 = Table.AddColumn( Expand1, "Final_Item", each (if [_lkp_] = null then [Alt_Item] else [_lkp_]), type number)
in
ChkJoin1
This is doable with the regular UI, using Merge Queries, then Expand Column and adding a custom column.
If yo want to support more than one level of indirection, turn it into a function to be called X times. For data-driven levels of indirection, you wrap the calls in a list.generate that drop the intermediate tables in a structured column, though that's a much more advanced level of PQ.

T-SQL xquery .modify method using a wildcard

I am working in SQL Server 2014. I have created a stored procedure which does its processing thing, and at the end, takes the final query output and formats it as XML. Due to the nature of the logic in the procedure, sometimes a node must be deleted from the final XML output. Here's a sample of the XML output (for brevity I have not included the root nodes; hopefully they won't be required to answer my question):
<DALink>
<InteractingIngredients>
<InteractingIngredient>
<IngredientID>1156</IngredientID>
<IngredientDesc>Lactobacillus acidophilus</IngredientDesc>
<HICRoot>Lactobacillus acidophilus</HICRoot>
<PotentiallyInactive>Not necessary</PotentiallyInactive>
<StatusCode>Live</StatusCode>
</InteractingIngredient>
</InteractingIngredients>
<ActiveProductsExistenceCode>Exist</ActiveProductsExistenceCode>
<IngredientTypeBasisCode>1</IngredientTypeBasisCode>
<AllergenMatch>Lactobacillus acidophilus</AllergenMatch>
<AllergenMatchType>Ingredient</AllergenMatchType>
</DALink>
<ScreenDrug>
<DrugID>1112894</DrugID>
<DrugConceptType>RxNorm_SemanticClinicalDr</DrugConceptType>
<DrugDesc>RxNorm LACTOBACILLUS ACIDOPHILUS/PECTIN ORAL capsule</DrugDesc>
<Prospective>true</Prospective>
</ScreenDrug>
In the procedure I have code that looks at the XML structure above and deletes a node when it shouldn't be there because it's easier to modify the xml than tweak the query output. Here is a sample of that code:
SET #xml_Out.modify('declare namespace ccxsd="http://schemas.foobar.com/CC/v1_3"; delete //ccxsd:InteractingIngredients[../../../ccxsd:ScreenDrug/ccxsd:DrugConceptType="OneThing"]');
SET #xml_Out.modify('declare namespace ccxsd="http://schemas.foobar.com/CC/v1_3"; delete //ccxsd:InteractingIngredients[../../../ccxsd:ScreenDrug/ccxsd:DrugConceptType="AnotherThing"]');
SET #xml_Out.modify('declare namespace ccxsd="http://schemas.foobar.com/CC/v1_3"; delete //ccxsd:InteractingIngredients[../../../ccxsd:ScreenDrug/ccxsd:DrugConceptType="SomethingElse"]');
SET #xml_Out.modify('declare namespace ccxsd="http://schemas.foobar.com/CC/v1_3"; delete //ccxsd:InteractingIngredients[../../../ccxsd:ScreenDrug/ccxsd:DrugConceptType="SomethingElseAgain"]');
SET #xml_Out.modify('declare namespace ccxsd="http://schemas.foobar.com/CC/v1_3"; delete //ccxsd:InteractingIngredients[../../../ccxsd:ScreenDrug/ccxsd:DrugConceptType="RxNorm*"]');
The final command is the one I can't figure out how to make work. All I need to do is to look for instances where the element "DrugConceptType" starts with the string "RxNorm", because there are multiple versions of the string that can possibly occur.
I have Googled and StackOverFlowed at length, but perhaps because of my inexperience in this area I didn't ask the question correctly.
Is there a relatively easy way to re-write the final .modify statement above to use a wildcard after "RxNorm"?
Your reduction of the root node is a problem acutally, as you are using the namespace "ccxsd" and your XML does not show this.
Anyway, better, than to write the declare namespace ... over and over, was this as first line of your statement:
;WITH XMLNAMESPACES('http://schemas.fdbhealth.com/CC/v1_3' AS ccxsd)
Well, as a .modify() is a one statement call it doesn't make such a difference...
But to your question of a wildcard
This would delete all nodes, where the Element's content starts with "RxNorm":
SET #xml.modify('delete //*[fn:substring(.,1,6)="RxNorm"]');
Be aware of missing namespaces... Cannot test it...
EDIT: A simplified working example:
You have to check this with your actual XML (with root and namespace)
DECLARE #xml_Out XML=
'<DALink>
<InteractingIngredients>
<InteractingIngredient>
<IngredientID>1156</IngredientID>
<IngredientDesc>Lactobacillus acidophilus</IngredientDesc>
<HICRoot>Lactobacillus acidophilus</HICRoot>
<PotentiallyInactive>Not necessary</PotentiallyInactive>
<StatusCode>Live</StatusCode>
</InteractingIngredient>
</InteractingIngredients>
<ActiveProductsExistenceCode>Exist</ActiveProductsExistenceCode>
<IngredientTypeBasisCode>1</IngredientTypeBasisCode>
<AllergenMatch>Lactobacillus acidophilus</AllergenMatch>
<AllergenMatchType>Ingredient</AllergenMatchType>
</DALink>
<ScreenDrug>
<DrugID>1112894</DrugID>
<DrugConceptType>RxNorm_SemanticClinicalDr</DrugConceptType>
<DrugDesc>RxNorm LACTOBACILLUS ACIDOPHILUS/PECTIN ORAL capsule</DrugDesc>
<Prospective>true</Prospective>
</ScreenDrug>';
SET #xml_Out.modify('delete //InteractingIngredients[fn:substring((../../ScreenDrug/DrugConceptType)[1],1,6)="RxNorm"]');
SELECT #xml_Out;

Lua string library choices for finding and replacing text

I'm new to Lua programming, having come over from python to basically make a small addon for world of warcraft for a friend. I'm looking into various ways of finding a section of text from a rather large string of plain text. I need to extract the information from the text that I need and then process it in the usual way.
The string of text could be a number of anything, however the below is what we are looking to extract and process
-- GSL --
items = ["itemid":"qty" ,"itemid":"qty" ,"itemid":"qty" ,]
-- ENDGSL --
We want to strip the whole block of text from a potentially large block of text surrounding it, then remove the -- GSL -- and -- ENDGSL -- to be left with:
items = ["itemdid":"qty …
I've looked into various methods, and can't seem to get my head around any of them.
Anyone have any suggestions on the best method to tackle this problem?
EDIT: Additional problem,
Based on the accepted answer I've changed the code slightly to the following.
function GuildShoppingList:GUILDBANKFRAME_OPENED()
-- Actions to be taken when guild bank frame is opened.
if debug == "True" then self:Print("Debug mode on, guild bank frame opened") end
gslBankTab = GetCurrentGuildBankTab()
gslBankInfo = GetGuildBankText(gslBankTab)
p1 = gslBankInfo:match('%-%- GSL %-%-%s+(.*)%s+%-%- ENDGSL %-%-')
self:Print(p1)
end
The string has now changed slightly the information we are parsing is
{itemid:qty, itemid:qty, itemid:qty, itemid:qty}
Now, this is a string that's being called in p1. I need to update the s:match method to strip the { } also, and iterate over each item and its key seperated by, so I'm left with
itemid:qty
itemid:qty
itemid:qty
itemid:qty
Then I can identify each line individually and place it where it needs to go.
try
s=[[-- GSL --
items = ["itemid":"qty" ,"itemid":"qty" ,"itemid":"qty" ,]
-- ENDGSL --]]
print(s:match('%-%- GSL %-%-%s+(.*)%s+%-%- ENDGSL %-%-'))
The key probably is that - is a pattern modifier that needs quoting if you want a literal hyphen. More info on patterns in the Lua Reference Manual, chapter 5.4.1
Edit:
To the additional problem of looping through keys of what is almost an array, you could do 2 things:
Either loop over it as a string, assuming both key and quantity are integers:
p="{1:10, 2:20, 3:30}"
for id,qty in p:gmatch('(%d+):(%d+)') do
--do something with those keys:
print(id,qty)
end
Or slightly change the string, evaluate it as a Lua table:
p="{1:10, 2:20, 3:30}"
p=p:gsub('(%d+):','[%1]=') -- replace : by = and enclose keys with []
t=loadstring('return '..p)() -- at this point, the anonymous function
-- returned by loadstring get's executed
-- returning the wanted table
for k,v in pairs(t) do
print(k,v)
end
If the formats of keys or quantities is not simply integer, changing it in the patterns should be trivial.

SQL bit field filtering

just a quick question here.
Lets say I have a stored procedure for seleting records from a table.
select * from tbl_table
where deleted = #deleted
So for this above, I must pass in a parameter being either TRUE or FALSE.
Is it possible to return results where deleted is equal to TRUE and FALSE but retaining the option to pass in a parameter. So I guess, if no parameter is passed in, there is no filter.
The other way I thought was to do this..
select * from tbl_table
where deleted = #deleted1
and deleted = #deleted2
So you have 2 parameters for the same filter. This way you can do true or false or set both filters the same - giving more leeway.
If anyone has any ideas or thoughts on this that would be great!
set your parameter as nullable (= NULL) and then
select * from tbl_table
where (#deleted IS NULL) OR (deleted = #deleted)
Then do not supply the parameter (or explicitly supply as NULL), when you do not want the filter by that parameter to be applied.
(But be aware this can sometimes have parameter sniffing consequences, especially with a large number of parameters)

Resources