FOR XML PATH : DISTINCT sort cost increased - sql-server

I'd like to put together only unique values in the concatenated string. My code is currently:
SELECT PITEM2.orderid,
(SELECT ISNULL(E.FIRSTNAME + ' ' + E.LASTNAME,' ') + ', ' AS [text()]
FROM F_PURCHASEITEM PITEM1
LEFT JOIN E__EMPLOYEE E ON e.EMPLOYEEID=PITEM1.APPROVED_BY
WHERE PITEM1.ORDERID = PITEM2.ORDERID
AND PITEM1.PISTATUS =
(SELECT POSTATUSID
FROM F_POSTATUS
WHERE POSTATUSNAME = 'Invoice Received') GROUP By ISNULL(E.FIRSTNAME + ' ' + E.LASTNAME,' ') + ', '
FOR XML PATH ('') ) [EmployeeNames]
FROM F_PURCHASEITEM PITEM2
WHERE ORDERID=305089 Group By PITEM2.orderid
This gives me the output I'd expect, but the cost of the query increased and execution plan shows the Distinct sort as 46.3%.
How can I decrease the cost for this distinct?

Related

Replace “<” and “>”and "<\" with “<” ,“>” and </ in sql server

I know that Replace < and > with < and > in sql server has been asked at
Replace “<” and “>” with “<” and “>” in sql server
but I need to expand upon it. I am trying to create formatted HTML code from a MS 2016 SQL server.
What I have is:
DECLARE #json nvarchar(max) = (SELECT DISTINCT AU2.PMID,
SUBSTRING(
(
SELECT ', '+ CASE WHEN AU1.Format IS Not Null Then +'<b>' +
AU1.LastName+ ' ' + AU1.Initials + '</' + AU1.Format + '>' ELSE
AU1.LastName + ' ' + AU1.Initials END AS [text()]
FROM dbo.[Publication.PubMed.Author] AU1
WHERE AU1.PMID = AU2.PMID
ORDER BY AU1.PmPubsAuthorID
FOR XML PATH ('')
), 3, 1000) + '. Journal Name ' [Authors]
FROM dbo.[Publication.PubMed.Author] AU2
WHERE AU2.[PMID] =27702896 FOR JSON AUTO)
SELECT #json
And what I get back is:
Name DG, Name K, Name A, Chen FK, Song X, Pirun M, Santiago PM, Kim-Kiselak C, Platt JT, Lee E, Hodges E, <b>Rosebrock AP</b>, Bronson RT, Socci ND, Hannon GJ, Jacks T, Varmus H Journal Name
If I try to put TYPE into the
FOR XML PATH(''), TYPE
I get an error saying:
Argument data type xml is invalid for argument 1 of substring function.
I've tried to add REPLACE
replace(CASE WHEN AU1.Format IS Not Null Then +'<b>' +
AU1.LastName+ ' ' + AU1.Initials + '</b>' ELSE
AU1.LastName + ' ' + AU1.Initials END, '<', '<')
But then I get an error
The xml data type cannot be selected as DISTINCT because it is not comparable.
The best that I can come up with is:
SET #json = replace(#json, '<\', '<')
SET #json = replace(#json, '<', '<')
SET #json = replace(#json, '>', '>')
Just before the final SELECT #json.
I will have over a 1000 records that I need to pull. Do I need to do this is steps?
Based upon TT code I changed mine to
DECLARE #json nvarchar(max) = (SELECT DISTINCT AU2.PMID,
SUBSTRING(
(
SELECT ', '+ CASE WHEN AU1.Format IS Not Null Then +'<b>' +
AU1.LastName+ ' ' + AU1.Initials + '</b>' ELSE
AU1.LastName + ' ' + AU1.Initials END AS [text()]
FROM dbo.[Publication.PubMed.Author] AU1
WHERE AU1.PMID = AU2.PMID
ORDER BY AU1.PmPubsAuthorID
FOR XML PATH (''),TYPE).value('.[1]','NVARCHAR(MAX)'
), 1, 1000) + '. Journal Name ' [Authors]
FROM dbo.[Publication.PubMed.Author] AU2
WHERE AU2.[PMID] =27702896 FOR JSON AUTO)
And that woks fine. but see comment below
As far as I can tell you are on the right track with FOR XML PATH(''), TYPE. It's just that you then have an XML text value which you need to convert to a string (i.e. of type [N]VARCHAR(X)). This can be done with the XML value() method.
An example (fiddle):
CREATE TABLE t(s NVARCHAR(16));
INSERT INTO t(s)VALUES(N'1'),(N'2'),(N'3'),(N'4'),(N'5'),(N'6');
SELECT
(
SELECT N'<b>'+s+N'</b>'
FROM t
FOR XML PATH(''),TYPE
).value('.[1]','NVARCHAR(MAX)');
results in
<b>1</b><b>2</b><b>3</b><b>4</b><b>5</b><b>6</b>

