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>
Related
ParseValuesJson and insertToTable are user defined function.
The following is the result of the execution ( ParseValuesJson ) :
declare
#json nvarchar(max)=N'{
"Book":{
"IssueDate":"02-15-2019"
, "Detail":{
"Type":"Any Type"
, "Author":{
"Name":"Annie"
, "Sex":"Female"
}
}
, "Chapter":[
{
"Section":"1.1"
, "Title":"Hello world."
}
,
{
"Section":"1.2"
, "Title":"Be happy."
}
]
, "Sponsor":["A","B","C"]
}
}'
declare
#tempTable table (
topKey nvarchar(4000)
, [key] nvarchar(4000)
, IsType bit
, IsList bit
, [value] nvarchar(4000))
--execute
insert #tempTable
select * from GetValuesJson(#json,default)
topKey Key isType isList Value
--
Book Type 0 0 Any Type
Book Author 1 0 {"Name":"Annie", "Sex":"Female"}
Book IssueDate 0 0 02-15-2019
Book Chapter 1 1 [{"Section":"1.1", "Title":"Hello world."}, {"Section":"1.2", "Title":"Be happy."}]
Book Sponsor 1 1 ["A","B","C"]
As title, If ignore what the function doing, how can I achieve the following purpose?
If IsType=1 , I want to call function ParseValuesJson;
If IsType=0 , I want to call function insertToTable.
But I found that sql case can not use like that.
This sql query may execute recursively and call different functions accordingly at the same level.
It means that I can't parse all string (ParseValuesJson) first and then insert the result (insertToTable) to the table.
Is there any other way to achieve?
select
case IsType when 1 then
ParseValuesJson('{"' + [key] + '":' + [value] + '}',IsList)
else
insertToTable(topKey,[key])
end
from ParseValuesJson(#json,default)
Well, the easiest thing to do is to split it into two separate SELECTs.
select ParseValuesJson('{"' + [key] + '":' + [value] + '}',IsList)
from ParseValuesJson(#json,default)
where IsType = 1
select insertToTable(topKey,[key])
from ParseValuesJson(#json,default)
where IsType = 0
But I guess this approach won't help you since inside user defined functions you cannot use INSERT, UPDATE, DELETE statements -> i.e modify table data
So I guess that to parse the JSON you'd need to user recursive CTE to parse all values first and then to insert them into temp table at once.
Something like this:
;WITH ParsedJSON(topKey, [key], IsType, IsList, [value])
AS
(
SELECT topKey, [key], IsType, IsList, [value]
FROM ParseValuesJson('{"' + [key] + '":' + [value] + '}',IsList)
UNION ALL
SELECT topKey, [key], IsType, IsList, [value]
FROM ParsedJSON
WHERE IsType = 1
)
insert #tempTable
select * from ParsedJSON
When I generate Xml in Sql Server 2008 R2 using For Explicit (because my consumer wants one of the elements wrapped in CDATA) and store the results in an Xml variable, the data I want wrapped in CDATA tags no longer appears wrapped in CDATA tags. If I don't push the For Xml Explicit results into an Xml variable then the CDATA tags are retained. I am using the #Xml variable as an SqlParameter from .Net.
In this example, the first select (Select #Xml) does not have Line2 wrapped in CDATA tags. But the second select (the same query used to populate the #Xml variable) does have the CDATA tags wrapping the Line2 column.
Declare #Xml Xml
Begin Try
Drop Table #MyTempTable
End Try
Begin Catch
End Catch
Select
'Record' As Record
, 'Line1' As Line1
, 'Line2' As Line2
Into
#MyTempTable
Select #Xml =
(
Select
x.Tag
, x.Parent
, x.[Root!1]
, x.[Record!2!Line1!Element]
, x.[Record!2!Line2!cdata]
From
(
Select
1 As Tag, Null As Parent
, Null As [Root!1]
, Null As [Record!2!Line1!Element]
, Null As [Record!2!Line2!cdata]
From
#MyTempTable
Union
Select
2 As Tag, 1 As Parent
, Null As [Root!1]
, Line1 As [Record!2!Line1!Element]
, Line2 As [Record!2!Line2!cdata]
From
#MyTempTable
) x
For
Xml Explicit
)
Select #Xml
Select
x.Tag
, x.Parent
, x.[Root!1]
, x.[Record!2!Line1!Element]
, x.[Record!2!Line2!cdata]
From
(
Select
1 As Tag, Null As Parent
, Null As [Root!1]
, Null As [Record!2!Line1!Element]
, Null As [Record!2!Line2!cdata]
From
#MyTempTable
Union
Select
2 As Tag, 1 As Parent
, Null As [Root!1]
, Line1 As [Record!2!Line1!Element]
, Line2 As [Record!2!Line2!cdata]
From
#MyTempTable
) x
For
Xml Explicit
Begin Try
Drop Table #MyTempTable
End Try
Begin Catch
End Catch
You can't. The XML data type does not preserve CDATA sections.
Have a look here for a discussion about the subject.
http://social.msdn.microsoft.com/forums/en-US/sqlxml/thread/e22efff3-192e-468e-b173-ced52ada857f/
I have a simple table tblAllUsers which stores simple values like Name,Date Of Birth etc of a UserId.
Another table tblInterest stores the interest(s) of a UserId.Here a user may have any number of Interest and are stored seperately in separate rows :
Create table tblInterest
(
Id int primary key identity,
UserId varchar(10),
InterestId int,
Interest varchar(20)
)
So when i want to display the set of Interest together of a particular user, I use the below query :
DECLARE #listStr VARCHAR(MAX)
SELECT #listStr = COALESCE(#listStr + ', ' ,'') + Interest FROM tblInterest where UserId=#UserId
SELECT #listStr
Now, want to display a users info from both these tables wherein the Interest(S) are displayed in ONE string.
I have tried the below ;
Create proc spPlayersGridview
#listStr VARCHAR(MAX)
as
begin
Select tblAllUsers.Category, tblAllUsers.DOB, tblAllUsers.FirstName, tblAllUsers.LastName, tblAllUsers.City, tblAllUsers.State,
#listStr = COALESCE(#listStr + ', ' ,'') + tblInterest.Interest
from tblAllUsers
INNER JOIN tblInterest
ON tblAllUsers.UserId=tblInterest.UserId
where Category='Player'
end
throws an exception "A SELECT statement that assigns a value to a variable must not be combined with data-retrieval operations."
I had a similar problem a while back, and a bit of SQL STUFF magic helps - Maybe it will work for you as well.
CREATE PROC spPlayersGridview
AS
BEGIN
SELECT
tblAllUsers.Category
, tblAllUsers.DOB
, tblAllUsers.FirstName
, tblAllUsers.LastName
, tblAllUsers.City
, tblAllUsers.State
, listStr = STUFF((
SELECT ',' + tblInterest.Interest
FROM tblInterest
WHERE tblAllUsers.UserId=tblInterest.UserId
ORDER BY tblInterest.Interest
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)'), 1, 1, '')
FROM tblAllUsers
WHERE Category='Player'
END
Hope it helps - For more reading look at: https://msdn.microsoft.com/en-us/library/ms188043.aspx
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.
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;