FOR XML EXPLICIT: Create multiple result rows - sql-server

I want to create multiple XML Files/Result Rows in one statement.
What I have:
with invoices as (
select '1' HID, 'Test1' HData, '1' PID, 'Pos1' PData union
select '1' HID, 'Test1' HData, '2' PID, 'Pos2' PData union
select '1' HID, 'Test1' HData, '3' PID, 'Pos3' PData union
select '2' HID, 'Test2' HData, '4' PID, 'Pos1' PData union
select '2' HID, 'Test2' HData, '5' PID, 'Pos2' PData union
select '2' HID, 'Test2' HData, '6' PID, 'Pos3' PData
)
select
case when GROUPING_ID(PID) = 0 then 4
when GROUPING_ID(HData) = 0 then 3
when GROUPING_ID(HID) = 0 then 2
else 1 end tag,
case when GROUPING_ID(PID) = 0 then 2
when GROUPING_ID(HData) = 0 then 2
when GROUPING_ID(HID) = 0 then 1
else null end parent,
null [Root!1],
HID [Invoice!2!HID],
HData [Header!3!HData!Element],
PID [Pos!4!PID],
PData [Pos!4!PData!Element]
from invoices
group by grouping sets ((), (HID), (HID, HData), (HID, HData, PID, PData))
order by HID, HData, PID
for xml explicit, type;
What I get:
What I want:
Is this possible? How would one do that?

You simply need to generate the XML output for each distinct HID:
;WITH invoices as (
select '1' HID, 'Test1' HData, '1' PID, 'Pos1' PData union
select '1' HID, 'Test1' HData, '2' PID, 'Pos2' PData union
select '1' HID, 'Test1' HData, '3' PID, 'Pos3' PData union
select '2' HID, 'Test2' HData, '4' PID, 'Pos1' PData union
select '2' HID, 'Test2' HData, '5' PID, 'Pos2' PData union
select '2' HID, 'Test2' HData, '6' PID, 'Pos3' PData
)
SELECT
XMLColumn = (
select
case when GROUPING_ID(PID) = 0 then 4
when GROUPING_ID(HData) = 0 then 3
when GROUPING_ID(HID) = 0 then 2
else 1
end tag,
case when GROUPING_ID(PID) = 0 then 2
when GROUPING_ID(HData) = 0 then 2
when GROUPING_ID(HID) = 0 then 1
else null
end parent,
null [Root!1],
HID [Invoice!2!HID],
HData [Header!3!HData!Element],
PID [Pos!4!PID],
PData [Pos!4!PData!Element]
from invoices
WHERE i.HID = HID
group by grouping sets ((), (HID), (HID, HData), (HID, HData, PID, PData))
order by HID, HData, PID
for xml explicit, type
)
FROM invoices i
GROUP BY HID

Related

Simplify TSQL by Getting Ids that matched or first in the row number if not matched

I have this example where Id1 serves as the group and Id2 is an Id unique in the group.
If Id2 is the same as Id1, I want to get its row as a representative of the whole group.
If there's no Id1 that matched to Id2, then I want to get the first row based on the order of ascending Id2.
This is how I did it but I just wonder how I can simplify the tsql:
WITH cte AS
(
SELECT 'A' AS Id1, '1' AS Id2, 'DSFSF' AS _detail
UNION ALL
SELECT 'A' AS Id1, '2' AS Id2, 'ASDF' AS _detail
UNION ALL
SELECT 'A' AS Id1, 'A' AS Id2, '434242' AS _detail
UNION ALL
SELECT 'B' AS Id1, '1' AS Id2, 'gsreew' AS _detail
UNION ALL
SELECT 'B' AS Id1, '2' AS Id2, 'werw' AS _detail
UNION ALL
SELECT 'B' AS Id1, '3' AS Id2, '67575' AS _detail
),
cte2 AS
(
SELECT
*,
ROW_NUMBER() OVER (PARTITION BY Id1 ORDER BY Id2) AS rn,
CASE
WHEN Id1 = Id2 THEN 1 ELSE 0
END AS _matchedid
FROM
cte
),
cte3 AS
(
SELECT
Id1, SUM(_matchedid) AS _matched
FROM
cte2
GROUP BY
Id1
)
SELECT *
FROM cte2 a
INNER JOIN cte3 b ON a.Id1 = b.Id1
WHERE b._matched = 1 AND a.Id1 = a.Id2
UNION ALL
SELECT *
FROM cte2 a
INNER JOIN cte3 b ON a.Id1 = b.Id1
WHERE b._matched = 0 AND a.rn = 1
You can use a conditional row-numbering solution for this
WITH cte AS
(
SELECT 'A' AS Id1, '1' AS Id2, 'DSFSF' AS _detail
UNION ALL
SELECT 'A' AS Id1, '2' AS Id2, 'ASDF' AS _detail
UNION ALL
SELECT 'A' AS Id1, 'A' AS Id2, '434242' AS _detail
UNION ALL
SELECT 'B' AS Id1, '1' AS Id2, 'gsreew' AS _detail
UNION ALL
SELECT 'B' AS Id1, '2' AS Id2, 'werw' AS _detail
UNION ALL
SELECT 'B' AS Id1, '3' AS Id2, '67575' AS _detail
),
cteWithRn AS
(
SELECT *
ROW_NUMBER() OVER (PARTITION BY Id1, CASE WHEN Id1 = Id2 THEN 0 ELSE 1 END, Id2) AS rn
FROM cte
)
SELECT *
FROM cteWithRn
WHERE rn = 1;
One idea uses TOP (1) WITH TIES and ROW_NUMBER with a conditional ORDER BY:
SELECT TOP (1) WITH TIES *
FROM CTE
ORDER BY ROW_NUMBER() OVER (PARTITION BY ID1 ORDER BY CASE ID1 WHEN ID2 THEN NULL ELSE ID2 END);
Note that as your column ID2 is a varchar, then a value like '10' will have a lower value than something like '2'.