Need help adding WHERE clause in pre-written SQL statement

Preface: my SQL is rudimentary. I received a SQL query from a vendor, it selects and exports every single employee comment and other data from a few different DBs as CSV meant for import, it was written by them but they're not helping with this request. The query is pulling so much data it makes a large time consuming file for import. So I want to add to / modify the query to have a "WHERE date > whateverdate" to narrow my results to recent data. For example, I want to pull only comments entered in the past 2 days.
The column I'm looking to add the clause for is the column "A.CMS502", defined as datetime. I believe this is the only relevant column in this query. An example date in this column is "2003-10-06 17:05:21.000". I am using SQL Server 2008 if it helps. Is it possible here? Thank you.
SELECT
'ID,Acct/LnNbr,NoteCreatedDate,CollectorId,ApplytoAll,Note'
UNION ALL
SELECT
ID + ',' + ID + ',' + NoteCreatedDate + ',' + CollectorId + ',' + 'No' + ',' + Note
FROM
(SELECT
CASE WHEN SUBSTRING(A.CMS301,LEN(A.CMS301),1) = 'S'
THEN SUBSTRING(A.CMS301,1,LEN(A.CMS301) - 1)
ELSE A.CMS301
END + '-' +
CASE WHEN SUBSTRING(A.CMS301,LEN(A.CMS301),1) = 'S'
THEN 'S' ELSE 'L'
END AS [ID],
REPLACE(CONVERT(VARCHAR, A.CMS501, 10), '-', '') AS [NoteCreatedDate],
CASE WHEN U.CMS1201 IS NOT NULL
THEN U.CMS1205 + ' ' + U.CMS1204
ELSE (SELECT CMS1205 + ' ' + CMS1204 FROM sysUSER WHERE CMS1201 = 'PSUSER')
END AS CollectorId,
CAST(A.CMS512 AS NVARCHAR(MAX)) AS [Note]
FROM
ACTIVITY AS A
LEFT JOIN
sysUSER AS U ON A.CMS503 = U.CMS1201
WHERE
A.CMS504 NOT IN (411,500,511,711,804,900,901,903,907,2000,999777)
AND A.CMS504 NOT BETWEEN 1102 AND 1199) AS S
Try this, this will output last 2 days.
SELECT 'ID,Acct/LnNbr,NoteCreatedDate,CollectorId,ApplytoAll,Note'
UNION ALL
SELECT ID + ',' + ID + ',' + NoteCreatedDate + ',' + CollectorId + ',' + 'No' + ',' + Note
FROM
(
SELECT CASE WHEN SUBSTRING(A.CMS301,LEN(A.CMS301),1) = 'S' THEN SUBSTRING(A.CMS301,1,LEN(A.CMS301) - 1) ELSE A.CMS301 END
+ '-' + CASE WHEN SUBSTRING(A.CMS301,LEN(A.CMS301),1) = 'S' THEN 'S' ELSE 'L'
END AS [ID]
,REPLACE(CONVERT(varchar,A.CMS501,10),'-','') AS [NoteCreatedDate]
,CASE WHEN U.CMS1201 IS NOT NULL THEN U.CMS1205 + ' ' + U.CMS1204 ELSE
(SELECT CMS1205 + ' ' + CMS1204 FROM sysUSER WHERE CMS1201 = 'PSUSER')
END AS CollectorId
,CAST(A.CMS512 AS nvarchar(max)) AS [Note]
FROM ACTIVITY AS A
LEFT JOIN sysUSER AS U
ON A.CMS503 = U.CMS1201
WHERE A.CMS504 NOT IN (411,500,511,711,804,900,901,903,907,2000,999777)
AND A.CMS504 NOT BETWEEN 1102 AND 1199
AND A.CMS502 >= DATEADD(D, -2, GETDATE())
) AS S

Order by Empty String Last on Concatenated Column

I am trying to order a table alphabetically, ascending, with nulls last but am having problems.
The code below produces the following error:
ORDER BY items must appear in the select list if SELECT DISTINCT is specified.
select distinct
'item' = othertab..item,
'stockedFor' = tab..stocked_for
+ ', ' + tab..stockedFor2
+ ', '+ tab..stockedFor3
from tab
order by case when stockedFor is null then 1 else 0 end, stockedFor
How can I return stockedFor alphabetically and nulls last?
Just wrap it in another select statement:
select stockedFor
from (
select distinct
'stockedFor' = tab..stocked_for
+ ', ' + tab..stockedFor2
+ ', '+ tab..stockedFor3
from tab
) x
order by case when stockedFor is null then 1 else 0 end, stockedFor
Since you are removing duplicates, a workaround is to use GROUP BY to remove duplicates instead of DISTINCT. The question has changed but the method still applies if putting all columns in the SELECT in the GROUP BY.
For example:
select
'item' = othertab..item,
'stockedFor' = tab..stocked_for
+ ', ' + tab..stockedFor2
+ ', '+ tab..stockedFor3
from tab
GROUP BY othertab..item,
tab..stocked_for
+ ', ' + tab..stockedFor2
+ ', '+ tab..stockedFor3
order by case when stockedFor is null then 1 else 0 end, stockedFor

