Query for comma-separated ids to comma-separated values - sql

I have 2 tables
Departments
ID Dept
---------
1 HR
2 Accts
3 IT
Employee
ID Name Depts
-------------------
1 Kevin 2,1
2 Michelle 1
3 Troy 1,3
4 Rheesa 2,3,1
I am looking for an output like the following with a SQL query.
Employee depts
ID Name Depts
-------------------------
1 Kevin Accts,HR
2 Michelle HR
3 Troy HR,IT
4 Rheesa Accts,IT,HR
I have tried the following that join s with depts but results in one row for each dept only. How do i get the above results using a query?
select
name, depts, dept
from
employee
CROSS APPLY
dbo.split_list(employee.depts ,',') split
inner join
dbo.department on depts= split.value
order by
name

DECLARE #Departments TABLE
(
ID INT PRIMARY KEY,
Dept VARCHAR(32) NOT NULL UNIQUE
);
DECLARE #Employees TABLE
(
ID INT PRIMARY KEY,
Name NVARCHAR(64) NOT NULL,
Depts VARCHAR(255) NOT NULL
);
INSERT #Departments VALUES
(1,'HR'), (2,'Accts'), (3,'IT');
INSERT #Employees VALUES
(1,'Kevin','2,1'), (2,'Michelle','1'),
(3,'Troy','1,3'), (4,'Rheesa','2,3,1');
SELECT ID, Name, Depts = STUFF((SELECT ',' + d.Dept
FROM #Departments AS d
INNER JOIN #Employees AS ei
ON ',' + ei.Depts + ',' LIKE '%,' + CONVERT(VARCHAR(12), d.id) + ',%'
WHERE ei.ID = e.ID
ORDER BY Dept
FOR XML PATH,
TYPE).value(N'/text().[1]', N'nvarchar(max)'), 1, 1, N'')
FROM #Employees AS e
ORDER BY ID;
The results don't quite match your required results, as the ordering is deterministic (ordered by department name):
ID Name Depts
---- -------- ----
1 Kevin Accts,HR
2 Michelle HR
3 Troy HR,IT
4 Rheesa Accts,HR,IT
If you want them ordered by the appearance in the comma-separated list, just change:
ORDER BY Dept
To:
ORDER BY CHARINDEX( ',' + CONVERT(VARCHAR(12), d.id) + ',', ',' + ei.Depts + ',')
Results:
ID Name Depts
---- -------- ----
1 Kevin Accts,HR
2 Michelle HR
3 Troy HR,IT
4 Rheesa Accts,IT,HR -- this is the only one affected as it turns out
However, in reality, you should normalize your database. This is an absolute nightmare.

Looking beyond how you're storing your data, let me try to help you out.
Well, you're asking a lot of questions here. First, to split the data, you can format it as XML and use CROSS APPLY -- trick I saw a while back that didn't require built in functions.
That will convert your comma delimited string to a list of strings. You can then use FOR XML to put them back together.
Give this a shot:
SELECT
E.Id,
E.Name,
STUFF(
(
SELECT ',' + D.Department AS [text()]
FROM (
SELECT A.[id],
Split.a.value('.', 'VARCHAR(100)') AS DeptId
FROM
(SELECT [id],
CAST ('<M>' + REPLACE(Depts, ',', '</M><M>') + '</M>' AS XML) AS String
FROM Employee
) AS A
CROSS APPLY String.nodes ('/M') AS Split(a)) A
JOIN Departments D ON A.DeptId = D.Id
WHERE E.Id = A.Id
FOR XML PATH('')
), 1, 1, '') AS Departments
FROM Employee E
And here is the SQL Fiddle.
Good luck.

