Why isn't STUFF and FOR XML PATH not concenating? - sql

I have these two tables
CREATE TABLE [dbo].[Things](
[testid] [int] NOT NULL,
[testdesc] [varchar](10) NULL
) ON [PRIMARY]
CREATE TABLE [dbo].[ThingsStaging](
[otherid] [int] NOT NULL,
[testid] [int] NOT NULL
) ON [PRIMARY]
INSERT INTO [dbo].[Things] ([testid], [testdesc]) VALUES (1, N'Stuff')
INSERT INTO [dbo].[Things] ([testid], [testdesc]) VALUES (2, N'Things')
INSERT INTO [dbo].[Things] ([testid], [testdesc]) VALUES (3, N'Orcs')
INSERT INTO [dbo].[Things] ([testid], [testdesc]) VALUES (4, N'Grubs')
INSERT INTO [dbo].[Things] ([testid], [testdesc]) VALUES (5, N'Shrooms')
INSERT INTO [dbo].[ThingsStaging] ([otherid], [testid]) VALUES (1, 1)
INSERT INTO [dbo].[ThingsStaging] ([otherid], [testid]) VALUES (1, 2)
INSERT INTO [dbo].[ThingsStaging] ([otherid], [testid]) VALUES (1, 3)
INSERT INTO [dbo].[ThingsStaging] ([otherid], [testid]) VALUES (2, 3)
INSERT INTO [dbo].[ThingsStaging] ([otherid], [testid]) VALUES (2, 4)
;with allThings(otherid, descs)
as
(
select ts.otherid ,
stuff ((select ', ' + blah.testdesc as [text()]
from (
select distinct t.testdesc
from Things as t
where t.testid = ts.testid ) as blah
for xml path('')), 1, 1, '') as stuffs
from ThingsStaging as ts
)
select *
from allThings
Now when run this query, I get
otherid stuffs
1 Stuff
1 Things
1 Orcs
2 Orcs
2 Grubs
But I should get:
otherid stuffs
1 Stuff, Things, Orcs
2 Orcs, Grubs
I'm not understanding what I'm doing wrong.

I understand what I did wrong. Code will explain better.
select otherid, stuff((select ', ' + t.testdesc as [text()]
from Things as t
inner join ThingsStaging as its on t.testid = its.testid
where its.otherid = ts.otherid
for xml path('')), 1, 1, '') as descs
from ThingsStaging as ts
group by otherid

Related

How to use Group By with where condition to concatenate column value in SQL Server

I want to return asset count as per type and also want to concatenate asset ids. I am using FOR XML and path which works fairly good but as soon as I add where clause, it does not work as expected.
This is my table schema and query:
CREATE TABLE [dbo].[Asset]
(
[AssetSeqNumber] [bigint] NULL,
[AssetType] [varchar](100) NULL
) ON [PRIMARY]
GO
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (1, N'Tree')
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (2, N'Tree')
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (3, N'Tree')
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (4, N'Barbecue')
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (5, N'Bridge')
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (101, N'Tree')
INSERT [dbo].[Asset] ([AssetSeqNumber], [AssetType])
VALUES (102, N'Tree')
GO
Query:
SELECT
AssetType,
COUNT(AssetSeqNumber) AS count,
STUFF((SELECT DISTINCT ',' + CAST(AssetSeqNumber AS varchar(100))
FROM Asset
WHERE AssetType = a.AssetType
FOR XML PATH ('')), 1, 1, '') AS AssetIds
FROM
Asset AS a
WHERE
a.AssetSeqNumber IN (1, 2, 3, 4, 5)
GROUP BY
AssetType
This query return result for ids which are not in the where condition (i.e. 101,102). I understand it is because inner query check asset types but I can't figure out how to show expected result.
Note: I am using SQL Server 2019 (v15.0.2095.3 (X64))
You need to modify the where clause for the select statement inside the STUFF function as the following:
WHERE AssetType = a.AssetType AND AssetSeqNumber IN (1, 2, 3, 4, 5)
Also, for your version of SQL Server, you could simplify this by using STRING_AGG function as the following:
SELECT AssetType,
COUNT(*) [Count],
STRING_AGG(AssetSeqNumber, ',') AssetIds
FROM Asset
WHERE AssetSeqNumber IN (1, 2, 3, 4, 5)
GROUP BY AssetType
See a demo for both queries.
your data
drop table if exists #Asset
CREATE TABLE #Asset
(
[AssetSeqNumber] [bigint] NULL,
[AssetType] [varchar](100) NULL
) ON [PRIMARY]
GO
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (1, N'Tree')
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (2, N'Tree')
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (3, N'Tree')
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (4, N'Barbecue')
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (5, N'Bridge')
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (101, N'Tree')
INSERT #Asset ([AssetSeqNumber], [AssetType])
VALUES (102, N'Tree')
GO
you should add your condition into your Xml query
SELECT
AssetType,
COUNT(AssetSeqNumber) AS count,
STUFF((SELECT DISTINCT ',' + CAST(AssetSeqNumber AS varchar(100))
FROM #Asset
WHERE AssetType = a.AssetType /*yourcondition*/and AssetSeqNumber IN (1, 2, 3, 4, 5)
FOR XML PATH ('')), 1, 1, '') AS AssetIds
FROM
#Asset AS a
WHERE
a.AssetSeqNumber IN (1, 2, 3, 4, 5)
GROUP BY
AssetType
by consider using SQL Server 2019,you can use string_agg
SELECT [assettype],
Count(assetseqnumber) count,
String_agg(assetseqnumber, ',') AssetIds
FROM #asset a
WHERE a.assetseqnumber IN ( 1, 2, 3, 4, 5 )
GROUP BY [assettype]

