Using T-SQL XML input element values for an IN Clause - sql-server

I'm trying to write a procedure to sync users from Active directory into my local application database. From my code, I'm passing XML in the following format to the stored procedure:
<AdUsers>
<AdUser AccountSid="S-1-5-21-111111111-111111111-111111111-1111" DisplayName="Test User" EmailAddress="tuser#mail.local" ExchangeServerFk="4" ExchangeServer="https://mail.local" Department="" StatusFK="1" UserName="TUSER">
<AccountSids>
<Sid>S-1-5-21-111111111-111111111-111111111-1111</Sid>
</AccountSids>
</AdUser>
</AdUsers>
I'd like to do a sync between the XML and the rows in my tb_Mailboxes table with the following Stored Procedure:
#adUsers XML, #lastSyncBy VARCHAR (50), #lastSyncOn DATETIME, #defaultProfileId INT, #adDomainId INT
AS
begin try
BEGIN TRANSACTION
--First delete all the mailboxes exist in the database but not in the xml.
delete tb_Mailboxes
where AccountSid not in (
select
rtrim(element.value('text()[1]', 'varchar(100)')) as AccountSid
from
#adUsers.nodes('/AdUsers/AdUser/AccountSids/Sid') t(element)
) AND #adDomainId = AdDomainFk
--Then insert or update existing accounts
MERGE tb_Mailboxes as [target]
USING
(
select
rtrim(element.value('data(#AccountSid)', 'varchar(100)')) as AccountSid
,rtrim(element.value('data(#DisplayName)', 'varchar(100)')) as DisplayName
,rtrim(element.value('data(#EmailAddress)', 'varchar(500)')) as EmailAddress
,rtrim(element.value('data(#ExchangeServerFk)', 'varchar(100)')) as ExchangeServerFk
,rtrim(element.value('data(#ExchangeServer)', 'varchar(150)')) as ExchangeServer
,rtrim(element.value('data(#Department)', 'varchar(100)')) as Department
,rtrim(element.value('data(#StatusFK)', 'varchar(100)')) as StatusFK
,rtrim(element.value('data(#UserName)', 'varchar(100)')) as UserName
,element.query('AccountSids') as SidList
from
#adUsers.nodes('/AdUsers/AdUser') t(element)
) as [source]
on [target].AccountSid IN
(
SELECT rtrim(A.value('text()[1]', 'varchar(100)')) as CurSid
FROM [source].SidList.nodes('Sid') AS FN(A)
)
WHEN MATCHED THEN UPDATE SET
DisplayName = [source].DisplayName
,EmailAddress = [source].EmailAddress
,ExchangeServerFk = [source].ExchangeServerFk
,ExchangeServer = [source].ExchangeServer
,Department = [source].Department
,UserName = [source].UserName
/*,StatusFK = [source].StatusFK*/
,LastSyncOn = #lastSyncOn
,LastSyncBy = #lastSyncBy
WHEN NOT MATCHED THEN INSERT
(
AdDomainFk,
UserName,
DisplayName,
Department,
EmailAddress,
ExchangeServerFk,
ExchangeServer,
AccountSid,
IsAutoDeleteEnabled,
ProfileFk,
Settings,
QueueLastPickedUp,
QueueLastProcessed,
QueueLastFinished,
LastSyncOn,
LastSyncBy,
StatusFK
)
VALUES
(
#adDomainId
,[source].UserName
,[source].DisplayName
,[source].Department
,[source].EmailAddress
,[source].ExchangeServerFk
,[source].ExchangeServer
,[source].AccountSid
,0
,#defaultProfileId
,NULL
,NULL
,NULL
,NULL
,#lastSyncOn
,#lastSyncBy
,[source].StatusFK
);
COMMIT TRANSACTION
END TRY
BEGIN CATCH
ROLLBACK TRANSACTION
END CATCH
However, the "NOT IN" in the delete section AND "IN" in the match section don't seem to work. Is this type of IN clause using multiple values in the XML even feasible? Is there a better approach to this problem that I'm missing?