You can also use a recursive CTE to split the data and then use FOR XML PATH to concatenate the rows into a single row:
;with cte (id, name, deptid, depts) as
(
select id, name,
cast(left(depts, charindex(',',depts+',')-1) as varchar(50)) deptid,
stuff(depts, 1, charindex(',',depts+','), '') depts
from employee
union all
select id, name,
cast(left(depts, charindex(',',depts+',')-1) as varchar(50)) deptid,
stuff(depts, 1, charindex(',',depts+','), '') depts
from cte
where depts > ''
)
select e.id, e.name,
stuff((
select distinct ', '+ d.dept
from cte c
inner join departments d
on c.deptid = d.id
where e.id = c.id
for XML path('')),1,1,'') Depts
from employee e
See SQL Fiddle with Demo
Result:
| ID | NAME | DEPTS |
----------------------------------
| 1 | Kevin | Accts, HR |
| 2 | Michelle | HR |
| 3 | Troy | HR, IT |
| 4 | Rheesa | Accts, HR, IT |

Also you can use option with dynamic management function sys.dm_fts_parser
Before script execution you need check full-text component is installed:
SELECT FULLTEXTSERVICEPROPERTY ('IsFulltextInstalled')
0 = Full-text is not installed.
1 = Full-text is installed.
NULL = Invalid input, or error.
If 0 = Full-text is not installed then this post is necessary to you How to install fulltext on sql server 2008?
SELECT b.ID, b.Name, STUFF((
SELECT ',' + d.Dept
FROM Employees e
JOIN Departments d ON d.ID IN(
SELECT display_term
FROM sys.dm_fts_parser('"' + e.Depts + '"', 1033, NULL, 0)
WHERE display_term NOT LIKE 'nn%'
)
WHERE b.ID = e.ID
ORDER BY d.Dept
FOR XML PATH('')), 1, 1, '') AS Depts
FROM Employees b
OR
SELECT e.ID, e.Name,
(
STUFF((
SELECT ',' + Dept
FROM sys.dm_fts_parser('"' + e.Depts + '"', 1033, NULL, 0) p JOIN Departments d ON p.display_term = d.ID
WHERE display_term NOT LIKE 'nn%'
FOR XML PATH('')), 1, 1, '')
) AS Depts
FROM Employees e

Write a function for splitting comma separated values. I wrote dbo.split
select * from dbo.split('1,2,3',',')
Will return as -
Data
1
2
3
SELECT tact.ActivityID,CONVERT(NVARCHAR(20),tact.createddate,103) AS CallDate,
ActivityOriginatedByPartyID , (ISNULL(p.firstname,'')+' '+ISNULL(p.lastname,'')) AS PartnerName,
u.UserName AS PSTName, ccv.codevalue AS CallType
**, ccv1.codevalue AS CallContext**
,tact.ActivityNote AS CallTrackerNote,
(CONVERT(VARCHAR(20),tact.ActivityTimeSpend) + ' Min') AS CallDuration
FROM txn_activity tact (NOLOCK)
INNER JOIN TXN_Party p (NOLOCK) ON p.PartyID = tact.ActivityOriginatedByPartyID
INNER JOIN TXN_User u (NOLOCK) ON u.userid = tact.createdby
INNER JOIN CFG_CodeValue ccv (NOLOCK) ON ccv.codevalueid = tact.ActivityTypeID
--INNER JOIN CFG_CodeValue ccv1 (NOLOCK) ON ccv1.codevalueid = tact.ActivityContext
**CROSS APPLY
dbo.split(tact.ActivityContext,',') split
inner join
dbo.CFG_CodeValue ccv1 (NOLOCK) ON ccv1.codevalueid = split.data**

Related

SQL Server mulitple results to one row by ID (Version 2012)