SQL Server output in desired formatted way

I have two tables, tblName and tblCode.
tblName:
CREATE TABLE [dbo].[TblName]
(
[Id] [int] IDENTITY(1,1) NOT NULL,
[Name] [varchar](50) NULL,
CONSTRAINT [PK_TblName]
PRIMARY KEY CLUSTERED ([Id] ASC)
) ON [PRIMARY]
tblCode:
CREATE TABLE [dbo].[tblCode]
(
[NameId] [int] NULL,
[Code] [varchar](50) NULL,
[Value] [varchar](50) NULL
) ON [PRIMARY]
Code:
INSERT INTO [dbo].[TblName]
([Name])
VALUES
('Rahul'),
('Rohit'),
('John'),
('David'),
('Stephen')
GO
INSERT INTO [dbo].[tblCode] ([NameId], [Code], [Value])
VALUES (1, 'DEL', 'Delivery'),
(1, 'DEL', 'Deployment'),
(2, 'REL', 'Release Management'),
(3, 'REL', 'Release Management'),
(4, 'TEST', 'Testing'),
(4, 'TEST', 'Final Testing')
I am trying to write a query to get all Names which are in tblCode with the Code and Value. For example I have NameId 1 present in tblCode with Code 'DEL' and Value as 'Delivery' and 'Deployment'. Similarly I have NameId 2,3 and 4 in tblCode with same or different Code and Value. So I am trying to get output in such a way if same name with same code is present in tblCode then it should come row with Name and Comma separated values as shown in below desired output.
This is they query I am using but its not giving the output I am looking for.
SELECT
N.Name,
CASE
WHEN C.Code = 'DEL'
THEN C.Value
ELSE ''
END As 'CodeValue'
FROM
TblName N
INNER JOIN
tblCode C ON N.Id = C.NameId
WHERE
C.NameId = 1 AND C.Code IN ('DEL', 'REL', 'TEST')
http://sqlfiddle.com/#!6/bdca78/11/0
CREATE TABLE tblName (id INTEGER, name VARCHAR(255));
INSERT INTO tblName VALUES(1, 'Rahul');
INSERT INTO tblName VALUES(2, 'Rohit');
INSERT INTO tblName VALUES(3, 'John');
INSERT INTO tblName VALUES(4, 'David');
INSERT INTO tblName VALUES(5, 'Steven');
CREATE TABLE tblCode(nameId INTEGER, code VARCHAR(255), value VARCHAR(255));
INSERT INTO tblCode VALUES(1, 'DEL', 'Delivery');
INSERT INTO tblCode VALUES(1, 'DEL', 'Development');
INSERT INTO tblCode VALUES(2, 'REL', 'Release Management');
INSERT INTO tblCode VALUES(3, 'REL', 'Release Management');
INSERT INTO tblCode VALUES(4, 'TEST', 'Testing');
INSERT INTO tblCode VALUES(4, 'TEST', 'Final Testing');
SELECT name,
codeValue
FROM
(SELECT tblName.name AS name,
STUFF((SELECT ',' + tblCode.value
FROM tblCode
WHERE tblCode.nameId = tblName.id
FOR XML PATH('')), 1 ,1, '') AS codeValue
FROM tblName) inline_view
WHERE codeValue IS NOT NULL;
Edit
ZoharPeled's solution is correct. This solution returns the name and a comma separated list of value and does not consider the code.
Zohar aggregates the list of value per code, which I think is what's required. OP, if the objective is to aggregate per name per code, including code in the output would make the result set more meaningful.
The following links show the difference, first is my SQL statement, then Zohar's.
My SQL: http://sqlfiddle.com/#!6/94fd0/1/0
Zohar's SQL: http://sqlfiddle.com/#!6/94fd0/2/0
Here is one way to do it:
;WITH CTE AS
(
SELECT DISTINCT
[NameId]
,[Code]
,(
SELECT STUFF(
(SELECT ',' + Value
FROM dbo.tblCode t1
WHERE t0.Code = t1.Code
AND t0.NameId = t1.NameId
FOR XML PATH(''))
, 1, 1, '')
) AS CodeValue
FROM dbo.tblCode t0
)
SELECT Name, CodeValue
FROM tblName
INNER JOIN CTE ON Id = CTE.NameId
ORDER BY Id
Results:
Name CodeValue
Rahul Delivery,Deployment
Rohit Release Management
John Release Management
David Testing,Final Testing
Read this SO post for an explanation on how to use STUFF and FOR XML to create a concatenated string from multiple rows.
You can see a live demo on rextester.

