Passing column value in stored procedure in sql - sql

Create Proc [dbo].[SprptAssessmentCompartion_Subject]
#intQstnMasterID int,
#intAssessmentID int,
#intQstnMasterID1 int,
#intAssessmentID1 int,
#intUserID int
As
Begin
select DISTINCT vchSubject,intUserID,SUM(FrstId) as 'FrstId' ,SUM(ScndId) as 'ScndId' from
( SELECT
intSubjectID,
intUserID,
ISNULL([#intAssessmentID],0) as 'FrstId',
ISNULL([#intAssessmentID1],0) as 'ScndId',
FinalsCORE
FROM
(
select DISTINCT intSubjectID,intUserID,intAssignmnetID,SUM(KidTtl) as 'KidScore',COUNT(intSubjectID) AS 'FinalsCORE' ,(SUM(KidTtl) /COUNT(intSubjectID)*100) as 'Ct' from
(
select Answetble.vchAssignmentName ,intAssignmnetID,intUserID,intSlNo,isnull(QuiestionPaper.vchTopic,'-') as 'VchTopic',isnull(Answetble.fltMark,0) as 'KidTtl' ,QuiestionPaper.intSubjectID from QuiestionMapping
inner join QuiestionPaper on QuiestionPaper.intQstnID=QuiestionMapping.intQstnID
Left outer join
(
select distinct intQstnID,intAssessmentID,intAssignmnetID,intUserID,intQstnMasterID,fltMark,fltTotalMark,intAssignedByUserID,vchAssignmentName from
(
SELECT * from ManageAssessment
inner join Assignment ON Assignment.intAssignmnetID=ManageAssessment.intAssessmentID) as B
INNER JOIN UserMapping ON UserMapping.intPTUserID= intAssignedByUserID
where intQstnMasterID=#intQstnMasterID and intAssessmentID=#intAssessmentID AND intPTUserID=#intUserID
OR( intQstnMasterID=#intQstnMasterID1 and intAssessmentID=#intAssessmentID1 AND intPTUserID=#intUserID)
)
as Answetble on Answetble.intQstnID=QuiestionPaper.intQstnID
where QuiestionMapping.intQstnMasterID=#intQstnMasterID OR QuiestionMapping.intQstnMasterID=#intQstnMasterID1
)as A
GROUP BY intAssignmnetID,intSubjectID,intUserID
)
AS t
PIVOT
(
Max(Ct)
FOR intAssignmnetID IN( [#intAssessmentID] ,[#intAssessmentID1])
) AS p
)as B
inner join subject on Subject.intSubjectID=b.intSubjectID
group by subject.vchSubject,B.intUserID
End
Here #intAssessmentID is one of the columns when I use this in stored procedure I will get error
Msg 8114, Level 16, State 1, Procedure SprptAssessmentCompartion_Subject, Line 47
Error converting data type nvarchar to int.
Msg 473, Level 16, State 1, Procedure SprptAssessmentCompartion_Subject, Line 47
The incorrect value "#intAssessmentID" is supplied in the PIVOT operator.
Can anyone help me to resolve the error?

You cannot specify columns for PIVOT this way, you have to use dynamic SQL.
Create Proc [dbo].[SprptAssessmentCompartion_Subject]
#intQstnMasterID int,
#intAssessmentID int,
#intQstnMasterID1 int,
#intAssessmentID1 int,
#intUserID int
As
Begin
declare #sql as nvarchar(max) = N'select DISTINCT vchSubject,intUserID,SUM(FrstId) as ''FrstId'' ,SUM(ScndId) as ''ScndId'' from
( SELECT
intSubjectID,
intUserID,
ISNULL([' + CONVERT(NVARCHAR(20), #intAssessmentID) + N'],0) as ''FrstId'',
ISNULL([' + CONVERT(NVARCHAR(20), #intAssessmentID1) + N'],0) as ''ScndId'',
FinalsCORE
FROM
(
select DISTINCT intSubjectID,intUserID,intAssignmnetID,SUM(KidTtl) as ''KidScore'',COUNT(intSubjectID) AS ''FinalsCORE'' ,(SUM(KidTtl) /COUNT(intSubjectID)*100) as ''Ct'' from
(
select Answetble.vchAssignmentName ,intAssignmnetID,intUserID,intSlNo,isnull(QuiestionPaper.vchTopic,''-'') as ''VchTopic'',isnull(Answetble.fltMark,0) as ''KidTtl'' ,QuiestionPaper.intSubjectID from QuiestionMapping
inner join QuiestionPaper on QuiestionPaper.intQstnID=QuiestionMapping.intQstnID
Left outer join
(
select distinct intQstnID,intAssessmentID,intAssignmnetID,intUserID,intQstnMasterID,fltMark,fltTotalMark,intAssignedByUserID,vchAssignmentName from
(
SELECT * from ManageAssessment
inner join Assignment ON Assignment.intAssignmnetID=ManageAssessment.intAssessmentID) as B
INNER JOIN UserMapping ON UserMapping.intPTUserID= intAssignedByUserID
where intQstnMasterID=#intQstnMasterID and intAssessmentID=#intAssessmentID AND intPTUserID=#intUserID
OR( intQstnMasterID=#intQstnMasterID1 and intAssessmentID=#intAssessmentID1 AND intPTUserID=#intUserID)
)
as Answetble on Answetble.intQstnID=QuiestionPaper.intQstnID
where QuiestionMapping.intQstnMasterID=#intQstnMasterID OR QuiestionMapping.intQstnMasterID=#intQstnMasterID1
)as A
GROUP BY intAssignmnetID,intSubjectID,intUserID
)
AS t
PIVOT
(
Max(Ct)
FOR intAssignmnetID IN( [' + CONVERT(NVARCHAR(20), #intAssessmentID) + N'] ,[' + CONVERT(NVARCHAR(20), #intAssessmentID1) + N'])
) AS p
)as B
inner join subject on Subject.intSubjectID=b.intSubjectID
group by subject.vchSubject,B.intUserID'
exec sp_executesql #sql
End

Related

SQL Server 2014 with clause inside function/procedure

Is it possible to make a user defined function /user defined procedure with the "with" clause inside it?
CREATE FUNCTION udf_UsersComments (
#Id INT
)
RETURNS #UsersComments TABLE (
CommentTextFormatted NVARCHAR(MAX),
DateCommented NVARCHAR(MAX),
Username NVARCHAR(255),
ParentCommentId INT,
Id INT
)
AS
BEGIN
WITH UpperHierarchy (Id, QuestionId, CommentText, ParentCommentId, DateCommented, UserId, HierarchyOrder,
lineage)
AS (SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
0 AS HierarchyOrder,
Cast ('/' AS VARCHAR(255)) AS Lineage
FROM Comments AS com
WHERE com.ParentCommentId IS NULL AND IsDeleted=0
UNION ALL
(SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
HierarchyOrder + 1,
Cast(lineage + Ltrim(Str(com.ParentCommentId, 6, 0))
+ '/' AS VARCHAR(255))
FROM Comments AS com
INNER JOIN UpperHierarchy AS parent
ON com.ParentCommentId = parent.Id
WHERE com.IsDeleted=0))
SELECT CommentTextFormatted, DateCommented, U.Username, ParentCommentId, Com.id
FROM Questions AS Q
INNER JOIN
(SELECT Space(HierarchyOrder*5) + CommentText AS CommentTextFormatted, Id, QuestionId, ParentCommentId, DateCommented, UserId, lineage
FROM UpperHierarchy) AS Com
ON Com.QuestionId=Q.Id
INNER JOIN Users AS U
ON U.Id=Com.UserId
WHERE Q.Id=#Id
ORDER BY lineage + Ltrim(Str(Q.Id, 6, 0))
RETURN
END
GO
And I am getting this error
Msg 444, Level 16, State 2, Procedure udf_UsersComments, Line 13
Select statements included within a function cannot return data to a
client.
Make it as a Inline table valued function. Check this question to know why I chose inline instead of multi line table valued function
CREATE FUNCTION udf_UsersComments (
#Id INT
)
RETURNS TABLE
AS
Return(
WITH UpperHierarchy (Id, QuestionId, CommentText, ParentCommentId, DateCommented, UserId, HierarchyOrder,
lineage)
AS (SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
0 AS HierarchyOrder,
Cast ('/' AS VARCHAR(255)) AS Lineage
FROM Comments AS com
WHERE com.ParentCommentId IS NULL AND IsDeleted=0
UNION ALL
(SELECT com.Id,
com.QuestionId,
com.CommentText,
com.ParentCommentId,
com.DateCommented,
com.UserId,
HierarchyOrder + 1,
Cast(lineage + Ltrim(Str(com.ParentCommentId, 6, 0))
+ '/' AS VARCHAR(255))
FROM Comments AS com
INNER JOIN UpperHierarchy AS parent
ON com.ParentCommentId = parent.Id
WHERE com.IsDeleted=0))
SELECT CommentTextFormatted, DateCommented, U.Username, ParentCommentId, Com.id,ordercol = lineage + Ltrim(Str(Q.Id, 6, 0))
FROM Questions AS Q
INNER JOIN
(SELECT Space(HierarchyOrder*5) + CommentText AS CommentTextFormatted, Id, QuestionId, ParentCommentId, DateCommented, UserId, lineage
FROM UpperHierarchy) AS Com
ON Com.QuestionId=Q.Id
INNER JOIN Users AS U
ON U.Id=Com.UserId
WHERE Q.Id=#Id)
Note, I have added another column in result to do the ordering while selecting the function. You cannot use Order by without TOP inside a function
select CommentTextFormatted, DateCommented, Username, ParentCommentId, id
from udf_UsersComments(1)--some id
order by ordercol
Regarding your original issue, you are missing insert into #UsersComments. CTE select should insert the records into #UsersComments

Manipulate Global Temp Table SQL from Dynamic Pivot

I have the following query:
if OBJECT_ID('TempDB..#t1') is not null
drop table #t1
if OBJECT_ID('TempDB..#t2') is not null
drop table #t2
if OBJECT_ID('TempDB..##temp') is not null
drop table ##temp
select
a.AssetInspectionID, a.ProjectNo, c.SystemDescription,
datepart(year, a.InspectionDate) InspectYear,
a.InspectionDate, a.AssetID, f.AssetNo, e.SurfaceCondition,
a.EquipmentGauging, a.Probe,
d.CalibrationBlock, a.Couplant,
b.AssetPartID, g.PartNo, g.PartDescription,
b.[0Degree], b.[90Degree], b.[180Degree], b.[270Degree]
into
#t1
from
AIM_AssetInspection a
inner join
AIM_AssetInspectionPart b on a.AssetInspectionID = b.AssetInspectionID
inner join
AIM_AssetSystem c on a.SystemID = c.SystemID
inner join
AIM_AssetCalibrationBlock d on a.CalibrationBlockID = d.CalibrationBlockID
inner join
AIM_AssetSurfaceCondition e on a.SurfaceConditionID = e.SurfaceConditionID
inner join
AIM_Asset f on a.AssetID = f.AssetID
inner join
AIM_AssetPart g on b.AssetPartID = g.AssetPartID
order by
a.InspectionDate desc
select
a.AssetNo, a.InspectYear, a.InspectionDate, a.ProjectNo,
a.SystemDescription, a.SurfaceCondition,
a.EquipmentGauging, a.Probe, a.CalibrationBlock, a.Couplant,
a.PartNo, a.PartDescription,
a.[0Degree], a.[90Degree], a.[180Degree], a.[270Degree]
into
#t2
from
#t1 a
-- pivot
declare #inspectyear as nvarchar(max), #query as nvarchar(max);
set #inspectyear = STUFF((select distinct ',' + quotename(InspectYear) from #t2 c
for XML path(''), type).value('.','NVARCHAR(MAX)'),1,1,'')
set #query =
';with data as
(
select assetno, inspectyear,
partno, PartDescription, Position, number
from #t2
unpivot
(
number
for Position in ([0degree], [90degree], [180degree], [270degree])
) unpvt
)
select * into ##temp
from data
pivot
(
sum(number)
for inspectyear in (' + #inspectyear + ')
) pvt
order by PartNo'
--execute(#query);
exec sp_executesql #query = #query;
select * from ##temp;
drop table ##temp;
The result should be:
----------------------------------------------------------------------
assetno partno partdescription Position 2009 2011 2013
----------------------------------------------------------------------
EQ001 p1 part #1 0degree 8 8 9
EQ001 p1 part #1 90degree 9 9 8
EQ001 p1 part #1 180degree 7 7 6
EQ001 p1 part #1 270degree 9 9 8
ff.
Since the temp table is dynamic, I have no idea how to make a calculation. For instance, I want to add another column on the result above. Let say, Calc column which is the calculation from 3 columns 2009, 2011, 2013. The result should be 25. And so on for the other rows.
Is there a way to achieve this?
Really appreciated.
Thank you

sql Id concatenation in sequence in a separate column like running total

I need running Id concatenation just like running balance or total..
Concatenate the previous Ids to current Id row wise just like shown in picture
query is
with relation (Id, [orderSequence])
as
(
select Id,cast(Id as varchar(20))
from [ACChartofAccount]
union all
select p.Id, cast(Cast(r.Id as varchar) + ',' + cast(p.Id as varchar) as varchar(20))
from [ACChartofAccount] p
inner join relation r on p.ParentId = r.Id
)
select Id,orderSequence
from relation
order by orderSequence
You can use below query to get above result.
DECLARE #Table TABLE(ID VARCHAR(10));
INSERT INTO #table(ID) VALUES ('320'),(332),(333),(334),(335);
SELECT mt.ID,
STUFF((
SELECT ', ' + ID
FROM #table t
WHERE t.ID <= mt.ID
FOR XML PATH('')), 1, 2, '') AS oldersequence
FROM #table mt
ORDER BY ID

Total sum wrong value in Dynamic Pivot

I have complicated query which works pretty good (MS SQL 2012). But it makes a mistake when sum up same price items. It count them correctly but only takes price once instead of taking them as a count number. It only appears when same item has same price and from same country. Here is my query ;
CREATE TABLE #ITEMS(ID INT,NAME VARCHAR(30))
INSERT INTO #ITEMS
SELECT 1, 'laptop'
UNION ALL
SELECT 2, 'phone'
UNION ALL
SELECT 3, 'playstation'
UNION ALL
SELECT 4, 'MacBook'
CREATE TABLE #Country(ID INT,NAME VARCHAR(30))
INSERT INTO #Country
SELECT 1, 'England'
UNION ALL
SELECT 2, 'Sweden'
UNION ALL
SELECT 3, 'Russia'
UNION ALL
SELECT 4, 'Italy'
CREATE TABLE [#Pre-Request](Id INT, countryId INT, ItemId INT)
INSERT INTO [#Pre-Request]
SELECT 1,1,3
UNION ALL
SELECT 2,2,1
UNION ALL
SELECT 3,2,2
UNION ALL
SELECT 4,3,3
UNION ALL
SELECT 5,3,3
UNION ALL
SELECT 6,2,3
CREATE TABLE #Offers(Id INT, PRICE VARCHAR(50))
INSERT INTO #Offers
SELECT 18,'257$'
UNION ALL
SELECT 19,'151$'
UNION ALL
SELECT 20,'424$'
UNION ALL
SELECT 21,'433$'
UNION ALL
SELECT 22,'151$'
CREATE TABLE #Request(Id INT, preReqId INT, requestStatus INT,winOfferId INT)
INSERT INTO #Request
SELECT 44, 1, 3, 18
UNION ALL
SELECT 11, 2, 4, 21
UNION ALL
SELECT 53, 3, 4, 20
UNION ALL
SELECT 87, 4, 3, 22
UNION ALL
SELECT 43, 5, 3, 19
UNION ALL
SELECT 43, 6, 2, Null
;WITH CTE AS
(
SELECT DISTINCT I.NAME ITEMNAME,C.NAME COUNTRYNAME
,CAST(REPLACE(TAB.PRICE,'$','')AS INT)PRICE
,COUNT(CASE WHEN TAB.PRICE IS NOT NULL THEN I.NAME END) OVER(PARTITION BY C.NAME,I.NAME) CNTITEM
FROM [#Pre-Request] PR
LEFT JOIN #Items I ON PR.ITEMID=I.ID
LEFT JOIN #COUNTRY C ON PR.COUNTRYID = C.ID
OUTER APPLY
(
SELECT R.preReqId,R.winOfferId,O.PRICE
FROM #Request R
JOIN #Offers O ON R.winOfferId=O.Id
WHERE PR.ID=R.preReqId
)TAB
UNION
-- Used to select Item name and country that are not in Pre-request table and other tables
SELECT I.NAME ,C.NAME ,NULL,0
FROM #Items I
CROSS JOIN #COUNTRY C
)
,CTE2 AS
(
-- Find the sum for number of items
SELECT DISTINCT ISNULL(ITEMNAME,'TOTAL')ITEMNAME,ISNULL(COUNTRYNAME,'TOTAL')COUNTRYNAME,
SUM(PRICE)PRICE
FROM CTE
GROUP BY ITEMNAME,COUNTRYNAME
WITH CUBE
)
,CTE3 AS
(
-- Find the sum of PRICE
SELECT DISTINCT ISNULL(ITEMNAME,'TOTAL')ITEMNAME,ISNULL(COUNTRYNAME,'TOTAL')COUNTRYNAME--,CNTITEM
,SUM(CNTITEM)CNTITEM
FROM
(
SELECT DISTINCT ITEMNAME,COUNTRYNAME,CNTITEM
FROM CTE
)TAB
GROUP BY ITEMNAME,COUNTRYNAME
WITH CUBE
)
SELECT C2.*,C3.CNTITEM,
CAST(C3.CNTITEM AS VARCHAR(20))+'x'+' ' + CAST(C2.PRICE AS VARCHAR(20))+'$' NEWCOL
INTO #NEWTABLE
FROM CTE2 C2
JOIN CTE3 C3 ON C2.COUNTRYNAME=C3.COUNTRYNAME AND C2.ITEMNAME=C3.ITEMNAME
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + ITEMNAME + ']', '[' + ITEMNAME + ']')
FROM (SELECT DISTINCT ITEMNAME FROM #NEWTABLE WHERE ITEMNAME<>'TOTAL') PV
ORDER BY ITEMNAME
-- Since we need Total in last column, we append it at last
SELECT #cols += ',[Total]'
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT COUNTRYNAME,' + #cols + ' FROM
(
SELECT DISTINCT ITEMNAME,COUNTRYNAME,ISNULL(NEWCOL,''0x 0$'')NEWCOL
FROM #NEWTABLE
) x
PIVOT
(
MIN(NEWCOL)
FOR ITEMNAME IN (' + #cols + ')
) p
ORDER BY CASE WHEN (COUNTRYNAME=''Total'') THEN 1 ELSE 0 END,COUNTRYNAME'
EXEC SP_EXECUTESQL #query
and here is result ;
As you can see there are 2 "playstation" from "Russia" it takes correct count (2x) but only take 1 price "151$" (normally it must be 302$). How can I fix this without making major changes from query? Thank you.
I think this does what you want. The problem is in your first CTE where you do the item count and get a distinct on the ItemName, CountryName, and Price.
Instead of getting a distinct, do a group by as shown below and SUM the price.
SELECT I.NAME ITEMNAME, C.NAME COUNTRYNAME
,SUM(CAST(REPLACE(TAB.PRICE,'$','')AS INT))PRICE
,COUNT(CASE WHEN TAB.PRICE IS NOT NULL THEN 1 ELSE NULL END) CNTITEM
FROM [#Pre-Request] PR
LEFT JOIN #Items I ON PR.ITEMID=I.ID
LEFT JOIN #COUNTRY C ON PR.COUNTRYID = C.ID
OUTER APPLY
(
SELECT R.preReqId,R.winOfferId,O.PRICE
FROM #Request R
JOIN #Offers O ON R.winOfferId=O.Id
WHERE PR.ID=R.preReqId
)TAB
GROUP BY
I.NAME
,C.NAME
EDIT:
Here are the results I get:
Here's all of your code starting from the CTEs:
;WITH CTE AS
(
SELECT I.NAME ITEMNAME, C.NAME COUNTRYNAME
,SUM(CAST(REPLACE(TAB.PRICE,'$','')AS INT))PRICE
,COUNT(CASE WHEN TAB.PRICE IS NOT NULL THEN 1 ELSE NULL END) CNTITEM
FROM [#Pre-Request] PR
LEFT JOIN #Items I ON PR.ITEMID=I.ID
LEFT JOIN #COUNTRY C ON PR.COUNTRYID = C.ID
OUTER APPLY
(
SELECT R.preReqId,R.winOfferId,O.PRICE
FROM #Request R
JOIN #Offers O ON R.winOfferId=O.Id
WHERE PR.ID=R.preReqId
)TAB
GROUP BY
I.NAME
,C.NAME
UNION
-- Used to select Item name and country that are not in Pre-request table and other tables
SELECT I.NAME ,C.NAME ,NULL,0
FROM #Items I
CROSS JOIN #COUNTRY C
)
,CTE2 AS
(
-- Find the sum for number of items
SELECT DISTINCT ISNULL(ITEMNAME,'TOTAL')ITEMNAME,ISNULL(COUNTRYNAME,'TOTAL')COUNTRYNAME,
SUM(PRICE)PRICE
FROM CTE
GROUP BY ITEMNAME,COUNTRYNAME
WITH CUBE
)
,CTE3 AS
(
-- Find the sum of PRICE
SELECT DISTINCT ISNULL(ITEMNAME,'TOTAL')ITEMNAME,ISNULL(COUNTRYNAME,'TOTAL')COUNTRYNAME--,CNTITEM
,SUM(CNTITEM)CNTITEM
FROM
(
SELECT DISTINCT ITEMNAME,COUNTRYNAME,CNTITEM
FROM CTE
)TAB
GROUP BY ITEMNAME,COUNTRYNAME
WITH CUBE
)
SELECT C2.*,C3.CNTITEM,
CAST(C3.CNTITEM AS VARCHAR(20))+'x'+' ' + CAST(C2.PRICE AS VARCHAR(20))+'$' NEWCOL
INTO #NEWTABLE
FROM CTE2 C2
JOIN CTE3 C3 ON C2.COUNTRYNAME=C3.COUNTRYNAME AND C2.ITEMNAME=C3.ITEMNAME
DECLARE #cols NVARCHAR (MAX)
SELECT #cols = COALESCE (#cols + ',[' + ITEMNAME + ']', '[' + ITEMNAME + ']')
FROM (SELECT DISTINCT ITEMNAME FROM #NEWTABLE WHERE ITEMNAME<>'TOTAL') PV
ORDER BY ITEMNAME
-- Since we need Total in last column, we append it at last
SELECT #cols += ',[Total]'
DECLARE #query NVARCHAR(MAX)
SET #query = 'SELECT COUNTRYNAME,' + #cols + ' FROM
(
SELECT DISTINCT ITEMNAME,COUNTRYNAME,ISNULL(NEWCOL,''0x 0$'')NEWCOL
FROM #NEWTABLE
) x
PIVOT
(
MIN(NEWCOL)
FOR ITEMNAME IN (' + #cols + ')
) p
ORDER BY CASE WHEN (COUNTRYNAME=''Total'') THEN 1 ELSE 0 END,COUNTRYNAME'
EXEC SP_EXECUTESQL #query

How to declare the columns dynamically in a Select query using PIVOT

I am writing a query to get the address for PersonID. Following query is working for me but it only returns with two Addresses. I want to handle the 'n' number of address with a single query. Is there any way to do this?
Many thanks
SELECT
PersonID, PersonName
[Address1], [Address2]
FROM
(
SELECT
P.PersonID,
P.PersonName,
(ROW_NUMBER() OVER(PARTITION BY P.PersonID ORDER BY A.AddressID)) RowID
FROM tblPerson
INNER JOIN tblAddress AS A ON A.PersonID = P.PersonID
) AS AddressTable
PIVOT
(
MAX(AddressID)
FOR RowID IN ([Address1], [Address2])
) AS PivotTable;
Assuming the following tables and sample data:
USE tempdb;
GO
CREATE TABLE dbo.tblPerson(PersonID INT, PersonName VARCHAR(255));
INSERT dbo.tblPerson SELECT 1, 'Bob'
UNION ALL SELECT 2, 'Charlie'
UNION ALL SELECT 3, 'Frank'
UNION ALL SELECT 4, 'Amore';
CREATE TABLE dbo.tblAddress(AddressID INT, PersonID INT, [Address] VARCHAR(255));
INSERT dbo.tblAddress SELECT 1,1,'255 1st Street'
UNION ALL SELECT 2,2,'99 Elm Street'
UNION ALL SELECT 3,2,'67 Poplar Street'
UNION ALL SELECT 4,2,'222 Oak Ave.'
UNION ALL SELECT 5,1,'36 Main Street, Suite 22'
UNION ALL SELECT 6,4,'77 Sicamore Ct.';
The following query gets the results you want, and shows how it handles 0, 1 or n addresses. In this case the highest number is 3 but you can play with more addresses if you like by adjusting the sample data slightly.
DECLARE #col NVARCHAR(MAX) = N'',
#sel NVARCHAR(MAX) = N'',
#from NVARCHAR(MAX) = N'',
#query NVARCHAR(MAX) = N'';
;WITH m(c) AS
(
SELECT TOP 1 c = COUNT(*)
FROM dbo.tblAddress
GROUP BY PersonID
ORDER BY c DESC
)
SELECT #col = #col + ',[Address' + RTRIM(n.n) + ']',
#sel = #sel + ',' + CHAR(13) + CHAR(10) + '[Address' + RTRIM(n.n) + '] = x'
+ RTRIM(n.n) + '.Address',
#from = #from + CHAR(13) + CHAR(10) + ' LEFT OUTER JOIN xMaster AS x'
+ RTRIM(n.n) + ' ON x' + RTRIM(n.n) + '.PersonID = p.PersonID AND x'
+ RTRIM(n.n) + '.rn = ' + RTRIM(n.n)
FROM m CROSS JOIN (SELECT n = ROW_NUMBER() OVER (ORDER BY [object_id])
FROM sys.all_columns) AS n WHERE n.n <= m.c;
SET #query = N';WITH xMaster AS
(
SELECT PersonID, Address,
rn = ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Address)
FROM dbo.tblAddress
)
SELECT PersonID, PersonName' + #col
+ ' FROM
(
SELECT p.PersonID, p.PersonName, ' + STUFF(#sel, 1, 1, '')
+ CHAR(13) + CHAR(10) + ' FROM dbo.tblPerson AS p ' + #from + '
) AS Addresses;';
PRINT #query;
--EXEC sp_executesql #query;
If you print the SQL you will see this result:
;WITH xMaster AS
(
SELECT PersonID, Address,
rn = ROW_NUMBER() OVER (PARTITION BY PersonID ORDER BY Address)
FROM dbo.tblAddress
)
SELECT PersonID, PersonName,[Address1],[Address2],[Address3] FROM
(
SELECT p.PersonID, p.PersonName,
[Address1] = x1.Address,
[Address2] = x2.Address,
[Address3] = x3.Address
FROM dbo.tblPerson AS p
LEFT OUTER JOIN xMaster AS x1 ON x1.PersonID = p.PersonID AND x1.rn = 1
LEFT OUTER JOIN xMaster AS x2 ON x2.PersonID = p.PersonID AND x2.rn = 2
LEFT OUTER JOIN xMaster AS x3 ON x3.PersonID = p.PersonID AND x3.rn = 3
) AS Addresses;
If you execute it, you will see this:
I know the query to get here is an ugly mess, but your requirement dictates it. It would be easier to return a comma-separated list as I suggested in my comment, or to have the presentation tier deal with the pivoting.