SQL Server build dynamic sql

I have a temp table called #temp, and I need to get all the CDate column from that table, to build a string.
The CDate list in that table is (20171209, 20171210....20171223)
I expected to see
'A.[20171209] as [20171209], A.[20171210] as [20171210],
A.[20171211] as [20171211], A.[20171212] as [20171212],
A.[20171213] as [20171213], A.[20171214] as [20171214],
A.[20171215] as [20171215], A.[20171216] as [20171216],
A.[20171217] as [20171217], A.[20171218] as [20171218],
A.[20171219] as [20171219], A.[20171220] as [20171220],
A.[20171221] as [20171221], A.[20171222] as [20171222],
A.[20171223] as [20171223], '
however the result I got is missing the first date , ie 'A.[20171209] as [20171209]'
Here is my code:
SELECT
#col2 = ISNULL(#col2 + 'A.' + QUOTENAME(CDate) + ' as ' + QUOTENAME(CDate) + ', ' , '')
FROM
(SELECT DISTINCT CDate FROM #temp) AS tmp;
Your current approach will not work in some cases, it is an undocumented feature, always use For Xml path to concatenating the rows into csv.
SET #col2 = stuff((SELECT ', A.' + Quotename(CDate) + ' as '
+ Quotename(CDate)
FROM (SELECT DISTINCT CDate
FROM #temp) a
FOR xml path('')),1,1,'')

TSQL query for tree view control and remove empty elements from an xml file

Query for treeview control, sometimes contains empty elements which throws an exception (when there are no attributes in an element) due to data-binding of the control the text values is set to "GrandChildOfFirstRow"
I did get rid of them in my query via xquery but is there an alternative way to doing this or a better smarter way to get rid of those empty elements, (I need the left outer join for proper records for this query) or is it possible to combine the xquery code into shorter code:
Query:
declare #x as xml
set #x =
(
SELECT distinct
Table1.AssetObjID, Table1.Asset_ID , Table1.FromLR, Table1.AssetType + ', ' + Table1.StreetName + ', ' + Table1.FromMunicNo as FirstRow,
Table2.ACIObjID ,Table2.PAssetObjID, Table2.Feature_ID + ', ' + Table2.FeatureName AS ChildOfFirstRow,
Table3.ITMObjID ,Table3.Item_ID + ',' + Table3.[DESC] as GrandChildOfFirstRow
FROM Table1 left outer join
Table2 ON Table1.AssetObjID = Table2.PAssetObjID left outer join
Table3 ON Table1.AssetObjID = Table3.AssetObjID AND Table2.ACIObjID = Table3.ACIObjID
where Table1.AssetType ='xxxx'
for xml auto,root('xml')
)
--what it does is it only grabs one empty element and deletes only occurrences of that
--specific element for the whole file
--so If I have 2 or more elements which are empty in an xml file
--I will have to repeat that code each time
SET #x.modify('delete //*[not(node()) and not(./#*)]')
SET #x.modify('delete //*[not(node()) and not(./#*)]')
You can use for xml path() and build your nested levels with correlated sub-queries.
select Table1.AssetObjID as "#AssetObjID",
Table1.Asset_ID as "#Asset_ID",
Table1.FromLR as "#FromLR",
Table1.AssetType + ', ' + Table1.StreetName + ', ' + Table1.FromMunicNo as "#FirstRow",
(
select Table2.ACIObjID as "#ACIObjID",
Table2.PAssetObjID as "#PAssetObjID",
Table2.Feature_ID + ', ' + Table2.FeatureName AS "#ChildOfFirstRow",
(
select Table3.ITMObjID as "#ITMObjID",
Table3.Item_ID + ',' + Table3.[DESC] as "#GrandChildOfFirstRow"
from Table3
where Table1.AssetObjID = Table3.AssetObjID and
Table2.ACIObjID = Table3.ACIObjID
for xml path('Table3'), type
)
from Table2
where Table1.AssetObjID = Table2.PAssetObjID
for xml path('Table2'), type
)
from Table1
where Table1.AssetType = 'xxxx'
for xml path('Table1'), root('xml')

Resources