T-SQL looping through XML data column to derive unique set of paths - sql

I have XML data column which contains question and answer as part of an application process.
What I am trying to achieve through T-SQL/ Dynamic SQL is to derive a unique set of path wherever there is a target tag.
So for the below xml example, I would expect something like
log/clients/client/section/questions/groupone/question/target
log/clients/client/section/questions/grouptwo/question/target
Idea is to then to use this and loop through the XML to derive the values of the desired tags. I.e
[DATA].value('(/log/clients/client/section/questions/groupone/question/target', 'NVARCHAR(MAX)')
Problem is each application has different set of questions and xml structure i.e. some might have more questions, some might have different grouping.
However all I want is if there is a tag then what is its path.
How can I best achieve this?
<log>
<clients>
<client>
<section name ="Apps”>
<questions>
<groupone>
<question>
<target>Age</target>
</question>
<question>
<target> Height</target>
</question>
<question>
<target> Weight</target>
</question>
</groupone>
<grouptwo name = "exercise">
<wording>what is your name</wording>
<question>
<id>1</id>
<target>def<target>
</question>
</grouptwo>
</questions>
</section>
</client>
</clients>
</log>

The outdated approach with FROM OPENXML might be an option here. Check this answer.
At this link you'll find a function John Cappelletti posted from time to time, which will shred any XML (credits below the function's code).
But I'm not sure, what you are really trying to achieve... Why do you need the path? If you are interested in the values of all target nodes you might do something like this (deep search with // does not need the exact XPath)
SELECT t.value(N'(text())[1]','nvarchar(max)')
FROM #xml.nodes('//target') AS A(t);
If you really need all and everything you can check this:
CREATE FUNCTION [dbo].[udf-XML-Hier](#XML xml)
Returns Table
As Return
with cte0 as (
Select Lvl = 1
,ID = Cast(1 as int)
,Pt = Cast(NULL as int)
,Element = x.value('local-name(.)','varchar(150)')
,Attribute = cast('' as varchar(150))
,Value = x.value('text()[1]','varchar(max)')
,XPath = cast(concat(x.value('local-name(.)','varchar(max)'),'[' ,cast(Row_Number() Over(Order By (Select 1)) as int),']') as varchar(max))
,Seq = cast(1000000+Row_Number() over(Order By (Select 1)) as varchar(max))
,AttData = x.query('.')
,XMLData = x.query('*')
From #XML.nodes('/*') a(x)
Union All
Select Lvl = p.Lvl + 1
,ID = Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10
,Pt = p.ID
,Element = c.value('local-name(.)','varchar(150)')
,Attribute = cast('' as varchar(150))
,Value = cast( c.value('text()[1]','varchar(max)') as varchar(max) )
,XPath = cast(concat(p.XPath,'/',c.value('local-name(.)','varchar(max)'),'[',cast(Row_Number() Over(PARTITION BY c.value('local-name(.)','varchar(max)') Order By (Select 1)) as int),']') as varchar(max) )
,Seq = cast(concat(p.Seq,' ',10000000+Cast( (Lvl + 1) * 1024 + (Row_Number() Over(Order By (Select 1)) * 2) as int ) * 10) as varchar(max))
,AttData = c.query('.')
,XMLData = c.query('*')
From cte0 p
Cross Apply p.XMLData.nodes('*') b(c)
)
, cte1 as (
Select R1 = Row_Number() over (Order By Seq),A.*
From (
Select Lvl,ID,Pt,Element,Attribute,Value,XPath,Seq From cte0
Union All
Select Lvl = p.Lvl+1
,ID = p.ID + Row_Number() over (Order By (Select NULL))
,Pt = p.ID
,Element = p.Element
,Attribute = x.value('local-name(.)','varchar(150)')
,Value = x.value('.','varchar(max)')
,XPath = p.XPath + '/#' + x.value('local-name(.)','varchar(max)')
,Seq = cast(concat(p.Seq,' ',10000000+p.ID + Row_Number() over (Order By (Select NULL)) ) as varchar(max))
From cte0 p
Cross Apply AttData.nodes('/*/#*') a(x)
) A
)
Select A.R1
,R2 = IsNull((Select max(R1) From cte1 Where Seq Like A.Seq+'%'),A.R1)
,A.Lvl
,A.ID
,A.Pt
,A.Element
,A.Attribute
,A.XPath
,Title = Replicate('|---',Lvl-1)+Element+IIF(Attribute='','','#'+Attribute)
,A.Value
From cte1 A
/*
Source: http://beyondrelational.com/modules/2/blogs/28/posts/10495/xquery-lab-58-select-from-xml.aspx
Taken from John Cappelletti: https://stackoverflow.com/a/42729851/5089204
Declare #XML xml='<person><firstname preferred="Annie" nickname="BeBe">Annabelle</firstname><lastname>Smith</lastname></person>'
Select * from [dbo].[udf-XML-Hier](#XML) Order by R1
*/
GO
DECLARE #xml XML=
'<log>
<clients>
<client>
<section name ="Apps">
<questions>
<groupone>
<question>
<target>Age</target>
</question>
<question>
<target> Height</target>
</question>
<question>
<target> Weight</target>
</question>
</groupone>
<grouptwo name = "exercise">
<wording>what is your name</wording>
<question>
<id>1</id>
<target>def</target>
</question>
</grouptwo>
</questions>
</section>
</client>
</clients>
</log>';
SELECT * FROM dbo.[udf-XML-Hier](#xml);
GO

Related

Microsoft SQL query to view

I have this complex query that i want to turn into a view.
This query comes from https://snippets.cacher.io/snippet/3e84b01b7d52b4ca7807 and i want to save it in a view or even as a table if possible.
`
/*##=============================================*/
/*## QUERY BODY */
/*##=============================================*/
/* #region QueryBody */
/* Testing variables !! Need to be commented for Production !! */
-- DECLARE #UserSIDs AS NVARCHAR(10) = 'Disabled';
-- DECLARE #CollectionID AS NVARCHAR(10) = 'SMS00001';
-- DECLARE #Locale AS INT = 2;
-- DECLARE #Categories AS NVARCHAR(250) = 'Tools';
-- DECLARE #Compliant AS INT = 0;
-- DECLARE #Targeted AS INT = 1;
-- DECLARE #Superseded AS INT = 0;
-- DECLARE #ArticleID AS NVARCHAR(10) = '';
-- DECLARE #ExcludeArticleIDs AS NVARCHAR(250) = '';
/* Variable declaration */
DECLARE #LCID AS INT = dbo.fn_LShortNameToLCID(#Locale);
DECLARE #HelperFunctionExists AS INT = 0;
/* Perform cleanup */
IF OBJECT_ID('tempdb..#MaintenanceInfo', 'U') IS NOT NULL
DROP TABLE #MaintenanceInfo;
/* Check for helper function */
IF OBJECT_ID('[dbo].[ufn_CM_GetNextMaintenanceWindow]') IS NOT NULL
SET #HelperFunctionExists = 1;
/* Initialize HealthState descriptor table */
DECLARE #HealthState TABLE (
BitMask INT
, StateName NVARCHAR(250)
)
/* Populate HealthState table */
INSERT INTO #HealthState (BitMask, StateName)
VALUES
('0', 'Healthy')
, ('1', 'Unmanaged')
, ('2', 'Inactive')
, ('4', 'Health Evaluation Failed')
, ('8', 'Pending Restart')
, ('16', 'Update Scan Failed')
, ('32', 'Update Scan Late')
, ('64', 'No Maintenance Window')
, ('128', 'Distant Maintenance Window')
, ('256', 'Expired Maintenance Window')
/* Initialize ClientState descriptor table */
DECLARE #ClientState TABLE (
BitMask INT
, StateName NVARCHAR(100)
)
/* Populate ClientState table */
INSERT INTO #ClientState (BitMask, StateName)
VALUES
('0', 'No Reboot')
, ('1', 'Configuration Manager')
, ('2', 'File Rename')
, ('4', 'Windows Update')
, ('8', 'Add or Remove Feature')
CREATE TABLE #MaintenanceInfo (
ResourceID INT
, NextServiceWindow DATETIME
)
/* Get maintenance data */
IF #HelperFunctionExists = 1
BEGIN
WITH Maintenance_CTE AS (
SELECT
CollectionMembers.ResourceID
, NextServiceWindow.Duration
, NextServiceWindow.NextServiceWindow
, RowNumber = DENSE_RANK() OVER (PARTITION BY ResourceID ORDER BY NextServiceWindow.NextServiceWindow)
, ServiceWindowType
, ServiceWindow.Enabled
FROM vSMS_ServiceWindow AS ServiceWindow
JOIN fn_rbac_FullCollectionMembership(#UserSIDs) AS CollectionMembers ON CollectionMembers.CollectionID = ServiceWindow.SiteID
JOIN fn_rbac_Collection(#UserSIDs) AS Collections ON Collections.CollectionID = CollectionMembers.CollectionID
AND Collections.CollectionType = 2 -- Device Collections
CROSS APPLY ufn_CM_GetNextMaintenanceWindow(ServiceWindow.Schedules, ServiceWindow.RecurrenceType) AS NextServiceWindow
WHERE NextServiceWindow.NextServiceWindow IS NOT NULL
AND ServiceWindowType <> 5 -- OSD Service
)
/* Populate MaintenanceInfo table and remove duplicates */
INSERT INTO #MaintenanceInfo(ResourceID, NextServiceWindow)
SELECT
ResourceID
, NextServiceWindow
FROM Maintenance_CTE
WHERE RowNumber = 1
END
/* Get update data */
;
WITH UpdateInfo_CTE
AS (
SELECT
ResourceID = Systems.ResourceID
, Missing = COUNT(*)
FROM fn_rbac_R_System(#UserSIDs) AS Systems
JOIN fn_rbac_UpdateComplianceStatus(#UserSIDs) AS ComplianceStatus ON ComplianceStatus.ResourceID = Systems.ResourceID
AND ComplianceStatus.Status = 2 -- Filter on 'Required' (0 = Unknown, 1 = NotRequired, 2 = Required, 3 = Installed)
JOIN fn_rbac_ClientCollectionMembers(#UserSIDs) AS CollectionMembers ON CollectionMembers.ResourceID = ComplianceStatus.ResourceID
JOIN fn_rbac_UpdateInfo(#LCID, #UserSIDs) AS UpdateCIs ON UpdateCIs.CI_ID = ComplianceStatus.CI_ID
AND UpdateCIs.IsSuperseded IN (#Superseded)
AND UpdateCIs.CIType_ID IN (1, 8) -- Filter on 1 Software Updates, 8 Software Update Bundle (v_CITypes)
AND UpdateCIs.ArticleID NOT IN ( -- Filter on ArticleID csv list
SELECT VALUE FROM STRING_SPLIT(#ExcludeArticleIDs, ',')
)
AND UpdateCIs.Title NOT LIKE ( -- Filter Preview updates
'[1-9][0-9][0-9][0-9]-[0-9][0-9]_Preview_of_%'
)
JOIN fn_rbac_CICategoryInfo_All(#LCID, #UserSIDs) AS CICategory ON CICategory.CI_ID = ComplianceStatus.CI_ID
AND CICategory.CategoryTypeName = 'UpdateClassification'
AND CICategory.CategoryInstanceName IN (#Categories) -- Filter on Selected Update Classification Categories
LEFT JOIN fn_rbac_CITargetedMachines(#UserSIDs) AS Targeted ON Targeted.ResourceID = ComplianceStatus.ResourceID
AND Targeted.CI_ID = ComplianceStatus.CI_ID
WHERE CollectionMembers.CollectionID = #CollectionID
AND IIF(Targeted.ResourceID IS NULL, 0, 1) IN (#Targeted) -- Filter on 'Targeted' or 'NotTargeted'
AND IIF(UpdateCIs.ArticleID = #ArticleID, 1, 0) = IIF(#ArticleID <> '', 1, 0)
GROUP BY
Systems.ResourceID
)
/* Get device info */
SELECT
Systems.ResourceID
/* Set Health states. You can find the coresponding values in the HealthState table above */
, HealthStates = (
IIF(CombinedResources.IsClient != 1, POWER(1, 1), 0)
+
IIF(
ClientSummary.ClientStateDescription = 'Inactive/Pass'
OR
ClientSummary.ClientStateDescription = 'Inactive/Fail'
OR
ClientSummary.ClientStateDescription = 'Inactive/Unknown'
, POWER(2, 1), 0)
+
IIF(
ClientSummary.ClientStateDescription = 'Active/Fail'
OR
ClientSummary.ClientStateDescription = 'Inactive/Fail'
, POWER(4, 1), 0
)
+
IIF(CombinedResources.ClientState != 0, POWER(8, 1), 0)
+
IIF(UpdateScan.LastErrorCode != 0, POWER(16, 1), 0)
+
IIF(UpdateScan.LastScanTime < (SELECT DATEADD(dd, -14, CURRENT_TIMESTAMP)), POWER(32, 1), 0)
+
IIF(ISNULL(NextServiceWindow, 0) = 0 AND #HelperFunctionExists = 1, POWER(64, 1), 0)
+
IIF(NextServiceWindow > (SELECT DATEADD(dd, 30, CURRENT_TIMESTAMP)), POWER(128, 1), 0)
+
IIF(NextServiceWindow < (CURRENT_TIMESTAMP), POWER(256, 1), 0)
)
, Missing = ISNULL(Missing, (IIF(CombinedResources.IsClient = 1, 0, NULL)))
, Device = (
IIF(
SystemNames.Resource_Names0 IS NOT NULL, UPPER(SystemNames.Resource_Names0)
, IIF(Systems.Full_Domain_Name0 IS NOT NULL, Systems.Name0 + '.' + Systems.Full_Domain_Name0, Systems.Name0)
)
)
, OperatingSystem = (
CASE
WHEN OperatingSystem.Caption0 != '' THEN
CONCAT(
REPLACE(OperatingSystem.Caption0, 'Microsoft ', ''), -- Remove 'Microsoft ' from OperatingSystem
REPLACE(OperatingSystem.CSDVersion0, 'Service Pack ', ' SP') -- Replace 'Service Pack ' with ' SP' in OperatingSystem
)
ELSE (
/* Workaround for systems not in GS_OPERATING_SYSTEM table */
CASE
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.1%' THEN 'Windows 7'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.2%' THEN 'Windows 8'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 6.3%' THEN 'Windows 8.1'
WHEN CombinedResources.DeviceOS LIKE '%Workstation 10.0%' THEN 'Windows 10'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.0' THEN 'Windows Server 2008'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.1' THEN 'Windows Server 2008R2'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.2' THEN 'Windows Server 2012'
WHEN CombinedResources.DeviceOS LIKE '%Server 6.3' THEN 'Windows Server 2012 R2'
WHEN Systems.Operating_System_Name_And0 LIKE '%Server 10%' THEN (
CASE
WHEN CAST(REPLACE(Build01, '.', '') AS INTEGER) > 10017763 THEN 'Windows Server 2019'
ELSE 'Windows Server 2016'
END
)
ELSE Systems.Operating_System_Name_And0
END
)
END
)
, LastBootTime = (
CONVERT(NVARCHAR(16), OperatingSystem.LastBootUpTime0, 120)
)
, PendingRestart = (
CASE
WHEN CombinedResources.IsClient = 0
OR CombinedResources.ClientState = 0
THEN NULL
ELSE (
STUFF(
REPLACE(
(
SELECT '#!' + LTRIM(RTRIM(StateName)) AS [data()]
FROM #ClientState
WHERE BitMask & CombinedResources.ClientState <> 0
FOR XML PATH('')
),
' #!',', '
),
1, 2, ''
)
)
END
)
, ClientState = (
CASE CombinedResources.IsClient
WHEN 1 THEN ClientSummary.ClientStateDescription
ELSE 'Unmanaged'
END
)
, ClientVersion = CombinedResources.ClientVersion
, LastUpdateScan = (
CONVERT(NVARCHAR(16), UpdateScan.LastScanTime, 120)
)
, LastScanLocation = NULLIF(UpdateScan.LastScanPackageLocation, '')
, LastScanError = NULLIF(UpdateScan.LastErrorCode, 0)
, NextServiceWindow = IIF(CombinedResources.IsClient != 1, NULL, CONVERT(NVARCHAR(16), NextServiceWindow, 120))
FROM fn_rbac_R_System(#UserSIDs) AS Systems
JOIN fn_rbac_CombinedDeviceResources(#UserSIDs) AS CombinedResources ON CombinedResources.MachineID = Systems.ResourceID
LEFT JOIN fn_rbac_RA_System_ResourceNames(#UserSIDs) AS SystemNames ON SystemNames.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_GS_OPERATING_SYSTEM(#UserSIDs) AS OperatingSystem ON OperatingSystem.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_CH_ClientSummary(#UserSIDs) AS ClientSummary ON ClientSummary.ResourceID = Systems.ResourceID
LEFT JOIN fn_rbac_UpdateScanStatus(#UserSIDs) AS UpdateScan ON UpdateScan.ResourceID = Systems.ResourceID
LEFT JOIN #MaintenanceInfo AS Maintenance ON Maintenance.ResourceID = Systems.ResourceID
LEFT JOIN UpdateInfo_CTE AS UpdateInfo ON UpdateInfo.ResourceID = Systems.ResourceID
JOIN fn_rbac_FullCollectionMembership(#UserSIDs) AS CollectionMembers ON CollectionMembers.ResourceID = Systems.ResourceID
WHERE CollectionMembers.CollectionID = #CollectionID
AND (
CASE -- Compliant (0 = No, 1 = Yes, 2 = Unknown)
WHEN Missing = 0 OR (Missing IS NULL AND Systems.Client0 = 1) THEN 1 -- Yes
WHEN Missing > 0 AND Missing IS NOT NULL THEN 0 -- No
ELSE 2 -- Unknown
END
) IN (#Compliant)
/* Perform cleanup */
IF OBJECT_ID('tempdb..#MaintenanceInfo', 'U') IS NOT NULL
DROP TABLE #MaintenanceInfo;
/* #endregion */
/*##=============================================*/
/*## END QUERY BODY */
/*##=============================================*/
`
Is there an easy way to achieve this?
I have tried to look at the official Microsoft documentation but are still not able to convert the query to a view. https://learn.microsoft.com/en-us/sql/t-sql/statements/create-view-transact-sql?view=sql-server-ver16
As I am new to SQL language I am not sure where to start.
So I agree with Larnu, that this probably doesn't make a ton of sense. But there are cases where one might want to be able to run multiple batches of queries / procedural code from an object that's as consumable as a view. I've done this once in a case where I needed to maximize my options for performance tuning without losing the consumability of the object. So for the sake of when it does make sense, this is something you could do:
Wrap your code in a stored procedure.
Use OPENQUERY() to call your procedure.
Wrap the OPENQUERY() call in a view.
Limitations with this methodology is it's rather static:
You can't pass parameters to your stored procedure
If you use temp tables in your stored procedure, then you need to use the WITH RESULT SETS clause to explicitly define the shape of your result set
The procedure can only return one result set
The SQL Server Engine puts a hard-coded cardinality estimate of 10,000 against OPENQUERY(). So in cases where your procedure returns a lot more rows (typically an order of magnitude or more) than 10,000, e.g. 1 million rows, then you may experience some performance issues with joining the wrapper view to other objects.
Example:
-- Step 1: Wrap the procedural code in a stored procedure
CREATE PROCEDURE dbo.RunSomeCode
AS
BEGIN
CREATE TABLE #Results (ID INT, SomeValue VARCHAR(100));
DECLARE #SomeVariable INT = 0;
WHILE (#SomeVariable < 5)
BEGIN
SET #SomeVariable = #SomeVariable + 1;
INSERT INTO #Results (ID, SomeValue)
SELECT ID, SomeValue
FROM SomeTable
WHERE ID = #SomeVariable
END
SELECT ID, SomeValue
FROM #Results;
END
GO
-- Step 2 & 3: Wrap an OPENQUERY() call to the procedure in a view.
CREATE VIEW dbo.SomeView
AS
SELECT ID, SomeValue
FROM OPENQUERY
(
'
LocalServerName,
EXEC dbo.RunSomeCode
WITH RESULT SETS
((
ID INT,
SomeValue VARCHAR(100)
))
'
);
Voila, you can now execute the procedure by SELECTing from the view:
SELECT ID, SomeValue
FROM dbo.SomeView;

how to back 1 level of path directory in oracle XMLTYPE XMLTABLE

Im trying to back 1 level of a folder in the path but it is not working property.
I tried to use "../" and "./" but didn't work.
How can i Do this in Oracle?
WITH xmlPrePos as
(
select
IDPraga,
IDUsina,
replace(valor, '<?xml version="1.0" encoding="utf-16"?>','') as XML
from dbo_Config
where SiglaCategoria = 'ConfigFiltros' and Sigla='ConfigFiltros'
)
SELECT
IDPraga,
IDUsina,
IDTpVinculo,
Descricao,
Reforma
FROM xmlPrePos,
xmltable('/ConfiguracaoFiltros/TpVinculoConfigFiltros/TpVinculoConfigFiltros'
passing XMLTYPE(xmlPrePos.XML)
COLUMNS
IDTpVinculo INT path 'IDTpVinculo',
Descricao VARCHAR(100) path 'Descricao',
Reforma VARCHAR(100) path '../../SituacaoAreasConfigFiltros/Reforma');
the last line is the code i'm having the issue.
my return is null.
XML>
<ConfiguracaoFiltros xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SituacaoAreasConfigFiltros>
<Reforma>VERDADEIRO</Reforma>
<Bloqueio>NAO_IMPORTA</Bloqueio>
</SituacaoAreasConfigFiltros>
<TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>11</IDTpVinculo>
<Descricao>ARRENDAMENTO / PARCERIA</Descricao>
</TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>12</IDTpVinculo>
<Descricao>PROPRIA</Descricao>
</TpVinculoConfigFiltros>
</TpVinculoConfigFiltros>
</ConfiguracaoFiltros>
Version>
Oracle Database 11g Enterprise Edition Release 11.1.0.6.0 - 64bit Production
PL/SQL Release 11.1.0.6.0 - Production
"CORE 11.1.0.6.0 Production"
TNS for Linux: Version 11.1.0.6.0 - Production
NLSRTL Version 11.1.0.6.0 - Production
Waiting Result>
As walking back up the tree doesn't seem to work in 11g, you can instead use two XMLTable calls:
WITH xmlPrePos as
(
select
IDPraga,
IDUsina,
replace(valor, '<?xml version="1.0" encoding="utf-16"?>','') as XML
from dbo_Config
where SiglaCategoria = 'ConfigFiltros' and Sigla='ConfigFiltros'
)
SELECT
IDPraga,
IDUsina,
x2.IDTpVinculo,
x2.Descricao,
x1.Reforma
FROM xmlPrePos
CROSS JOIN xmltable(
'/ConfiguracaoFiltros'
passing XMLTYPE(xmlPrePos.XML)
COLUMNS
Reforma VARCHAR(100) path 'SituacaoAreasConfigFiltros/Reforma',
TpVinculoConfigFiltros xmltype path 'TpVinculoConfigFiltros'
) x1
CROSS JOIN xmltable(
'/TpVinculoConfigFiltros/TpVinculoConfigFiltros'
passing x1.TpVinculoConfigFiltros
COLUMNS
IDTpVinculo INT path 'IDTpVinculo',
Descricao VARCHAR(100) path 'Descricao'
) x2;
which with your example gets:
IDPRAGA IDUSINA IDTPVINCULO DESCRICAO REFORMA
------- ------- ----------- ----------------------- ----------
3 1 11 ARRENDAMENTO / PARCERIA VERDADEIRO
3 1 12 PROPRIA VERDADEIRO
db<>fiddle
As #Sayan said, and I sort of commented, you shouldn't need to remove the XML header; it works with it left in, and doesn't need the CTE:
SELECT
dc.IDPraga,
dc.IDUsina,
x2.IDTpVinculo,
x2.Descricao,
x1.Reforma
FROM dbo_Config dc
CROSS JOIN xmltable(
'/ConfiguracaoFiltros'
passing XMLTYPE(dc.valor)
COLUMNS
Reforma VARCHAR(100) path 'SituacaoAreasConfigFiltros/Reforma',
TpVinculoConfigFiltros xmltype path 'TpVinculoConfigFiltros'
) x1
CROSS JOIN xmltable(
'/TpVinculoConfigFiltros/TpVinculoConfigFiltros'
passing x1.TpVinculoConfigFiltros
COLUMNS
IDTpVinculo INT path 'IDTpVinculo',
Descricao VARCHAR(100) path 'Descricao'
) x2;
db<>fiddle
If you data looks like this:
<?xml version="1.0" encoding="utf-16"?>
<ConfiguracaoFiltros>
<TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>1</IDTpVinculo>
<Descricao>Test1</Descricao>
</TpVinculoConfigFiltros>
</TpVinculoConfigFiltros>
<SituacaoAreasConfigFiltros>
<Reforma>Reforma Test1</Reforma>
</SituacaoAreasConfigFiltros>
</ConfiguracaoFiltros>
You can just simple add ./ before ../../:
DBFiddle
WITH
dbo_Config as (
select
'ConfigFiltros' SiglaCategoria,
'ConfigFiltros' Sigla,
'IDPraga' IDPraga,
'IDUsina' IDUsina,
q'[<?xml version="1.0" encoding="utf-16"?>
<ConfiguracaoFiltros>
<TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>1</IDTpVinculo>
<Descricao>Test1</Descricao>
</TpVinculoConfigFiltros>
</TpVinculoConfigFiltros>
<SituacaoAreasConfigFiltros>
<Reforma>Reforma Test1</Reforma>
</SituacaoAreasConfigFiltros>
</ConfiguracaoFiltros>
]' as valor
from dual
)
,xmlPrePos as
(
select
IDPraga,
IDUsina,
replace(valor, '<?xml version="1.0" encoding="utf-16"?>','') as XML
from dbo_Config
where SiglaCategoria = 'ConfigFiltros' and Sigla='ConfigFiltros'
)
SELECT
IDPraga,
IDUsina,
IDTpVinculo,
Descricao,
Reforma
FROM xmlPrePos,
xmltable('/ConfiguracaoFiltros/TpVinculoConfigFiltros/TpVinculoConfigFiltros'
passing XMLTYPE(xmlPrePos.XML)
COLUMNS
IDTpVinculo INT path 'IDTpVinculo',
Descricao VARCHAR(100) path 'Descricao',
Reforma VARCHAR(100) path './../../SituacaoAreasConfigFiltros/Reforma');
But I wouldn't remove <?xml ...?> header:
DBFiddle2
WITH
dbo_Config as (
select
'ConfigFiltros' SiglaCategoria,
'ConfigFiltros' Sigla,
'IDPraga' IDPraga,
'IDUsina' IDUsina,
q'[<?xml version="1.0" encoding="utf-16"?>
<ConfiguracaoFiltros>
<TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>1</IDTpVinculo>
<Descricao>Test1</Descricao>
</TpVinculoConfigFiltros>
</TpVinculoConfigFiltros>
<SituacaoAreasConfigFiltros>
<Reforma>Reforma Test1</Reforma>
</SituacaoAreasConfigFiltros>
</ConfiguracaoFiltros>
]' as valor
from dual
)
,xmlPrePos as
(
select
IDPraga,
IDUsina,
valor as XML
from dbo_Config
where SiglaCategoria = 'ConfigFiltros' and Sigla='ConfigFiltros'
)
SELECT
IDPraga,
IDUsina,
IDTpVinculo,
Descricao,
Reforma
FROM xmlPrePos,
xmltable('/ConfiguracaoFiltros/TpVinculoConfigFiltros/TpVinculoConfigFiltros'
passing XMLTYPE(xmlPrePos.XML)
COLUMNS
IDTpVinculo INT path 'IDTpVinculo',
Descricao VARCHAR(100) path 'Descricao',
Reforma VARCHAR(100) path './../../SituacaoAreasConfigFiltros/Reforma');
The same example but with your updated data on DBFiffle:
https://dbfiddle.uk/?rdbms=oracle_11.2&fiddle=23c0d9d500d0b87f1bc3469efd4960b9
Workaround for Oracle <11.2:
WITH
dbo_Config as (
select
'ConfigFiltros' SiglaCategoria,
'ConfigFiltros' Sigla,
'IDPraga' IDPraga,
'IDUsina' IDUsina,
q'[<ConfiguracaoFiltros xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<SituacaoAreasConfigFiltros>
<Reforma>VERDADEIRO</Reforma>
<Bloqueio>NAO_IMPORTA</Bloqueio>
</SituacaoAreasConfigFiltros>
<TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>11</IDTpVinculo>
<Descricao>ARRENDAMENTO / PARCERIA</Descricao>
</TpVinculoConfigFiltros>
<TpVinculoConfigFiltros>
<IDTpVinculo>12</IDTpVinculo>
<Descricao>PROPRIA</Descricao>
</TpVinculoConfigFiltros>
</TpVinculoConfigFiltros>
</ConfiguracaoFiltros>
]' as valor
from dual
)
,xmlPrePos as
(
select
IDPraga,
IDUsina,
valor as XML
from dbo_Config
where SiglaCategoria = 'ConfigFiltros' and Sigla='ConfigFiltros'
)
SELECT
IDPraga,
IDUsina,
xdata.*
FROM xmlPrePos,
xmltable(
'
for $x in ./ConfiguracaoFiltros
for $y in $x/TpVinculoConfigFiltros/TpVinculoConfigFiltros
return <row>
{$y}
{$x/SituacaoAreasConfigFiltros/Reforma}
</row>'
passing XMLTYPE(xmlPrePos.XML)
COLUMNS
xdata xmltype path '.',
IDTpVinculo INT path './TpVinculoConfigFiltros/IDTpVinculo',
Descricao VARCHAR(100) path './TpVinculoConfigFiltros/Descricao',
Reforma VARCHAR(100) path 'Reforma'
) xdata;