SQL : Filter Column with row wise values

T-SQL:
Below is my table on which i need to apply row wise filter on a col.
my criteria is that the Val should be : 0 AND (1 OR 1.1) AND 2
The original table is
name val
aaa 0
aaa 1
aaa 1.1
aaa 2
bbb 0
bbb 2
ccc 0
ccc 1
ccc 2
The expected result will be
name val
aaa 0
aaa 1
aaa 1.1
aaa 2
ccc 0
ccc 1
ccc 2
Could anyone suggest a solution. I am trying the Where clause but i am not able to give the conditions within the where clause.
Appreciate any help
The following will return all rows from the group when the conditions are satisfied:
DECLARE #test TABLE(
name varchar(10) NOT NULL
, value varchar(10) NOT NULL
);
INSERT INTO #test(name, value)
VALUES
('aaa', '0')
,('aaa', '1')
,('aaa', '1.1')
,('aaa', '2')
,('bbb', '0')
,('bbb', '2')
,('ccc', '0')
,('ccc', '1')
,('ccc', '2');
SELECT a.name, a.value
FROM #test a
CROSS APPLY(SELECT COUNT(*) FROM #test b WHERE b.name = a.name AND value IN('0', '2')) AS b(cnt)
CROSS APPLY(SELECT COUNT(*) FROM #test b WHERE b.name = a.name AND value IN('1', '1.1')) AS c(cnt)
WHERE b.cnt = 2 AND c.cnt >= 1;
The 3 conditions are specified as a cte and join to your original table. The matching rows should be 3
; with
-- your table
tbl as
(
select name = 'aaa', val = 0 union all
select name = 'aaa ', val = 1 union all
select name = 'aaa', val = 1.1 union all
select name = 'aaa', val = 2 union all
select name = 'bbb', val = 0 union all
select name = 'bbb', val = 2 union all
select name = 'ccc', val = 0 union all
select name = 'ccc', val = 1 union all
select name = 'ccc', val = 2
),
-- your condition
val as
(
select val1 = 0, val2 = 0 union all
select val1 = 1, val2 = 1.1 union all
select val1 = 2, val2 = 2
),
cte_name as
(
select t.name
from tbl t
inner join val v on t.val = v.val1
or t.val = v.val2
group by name
having count(distinct v.val1) = 3 -- matching rows should be 3
)
-- the query
select t.*
from tbl t
inner join cte_name n on t.name = n.name
One other version:
DECLARE #test TABLE(
name varchar(10) NOT NULL
, value decimal(5,1) NOT NULL
);
INSERT INTO #test(name, value)
VALUES
('aaa', 0)
,('aaa', 1)
,('aaa', 1.1)
,('aaa', 2)
,('bbb', 0)
,('bbb', 2)
,('ccc', 0)
,('ccc', 1)
,('ccc', 2);
select a.name, a.value
from #test a
join (
select name,
sum(case when value IN(0,2) then 1 else 0 end) as c02,
sum(case when value IN(1,1.1) then 1 else 0 end) as c1
from #test group by name
) g on g.name=a.name and c02=2 and c1>=1

Use Results of CTE to Update Another Table

I have the following CTE that does what I want it to do.
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
FROM numbering
GROUP BY SrcID
I need to use that, as well as the AsOfDate and PID, but I don’t want to display these in the CTE, as that throws off populating PID1, PID2, PID3, and PID4 . . . all of which is correct now. I do need the SrcID and AsOfDate to do an update to another table, named ‘RAW_DATA’. How can I run the CTE to generate the specific data set that I need, and then do an update to the RAW_DATA table, based on joins between SrcID and AsOfDate?
I think it should be something like this:
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
FROM numbering
GROUP BY SrcID
INSERT INTO RAW_DATA(SrcID, AsOfDate, PID, PID1, PID2, PID3, PID4)
Select *
FROM RAW_DATA INNER JOIN numbering
ON RAW_DATA.SrcID = numbering.SrcID
AND RAW_DATA.AsofDate = numbering.AsofDate
However, that throws this error: Invalid object name 'numbering'. I am on SQL Server 2008.
Update
Modifying my original post just a bit here.
Jeffrey, I'm testing your solution:
--drop table Count_Unique_PID
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,PID1,PID2,PID3,PID4,
dense_rank() OVER (PARTITION BY AsOfDate, SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,AsOfDate, PID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
INTO Count_Unique_PID
FROM numbering
GROUP BY SrcID,AsOfDate, PID
SELECT SrcID,
AsOfDate,
PID,
PID1,
PID2,
PID3,
PID4
FROM Count_Unique_PID
GROUP BY SrcID,AsOfDate, PID,PID1,PID2,PID3,PID4
UPDATE RAW_DATA
SET PID1 = B.PID1,
PID2 = B.PID2,
PID3 = B.PID3,
PID4 = B.PID4
FROM RAW_DATA AS A INNER JOIN Count_Unique_PID As B
ON A.SrcID = B.SrcID
AND A.AsofDate = B.AsofDate
This runs, but it blows up my rows from 357,518 to 724,150. The number of records should stay the same; it should remain 357,518 after the Update is done...something is still not quite right here. Maybe I am missing a Group By somewhere, or something like that. I don't see what the actual problem is. Any additional thoughts on this?
CTEs can only be referenced by the immediately-following statement. If you need the results later, you could insert the CTE into a temp table, then select from that temp table and then insert from that temp table:
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
SELECT SrcID,
MAX(CASE rowno WHEN 1 THEN PID END) AS PID1,
MAX(CASE rowno WHEN 2 THEN PID END) AS PID2,
MAX(CASE rowno WHEN 3 THEN PID END) AS PID3,
MAX(CASE rowno WHEN 4 THEN PID END) AS PID4
INTO #tmp
FROM numbering
GROUP BY SrcID
SELECT SrcID,
PID1,
PID2,
PID3,
PID4
FROM #tmp
GROUP BY SrcID
INSERT INTO RAW_DATA(SrcID, AsOfDate, PID, PID1, PID2, PID3, PID4)
Select *
FROM RAW_DATA INNER JOIN #tmp
ON RAW_DATA.SrcID = #tmp.SrcID
AND RAW_DATA.AsofDate = #tmp.AsofDate
You need encapsulate your query as a second cte:
sintaxis is
WITH cte1 as (
SELECT ...
), cte2 as (
SELECT *
FROM cte1
.....
)
INSERT INTO table_name
SELECT *
FROM cte2
Your SELECT statement is doing nothing for the insert (maybe it's for validation). Simply remove the select statement and your insert should work.
;WITH numbering AS
(
SELECT SrcID, AsOfDate, PID,
dense_rank() OVER (PARTITION BY SrcID ORDER BY PID) AS rowno
FROM RAW_DATA
)
INSERT INTO RAW_DATA(SrcID, AsOfDate, PID, PID1, PID2, PID3, PID4)
Select *
FROM
RAW_DATA INNER JOIN numbering
ON RAW_DATA.SrcID = numbering.SrcID
AND RAW_DATA.AsofDate = numbering.AsofDate
If you need to run a select on the CTE as well, you should use a temp table to it will exist as long as the query is running.

T-SQL Assigning a running count of days in-between product launches resetting to zero when a product launches

I need to assign a count of the days that have elapsed in-between different product launches. This is an example of the data that I have:
SELECT '2016-01-01' TiD, 1 Launch, NULL DaysBetween
INTO #TestingData
UNION
SELECT '2016-01-02', 0, NULL DaysBetween
UNION
SELECT '2016-01-03', 0, NULL DaysBetween
UNION
SELECT '2016-01-04', 1, NULL DaysBetween
UNION
SELECT '2016-01-05', 0, NULL DaysBetween;
I attempted to use this query:
SELECT TiD
, Launch
, COUNT(1) OVER(PARTITION BY Launch ORDER BY TiD) DaysSinceLastLaunch
FROM #TestingData
ORDER BY TiD ASC
However it does not reset the count to zero. If I need my end result to look like this:
SELECT '2016-01-01' TiD, 1 Launch, 0 DaysBetween
INTO #CorrectData
UNION
SELECT '2016-01-02', 0, 1 DaysBetween
UNION
SELECT '2016-01-03', 0, 2 DaysBetween
UNION
SELECT '2016-01-04', 1, 0 DaysBetween
UNION
SELECT '2016-01-05', 0, 1 DaysBetween;
What do I need to do? Should I use a cursor?
Thanks
Here's a quick CTE that does the job. The difference between this and the other answer is in the first part of the CTE where it takes TOP 1 as opposed to Launch = 1, which could return more than a single row. (Honestly I've not tested the other answer).
Note: this assumes you always have consecutive days, which your sample data indicates that you do.
SELECT '2016-01-01' TiD, 1 Launch, NULL DaysBetween
INTO #TestingData
UNION
SELECT '2016-01-02', 0, NULL DaysBetween
UNION
SELECT '2016-01-03', 0, NULL DaysBetween
UNION
SELECT '2016-01-04', 1, NULL DaysBetween
UNION
SELECT '2016-01-05', 0, NULL DaysBetween
;WITH cte
AS ( SELECT TOP 1
TiD ,
Launch ,
0 AS DaysBetween
FROM #TestingData
ORDER BY TiD
UNION ALL
SELECT t1.TiD ,
t1.Launch ,
CASE WHEN t1.Launch = 1 THEN 0
ELSE cte.DaysBetween + 1
END AS Launch
FROM #TestingData t1
INNER JOIN cte ON t1.TiD = DATEADD(DAY, 1, cte.TiD)
)
SELECT *
FROM cte
DROP TABLE #TestingData
Output:
TiD Launch DaysBetween
2016-01-01 1 0
2016-01-02 0 1
2016-01-03 0 2
2016-01-04 1 0
2016-01-05 0 1
WITH REC_CTE (tid,launch,Daysbetween)
AS (
-- Anchor definition
SELECT tid,
launch,
0 as Daysbetween
FROM #tmp
WHERE launch = 1
-- Recursive definition
UNION ALL
SELECT son.tid,
son.launch,
father.Daysbetween + 1
FROM #tmp son INNER JOIN
REC_CTE father
on son.tid = dateadd(dd,1,father.tid)
WHERE son.launch = 0
)
SELECT * from REC_CTE order by tid asc
you can use a recursive CTE to do the job for you. On every iteration you will had +1 for each day from the last launch.
I can provide a sqlfiddle if you cant get it working, just let me know

How to group names which are dependent on other columns

I have a table like below.
CREATE TABLE #Test
(
CustId INT ,
CustName VARCHAR(100) ,
CustHeading VARCHAR(100)
)
INSERT INTO #Test
SELECT '1','john carroll','Heading 1'
UNION ALL
SELECT '1','john carroll','Heading 2'
UNION ALL
SELECT '2','john c','Heading 1'
UNION ALL
SELECT '2','john c','Heading 2'
UNION ALL
SELECT '2','john c','Heading 3'
UNION ALL
SELECT '3','john lynch','Heading 1'
UNION ALL
SELECT '4','john carroll lynch','Heading 1'
UNION ALL
SELECT '4','john carroll lynch','Heading 4'
UNION ALL
SELECT '4','john carroll lynch','Heading 5'
UNION ALL
SELECT '5','john c lynch','Heading 1'
UNION ALL
SELECT '5','john c lynch','Heading 3'
UNION ALL
SELECT '6','john c l','Heading 11'
UNION ALL
SELECT '6','john c l','Heading 12'
UNION ALL
SELECT '7','john c ln','Heading 1'
UNION ALL
SELECT '7','john c ln','Heading 2'
UNION ALL
SELECT '2','john c','Heading 11'
UNION ALL
SELECT '2','john c','Heading 12'
In this, we need to group the customers who are having atleast two matching headings among them.
For example,custID :: 1,2 and 7 are having two matching CustHeading :: Header 1 and Header 2, so they are grouped.CustID :: 2 and 5 having two matching CustHeading :: Header 1 and Header 3,they also can be grouped. Please let me know how to achieve this
without using WHILE loop
Thanks in advance.
SELECT
DISTINCT
CASE WHEN c.num = 1 THEN a.CustId
ELSE a.[_CustId]
END ,
CASE WHEN c.num = 1 THEN a.CustName
ELSE a.[_CustName]
END ,
'M' + CAST(DENSE_RANK() OVER ( ORDER BY mx, mn ) AS VARCHAR(100)) AS gr_nbr
FROM
(
SELECT
a.CustId ,
a.CustName ,
c.CustId _CustId ,
c.CustName _CustName,
MAX(a.CustHeading) mx,
MIN(a.CustHeading) mn
FROM
#Test a
JOIN #Test c ON c.CustHeading = a.CustHeading
AND c.CustId > a.CustId
GROUP BY
a.CustId ,
a.CustName ,
c.CustId ,
c.CustName
HAVING
MAX(a.CustHeading) <> MIN(a.CustHeading)
) a
JOIN #Test b ON b.CustId = a.[_CustId]
CROSS JOIN (
SELECT
1 num
UNION ALL
SELECT
2 num
) AS c
ORDER BY
3 ,
1
Sorry my brain is not working today. I'm sure there is a simpler way to do this but I can think right now. This seems to work fine for me. Let me know if it needs any changes.
CREATE TABLE #Test
(
CustId INT ,
CustName VARCHAR(100) ,
CustHeading VARCHAR(100)
)
INSERT INTO #Test
SELECT '1','john carroll','Heading 1'
UNION ALL
SELECT '1','john carroll','Heading 2'
UNION ALL
SELECT '2','john c','Heading 1'
UNION ALL
SELECT '2','john c','Heading 2'
UNION ALL
SELECT '2','john c','Heading 3'
UNION ALL
SELECT '3','john lynch','Heading 1'
UNION ALL
SELECT '4','john carroll lynch','Heading 1'
UNION ALL
SELECT '4','john carroll lynch','Heading 4'
UNION ALL
SELECT '4','john carroll lynch','Heading 5'
UNION ALL
SELECT '5','john c lynch','Heading 1'
UNION ALL
SELECT '5','john c lynch','Heading 3'
UNION ALL
SELECT '6','john c l','Heading 11'
UNION ALL
SELECT '6','john c l','Heading 12'
UNION ALL
SELECT '7','john c ln','Heading 1'
UNION ALL
SELECT '7','john c ln','Heading 2'
UNION ALL
SELECT '2','john c','Heading 11'
UNION ALL
SELECT '2','john c','Heading 12';
WITH CTE_Heading
AS
(
SELECT DISTINCT custHeading
FROM #Test
),
CTE_Paired_Headings
AS
(
SELECT A.custHeading AS Head1,
B.CustHeading AS Head2
FROM CTE_Heading A
INNER JOIN CTE_Heading B
ON A.custHeading < B.custHeading
),
CTE_Matching_Cust
AS
(
SELECT A.Head1,
A.Head2,
B.CustId,
B.CustName
FROM CTE_Paired_Headings A
INNER JOIN #Test B
ON A.Head1 = B.CustHeading
OR A.Head2 = B.CustHeading
GROUP BY A.Head1,A.Head2,B.CustId,B.CustName
HAVING COUNT(*) >= 2
),
CTE_HeadingGroups
AS
(
SELECT 'M' + CAST(ROW_NUMBER() OVER (ORDER BY Head1,Head2) AS VARCHAR(5)) MatchingID,
Head1,
Head2
FROM CTE_Matching_Cust
GROUP BY Head1,Head2
HAVING COUNT(*) >= 2
)
SELECT B.CustId,
B.CustName,
A.MatchingID
FROM CTE_HeadingGroups A
INNER JOIN CTE_Matching_Cust B
ON A.Head1 = B.Head1
AND A.Head2 = B.Head2
ORDER BY 3,1
DROP TABLE #Test
Results:
CustId CustName MatchingID
-----------------------------------------------
1 john carroll M1
2 john c M1
7 john c ln M1
2 john c M2
5 john c lynch M2
2 john c M3
6 john c l M3

Resources