Consider this query and result set:
Select udbA.userId, d.dbName
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid
Order By udbA.userId
1 az
1 nc_west
1 bsc_mo
1 NS_002
What I am looking for is a way to flatten this into one record. I know I can do it with a temp table and select into, but I was curious to see if a query could do it directly. A user could have up to 15 databases available to them.
Looking for results like below ( 2 columns -- userid and the database names ):
userid dbname
1 az nc_west bsc_mo NS_002
SQL Server Version: Microsoft SQL Server 2012 (SP3) (KB3072779) - 11.0.6020.0 (X64) Oct 20 2015 15:36:27 Copyright (c) Microsoft Corporation Enterprise Edition (64-bit) on Windows NT 6.3 (Build 9600: ) (Hypervisor)
Assuming you want a space-delimited list of database names:
DECLARE #Access table ( userId int, dbName varchar(50) );
INSERT INTO #Access VALUES
( 1, 'az' ), ( 1, 'nc_west' ), ( 1, 'bsc_mo' ), ( 1, 'NS_002' );
SELECT DISTINCT
ax.userId, db.list
FROM #Access AS ax
OUTER APPLY (
SELECT LTRIM ( (
SELECT ' ' + dbName AS "text()" FROM #Access AS x WHERE x.userId = ax.userId
FOR XML PATH ( '' )
) ) AS list
) AS db;
Returns
+--------+--------------------------+
| userId | list |
+--------+--------------------------+
| 1 | az nc_west bsc_mo NS_002 |
+--------+--------------------------+
For a comma-delimited list:
SELECT DISTINCT
ax.userId, db.list
FROM #Access AS ax
OUTER APPLY (
SELECT STUFF ( (
SELECT ',' + dbName AS "text()" FROM #Access AS x WHERE x.userId = ax.userId
FOR XML PATH ( '' )
), 1, 1, '' ) AS list
) AS db;
Returns
+--------+--------------------------+
| userId | list |
+--------+--------------------------+
| 1 | az,nc_west,bsc_mo,NS_002 |
+--------+--------------------------+
You need GROUP BY and row_number as follows:
select userId,
max(case when rn = 1 then dbName end) as val1,
max(case when rn = 2 then dbName end) as val2,
max(case when rn = 3 then dbName end) as val3,
max(case when rn = 4 then dbName end) as val4
from
(Select udbA.userId, d.dbName,
row_number() over (partition by udbA.userId order by d.dbName) as rn
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid ) t
group by userId
-- update
You just need two columns then use the STRING_AGG as follows:
Select udbA.userId,
string_agg(d.dbName, ' ') within group (order by d.dbName) as dbName
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid
group by udbA,userId
-- For SQL server 2012
Select distinct udbA.userId,
STUFF((SELECT distinct '' + d.dbName
from dbList d
where d.dbid = udbA.dbid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,0,'') AS dbid
FROM user_db_access udbA
WHERE EXISTS (SELECT 1 FROM dbList d
where d.dbid = udbA.dbid)
Or use CTE as follows:
with CTE as
(Select udbA.userId, d.dbName
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid)
Select distinct c.userId,
STUFF((SELECT distinct '' + cc.dbName
from cte cc
where c.dbid = cc.dbid
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,0,'') AS dbid
FROM cte c
Depending on your SQL Server version you can use STRING_AGG
Like this:
Select udbA.userId, string_agg(d.dbName, ', ') as name
from user_db_access udbA
Inner join dbList d on d.dbid = udbA.dbid
group by udbA.userId
If you are using an older version you already have answers here: How to make a query with group_concat in sql server
Like this:
select
udbA.userId,
stuff((
select cast(',' as varchar(max)) + d.dbName
from dbList d
where d.dbid = udbA.dbid
order by d.dbName
for xml path('')), 1, 1, '') as dbList
from
user_db_access udbA
inner join dbList dl on dl.dbid = udbA.dbid
order by
udbA.userId;

Update a column separated by comma for multiple values?

I have following table Userinfo_attarea:
id employee_id area_id
182521 4391 2
182522 4391 3
Personnel_area:
id areaid
1 Area Name
2 PROJECT80
3 PROJECT69
When i update it works fine foe single value but i need a column with multiple values separeted by comma as mentioned below
Expected Output:
areaname
PROJECT80,PROJECT69
i am using following query for update
UPDATE employee
SET employee.areaname = p.areaname
FROM employee join userinfo u
on u.badgenumber=employee.emp_reader_id join userinfo_attarea ua on ua.employee_id=u.userid join personnel_area p on ua.area_id=p.id
JOIN inserted I ON u.userid= I.employee_id
Thanks in advance...
You can try this :
Update E set areaname = T.areaname from #Employee E
Inner Join
(
Select employee_id,STUFF((SELECT ', ' + CAST(areaid AS VARCHAR(10)) [text()]
FROM (
Select U.employee_id,P.areaid from #Userinfo_attarea U
Inner Join #Personnel_area P on U.area_id = P.id
) B
WHERE employee_id = A.employee_id
FOR XML PATH(''), TYPE)
.value('.','NVARCHAR(MAX)'),1,2,' ') areaname from
(
Select U.employee_id,P.areaid from #Userinfo_attarea U
Inner Join #Personnel_area P on U.area_id = P.id
) A
GROUP BY employee_id
) T on T.employee_id = E.id
Working demo

Convert a column of Rows to only one column column in SQL Server query

I have this query in SQL Server 2012 :
select
tblplantitle.id, tblplantoproductpayment.productid
from
tblplantitle
inner join
tblplan on tblplan.plantitleid = tblplantitle.id
inner join
tblplantoproductpayment on tblplantoproductpayment.planid = tblplan.id
group by
tblplantitle.id, tblplantoproductpayment.productid
The result is like this :
id productid
1 1
1 2
1 3
1 10
2 5
2 1
3 4
3 11
but I want this result :
1 1,2,3,10
2 5,1
3 4,11
How can I get that result ?
Try this:
WITH cte as
(
select
tblplantitle.id, tblplantoproductpayment.productid
from
tblplantitle
inner join
tblplan on tblplan.plantitleid = tblplantitle.id
inner join
tblplantoproductpayment on tblplantoproductpayment.planid = tblplan.id
group by
tblplantitle.id, tblplantoproductpayment.productid
)
SELECT id, productid =
STUFF((SELECT ', ' + productid
FROM cte b
WHERE b.id= a.id
FOR XML PATH('')), 1, 2, '')
FROM cte a
GROUP BY id
Use the below query for the desired output.
SELECT tblplantitle.id
, STUFF((SELECT ', ' + CAST(tblplantoproductpayment.productid AS VARCHAR(10)) [text()]
FROM tblplantoproductpayment
WHERE tblplantoproductpayment.planid = tblplan.id
FOR XML PATH(''), TYPE).value('.','NVARCHAR(MAX)'),1,2,' ') List_ProductIds
FROM tblplantitle
INNER JOIN tblplan on tblplan.plantitleid = tblplantitle.id
GROUP BY tblplantitle.id

TSQL + Entity Framework Join tables and concatenate rows into one string column

I am trying to join 3 tables in a query to get a cohesive output that summarizes the employees and their titles. The setup is as below:
CREATE TABLE employees (id INT, name VARCHAR(16))
CREATE TABLE titles (employeeId INT, standardTitleId INT)
CREATE TABLE standard_titles(standardTitleId INT, title VARCHAR(16))
GO
INSERT INTO standard_titles VALUES
(0, 'Co-Founder'),
(1, 'CEO'),
(2, 'CTO'),
(3, 'CFO')
INSERT INTO employees VALUES
(0, 'Bill'),
(1, 'Bob'),
(2, 'Sue')
INSERT INTO titles VALUES
(0, 0),
(0, 1),
(1, 0),
(1, 2),
(2, 3)
GO
My desired output would be something like this:
id name titles
================================
0 Bill Co-Founder, CEO
1 Bob Co-Founder, CTO
2 Sue CFO
After reading through posts, I have managed to come close but also managed to finagle something:
SELECT
e.id,
e.name,
STUFF((
SELECT ', ' + s.title
FROM standard_titles s
INNER JOIN titles t ON t.standardTitleId = s.standardTitleId
INNER JOIN employees e ON e.id = t.employeeId
WHERE t.standardTitleId = s.standardTitleId
FOR XML PATH ('')), 1, 1, '')
FROM employees e
which gives me this:
id name (No column name)
===========================================================
0 Bill Co-Founder, CEO, Co-Founder, CTO, CFO
1 Bob Co-Founder, CEO, Co-Founder, CTO, CFO
2 Sue Co-Founder, CEO, Co-Founder, CTO, CFO
The questions:
What I am doing wrong in the FOR XML PATH part.
Once the TSQL is fixed, is there a way to do this via Entity Framework?
So, here is half an answer, only for your first question:
SELECT
e.id,
e.name,
STUFF((
SELECT ', ' + s.title
FROM standard_titles s
INNER JOIN titles t ON t.standardTitleId = s.standardTitleId
--INNER JOIN employees e ON e.id = t.employeeId
WHERE e.id = t.employeeId
FOR XML PATH ('')), 1, 1, '')
FROM employees e
And here is a demo for you to try.
try this:
SELECT
e.id,
e.name,
STUFF((
SELECT ', ' + s.title
FROM standard_titles s
INNER JOIN titles t ON t.standardTitleId = s.standardTitleId
WHERE e.id = t.employeeId
FOR XML PATH ('')), 1, 1, '')
FROM employees e
You were not correlating to the outer employees table.

sql comma delimited list of a column in analytical function

I am using SQL server 2008, and I need to make a common delimeted list of a column. I know how to do it, but I need this time while I use analytical function, I mean I don't want to use group by clause.
Since I will also select the records in outer query "where row_num=1"
Here is the query:
SELECT UserId
,ProductList
,Value
FROM
(
SELECT p.UserId
,p.Value
, ROW_NUMBER()OVER (PARTITION BY p.UserId ORDER BY p.RecordCreateDate asc) AS 'row_num'
--here I need a solution OVER (PARTITION BY p.UserId) AS 'ProductList'
FROM Products p
INNER JOIN
Users u
ON p.UserId = u.Id
) result
WHERE result.row_num = 1
Users data:
Id Name ....
1 John
2 Anton
3 Craig
Products data:
Id UserId Name RecordCreateDate Value
1 1 a 21.12.2012 10
2 1 b 11.12.2012 20
3 1 c 01.12.2012 30
4 2 e 05.12.2012 40
5 2 f 17.12.2012 50
6 3 d 21.12.2012 60
7 3 i 31.12.2012 70
I need a result such as:
UserId ProductList Value
1 a,b,c 30
2 e,f 40
3 d,i 60
Thanks for your help
Since you are going to filter on row_num = 1 you need to put your query in a CTE or the likes where you include the extra columns from Products. Then you can build your comma separated string in the outer query using the for XML path trick without using group by.
;WITH C as
(
SELECT p.UserId
, ROW_NUMBER()OVER (PARTITION BY p.UserId ORDER BY p.RecordCreateDate asc) AS 'row_num'
--, Some other fields from Products
FROM Products p
INNER JOIN
Users u
ON p.UserId = u.Id
)
SELECT UserId,
--, Some other fields from Products
--, Build the concatenated list here using for xml path()
FROM C
WHERE C.row_num = 1
Just for completeness. Remove the # symbols for your actual solution.
SET NOCOUNT ON;
CREATE TABLE #users
(
Id INT,
Name VARCHAR(32)
);
INSERT #users VALUES
(1,'John'),
(2,'Anton'),
(3,'Craig');
CREATE TABLE #products
(
Id INT,
UserId INT,
Name VARCHAR(32),
RecordCreateDate DATE,
Value INT
);
INSERT #products VALUES
(1,1,'a','2012-12-21',10),
(2,1,'b','2012-12-11',20),
(3,1,'c','2012-12-01',30),
(4,2,'e','2012-12-05',40),
(5,2,'f','2012-12-17',50),
(6,3,'d','2012-12-21',60),
(7,3,'i','2012-12-31',70);
The query:
;WITH x AS
(
SELECT UserId, Value,
row_num = ROW_NUMBER() OVER
(
PARTITION BY UserId
ORDER BY RecordCreateDate
)
FROM #products
)
SELECT
x.UserId,
u.Name,
ProductList = STUFF((
SELECT ',' + Name
FROM #Products AS p
WHERE p.UserId = x.UserId
FOR XML PATH(''),
TYPE).value(N'./text()[1]', N'varchar(max)'),1,1,''),
x.Value
FROM x
INNER JOIN #users AS u
ON x.UserId = u.Id
WHERE x.row_num = 1;
Then clean up:
DROP TABLE #users, #products;
Results:
UserId Name ProductList Value
1 John a,b,c 30
2 Anton e,f 40
3 Craig d,i 60
I'm not sure what you're asking in the beginning, but this will give you the requested output
SELECT UserId,
STUFF((SELECT ',' + ProductName from Products p WHERE p.UserID = u.UserID FOR XML PATH('')), 1, 1, '') as ProductList
FROM Users u