The issue with your MERGE query is the join between the [source] and the [target] tables. Rather than joining the target and source tables using
ON [target].AccountSid IN
(
SELECT rtrim(A.value('text()[1]', 'varchar(100)')) as CurSid
FROM [source].SidList.nodes('Sid') AS FN(A)
)
use this instead:
ON [target].AccountSid = [source].AccountSid
[source] will be materialised as a table and you join to it like you would any other table. Your IN statement doesn't make much sense as it is a completely different entity so will equivalent to a kind of Cartesian join (FULL OUTER).
Another comment I would make is why the separate DELETE statement to remove mailboxes that no longer exist in the XML? Why not simply put the DELETE within the MERGE statement by using the following statement?
WHEN NOT MATCHED BY SOURCE
THEN DELETE
Applying all this, your MERGE statement becomes:
MERGE tb_Mailboxes AS [target]
USING
(SELECT RTRIM(element.value('data(#AccountSid)', 'varchar(100)')) AS AccountSid
, RTRIM(element.value('data(#DisplayName)', 'varchar(100)')) AS DisplayName
, RTRIM(element.value('data(#EmailAddress)', 'varchar(500)')) AS EmailAddress
, RTRIM(element.value('data(#ExchangeServerFk)', 'varchar(100)')) AS ExchangeServerFk
, RTRIM(element.value('data(#ExchangeServer)', 'varchar(150)')) AS ExchangeServer
, RTRIM(element.value('data(#Department)', 'varchar(100)')) AS Department
, RTRIM(element.value('data(#StatusFK)', 'varchar(100)')) AS StatusFK
, RTRIM(element.value('data(#UserName)', 'varchar(100)')) AS UserName
FROM #adUsers.nodes('/AdUsers/AdUser') t (element)) AS [source]
ON [target].AccountSid = [source].AccountSid
WHEN MATCHED
THEN UPDATE
SET DisplayName = [source].DisplayName
, EmailAddress = [source].EmailAddress
, ExchangeServerFk = [source].ExchangeServerFk
, ExchangeServer = [source].ExchangeServer
, Department = [source].Department
, UserName = [source].UserName
/*,StatusFK = [source].StatusFK*/
, LastSyncOn = #lastSyncOn
, LastSyncBy = #lastSyncBy
WHEN NOT MATCHED BY TARGET
THEN INSERT (AdDomainFk
, UserName
, DisplayName
, Department
, EmailAddress
, ExchangeServerFk
, ExchangeServer
, AccountSid
, IsAutoDeleteEnabled
, ProfileFk
, Settings
, QueueLastPickedUp
, QueueLastProcessed
, QueueLastFinished
, LastSyncOn
, LastSyncBy
, StatusFK)
VALUES (#adDomainId
, [source].UserName
, [source].DisplayName
, [source].Department
, [source].EmailAddress
, [source].ExchangeServerFk
, [source].ExchangeServer
, [source].AccountSid
, 0
, #defaultProfileId
, NULL
, NULL
, NULL
, NULL
, #lastSyncOn
, #lastSyncBy
, [source].StatusFK)
WHEN NOT MATCHED BY SOURCE
THEN DELETE;

Related

Error creating stored procedure in SQL Server with PIVOT operator

I am getting this error when I attempt to create a stored procedure:
SQL Server Database Error: Incorrect syntax near the keyword 'PROC'.
When I execute the SQL between the lines, everything works. However, when I attempt to execute the stored procedure, I get the error.
I'm new to stored procedures, so I am hoping that it is just a misplaced ; or GO.
SET ANSI_NULLS ON;
GO
SET QUOTED_IDENTIFIER ON;
GO
IF EXISTS (SELECT object_id FROM AppGovernmentPrograms.Sys.Objects
WHERE type = 'P'
AND name = 'usp_AppGovernmentPrograms_rptObjectives')
BEGIN
DROP PROCEDURE dbo.usp_AppGovernmentPrograms_rptObjectives
END
GO
CREATE PROC [dbo].[usp_AppGovernmentPrograms_rptObjectives]
#ProgramYear INT,
#SummaryLevel VARCHAR(50)
AS
SET NOCOUNT ON;
WITH measuresPHS AS
(
SELECT
mi.definition_id, mi.metric_name
FROM
StgEpicClarityPHS.dbo.metric_info mi WITH (nolock)
WHERE
mi.definition_id IN (90020350, 90020300, 90020301, 90020302,
90020303, 90020304, 90020305, 90020306,
90020307, 90020308, 90020309, 90020310,
90020311, 90020351, 90020352, 90020353,
90020354, 90020355, 90020400)
)
SELECT
provCID, NPI, TIN, year_dt,
SUM([n90020350]) as "1.1n",
SUM([d90020350]) as "1.1d",
SUM([n90020300]) as "2.1n",
SUM([d90020300]) as "2.1d",
SUM([n90020301]) as "4.1n",
SUM([d90020301]) as "4.1d",
SUM([n90020302]) as "4.2n",
SUM([d90020302]) as "4.2d",
SUM([n90020303]) as "4.3n",
SUM([d90020303]) as "4.3d",
SUM([n90020304]) as "5.1n",
SUM([d90020304]) as "5.1d",
SUM([n90020305]) as "5.2n",
SUM([d90020305]) as "5.2d",
SUM([n90020306]) as "6.1n",
sum([d90020306]) as "6.1d"
, sum([n90020307]) as "6.2n"
, sum([d90020307]) as "6.2d"
, sum([n90020308]) as "6.3n"
, sum([d90020308]) as "6.3d"
, sum([n90020309]) as "7.1n"
, sum([d90020309]) as "7.1d"
, sum([n90020310]) as "7.2n"
, sum([d90020310]) as "7.2d"
, sum([n90020311]) as "7.3n"
, sum([d90020311]) as "7.3d"
, sum([n90020351]) as "8.1n"
, sum([d90020351]) as "8.1d"
, sum([n90020352]) as "8.2n"
, sum([d90020352]) as "8.2d"
, sum([n90020353]) as "8.3n"
, sum([d90020353]) as "8.3d"
, sum([n90020354]) as "8.4n"
, sum([d90020354]) as "8.4d"
, sum([n90020355]) as "8.5n"
, sum([d90020355]) as "8.5d"
, sum([n90020400]) as "IAn"
, sum([d90020400]) as "IAd"
From
(
Select
sfi.prov_target_id as provCID
, prov2.npi
, cmi.facility_group_id as TIN
, sum_data.year_dt
, 'n' + cast(sfi.definition_id as varchar(15)) as id1 -- use for numerator
, 'd' + cast(sfi.definition_id as varchar(15)) as id2 -- use for denominator
, sum_data.numerator_year
, sum_data.denominator_year
from StgEpicClarityPHS.dbo.sum_facts_info sfi with(nolock)
inner join StgEpicClarityPHS.dbo.sum_facts_info_2 sfi2 with(nolock) on sfi2.sum_facts_id = sfi.sum_facts_id
inner join measuresPHS m with(nolock) on m.definition_id = sfi.definition_id
inner join StgEpicClarityPHS.dbo.yearly_data sum_data with(nolock) on sum_data.sum_facts_id = sfi.sum_facts_id
left outer join StgEpicClarityPHS.dbo.cms_mu_info cmi with(nolock) on cmi.cms_mu_id = sfi2.tin_target_id
left outer join StgEpicClarityPHS.dbo.clarity_ser_2 prov2 with(nolock) on sfi.prov_target_id = prov2.prov_id
Where sfi.record_type_c = 1 -- standard summary, not a benchmark
and (sum_data.year_dt = cast(concat('01/01/',:ProgramYear) as datetime)) -- (always use the start date of the reporting period)
and (
(:SummaryLevel = 'NT' and sfi.compld_sum_level = '4^73') -- NPI/TIN (i.e. individual MIPS EC)
or
(:SummaryLevel = 'T' and sfi.compld_sum_level = '73') -- TIN (i.e. Group)
or
(:SummaryLevel = 'N' and sfi.compld_sum_level = '4') -- NPI only
)
) nd
pivot
( sum(NUMERATOR_YEAR)
for id1 in ([n90020350], [n90020300], [n90020301], [n90020302], [n90020303], [n90020304], [n90020305], [n90020306], [n90020307], [n90020308], [n90020309], [n90020310], [n90020311], [n90020351], [n90020352], [n90020353], [n90020354], [n90020355], [n90020400])
) as p1
pivot
( sum(DENOMINATOR_YEAR)
for id2 in ([d90020350], [d90020300], [d90020301], [d90020302], [d90020303], [d90020304], [d90020305], [d90020306], [d90020307], [d90020308], [d90020309], [d90020310], [d90020311], [d90020351], [d90020352], [d90020353], [d90020354], [d90020355], [d90020400])
) as p2
Group By provCID, NPI, TIN, year_dt
--------------------------------------------------------------------------------------------------------------------------------------
;
GO
Your problem may simply be that your query contains a colon at
cast(concat('01/01/',:ProgramYear) as datetime)
in your where clause. It should probably be a #ProgramYear
Also :SummaryLevel should be #SummaryLevel

if Condition for this query

How to do if condition for this query....if record not exist then insert else update from this select
SELECT CAST(left(TerminalName, patindex('%[^0-9]%', TerminalName+'.') - 1) as int)
,'04944700'
,SUBSTRING(TerminalName , CHARINDEX('-' , TerminalName) + 1, LEN(TerminalName))
,TerminalName
,'Bulk'
,'0010'
,user
,GETDATE()
,user
,GETDATE()
FROM External_Blk_Itm_Contracts WHERE TerminalName NOT IN (SELECT MBFTERMINALNAME FROM budterminals)
What you want to use is the MERGE statement. Your Select statement will be the your source of data, and then you will insert or update depending on whether a row matching the join condition exists.
Here is an example from T-SQL I wrote:
MERGE ODS.dbo.Mytable as [Target]
USING [Source Query] as [Source]
ON [Target].SubId = [Source].SubId And [Target].WorkflowId = [Source].WorkflowId
WHEN NOT MATCHED BY TARGET THEN
INSERT
(
[SubId]
,[WorkflowId]
,[ReadTime]
,[ReadTimeLocal]
)
VALUES
(
[SubId]
,[WorkflowId]
,[ReadTime]
,[ReadTimeLocal]
)
WHEN MATCHED THEN UPDATE Set
[Target].ReadTime = [Source].ReadTime,
[Target].ReadTimeLocal = [Source].ReadTimeLocal,
;

SCD type 2 using SQL Server MERGE, how to capture counts?

New to SQL Server and MERGE.
I am working on a MERGE statement to populate a slowly changing dimension table. My example includes both type 1 and type 2 attributes. I see examples of how to use OUTPUT to capture counts of actions, and I understand how to use OUTPUT to pass values out to an INSERT statement. What I would like to do is take the following code and somehow capture the count of UPDATE and INSERT actions for audit/logging purposes.
Very confused reading articles on OUTPUT and OUTPUT INTO, but from what I can tell, I don't think I can do what I want to do, at least not using OUTPUT.
Is there a way to capture the ACTION counts from the below statement? Is there a better way to accomplish this?
Thank you
BEGIN
MERGE dbo.dimTable tgt
USING dbo.stgTable src
ON tgt.NaturalKey = src.NaturalKey
AND tgt.IsActiveRow = 'Y'
WHEN MATCHED
AND EXISTS
(SELECT src.SCD1Field
EXCEPT
SELECT tgt.SCD1Field
)
THEN
UPDATE SET
tgt.SCD1Field = src.SCD1Field ;
INSERT dbo.dimTable (
tgt.NaturalKey
, tgt.SCD1Field
, tgt.SCD2Field
, tgt.RowStartDate
, tgt.RowEndDate
, tgt.IsActiveRow
)
SELECT
NaturalKey
, SCD1Field
, SCD2Field
, RowStartDate
, RowEndDate
, IsActiveRow
FROM (
MERGE dbo.dimTable tgt
USING dbo.stgTable src
ON tgt.NaturalKey = src.NaturalKey
WHEN NOT MATCHED BY TARGET
THEN
INSERT (
NaturalKey
, SCD1Field
, SCD2Field
, RowStartDate
, RowEndDate
, IsActiveRow
)
VALUES (
src.NaturalKey
, src.SCD1Field
, src.SCD2Field
, GETDATE()
, NULL
, 'Y'
)
WHEN MATCHED
AND tgt.IsActiveRow = 'Y'
AND EXISTS
(
SELECT src.SCD2Field
EXCEPT
SELECT tgt.SCD2Field
)
THEN
UPDATE
SET IsActiveRow = 'N'
, RowEndDate = DATEADD(dd,-1,GETDATE())
OUTPUT $ACTION Action_Out
, src.NaturalKey
, src.SCD1Field
, src.SCD2Field
, GETDATE() RowStartDate
, NULL RowEndDate
, 'Y' IsActiveRow
)m
WHERE m.Action_Out = 'UPDATE'
END ;
Unfortunately, the MERGE command itself does not provide a way to capture the ACTION counts.
What you can do, however, is to add another step of counting the ACTION results from the table you are logging them to, assuming that you have some sort of audit key or date column you can use to separate the results from the most recent execution.

Invalid column in stored procedure

I have a query in a stored procedure, it works fine. now I want to add a column the it show error.
My stored procedure code is:
ALTER PROCEDURE dbo.test
#SDate DATETIME =Null
, #EDate DATETIME=Null
,#period int=Null
AS BEGIN
SET NOCOUNT ON;
if #period = 1
Begin
SELECT
t.TotalQuote
, t.QuoteAmount
,t.avgProbQ
, t2.TotalOrders
, t2.OrderAmount
,t3.totalSales
,t3.Prob
FROM (SELECT a = 1) a
CROSS JOIN (
SELECT
TotalQuote = COUNT(quoteid)
, QuoteAmount = SUM(totalamount)
,avgProbQ=SUM(CloseProbability)/COUNT(CloseProbability)
FROM dbo.QuoteBase join dbo.OpportunityBase on dbo.QuoteBase.opportunityid=dbo.OpportunityBase.opportunityid
WHERE
Month(dbo.QuoteBase.CreatedOn)=Month(getdate()) And YEAR(dbo.QuoteBase.CreatedOn)=YEAR(GETDATE())
) t
CROSS JOIN (
SELECT
TotalOrders = COUNT(salesorderid)
, OrderAmount = SUM(totalamount)
FROM dbo.SalesOrderBase join dbo.OpportunityBase on dbo.SalesOrderBase.Opportunityid=dbo.OpportunityBase.Opportunityid
Where Month(dbo.SalesOrderBase.CreatedOn)=Month(getdate()) And YEAR(dbo.SalesOrderBase.CreatedOn)=YEAR(GETDATE())
) t2
CROSS Join(
SELECT
TotalSales=COUNT(dbo.OpportunityBase.opportunityid)
,Prob=SUM(CloseProbability)/COUNT(CloseProbability)
FROM dbo.OpportunityBase join dbo.SalesorderBase on dbo.SalesOrderBase.Opportunityid=dbo.OpportunityBase.Opportunityid
WHERE Month(dbo.OpportunityBase.CreatedOn)=Month(getdate()) And YEAR(dbo.OpportunityBase.CreatedOn)=YEAR(GETDATE())
And dbo.SalesorderBase.StateCode=4
)t3
END
It works fine but when I add a new column like t.test, then it shows error
Msg 207, Level 16, State 1, Procedure test, Line 23
Invalid column name 'test'.
If anyone has an idea please share with me
I am not sure what is your table looked like
it seems you are adding test to your stored procedure but its not added in your database table
This is what I can say by looking the error message. Hope it helps
Not sure what you are trying to do, but guessing, if you are trying to add a column to the output of stored procedure, that is not in the table that the stored procedure is reading data from, then you have to put a literal expression into the select clause, with a defined column name like below: This example uses a string literal, but it can be any datatype...
SELECT 'A String literal to be added to output' As NewColumnName,
t.TotalQuote
, t.QuoteAmount
,t.avgProbQ
, t2.TotalOrders
, t2.OrderAmount
,t3.totalSales
,t3.Prob
etc....
You're getting this error because the column test does not exist in this query:
CROSS JOIN (
SELECT
TotalQuote = COUNT(quoteid)
, QuoteAmount = SUM(totalamount)
,avgProbQ=SUM(CloseProbability)/COUNT(CloseProbability)
FROM dbo.QuoteBase join dbo.OpportunityBase on dbo.QuoteBase.opportunityid=dbo.OpportunityBase.opportunityid
WHERE
Month(dbo.QuoteBase.CreatedOn)=Month(getdate()) And YEAR(dbo.QuoteBase.CreatedOn)=YEAR(GETDATE())
) t
but, if you were to add to that query a column named test then it would succeed. It could be a string literal like 'Some literal value' AS test if necessary.

Using stored procedures to simplify XML generation

I am trying to write an SP to return invoice details in XML for transferring to a third party.
I have a working SP but it's a bit messy (simplified below):
SELECT (
SELECT GETDATE() AS HEADER_SLAStartTime
, DATEADD(HOUR, #SLA_HOURS, GETDATE()) AS HEADER_SLAEndTime
FOR XML PATH ('Header'), TYPE
) , (
SELECT ACCT AS CustomerCode
, ACCTNAME As CustomerName
, ADDR#1 As AddressLine1
, ADDR#2 AS AddressLine2
, ADDR#3 AS AddressLine3
, ADDR#4 AS AddressLine4
, POSTCODE AS AddressPostcode
, TELNO AS AddressTelno
FROM InvHdr
WHERE INVNO = #INVNO
FOR XML PATH('Customer'), TYPE
) , (
SELECT (
SELECT INVNO AS InvoiceNo
, [DATE] AS InvoiceDate
, [INVTYPE] AS InvoiceType
, CASE [SOURCE] WHEN 0 THEN 'Contract' WHEN 1 THEN 'Manual' WHEN 2 THEN 'Sales Order' ELSE '' END AS InvoiceSourceText
, THEIRREF AS CustomerReference
, YOURREF AS InternalReference
, (
SELECT ITEMNO AS ItemCode
, [ITEMDESC#1] AS ItemDesc
, [TYPE] AS ItemType
, [MEMO] AS ItemMemo
, [GOODS] AS ItemCharge
, [DISCOUNT] AS ItemDiscount
FROM InvItems
WHERE INVNO = HDR.INVNO
FOR XML PATH('InvItem'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceHeader'), TYPE
) , (
SELECT HDR.[GOODS] AS InvoiceNet
, HDR.VAT AS InvoiceVAT
, HDR.[GOODS] + HDR.VAT AS InvoiceGross
, (
SELECT VATCODE AS VATListCode
, VATAMT AS VATListAmount
, VATDESC AS VATListDescription
, VATRATE AS VATListRate
, VATGOODS AS VATListGoods
FROM InvVAT
WHERE InvVAT.INVNO = HDR.INVNO
ORDER BY VATAMT DESC
FOR XML PATH('VATSummary'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceFooter'), TYPE
)
FOR XML PATH('Invoices'), TYPE
)
FOR XML PATH(''), ROOT('Output')
This procedure works but I have to create lots of these to get different bits of information in different orders, I have tried creating separate SP's to get the data in sections, below is my first section SP:
CREATE PROCEDURE UDEF_DC_XML_INVOICEFOOTER(
#INVNO INT
)
AS
BEGIN
SELECT HDR.[GOODS] AS InvoiceNet
, HDR.VAT AS InvoiceVAT
, HDR.[GOODS] + HDR.VAT AS InvoiceGross
, (
SELECT VATCODE AS VATListCode
, VATAMT AS VATListAmount
, VATDESC AS VATListDescription
, VATRATE AS VATListRate
, VATGOODS AS VATListGoods
FROM InvVAT
WHERE InvVAT.INVNO = HDR.INVNO
ORDER BY VATAMT DESC
FOR XML PATH('VATSummary'), TYPE
)
FROM InvHdr HDR
WHERE INVNO = #INVNO
FOR XML PATH('InvoiceFooter'), TYPE
END
When I try calling this:
SELECT UDEF_DC_XML_INVOICEFOOTER(#INVNO)
FOR XML PATH('Invoices'), TYPE
I get the error:
Msg 4121, Level 16, State 1, Line 1
Cannot find either column "dbo" or the user-defined function or aggregate "dbo.UDEF_DC_XML_INVOICEFOOTER", or the name is ambiguous.
In the end I'm hoping to be able to create multiple 4/5 line SP's that will call all the sections in the correct order. Either via calling the individual SP's in order or writing each section to variables and building the full XML afterwards.
Is it possible to call multiple stored procedures returning XML within a single statement?
Is it possible to call multiple stored procedures returning XML within
a single statement?
No, but you can use functions that return XML.
create function dbo.GetXML(#Value int) returns xml
as
begin
return (
select #Value as X
for xml path('Y'), type
)
end
Use like this:
select dbo.GetXML(1)
for xml path('Z')
Result:
<Z>
<Y>
<X>1</X>
</Y>
</Z>

Resources