Click here to monitor SSC
SQLServerCentral is supported by Red Gate Software Ltd.
 
Log in  ::  Register  ::  Not logged in
 
 
 
        
Home       Members    Calendar    Who's On


Add to briefcase

Stored Proc with XML input param to create Dynamic SQL: Is there a better way? Expand / Collapse
Author
Message
Posted Wednesday, November 07, 2012 6:54 AM
SSC Veteran

SSC VeteranSSC VeteranSSC VeteranSSC VeteranSSC VeteranSSC VeteranSSC VeteranSSC Veteran

Group: General Forum Members
Last Login: Monday, May 13, 2013 7:33 AM
Points: 234, Visits: 441
Hi Guys,

I have a requirement to generate record sets which are built using a number of different filters. The filters come in different types and combinations AND the organisation has not decided who will be generating the record sets or what tool they will use to do so (options include Excel VBA, Access Form/VBA, Progress, .Net and [most likely] pass a piece of paper with the selections on to the DBA

Because of the complexity of the criteria and the need to make it technology agnostic I have elected to have an input parameter of type XML and use XQuery to strip it down.

This results in building a dynamic SQL statement which is becoming increasingly complicated.

I know that DSQL is not cachable in the Query plan chache, Is there a better way to approach this. All the examples I have found on-line have very simple XML structures and mine is a bit more complicated as different elements have different levels and the nodes need to be treated differently.

/* XML FORMAT IS AS FOLLOWS:
<PARAMS> 1..1 Always 1 instance
<MAIN>
<ACTION> 1..1 Always 1 instance
COUNT 'COUNT' or 'SELECT'
</ACTION>
<VERSION> 1..1 Always 1 instance
CURRENT CURRENT or a date in YYYMMDD format
</VERSION>
<POT> 1..1 Always 1 instance
ACTIVE ACTIVE, CANCELLED or COMPLEX
</POT>
<THRESHOLD> 1..1 Always 1 instance
lt150 lt999 or ge999 where 999 is a numeric value
<THRESHOLD>
</MAIN>
<FILTERS> 1..1 Always 1 instance
<DECEASED> 1..1 Always 1 instance
INCLUDE INCLUDE, EXCLUDE or ONLY
</DECEASED>
<VIP> 1..1 Always 1 instance
EXCLUDE INCLUDE, EXCLUDE or ONLY
</VIP>
<CLAIMS> 1..1 Always 1 instance
ONLY INCLUDE, EXCLUDE or ONLY
</CLAIMS>
<CRM> 1..1 Always 1 instance
INCLUDE
</CRM>
<SPECIALNEEDS> 1..1 Always 1 instance: values are 'Include' and 'Only'
<NEED> 0..4 If 'Include', then 0-4 choices: 0 = no includes.
Deaf If 'Only', then 1-4 choices otherwise nothing is selected
</NEED> NEED options are Deaf/Visually Impared/Blind Audio/Blind Braille
<NEED>
Braille Audio
</NEED>
</SPECIALNEEDS>
<MULTIPOL> 1..1 Always 1 instance
INCLUDE INCLUDE, EXCLUDE or ONLY
</MULTIPOL>
<MULTICASE> 1..1 Always 1 instance
EXCLUDE INCLUDE, EXCLUDE or ONLY
</MULTICASE>
<POLICIES> 0..1 May be none, but never more than one
<POLICY> 0/1..0/n If Policies exists, there must be at least one and could be many
</POLICY>
</POLICIES>
</FILTERS>
<CALLER> 0..1 Calling Appliation and user details are optional
<APPLICATION> 0..1
<NAME> 0..1
</NAME>
<VERSION> 0..1
</VERSION>
<APPLICATION>
<USER> 0..1
<ID> 0..1
</ID>
<NAME> 0..1
</NAME>
<IPADDRESS> 0..1
</IPADDRESS>
</USER>
</CALLER>
</PARAMS> */


For most of the filters, an INCLUDE means no join is necessary, an EXCLUDE means that a left join is required with a Where righthandtable is null and an ONLY means that an inner join is required.


I end up with a DSQL statement that looks something like this
SELECT COUNT(*) FROM 
(
SELECT
B.policy as 'Policy'
FROM
Pot B
JOIN
PolicyPrice PF on PF.policy = B.policy
LEFT JOIN
OpsBatchRecord OBR on OBR.policy= PF.policy
AND OBR.MailStatus <> 'Cancelled'
LEFT JOIN
PolicyHdr F on F.policy= B.policy
LEFT JOIN
DECEASED DECEASED on DECEASED.householdref = F.householdref
JOIN
ClaimHdr CLAIMS on CLAIMS.policy= B.policy
JOIN
CRM CRM on CRM.policy= B.policy
LEFT JOIN
SpecialNeeds SPN on SPN.householdref = F.householdref
WHERE
B.Band = 'ACTIVE'
AND
PF.NetDetriment >150
AND
OBR.BatchID is null
AND
DECEASED.householdref is null

) X


[color=#55AA55]NOTE: The above SQL was not generated with the example values![/color]
The code is becoming hader to maintain and test as new criteria are added (e.g. Special needs changed from being an Inc/Exc/Only option like the others to being a four part selection as there are four different reasons for being classed as special needs.

I can't believe I am the only person who has tried to do this, does anyone have any tips as to whether there is a better approach

Post #1381958
Posted Wednesday, November 07, 2012 8:16 AM


Hall of Fame

Hall of FameHall of FameHall of FameHall of FameHall of FameHall of FameHall of FameHall of FameHall of Fame

Group: General Forum Members
Last Login: Yesterday @ 3:48 AM
Points: 3,125, Visits: 4,311
Consider ripping the XML into a relational table then using that in your joins and filtering where possible. This should eliminate the need for DSQL.
as a starting point, consider for example:
declare @Param XML = '<PARAMS>
<!--1..1 Always 1 instance -->
<MAIN>
<ACTION>
<!-- 1..1 Always 1 instance "COUNT" or "SELECT"--> COUNT
</ACTION>
<VERSION>
<!--1..1 Always 1 instance CURRENT or a date in YYYMMDD format-->CURRENT
</VERSION>
<POT>
<!--1..1 Always 1 instance ACTIVE, CANCELLED or COMPLEX -->ACTIVE
</POT>
<THRESHOLD>
<!--1..1 Always 1 instance lt999 or ge999 where 999 is a numeric value--> lt150
</THRESHOLD>
</MAIN>
<FILTERS>
<!-- 1..1 Always 1 instance -->
<DECEASED>
<!-- 1..1 Always 1 instance INCLUDE, EXCLUDE or ONLY -->INCLUDE
</DECEASED>
<VIP>
<!--1..1 Always 1 instance INCLUDE, EXCLUDE or ONLY--> EXCLUDE
</VIP>
<CLAIMS>
<!-- 1..1 Always 1 instance INCLUDE, EXCLUDE or ONLY --> ONLY
</CLAIMS>
<CRM>
<!-- 1..1 Always 1 instance -->INCLUDE
</CRM>
<SPECIALNEEDS>
<!-- 1..1 Always 1 instance: values are "" and "Only" --> Include
<NEED>
<!-- 0..4 If "Include", then 0-4 choices: 0 = no includes. If "Only", then 1-4 choices otherwise nothing is selected -->
Deaf
</NEED> <!-- NEED options are Deaf/Visually Impared/Blind Audio/Blind Braille -->
<NEED> Braille Audio </NEED>
</SPECIALNEEDS>
<MULTIPOL>
<!--1..1 Always 1 instance INCLUDE, EXCLUDE or ONLY--> INCLUDE
</MULTIPOL>
<MULTICASE>
<!--1..1 Always 1 instance INCLUDE, EXCLUDE or ONLY--> EXCLUDE
</MULTICASE>
<POLICIES>
<!-- 0..1 May be none, but never more than one -->
<POLICY>
<!-- 0/1..0/n If Policies exists, there must be at least one and could be many-->
</POLICY>
</POLICIES>
</FILTERS>
<CALLER>
<!-- 0..1 Calling Appliation and user details are optional -->
<APPLICATION>
0..1
<NAME> 0..1 </NAME>
<VERSION> 0..1 </VERSION>
</APPLICATION>
<USER>
0..1
<ID> 0..1 </ID>
<NAME> 0..1 </NAME>
<IPADDRESS> 0..1 </IPADDRESS>
</USER>
</CALLER>
</PARAMS>'
--select @Param
SELECT Main.c.value('(./ACTION/text())[1]','varchar(10)') AS Action,
Main.c.value('(./VERSION/text())[1]','varchar(10)') AS Version,
Main.c.value('(./POT/text())[1]','varchar(10)') AS Pot,
Main.c.value('(./THRESHOLD/text())[1]','varchar(6)') as Threshold,
Filters.c.value('(./DECEASED/text())[1]','varchar(10)') AS Deceased,
Filters.c.value('(./VIP/text())[1]','varchar(10)') AS VIP,
Filters.c.value('(./CLAIMS/text())[1]','varchar(10)') AS Claims,
Filters.c.value('(./CRM/text())[1]','varchar(10)') AS CRM,
Special.n.value('(./NEED/text())[1]','varchar(10)') AS Need,
Special.n.value('(./NEED/text())[2]','varchar(10)') AS Need,
Special.n.value('(./NEED/text())[3]','varchar(10)') AS Need,
Special.n.value('(./NEED/text())[4]','varchar(10)') AS Need,
Filters.c.value('(./MULTIPOL/text())[1]','varchar(10)') AS MiltiPol,
Filters.c.value('(./MULTICASE/text())[1]','varchar(10)') AS MultiCase,
Filters.c.value('(./POLICIES/POLICY/text())[1]','varchar(10)') AS Policies,
Caller.c.value('(./APPLICATION/NAME/text())[1]','varchar(10)') AS ApplicationName,
Caller.c.value('(./APPLICATION/VERSION/text())[1]','varchar(10)') AS ApplicationVersion,
Caller.c.value('(./USER/ID/text())[1]','varchar(10)') AS UserId,
Caller.c.value('(./USER/NAME/text())[1]','varchar(75)') AS UserName,
Caller.c.value('(./USER/IPADDRESS/text())[1]','varchar(16)') AS UserIPAdress
FROM @Param.nodes('PARAMS') p(c)
CROSS APPLY p.c.nodes('MAIN') Main(c)
CROSS APPLY p.c.nodes('FILTERS') Filters(c)
CROSS APPLY Filters.c.nodes('SPECIALNEEDS') Special(n)
CROSS APPLY p.c.nodes('CALLER') Caller(c)

as the content of a CTE, or insert into a temp table (or create and insert into a table variable) then joining to the actual table(s) et al


____________________________________________
Space, the final frontier? not any more...
All limits henceforth are self-imposed.
“libera tute vulgaris ex”
Post #1382014
« Prev Topic | Next Topic »

Add to briefcase

Permissions Expand / Collapse