Generating efficient LEFT JOIN with COUNT in Linq to Entities

I have this schema:
Lists ( ListId, Name, DateCreated, ... )
ListItems( ListId, Text, Foo, Baz, Qux, ... )
I have an IQueryable<List> which represents another Linq query which returns some List entities.
I want to JOIN it with some aggregate data of ListItems, but this proving difficult as Linq is generating inefficient SQL - but I also want to make the query composable.
Here is something similar to the SQL I want Linq to generate:
SELECT
*
FROM
(
-- This part represents the IQueryable:
SELECT
ListId,
Name,
...
FROM
Lists
ORDER BY
Lists.DateCreated
OFFSET
0 ROWS FETCH NEXT 25 ROWS ONLY -- Linq's .Skip(0).Take(25)
) AS ListsResult
LEFT JOIN
(
-- This is the aggregate data query I want Linq to generate:
SELECT
ListId,
COUNT(1) AS [Count],
COUNT( CASE WHEN Foo = 'bar' THEN 1 ELSE NULL END ) AS CountFooBar,
COUNT( CASE WHEN Baz > 5 THEN 1 ELSE NULL END ) AS CountBaz5
FROM
ListItems
WHERE
Qux IS NOT NULL
GROUP BY
ListId
) AS ItemsStats ON ListResults.ListId = ItemsStats.ListId
This is the Linq I have - I prefer the Extension Method syntax:
IQueryable lists = GetLists( 0, 25 );
var stats = this.dbContext.ListItems
.Where( (ListItem li) => li.Qux != null )
.GroupBy( (ListItem li) => li.ListId )
.Select( grp => new
{
grp.Key,
Count = grp.Count(),
CountFooBar = grp.Count( (ListItem li) => li.Foo == "bar" )
CountBaz5 = grp.Count( (ListItem li) => li.Baz > 5 )
} )
return lists
.Join(
inner: stats,
outerKeySelector: (List l) => l.ListId,
innerKeySelector: grp => grp.Key,
resultSelector: (list, itemStats) => new { list, itemsStats }
)
However this generates SQL looking like this (this query shows my real table and column names, which is a bit more complicated than the schema I posted earlier:)
SELECT
[Project13].[C2] AS [C1],
[Project13].[ListId] AS [ListId],
[Project13].[C1] AS [C2],
[Project13].[C3] AS [C3],
[Project13].[C4] AS [C4],
[Project13].[C5] AS [C5],
[Project13].[C6] AS [C6],
[Project13].[C7] AS [C7]
FROM ( SELECT
[Project11].[C1] AS [C1],
[Project11].[ListId] AS [ListId],
[Project11].[C2] AS [C2],
[Project11].[C3] AS [C3],
[Project11].[C4] AS [C4],
[Project11].[C5] AS [C5],
[Project11].[C6] AS [C6],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project12].[ListId] AS [ListId]
FROM ( SELECT
[Extent11].[ListId] AS [ListId],
[Extent11].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent11]
WHERE ([Extent11].[TenantId] = 8) AND ([Extent11].[BlarghId] = 8)
) AS [Project12]
ORDER BY [Project12].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit6]
INNER JOIN [dbo].[ListItems] AS [Extent12] ON ([Limit6].[TenantId] = [Extent12].[TenantId]) AND ([Limit6].[BlarghId] = [Extent12].[BlarghId]) AND ([Limit6].[ListId] = [Extent12].[ListId])
WHERE (([Extent12].[Baz] > 0) OR ((LEN([Extent12].[Notes])) > 0) OR ((LEN([Extent12].[Value])) > 0)) AND ([Project11].[TenantId] = [Extent12].[TenantId]) AND ([Project11].[BlarghId] = [Extent12].[BlarghId]) AND ([Project11].[ListId] = [Extent12].[ListId]) AND ([Extent12].[RecommendationRevision] IS NOT NULL)) AS [C7]
FROM ( SELECT
[Project9].[C1] AS [C1],
[Project9].[TenantId] AS [TenantId],
[Project9].[BlarghId] AS [BlarghId],
[Project9].[ListId] AS [ListId],
[Project9].[C2] AS [C2],
[Project9].[C3] AS [C3],
[Project9].[C4] AS [C4],
[Project9].[C5] AS [C5],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project10].[TenantId] AS [TenantId], [Project10].[BlarghId] AS [BlarghId], [Project10].[ListId] AS [ListId]
FROM ( SELECT
[Extent9].[TenantId] AS [TenantId],
[Extent9].[BlarghId] AS [BlarghId],
[Extent9].[ListId] AS [ListId],
[Extent9].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent9]
WHERE ([Extent9].[TenantId] = 8) AND ([Extent9].[BlarghId] = 8)
) AS [Project10]
ORDER BY [Project10].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit5]
INNER JOIN [dbo].[ListItems] AS [Extent10] ON ([Limit5].[TenantId] = [Extent10].[TenantId]) AND ([Limit5].[BlarghId] = [Extent10].[BlarghId]) AND ([Limit5].[ListId] = [Extent10].[ListId])
WHERE (([Extent10].[Baz] > 0) OR ((LEN([Extent10].[Notes])) > 0) OR ((LEN([Extent10].[Value])) > 0)) AND ([Project9].[TenantId] = [Extent10].[TenantId]) AND ([Project9].[BlarghId] = [Extent10].[BlarghId]) AND ([Project9].[ListId] = [Extent10].[ListId]) AND (3 = [Extent10].[Baz])) AS [C6]
FROM ( SELECT
[Project7].[C1] AS [C1],
[Project7].[TenantId] AS [TenantId],
[Project7].[BlarghId] AS [BlarghId],
[Project7].[ListId] AS [ListId],
[Project7].[C2] AS [C2],
[Project7].[C3] AS [C3],
[Project7].[C4] AS [C4],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project8].[TenantId] AS [TenantId], [Project8].[BlarghId] AS [BlarghId], [Project8].[ListId] AS [ListId]
FROM ( SELECT
[Extent7].[TenantId] AS [TenantId],
[Extent7].[BlarghId] AS [BlarghId],
[Extent7].[ListId] AS [ListId],
[Extent7].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent7]
WHERE ([Extent7].[TenantId] = 8) AND ([Extent7].[BlarghId] = 8)
) AS [Project8]
ORDER BY [Project8].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit4]
INNER JOIN [dbo].[ListItems] AS [Extent8] ON ([Limit4].[TenantId] = [Extent8].[TenantId]) AND ([Limit4].[BlarghId] = [Extent8].[BlarghId]) AND ([Limit4].[ListId] = [Extent8].[ListId])
WHERE (([Extent8].[Baz] > 0) OR ((LEN([Extent8].[Notes])) > 0) OR ((LEN([Extent8].[Value])) > 0)) AND ([Project7].[TenantId] = [Extent8].[TenantId]) AND ([Project7].[BlarghId] = [Extent8].[BlarghId]) AND ([Project7].[ListId] = [Extent8].[ListId]) AND (2 = [Extent8].[Baz])) AS [C5]
FROM ( SELECT
[Project5].[C1] AS [C1],
[Project5].[TenantId] AS [TenantId],
[Project5].[BlarghId] AS [BlarghId],
[Project5].[ListId] AS [ListId],
[Project5].[C2] AS [C2],
[Project5].[C3] AS [C3],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project6].[TenantId] AS [TenantId], [Project6].[BlarghId] AS [BlarghId], [Project6].[ListId] AS [ListId]
FROM ( SELECT
[Extent5].[TenantId] AS [TenantId],
[Extent5].[BlarghId] AS [BlarghId],
[Extent5].[ListId] AS [ListId],
[Extent5].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent5]
WHERE ([Extent5].[TenantId] = 8) AND ([Extent5].[BlarghId] = 8)
) AS [Project6]
ORDER BY [Project6].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit3]
INNER JOIN [dbo].[ListItems] AS [Extent6] ON ([Limit3].[TenantId] = [Extent6].[TenantId]) AND ([Limit3].[BlarghId] = [Extent6].[BlarghId]) AND ([Limit3].[ListId] = [Extent6].[ListId])
WHERE (([Extent6].[Baz] > 0) OR ((LEN([Extent6].[Notes])) > 0) OR ((LEN([Extent6].[Value])) > 0)) AND ([Project5].[TenantId] = [Extent6].[TenantId]) AND ([Project5].[BlarghId] = [Extent6].[BlarghId]) AND ([Project5].[ListId] = [Extent6].[ListId]) AND (1 = [Extent6].[Baz])) AS [C4]
FROM ( SELECT
[Project3].[C1] AS [C1],
[Project3].[TenantId] AS [TenantId],
[Project3].[BlarghId] AS [BlarghId],
[Project3].[ListId] AS [ListId],
[Project3].[C2] AS [C2],
(SELECT
COUNT(1) AS [A1]
FROM (SELECT [Project4].[TenantId] AS [TenantId], [Project4].[BlarghId] AS [BlarghId], [Project4].[ListId] AS [ListId]
FROM ( SELECT
[Extent3].[TenantId] AS [TenantId],
[Extent3].[BlarghId] AS [BlarghId],
[Extent3].[ListId] AS [ListId],
[Extent3].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent3]
WHERE ([Extent3].[TenantId] = 8) AND ([Extent3].[BlarghId] = 8)
) AS [Project4]
ORDER BY [Project4].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit2]
INNER JOIN [dbo].[ListItems] AS [Extent4] ON ([Limit2].[TenantId] = [Extent4].[TenantId]) AND ([Limit2].[BlarghId] = [Extent4].[BlarghId]) AND ([Limit2].[ListId] = [Extent4].[ListId])
WHERE (([Extent4].[Baz] > 0) OR ((LEN([Extent4].[Notes])) > 0) OR ((LEN([Extent4].[Value])) > 0)) AND ([Project3].[TenantId] = [Extent4].[TenantId]) AND ([Project3].[BlarghId] = [Extent4].[BlarghId]) AND ([Project3].[ListId] = [Extent4].[ListId]) AND ([Extent4].[Foo] = 1)) AS [C3]
FROM ( SELECT
[GroupBy1].[A1] AS [C1],
[GroupBy1].[K1] AS [TenantId],
[GroupBy1].[K2] AS [BlarghId],
[GroupBy1].[K3] AS [ListId],
[GroupBy1].[K4] AS [C2]
FROM ( SELECT
[Project2].[K1] AS [K1],
[Project2].[K2] AS [K2],
[Project2].[K3] AS [K3],
[Project2].[K4] AS [K4],
COUNT([Project2].[A1]) AS [A1]
FROM ( SELECT
[Project2].[TenantId] AS [K1],
[Project2].[BlarghId] AS [K2],
[Project2].[ListId] AS [K3],
1 AS [K4],
1 AS [A1]
FROM ( SELECT
[Extent2].[ListId] AS [ListId]
FROM (SELECT [Project1].[ListId] AS [ListId]
FROM ( SELECT
[Extent1].[ListId] AS [ListId],
[Extent1].[Created] AS [Created]
FROM [dbo].[Lists] AS [Extent1]
WHERE ([Extent1].[TenantId] = 8) AND ([Extent1].[BlarghId] = 8)
) AS [Project1]
ORDER BY [Project1].[Created] DESC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit1]
INNER JOIN [dbo].[ListItems] AS [Extent2] ON (([Limit1].[ListId] = [Extent2].[ListId])
WHERE ([Extent2].[Baz] > 0) OR ((LEN([Extent2].[Notes])) > 0) OR ((LEN([Extent2].[Value])) > 0)
) AS [Project2]
) AS [Project2]
GROUP BY [K1], [K2], [K3], [K4]
) AS [GroupBy1]
) AS [Project3]
) AS [Project5]
) AS [Project7]
) AS [Project9]
) AS [Project11]
) AS [Project13]
It doesn't compose the COUNT() statements together at all, and it moves the COUNT predicates to separate WHERE clauses. Also note the repeated paged subqueries (where OFFSET 0 ROW FETCH NEXT 25 is used) whereas my hand-written query only executes it once.
Here's a semi-workaround I have:
I realised the best short-term solution is to have the SQL in the database (as a UDF FUNCTION or VIEW), this would mean that some data code would therefore have to be in the database (rather than using the DB as a "dumb store").
I first created a table-valued UDF which would accept a table-valued parameter, reasoning that would allow for composition, at the cost of needing to generate the input parameter table (an array of ListId values from the paged query). However in doing so, I realised that Linq-to-Entities (version 6) does not support table-valued parameters in Function Imports yet.
I then reasoned a better approach would be to move the COUNT operation to a VIEW (which represents one half of the LEFT JOIN I had in my hand-written query), and then I can get Linq to JOIN that against the existing IQueryable, thus retaining composability and generating an efficient runtime query (indeed, when I run it, the query takes 34ms to execute according to SQL Server Profiler, whereas the old Linq-generated inefficient query took 830ms).
Here's what I used:
CREATE VIEW ListItemStatistics AS
SELECT
ListId,
COUNT(*) AS [CountAll],
COUNT( CASE WHEN ... ) AS Count...
FROM
ListItems
WHERE
Foo = 'bar'
GROUP BY
ListId
And then from within Linq:
IQueryable lists = GetListsQuery( 0, 25 );
var listsWithItemsStats = lists.
.Join(
inner: this.dbContext.ListItemStatistics,
outerKeySelector: list => list.ListId,
innerKeySelector: row => row.ListId,
resultSelector: (list,row) => new { list, row }
);
However because this does use database-side logic (in the VIEW) it is not ideal.
In my experience EF generates such queries when you use aggregate functions that apply filtering like Count(predicate) in your case. You'll get much better SQL query if you replace Count(condition) construct with conditional sum (Sum(condition ? 1 : 0)) like this:
var stats = db.ListItems
.Where(li => li.Qux != null)
.GroupBy(li => li.ListId)
.Select(grp => new
{
grp.Key,
Count = grp.Count(),
CountFooBar = grp.Sum(li => li.Foo == "bar" ? 1 : 0),
CountBaz5 = grp.Sum(li => li.Baz > 5 ? 1 : 0)
});
The other parts stay the same. Before the mod I was getting similar SQL to the posted, and here is what I'm getting after this little mod:
SELECT
[Limit1].[ListId] AS [ListId],
[Limit1].[Name] AS [Name],
[Limit1].[DateCreated] AS [DateCreated],
[GroupBy1].[K1] AS [ListId1],
[GroupBy1].[A1] AS [C1],
[GroupBy1].[A2] AS [C2],
[GroupBy1].[A3] AS [C3]
FROM (SELECT [Extent1].[ListId] AS [ListId], [Extent1].[Name] AS [Name], [Extent1].[DateCreated] AS [DateCreated]
FROM [dbo].[List] AS [Extent1]
ORDER BY [Extent1].[DateCreated] ASC
OFFSET 0 ROWS FETCH NEXT 25 ROWS ONLY ) AS [Limit1]
INNER JOIN (SELECT
[Filter1].[K1] AS [K1],
COUNT([Filter1].[A1]) AS [A1],
SUM([Filter1].[A2]) AS [A2],
SUM([Filter1].[A3]) AS [A3]
FROM ( SELECT
[Extent2].[ListId] AS [K1],
1 AS [A1],
CASE WHEN (N'bar' = [Extent2].[Foo]) THEN 1 ELSE 0 END AS [A2],
CASE WHEN ([Extent2].[Baz] > 5) THEN 1 ELSE 0 END AS [A3]
FROM [dbo].[ListItem] AS [Extent2]
WHERE [Extent2].[Qux] IS NOT NULL
) AS [Filter1]
GROUP BY [K1] ) AS [GroupBy1] ON [Limit1].[ListId] = [GroupBy1].[K1]
UPDATE: The above is just the LINQ equivalent of your manual SQL query (when you adjust the join code to produce LEFT OUTER JOIN). However, taking into account the master data paging, OUTER APPLY SQL query might perform better. You can let LINQ to Entities generate such type of query like this:
var listsWithItemsStats = lists
.SelectMany(l => db.ListItems
.Where(li => li.ListId == l.ListId && li.Qux != null)
.GroupBy(li => li.ListId)
.Select(grp => new
{
grp.Key,
Count = grp.Count(),
CountFooBar = grp.Sum(li => li.Foo == "bar" ? 1 : 0),
CountBaz5 = grp.Sum(li => li.Baz > 5 ? 1 : 0)
})
.DefaultIfEmpty(),
(list, itemsStats) => new { list, itemsStats });