SQL merging tables [duplicate]

This question already has answers here:
Efficiently convert rows to columns in sql server
(5 answers)
Closed 8 years ago.
I am trying to merge a few tables in order to get the output as outlined in the image below.
My issue is that I am not sure what type of joins to use to achieve that
Can someone please help me with the syntax.
You could do something like this, it's a dynamic pivot as you might add/ take away users?
CREATE TABLE #Tests (
Test_ID INT,
TestName VARCHAR(50));
INSERT INTO #Tests VALUES (1, 'SQL Test');
INSERT INTO #Tests VALUES (2, 'C# Test');
INSERT INTO #Tests VALUES (3, 'Java Test');
CREATE TABLE #Users (
[User_ID] INT,
UserName VARCHAR(50));
INSERT INTO #Users VALUES (1, 'Joe');
INSERT INTO #Users VALUES (2, 'Jack');
INSERT INTO #Users VALUES (3, 'Jane');
CREATE TABLE #UserTests (
ID INT,
[User_ID] INT,
Test_ID INT,
Completed INT);
INSERT INTO #UserTests VALUES (1, 1, 1, 0);
INSERT INTO #UserTests VALUES (2, 1, 2, 1);
INSERT INTO #UserTests VALUES (3, 1, 3, 1);
INSERT INTO #UserTests VALUES (4, 2, 1, 0);
INSERT INTO #UserTests VALUES (5, 2, 2, 0);
INSERT INTO #UserTests VALUES (6, 2, 3, 0);
INSERT INTO #UserTests VALUES (7, 3, 1, 1);
INSERT INTO #UserTests VALUES (8, 3, 2, 1);
INSERT INTO #UserTests VALUES (9, 3, 3, 1);
DECLARE #Cols VARCHAR(MAX);
SELECT #Cols = STUFF((SELECT distinct ',' + QUOTENAME(u.UserName)
FROM #Users u
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'');
DECLARE #Query NVARCHAR(MAX);
SELECT #Query = 'SELECT TestName, ' + #Cols + ' FROM
(
SELECT
t.TestName,
u.UserName,
ut.Completed
FROM
#Tests t
INNER JOIN #UserTests ut ON ut.Test_ID = t.Test_ID
INNER JOIN #Users u ON u.[User_ID] = ut.[User_ID]) x
PIVOT (
MAX(Completed)
FOR UserName IN (' + #Cols + ')
) AS pt';
EXEC(#Query);
Results are:
TestName Jack Jane Joe
C# Test 0 1 1
Java Test 0 1 1
SQL Test 0 1 0
(Same results as yours, but in a different sort order.)

Hierarchical data with CTE

I am sorry if the answer has been posted already, but I was not able to find the answer even after searching.
I have the following table
CREATE TABLE [dbo].[emp](
[id] [int] NOT NULL,
[name] [varchar](20) NULL,
[mgrid] [int] NULL
) ON [PRIMARY]
With data
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (1, N'a', 0)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (2, N'a1', 1)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (3, N'a11', 2)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (4, N'a12', 2)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (5, N'a13', 2)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (6, N'a2', 1)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (7, N'a3', 1)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (8, N'a31', 7)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (9, N'a32', 7)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (10, N'b', 0)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (11, N'b1', 10)
INSERT [dbo].[emp] ([id], [name], [mgrid]) VALUES (12, N'b2', 10)
And I would like the following output
a
a1
a11
a12
a13
a2
a3
a31
a32
b
b1
b2
Is this possible in SQL Server?
SQLFiddle demo
You can use ' ' instead of '+' in the last line to pad a string.
with t as
(
select id,name,mgrid,1 as level,cast(name as varchar(max)) as path
from emp where mgrid=0
union all
select emp.id,emp.name,emp.mgrid, t.level+1 as level,
t.path+cast(emp.name as varchar(max)) as path
from emp
join t on emp.mgrid=t.id
)
select replicate('+', level)+name from t order by path
with CTE as
(
select *
, level = 1
from emp
where mgrid = 0
union all
select emp.id,
name = cast(space((level) * 3) + emp.name as varchar(20)),
emp.mgrid,
level = level + 1
from emp
inner join CTE on CTE.id = emp.mgrid
)
select name
from CTE
order by ltrim(name)
Query:
DECLARE #temp TABLE
(
[id] [int] NOT NULL,
[name] [varchar](20) NULL,
[mgrid] [int] NULL
)
INSERT INTO #temp ([id], [name], [mgrid])
VALUES
(1, N'a', 0), (2, N'a1', 1),
(3, N'a11', 2), (4, N'a12', 2),
(5, N'a13', 2), (6, N'a2', 1),
(7, N'a3', 1), (8, N'a31', 7),
(9, N'a32', 7), (10, N'b', 0),
(11, N'b1', 10), (12, N'b2', 10)
DECLARE #out VARCHAR(MAX) = ''
;WITH cte AS
(
SELECT *, Lvl = 0, nn = CAST(name AS VARCHAR(MAX))
FROM #temp
WHERE mgrid = 0
UNION ALL
SELECT t.*, c.Lvl + 1, nn = CAST(REPLICATE(' ', c.Lvl + 1) + t.name AS VARCHAR(MAX))
FROM #temp t
JOIN cte c ON c.id = t.mgrid
)
SELECT #out = (
SELECT nn + CHAR(13)
FROM cte
ORDER BY LTRIM(nn)
FOR XML PATH(''), TYPE).value('.', 'NVARCHAR(MAX)')
PRINT #out
Output:
a
a1
a11
a12
a13
a2
a3
a31
a32
b
b1
b2
You can use CTE (Comnon table expression to achieve this)
check this sample code:
declare #emp table(
[id] [int] NOT NULL,
[name] [varchar](20) NULL,
[mgrid] [int] NULL
)
INSERT #emp ([id], [name], [mgrid]) VALUES (1, N'a', 0)
INSERT #emp ([id], [name], [mgrid]) VALUES (2, N'a1', 1)
INSERT #emp ([id], [name], [mgrid]) VALUES (3, N'a11', 2)
INSERT #emp ([id], [name], [mgrid]) VALUES (4, N'a12', 2)
INSERT #emp ([id], [name], [mgrid]) VALUES (5, N'a13', 2)
INSERT #emp ([id], [name], [mgrid]) VALUES (6, N'a2', 1)
INSERT #emp ([id], [name], [mgrid]) VALUES (7, N'a3', 1)
INSERT #emp ([id], [name], [mgrid]) VALUES (8, N'a31', 7)
INSERT #emp ([id], [name], [mgrid]) VALUES (9, N'a32', 7)
INSERT #emp ([id], [name], [mgrid]) VALUES (10, N'b', 0)
INSERT #emp ([id], [name], [mgrid]) VALUES (11, N'b1', 10)
INSERT #emp ([id], [name], [mgrid]) VALUES (12, N'b2', 10);
with cte (id,name, MGRID) as
(
select id, name,MGRID
from #emp
union all
select c.id, c.name, c.MGRID
from #emp c
inner join cte p
on c.mgrid = P.id
)
SELECT Distinct * FROM CTE
WITH RCTE AS
(
SELECT id, name, mgrid, CAST('' AS NVARCHAR(MAX)) AS blanks , CAST(name AS NVARCHAR(MAX)) AS ordr
FROM dbo.emp WHERE mgrid = 0
UNION ALL
SELECT e.id, e.name, e.mgrid, blanks + ' ' AS blanks, ordr + e.name AS ordr
FROM dbo.emp e
INNER JOIN RCTE r ON e.mgrid = r.id
)
SELECT blanks + name FROM RCTE
ORDER BY ordr

