SQL server query to search and stuff multiple rows - sql

I have table employee_table which is like this
org employeeid (int) firstname lastname
1234 56788934 Suresh Raina
1234 56793904 Virat Kohli
then i have project_table which is like this
Project members (varchar)
A123 56788934,56793900
Now i need to fetch corresponding names of employees and stuff in a single row like this.
Project members (varchar)
A123 Suresh Raina, Virat Kohli
I have written below query which is not working. please help.
SELECT project,
(
SELECT message_text = Stuff(
(
SELECT ', ' + Concat(firstname,' ',lastname)
FROM employee_table t1
WHERE t1.org = t2.org
AND CONVERT(VARCHAR,t1.userid) IN (Concat('''',Replace(pt.members,',',''','''),'''')) --adding single quotes at start and end of each number
FOR xml path ('')) , 1, 1, '')
FROM employee_table t2
WHERE t2.userid IN
group BY org;) FROM project_table pt

Here is a solution for SQL Server 2017 onwards.
It is using a pair of handy functions available in SQL Server 2017:
STRING_SPLIT()
STRING_AGG()
Method #2 covers solution for SQL Server 2008 onwards.
SQL
-- DDL and sample data population, start
DECLARE #employee TABLE (org INT, employeeid INT, firstname VARCHAR(20), lastname VARCHAR(30));
INSERT INTO #employee (org, employeeid, firstname, lastname) VALUES
(1234, 56788934, 'Suresh', 'Raina'),
(1234, 56793904, 'Virat', 'Kohli');
DECLARE #project TABLE (project CHAR(4), members VARCHAR(MAX));
INSERT INTO #project (project, members) VALUES
('A123', '56788934,56793904');
-- DDL and sample data population, end
-- Method #1, SQL Server 2017 onwards
SELECT p.project, STRING_AGG(CONCAT(e.firstname, SPACE(1), e.lastname), ', ') AS members
FROM #project AS p CROSS APPLY STRING_SPLIT(members, ',') AS s
INNER JOIN #employee AS e ON s.value = e.employeeid
GROUP BY p.project;
-- Method #2, SQL Server 2008 onwards
DECLARE #separator CHAR(1) = ',';
;WITH rs AS
(
SELECT *
, TRY_CAST('<root><r><![CDATA[' +
REPLACE(members, #separator, ']]></r><r><![CDATA[') + ']]></r></root>' AS XML) AS xmldata
FROM #project
), cte AS
(
SELECT project
, e.*
FROM rs CROSS APPLY xmldata.nodes('/root/r/text()') AS t(c)
INNER JOIN #employee AS e ON c.value('.','INT') = e.employeeid
)
SELECT project
, STUFF((SELECT #separator + CAST(CONCAT(o.firstname, SPACE(1), o.lastname) AS VARCHAR(30)) AS [text()]
FROM cte AS O
WHERE O.project = C.project
FOR XML PATH('')), 1, 1, NULL) AS Members
FROM cte AS c
GROUP BY project;
Output
+---------+---------------------------+
| project | members |
+---------+---------------------------+
| A123 | Suresh Raina, Virat Kohli |
+---------+---------------------------+

Related

How to concatenate strings in SQL Server, and sort/ order by a different column?

I've seen many examples of concatenating strings in SQL Server, but if they worry about sorting, it's always by the column being concatenated.
I need to order the values based on data in a different fields.
Sample table:
ClassID | StudentName | SortOrder
-----------------------------
A |James |1
A |Janice |3
A |Leonard |2
B |Luke |2
B |Leia |1
B |Artoo |3
And the results I'd like to get are:
ClassID |StudentName
--------------------------------
A |James, Leonard, Janice
B |Leia, Luke, Artoo
How can this be done in SQL Server 2016?
(I'm looking forward to STRING_AGG in 2017, but we're not there yet...)
Thanks!
Here you go:
SELECT
s1.ClassID
, STUFF((SELECT
',' + s2.StudentName
FROM dbo.Student AS s2
WHERE s1.classID = s2.ClassID
ORDER BY s2.SortOrder
FOR XML PATH('')), 1, 1, '') AS StudentNames
FROM dbo.Student AS s1
GROUP BY s1.ClassID
SQL Fiddle
MS SQL Server 2017 Schema Setup:
CREATE TABLE MyTable(ClassID varchar(255),StudentName varchar(255),SortOrder int)
INSERT INTO MyTable(ClassID,StudentName,SortOrder)VALUES('A','James',1),('A','Janice',3),('A','Leonard',2),
('B','Luke',2),('B','Lela',1),('B','Artoo',3)
Query 1:
SELECT
t.ClassID
, STUFF((SELECT
',' + t1.StudentName
FROM MyTable t1
WHERE t.classID = t1.ClassID
ORDER BY t1.SortOrder
FOR XML PATH('')), 1, 1, '') AS StudentNamesConcat
FROM MyTable AS t
GROUP BY t.ClassID
Results:
| ClassID | StudentNamesConcat |
|---------|----------------------|
| A | James,Leonard,Janice |
| B | Lela,Luke,Artoo |
Here the query
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL DROP TABLE #Temp;
CREATE TABLE #Temp(ClassId varchar(10),studName varchar(100),SortOrder int)
INSERT INTO #Temp(ClassId , studName, SortOrder)
SELECT 'A','James',1 UNION ALL
SELECT 'A','Janice',3UNION ALL
SELECT 'A','Leonard',2 UNION ALL
SELECT 'B','Luke',2 UNION ALL
SELECT 'B','Leia',1 UNION ALL
SELECT 'B','Artoo',3
-- select * from #Temp
select
distinct
stuff((
select ',' + u.studName
from #Temp u
where u.studName = studName and U.ClassId = L.ClassId
order by u.SortOrder
for xml path('')
),1,1,'') as userlist,ClassId
from #Temp L
group by ClassId
You can try using PIVOT also. This will support even older version of SQL server.
Only limitation : you should know the maximum SortOrder value. Below code will work for SortOrder <=20 of any ClassID
SELECT ClassID, ISNULL([1],'') +ISNULL(', '+[2],'')+ISNULL(', '+[3],'')+ISNULL(', '+
[4],'')+ISNULL(', '+[5],'')+ISNULL(', '+[6],'')+ISNULL(', '+[7],'')+ISNULL(', '+
[8],'')+ISNULL(', '+[9],'')+ISNULL(', '+[10],'')+ISNULL(', '+[11],'')+ISNULL(', '+
[12],'')+ISNULL(', '+[13],'')+ISNULL(', '+[14],'')+ISNULL(', '+[15],'')+ISNULL(', '+
[16],'')+ISNULL(', '+[17],'')+ISNULL(', '+[18],'')+ISNULL(', '+[19],'')+ISNULL(', '+
[20],'') AS StudentName
FROM
(SELECT SortOrder,ClassID,StudentName
FROM [Table1] A
) AS SourceTable
PIVOT
(
MAX(StudentName)
FOR SortOrder IN ([1],[2],[3],[4],[5],[6],[7],[8],[9],[10],[11],[12],[13],[14],[15],[16],[17],[18],[19],[20])
) AS PivotTable

How to Append the details of a person having multiple values in a single Row in in sql [duplicate]

This question already has answers here:
How Stuff and 'For Xml Path' work in SQL Server?
(8 answers)
Closed 5 years ago.
Hi I want to append the 5th Column as "Leave",I am getting the below result with this query
Empname Deptname LeaveType TotalLeave
------------------------------------------------
Andrew CSE SickLeave 3
George IT CasualLeave 1
Andrew CSE CasualLeave 2
George IT SickLeave 2
Here is my query
Select EmployeeDetails.Empname,
DepartmentDetails.Deptname ,
LeaveApplication.LeaveType,
Sum(LeaveApplication.NoOfDays) As TotalLeave
From DepartmentDetails
Inner JOIN EmployeeDetails on EmployeeDetails.DeptID = DepartmentDetails.DeptID
INNER JOIN LeaveApplication On EmployeeDetails.EmpID = LeaveApplication.EmpID
Where LeaveApplication.LeaveFromDate >='2017-01-01'
AND LeaveApplication.LeaveFromDate <='2017-05-31'
and EmployeeDetails.Status=0
and LeaveApplication.leavetype not in ('Forgot Access Card','Permission','Work from Home','Holiday Allowance/Weekend Allowance','On Duty','Night Shift Allowance')
and LeaveApplication.LeaveStatus<>'Rejected'
GROUP BY LeaveApplication.EmpID ,DepartmentDetails.Deptname,EmployeeDetails.Empname,LeaveApplication.LeaveType
Result Needed Like
Empname Deptname LeaveType TotalLeave
-----------------------------------------------------------
Andrew CSE SickLeave-3,Casual-2 5
George IT CasualLeave-1,Sickleave-2 3
--Try This
BEGIN TRAN
CREATE TABLE #Detail( Empname NVARCHAR(50),Deptname NVARCHAR(50),LeaveType NVARCHAR(100),Leave INT )
------------------------------------------------
INSERT INTO #Detail
Select 'Andrew' ,'CSE','SickLeave',3 UNION ALL
SELECT 'George','IT','CasualLeave',1 UNION ALL
Select 'Andrew','CSE','CasualLeave',2 UNION ALL
SELECT 'George','IT','SickLeave',2
SELECT
c.Empname,c.Deptname,
STUFF((
SELECT ', ' + CONVERT(Nvarchar,CP.LeaveType)
from
#Detail CP
WHERE
C.Empname = CP.Empname
FOR XML PATH('')), 1, 2, '') LeaveType,sum(Leave)Total_Leave
FROM
#Detail C
GROUP BY Empname,Deptname
ROLLBACK TRAN
Use STUFF and SUM built in functions :
CREATE TABLE #table1(Empname VARCHAR(20), Deptname VARCHAR(20),LeaveType
VARCHAR(20), TotalLeave INT)
INSERT INTO #table1(Empname , Deptname ,LeaveType , TotalLeave)
SELECT 'Andrew','CSE','SickLeave',3 UNION ALL
SELECT 'George','IT','CasualLeave',1 UNION ALL
SELECT 'Andrew','CSE','CasualLeave',2 UNION ALL
SELECT 'George','IT','SickLeave',2
SELECT Empname , Deptname , STUFF( (SELECT ',' + LeaveType FROM #table1 I2
WHERE I2.Empname = I1.Empname FOR XML PATH('')),1,1,'') LeaveType ,
SUM(TotalLeave) TotalLeave
FROM #table1 I1
GROUP BY Empname , Deptname

SQL Query using inner join

CategoryTable
Code Name
1 Food
2 Non-Food
Existing Table Consists list of category, as for example, I have two only Food and Non-Food
As challenge, I am assigning tenants with category or categories (multiple assignment, as there are tenants which are categorized as food and non-food). I i used to insert Tenant and Code to a new table creating this output
TenantAssignTable
Tenant Code
Tenant1 1,2
Tenant2 1
What I need to do, is to load the TenantAssingTable to gridview consisting the Name of the CategoryCode too like this
Desired Output
Tenant CCode Name
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
I used inner join in my code, but this is limited as I have a string of combined code in Code column.
Select a.tenant, a.ccode, b.name
from TenantAssignTable a inner join CategoryTable b
on a.CCode = b.code
Is there anyway to achieve this kind of output? I know that this is unusual in SQL coding but this is what is challenge as what the desired output is concerned and needs which is to have a multiple assignment of category to a single tenant.
Thanks in advance!
Think simple;
You can with LIKE and XML PATH
DECLARE #CategoryTable TABLE (Code VARCHAR(50), Name VARCHAR(50))
INSERT INTO #CategoryTable
VALUES
('1', 'Food'),
('2', 'Non-Food')
DECLARE #TenantAssignTable TABLE (Tenant VARCHAR(50), Code VARCHAR(50))
INSERT INTO #TenantAssignTable
VALUES
('Tenant1', '1,2'),
('Tenant2', '1')
SELECT
T.Tenant ,
T.Code,
STUFF(
(SELECT
',' + C.Name
FROM
#CategoryTable C
WHERE
',' + REPLACE(T.Code, ' ', '') + ',' LIKE '%,' + C.Code + ',%'
FOR XML PATH('')
), 1, 1, '') A
FROM
#TenantAssignTable T
Result:
Tenant Code A
--------------- ------------ ---------------
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
You can use some XML transformations:
DECLARE #x xml
SELECT #x = (
SELECT CAST('<t name="'+a.tenant +'"><a>'+REPLACE(a.code,',','</a><a>') +'</a></t>' as xml)
FROM TenantAssignTable a
FOR XML PATH('')
)
;WITH cte AS (
SELECT t.v.value('../#name','nvarchar(max)') as Tenant,
t.v.value('.','int') as CCode,
ct.Name
FROM #x.nodes('/t/a') as t(v)
INNER JOIN CategoryTable ct
ON ct.Code = t.v.value('.','int')
)
SELECT DISTINCT
c.Tenant,
STUFF((SELECT ','+CAST(CCode as nvarchar(10))
FROM cte
WHERE c.Tenant = Tenant
FOR XML PATH('')
),1,1,'') as CCode,
STUFF((SELECT ','+Name
FROM cte
WHERE c.Tenant = Tenant
FOR XML PATH('')
),1,1,'') as Name
FROM cte c
Output:
Tenant CCode Name
Tenant1 1,2 Food,Non-Food
Tenant2 1 Food
The first part (defining #x variable) will bring your table to this kind of XML:
<t name="Tenant1">
<a>1</a>
<a>2</a>
</t>
<t name="Tenant2">
<a>1</a>
</t>
Then in CTE part we join XML with table of categories. And after all get data from CTE with the help of FOR XML PATH.
Create Function as below which return Table from separated Value
CREATE FUNCTION [dbo].[fnSplit]
(
#String NVARCHAR(4000),
#Delimiter NCHAR(1)
)
RETURNS TABLE
AS
RETURN
(
WITH Split(stpos,endpos)
AS(
SELECT 0 AS stpos, CHARINDEX(#Delimiter,#String) AS endpos
UNION ALL
SELECT endpos+1, CHARINDEX(#Delimiter,#String,endpos+1)
FROM Split
WHERE endpos > 0
)
SELECT 'Id' = ROW_NUMBER() OVER (ORDER BY (SELECT 1)),
'Data' = SUBSTRING(#String,stpos,COALESCE(NULLIF(endpos,0),LEN(#String)+1)-stpos)
FROM Split
)
Create Function as below which return comma separated Name
CREATE FUNCTION [dbo].[GetCommaSeperatedCategory]
(
#Codes VARCHAR(50)
)
RETURNS VARCHAR(5000)
AS
BEGIN
-- Declare the return variable here
DECLARE #Categories VARCHAR(5000)
SELECT #Categories= STUFF
(
(SELECT ',' + convert(varchar(10), Name, 120)
FROM Category
WHERE Code IN (SELECT Id FROM [dbo].[fnSplit] (#Codes,',') )
ORDER BY Code
FOR XML PATH (''))
, 1, 1, '')
RETURN #Categories
END
AND Last:
SELECT
Tenant,
Code,
(SELECT [dbo].[GetCommaSeperatedCategory] (Code)) AS Name
FROM TblTenant

Not able to formulate query to combine different row values in single row using pivot table

Below is the actual table
In the table above:
1) FEID is the examination ID which remains same for one exam, like ist semester examination of particular class. So it will remain same for all rows in above table as it consists of data of single exam always.
2) To store result of single student, marks for each subject are entered in each row. So if there are 5 subjects in a class,For each student marks of 5 subjects will be stored in 5 separate rows with marks obtained in each subject
3) Result, Result_code, NCHMCTID remain same in each row of single student. Like in above table, their values remain same in 3 rows.
Due to some reasons I cant remove this redundancy
So my question is, I need to store result of one student in single row, but number of rows related to single student to store each subject marks is not pre determined(number of subjects can change and determined dynamically)
So , if I have 5 subjects marks in 5 rows, I need those in single row.
Below is exactly what I need to convert above table into:
Above there are only 3 subjects, but they can be more than 3 subjects also.
To get subjects list, I use below query for the same and get subjects like:
[vb],[c(p)],VB(p) stored in single variable which I was trying to use in pivot table.
DECLARE #values varchar(max);
SET #values = '';
SELECT #values = #values +'['+ CAST(SubjectName AS varchar(max))+ ']' + ','
FROM tbSubjects where SubID IN(Select SubID from tbFinalMarks Where FEID=2) ;
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
Full procedure is below :
ALTER PROCEDURE [dbo].[prFinalMarksLoadByFEID]
#FEID int
AS
BEGIN
SET NOCOUNT ON;
DECLARE #values varchar(max);
SET #values = '';
SELECT #values = #values +'['+ CAST(SubjectName AS varchar(max))+ ']' + ','
FROM tbSubjects where SubID IN(Select SubID from tbFinalMarks Where FEID=2) ;
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
SELECT #values As 'Values'
select Student_Name,#values,Result,NCHMCTID,Examination_Name from
-- SET NOCOUNT ON added to prevent extra result sets from
-- interfering with SELECT statements.
(SELECT dbo.tbStudent.Name AS Student_Name, dbo.tbSubjects.SubjectName AS Subject_Name, dbo.tbFinalMarks.MarksObtained AS Marks_Obtained,
dbo.tbFinalMarks.Result, dbo.tbFinalMarks.ResultCode AS Result_Code, ISNULL(dbo.tbStudent.NCHMCTID, 'Not Available') AS NCHMCTID,
dbo.tbFinalExam.ExaminationName as Examination_Name
FROM dbo.tbFinalMarks INNER JOIN
dbo.tbSubjects ON dbo.tbFinalMarks.SubID = dbo.tbSubjects.SubID INNER JOIN
dbo.tbStudent ON dbo.tbFinalMarks.StdID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbFinalExam ON dbo.tbFinalMarks.FEID = dbo.tbFinalExam.FEID
Where FEID =#FEID
) ps
PIVOT
(
MAX(Marks_Obtained) For Subject_Name IN ([VB],[VB(P)],[C(P)])
) AS pvt
But I am not able to do it. Please help
Below part give me actual table which i need to manipulate for result table
(SELECT dbo.tbStudent.Name AS Student_Name, dbo.tbSubjects.SubjectName AS Subject_Name, dbo.tbFinalMarks.MarksObtained AS Marks_Obtained,
dbo.tbFinalMarks.Result, dbo.tbFinalMarks.ResultCode AS Result_Code, ISNULL(dbo.tbStudent.NCHMCTID, 'Not Available') AS NCHMCTID,
dbo.tbFinalExam.ExaminationName as Examination_Name
FROM dbo.tbFinalMarks INNER JOIN
dbo.tbSubjects ON dbo.tbFinalMarks.SubID = dbo.tbSubjects.SubID INNER JOIN
dbo.tbStudent ON dbo.tbFinalMarks.StdID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbFinalExam ON dbo.tbFinalMarks.FEID = dbo.tbFinalExam.FEID
Where FEID =#FEID
)
I used [vb],[vb(p)],[C(P)] instead of #values ( it contains subjects list) as using # values in below part gives me error:
PIVOT
(
MAX(Marks_Obtained) For Subject_Name IN ([VB],[VB(P)],[C(P)])
) AS pvt
Below is the data:
FEID Student_Name Subject_Name Marks_Obtained Result Result_Code NCID Exam_Name
2 roof VB 100 First 1234 ist semester
2 roof VB(P) 100 First 1234 ist semester
2 roof C(P) 100 First 1234 ist semester
2 Amir VB 100 First nbb 8 ist semester
2 Amir VB(P) 100 First nbb 8 ist semester
2 Amir C(P) 100 First nbb 8 ist semester
Here's your query:
create table #t (FEID int, Student_Name char(4), Subject_Name char(5), Marks_Obtained int,
Result char(5), Result_Code int, NCID char(5), Exam_Name char(12))
go
insert #t values
( 2, 'roof', 'VB ', 100, 'First', NULL, '1234 ', 'ist semester'),
( 2, 'roof', 'VB(P)', 100, 'First', NULL, '1234 ', 'ist semester'),
( 2, 'roof', 'C(P) ', 100, 'First', NULL, '1234 ', 'ist semester'),
( 2, 'Amir', 'VB ', 100, 'First', NULL, 'nbb 8', 'ist semester'),
( 2, 'Amir', 'VB(P)', 100, 'First', NULL, 'nbb 8', 'ist semester'),
( 2, 'Amir', 'C(P) ', 100, 'First', NULL, 'nbb 8', 'ist semester')
go
declare #collist nvarchar(max)
SET #collist = stuff((select distinct ',' + QUOTENAME(subject_name)
FROM #t -- your table here
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
select #collist
declare #q nvarchar(max)
set #q = '
select *
from (
select
Student_Name, subject_name, Marks_Obtained, Exam_Name, Result, NCID, Result_Code
from (
select *
from #t -- your table here
) as x
) as source
pivot (
max(Marks_Obtained)
for subject_name in (' + #collist + ')
) as pvt
'
exec (#q)

SQL Server 2008 inner join with commas

I am trying to figure out how to inner join a table that has ID's to my table that have all the ids in one row separated by commas.
The table with the commas is called (PA):
usersIDs
56488,51233,71055,98304,21577,12500
The table (SU) has the id and the name of the user:
UserID | Name
51233 | Bob Barker
21577 | Billy Knox
56488 | David Miller
etc etc
How can I inner join the name with the comma separated ID's in my first table?
SELECT DISTINCT SU.Name, SU.UserID, SU.Enabled
FROM Sec_User AS SU INNER JOIN
Program_Access AS PA ON SU.UserId = PA.userIDS
Here's a way without a function:
SELECT DISTINCT SU.UserID, SU.Name, SU.Enabled
FROM dbo.Sec_User AS SU
INNER JOIN dbo.Program_Access AS PA
ON ',' + PA.usersIDs + ',' LIKE '%,' + RTRIM(SU.UserID) + ',%';
But including the function for completeness (and because having such a function is very useful for many purposes). First create a table-valued function:
CREATE FUNCTION dbo.SplitInts
(
#List VARCHAR(MAX),
#Delimiter CHAR(1)
)
RETURNS TABLE
AS
RETURN ( SELECT Item = CONVERT(INT, Item) FROM (
SELECT Item = x.i.value('(./text())[1]', 'int') FROM (
SELECT [XML] = CONVERT(XML, '<i>' + REPLACE(#List, #Delimiter, '</i><i>')
+ '</i>').query('.') ) AS a CROSS APPLY [XML].nodes('i') AS x(i)) AS y
WHERE Item IS NOT NULL
);
Now the JOIN:
SELECT DISTINCT SU.UserID, SU.Name, SU.Enabled
FROM dbo.Program_Access AS PA
CROSS APPLY dbo.SplitInts(PA.usersIDs, ',') AS s
INNER JOIN dbo.Sec_User AS SU
ON SU.UserID = s.Item;
1) First of all, create split function.
Example here.
2) Then, try to do like this:
DECLARE #PA TABLE
( usersIDs VARCHAR(50))
INSERT INTO #PA(usersIDs) VALUES('56488,51233,71055,98304,21577,12500')
DECLARE #SU TABLE
(UserID INT, Name VARCHAR(50))
[enter link description here][2]INSERT INTO #SU(UserID, Name) VALUES (51233, 'Bob Barker')
INSERT INTO #SU(UserID, Name) VALUES (21577, 'Billy Knox')
INSERT INTO #SU(UserID, Name) VALUES (56488, 'David Miller')
SELECT * FROM #PA
CROSS APPLY dbo.fnSplit(usersIDs,',')
LEFT JOIN #SU ON item = UserID
According to this link How To Split A Comma Delimited String, you can have results like this:
NewPA_Table
RecID UserID
1 56488
2 51233
3 71055
4 98304
5 21577
6 12500
and by generating that result, you could now join it to table SU.