Faster Query Execution

I have 42 columns and 4 joins in my table. Execution needs 140 seconds, I want to reduce the time of execution. What to do?
SELECT DISTINCT GR_ID,
CONVERT(VARCHAR, GR_DT, 106) AS GRDate,
GR_GN,
GR_GID,
GR_BS,
GR_BN,
GR_DC,
GR_COP,
GR_PA,
GR_AR,
GR_BG,
GR_SM,
GR_KG,
GR_IN,
GR_TS,
CMLC_BC,
CMLC_SC,
CMLC_KG,
CMLC_BL,
CMBC_BC,
CMBC_SC,
CMBC_KG,
CMBC_BL,
GR_BRN,
GR_BLC,
GR_LEA,
GR_ALF,
GR_BTA,
GR_PID,
GR_UTS,
GR_LR,
CMGB_BC,
CMGB_SC,
CMGB_KG,
CMGB_BL,
CMGB_LR,
GR_UPC,
GR_UPT,
GR_UTA,
GR_TTA,
GR_SPA,
GR_SAR,
GR_NR,
ISNULL(ACK_ACK, 'NO') AS ACK_ACK
FROM GR
LEFT JOIN CMBC
ON ( CMBC_ID = GR_GID )
AND ( CMBC_IID = GR_PID )
RIGHT JOIN CMLC
ON ( CMLC_ID = GR_GID )
AND ( CMLC_IID = GR_PID )
RIGHT JOIN CMGB
ON ( CMGB_ID = GR_GID )
AND ( CMGB_IID = GR_PID )
AND ( CMGB_TS = GR_UTS )
LEFT JOIN ACK
ON ( ACK_GN = GR_GN )
AND ( ACK_GID = GR_GID )
ORDER BY GR_GN,
GR_GID