Add not relevant column to group by script

This is my sample table and values:
CREATE TABLE [dbo].[Test]
(
[Id] BIGINT NOT NULL DEFAULT(0),
[VId] BIGINT NOT NULL DEFAULT(0),
[Level] INT NOT NULL DEFAULT(0)
);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (100, 1, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (101, 1, 2);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (102, 1, 3);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (103, 2, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (104, 3, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (105, 3, 2);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (106, 4, 1);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (107, 4, 2);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (108, 4, 3);
INSERT INTO [dbo].[Test] ([Id], [VId], [Level]) VALUES (109, 4, 4);
So at now I use this script:
SELECT
[T].[VId], MAX ([T].[Level]) AS [MaxLevel]
FROM
[dbo].[Test] AS [T]
GROUP BY
[T].[VId];
And it returns:
VId MaxLevel
1 3
2 1
3 2
4 4
But I need Id column also and I can't add it to Group by script, I need the following values:
VId MaxLevel Id
1 3 102
2 1 103
3 2 105
4 4 109
What is your suggestion?
Also The following values is enough The Id's With Max(Level) in any VId :
Id
102
103
105
109
A 2008 take on the question, since that's what you're working with:
declare #Test table
(
[Id] BIGINT NOT NULL DEFAULT(0),
[VId] BIGINT NOT NULL DEFAULT(0),
[Level] INT NOT NULL DEFAULT(0)
);
INSERT INTO #Test ([Id], [VId], [Level])
VALUES (100, 1, 1),(101, 1, 2),(102, 1, 3),(103, 2, 1),(104, 3, 1),
(105, 3, 2),(106, 4, 1),(107, 4, 2),(108, 4, 3),(109, 4, 4);
;With Numbered as (
select *,
RANK() OVER (PARTITION BY VId ORDER BY [Level] desc) as rn
from #Test)
select VId,Level,Id from Numbered where rn=1
Note that (as with the other solutions) this will output multiple rows per VId if there are two rows with the same maximum level. If you don't want that, switch RANK() to ROW_NUMBER() and an arbitrary one will win - or if you want a specific winner in the case of a tie, add that condition into the ORDER BY of the window function.
Use joining with the same table by VId column
something like this:
SELECT [T].[VId], [T].[MaxLevel], [T1].[Id]
FROM [dbo].[Test] AS [T1] JOIN
(SELECT [T].[VId], MAX ([T].[Level]) AS [MaxLevel]
FROM [dbo].[Test] AS [T]
GROUP BY [T].[VId]) AS [T]
ON [T1].[VId] = [T].[VId]
AND [T1].[Level] = [T].[MaxLevel]
ORDER BY [T].[VId];
the result will be:
VId MaxLevel Id
1 3 102
2 1 103
3 2 105
4 4 109
Use this:
WITH LastLevels AS
(
SELECT
[T].[VId] AS [VID],
MAX ([T].[Level]) AS [MaxLevel]
FROM [dbo].[Test] AS [T]
GROUP BY [T].[VId]
)
SELECT [LastLevels].[VID],[LastLevels].[MaxLevel], [Te].[Id]
FROM [dbo].[Test] AS [Te]
INNER JOIN [LastLevels]
ON [LastLevels].[VID]=[Te].[VId]
AND [LastLevels].[MaxLevel]=[Te].[Level]
ORDER BY [LastLevels].[VID];
SELECT ID, [VId], [MaxLevel] From
(
SELECT
[T].[VId], MAX ([T].[Level]) AS [MaxLevel]
FROM
[dbo].[Test] AS [T]
GROUP BY
[T].[VId]
)K
INNER JOIN [Test] T on T.[VId] = K.[VId] and T.[Level] = K.MaxLevel