How to parse XML attribute using MS SQL

I need to get the values of attributes present in XML document using MS SQL query
Ex : I have a XML which looks below
<trade xmlns="www.somewebsite.com" Action = "Insert" TradeNumber = "1053" Volume = "25" DateTime = "2013-12-06T10:22:47.497" PNC = "false">
<Specifier Specifierid = "112" Span = "Single" Name = "Indian"/>
</trade>
I need to fetch
The values of "TradeNumber", "Volume", "DateTime" in trade tag
"Name" from Specifier tag
in a single row under their specific columns
Like
TradeNumber Volume DateTime Name
1053 25 2013-12-06T10:22:47.497 Indian
I tried using many ways but couldn't figure it out.
Please help
declare #data xml ='
<trade xmlns="www.somewebsite.com" Action = "Insert" TradeNumber = "1053" Volume = "25" DateTime = "2013-12-06T10:22:47.497" PNC = "false">
<Specifier Specifierid = "112" Span = "Single" Name = "Indian"/>
</trade>'
;with xmlnamespaces(default 'www.somewebsite.com')
select
#data.value('trade[1]/#TradeNumber', 'int') as TradeNumber,
#data.value('trade[1]/#Volume', 'int') as Volume,
#data.value('trade[1]/#DateTime', 'datetime') as [DateTime],
#data.value('(trade/Specifier)[1]/#Name', 'nvarchar(max)') as Name
--------------------------------------------------------
TradeNumber Volume DateTime Name
1053 25 2013-12-06 10:22:47.497 Indian
Or, if there're could be more than one trades:
;with xmlnamespaces(default 'www.somewebsite.com')
select
t.c.value('#TradeNumber', 'int') as TradeNumber,
t.c.value('#Volume', 'int') as Volume,
t.c.value('#DateTime', 'datetime') as [DateTime],
t.c.value('Specifier[1]/#Name', 'nvarchar(max)') as Name
from #data.nodes('trade') as t(c)
Another variant:
declare #doc xml
select #doc= '
<trade xmlns="www.somewebsite.com" Action = "Insert" TradeNumber = "1053" Volume = "25" DateTime = "2013-12-06T10:22:47.497" PNC = "false">
<Specifier Specifierid = "112" Span = "Single" Name = "Indian"/>
</trade>
'
;WITH XMLNAMESPACES('www.somewebsite.com' AS p)
SELECT
ActionAttribute = Y.i.value('(#Action)[1]', 'varchar(40)')
, TradeNumber = Y.i.value('#TradeNumber[1]', 'varchar(40)')
, Specifierid = Y.i.value('(./p:Specifier)[1]/#Specifierid', 'nvarchar(max)')
FROM
#doc.nodes('/p:trade') AS Y(i)