For Nvarchar(Max) I am only getting 4000 characters in TSQL? - sql

This is for SS 2005.
Why I am i only getting 4000 characters and not 8000?
It truncates the string #SQL1 at 4000.
ALTER PROCEDURE sp_AlloctionReport(
#where NVARCHAR(1000),
#alldate NVARCHAR(200),
#alldateprevweek NVARCHAR(200))
AS
DECLARE #SQL1 NVARCHAR(Max)
SET #SQL1 = 'SELECT DISTINCT VenueInfo.VenueID, VenueInfo.VenueName, VenuePanels.PanelID,
VenueInfo.CompanyName, VenuePanels.ProductCode, VenuePanels.MF, VenueInfo.Address1,
VenueInfo.Address2, '' As AllocationDate, '' As AbbreviationCode, VenueInfo.Suburb, VenueInfo.Route, VenueInfo.ContactFirstName,
VenueInfo.ContactLastName, VenueInfo.SuitableTime, VenueInfo.OldVenueName,
VenueCategories.Category, VenueInfo.Phone, VenuePanels.Location, VenuePanels.Comment,
[VenueCategories].[Category] + '' Allocations'' AS ReportHeader,
ljs.AbbreviationCode AS PrevWeekCampaign
FROM (((VenueInfo INNER JOIN VenuePanels ON VenueInfo.VenueID = VenuePanels.VenueID)
INNER JOIN VenueCategories ON VenueInfo.CategoryID = VenueCategories.CategoryID)
LEFT JOIN (SELECT CampaignProductions.AbbreviationCode, VenuePanels.PanelID, CampaignAllocations.AllocationDate
FROM (((VenueInfo INNER JOIN VenuePanels ON VenueInfo.VenueID=VenuePanels.VenueID) INNER JOIN CampaignAllocations ON VenuePanels.PanelID=CampaignAllocations.PanelID) INNER JOIN CampaignProductions ON CampaignAllocations.CampaignID=CampaignProductions.CampaignID) INNER JOIN VenueCategories ON VenueInfo.CategoryID=VenueCategories.CategoryID
WHERE ' + #alldateprevweek + ') ljs
ON VenuePanels.PanelID = ljs.PanelID)
INNER JOIN (SELECT VenueInfo.VenueID, VenuePanels.PanelID, VenueInfo.VenueName, VenueInfo.CompanyName, VenuePanels.ProductCode,
VenuePanels.MF, VenueInfo.Address1, VenueInfo.Address2, CampaignAllocations.AllocationDate,
CampaignProductions.AbbreviationCode, VenueInfo.Suburb, VenueInfo.Route, VenueInfo.ContactFirstName,
VenueInfo.ContactLastName, VenueInfo.SuitableTime, VenueInfo.OldVenueName, VenueCategories.Category,
VenueInfo.Phone, VenuePanels.Location, VenuePanels.Comment, [Category] + '' Allocations'' AS ReportHeader,
ljs2.AbbreviationCode AS PrevWeekCampaign
FROM ((((VenueInfo INNER JOIN VenuePanels ON VenueInfo.VenueID = VenuePanels.VenueID)
INNER JOIN CampaignAllocations ON VenuePanels.PanelID = CampaignAllocations.PanelID)
INNER JOIN CampaignProductions ON CampaignAllocations.CampaignID = CampaignProductions.CampaignID)
INNER JOIN VenueCategories ON VenueInfo.CategoryID = VenueCategories.CategoryID)
LEFT JOIN (SELECT CampaignProductions.AbbreviationCode, VenuePanels.PanelID, CampaignAllocations.AllocationDate
FROM (((VenueInfo INNER JOIN VenuePanels ON VenueInfo.VenueID=VenuePanels.VenueID) INNER JOIN CampaignAllocations ON VenuePanels.PanelID=CampaignAllocations.PanelID) INNER JOIN CampaignProductions ON CampaignAllocations.CampaignID=CampaignProductions.CampaignID) INNER JOIN VenueCategories ON VenueInfo.CategoryID=VenueCategories.CategoryID
WHERE ' + #alldateprevweek + ') ljs2
ON VenuePanels.PanelID = ljs2.PanelID
WHERE ' + #alldate + ' AND ' + #where + ') ljs3
ON VenueInfo.VenueID = ljs3.VenueID
WHERE (((VenuePanels.PanelID)<>ljs3.[PanelID] And
(VenuePanels.PanelID) Not In (SELECT PanelID FROM CampaignAllocations WHERE ' + #alldateprevweek + '))
AND ' + #where + ')
UNION ALL
SELECT VenueInfo.VenueID, VenueInfo.VenueName, VenuePanels.PanelID, VenueInfo.CompanyName, VenuePanels.ProductCode,
VenuePanels.MF, VenueInfo.Address1, VenueInfo.Address2, CampaignAllocations.AllocationDate,
CampaignProductions.AbbreviationCode, VenueInfo.Suburb, VenueInfo.Route, VenueInfo.ContactFirstName,
VenueInfo.ContactLastName, VenueInfo.SuitableTime, VenueInfo.OldVenueName, VenueCategories.Category,
VenueInfo.Phone, VenuePanels.Location, VenuePanels.Comment, [Category] + '' Allocations'' AS ReportHeader,
ljs.AbbreviationCode AS PrevWeekCampaign
FROM ((((VenueInfo INNER JOIN VenuePanels ON VenueInfo.VenueID = VenuePanels.VenueID)
INNER JOIN CampaignAllocations ON VenuePanels.PanelID = CampaignAllocations.PanelID)
INNER JOIN CampaignProductions ON CampaignAllocations.CampaignID = CampaignProductions.CampaignID)
INNER JOIN VenueCategories ON VenueInfo.CategoryID = VenueCategories.CategoryID)
LEFT JOIN (SELECT CampaignProductions.AbbreviationCode, VenuePanels.PanelID, CampaignAllocations.AllocationDate
FROM (((VenueInfo INNER JOIN VenuePanels ON VenueInfo.VenueID=VenuePanels.VenueID) INNER JOIN CampaignAllocations ON VenuePanels.PanelID=CampaignAllocations.PanelID) INNER JOIN CampaignProductions ON CampaignAllocations.CampaignID=CampaignProductions.CampaignID) INNER JOIN VenueCategories ON VenueInfo.CategoryID=VenueCategories.CategoryID
WHERE ' + #alldateprevweek + ') ljs
ON VenuePanels.PanelID = ljs.PanelID
WHERE ' + #alldate + ' AND ' + #where
Select #SQL1

You have declared this as nvarchar(max) which allows 2GB of data so it will store 2GB.
What is happening:
The datatype is not yet nvarchar(max) until assignment to #sql1
Before that, it's a collection of strings, each less than 4000 (constants)
You are concatenating short constants with short variables (short = < 4000)
So you have 4000 characters put into #sql1
So, you have make sure you have nvarchar(max) on the right hand side.
One idea. The 2nd line concatenates nvarchar(max) with a constant = nvarchar(max)
SET #SQL1 = ''
SET #SQL1 = #SQL1 + 'SELECT DISTINCT Venue...
....
It's no different to the integer division that happens in every langauge.
declare #myvar float
set #myvar = 1/2 --gives zero because it's integer on the right
Operator precedence (infers datatype precedence) is always "assignment" last... why should unicode strings in SQL Server be any different?

Update: gbn's answer is right, and I was wrong. As MSDN points out, nvarchar(max) supports up to 2^31-1 bytes of data, stored as UCS-2 (2 bytes per character, plus 2 for BOM). Your problem seems to be with string concatenation, not data type limits.
That said, if you're using it to build a SQL string, why not use VARCHAR? Do you have field names that aren't representable by the database's native character set (usually Latin-1)?
Finally -- you could simplify your entire problem by just not using dynamic SQL in your stored procedure. Create some table-valued functions that take your where-clause strings and return tables, and then just JOIN them in your procedure. As a bonus it will almost certainly be much faster, since at very least the database will be able to cache the SP body as a prepared statement.

i resolve problem
just include N character before every string and problem solved
for example
declare #sql nvarchar(max) = '' + #Where + 'SomeThing';
must be
declare #sql nvarchar(max) = N'' + #Where + N'SomeThing';
if you set string to empty also must set N''
if #where is null
set #where = N''
:-) simple answer

Related

SQL - concatenate strings in variable while loop with +=

I can't concatenate in this example bellow!
When I loop I get my 2 correct results.
When I concatenate #MaterialCompositionEn += ', ' it works fine
When I try to concatenate #MaterialCompositionEn += same query to get the 2nd row, I have a null!
DECLARE #MaterialCompositionId int = 475;
DECLARE #MaterialCompositionKey nvarchar(50) = '202071512324138';
DECLARE #Records nvarchar(250);
DECLARE #RecordProceed int;
DECLARE #MaterialCompositionEn nvarchar(500);
SET #Records = (SELECT STRING_AGG(Id, ',') FROM MaterialCompositions mc WHERE mc.MaterialCompositionId = #MaterialCompositionId)
WHILE len(#Records) > 0
BEGIN
SET #RecordProceed = CAST(LEFT(#Records,4) AS int)
if #RecordProceed > 0
BEGIN
SET #Records = REPLACE(#Records,substring(#Records, 1, 4),'')
END
if len(#Records) > 4
BEGIN
SET #Records = REPLACE(#Records,substring(#Records, 1, 1),'')
END
if len(#MaterialCompositionEn) > 0
BEGIN
SET #MaterialCompositionEn += ', '
END
PRINT 'MaterialCompositionEn1: ' + #MaterialCompositionEn
SET #MaterialCompositionEn =
(SELECT COALESCE (CAST(MaterialProportion AS nvarchar) + '% ', '') +
(SELECT mp.MaterialPrimaryEn +
COALESCE(
(SELECT ' (' + ms.MaterialSecondaryEn + ')' AS MS1 FROM dbo.MaterialSecondaries AS ms WHERE ms.Id = mc.MaterialSecondaryId)
, '')
FROM dbo.MaterialPrimaries AS mp WHERE mp.Id = mc.MaterialPrimaryId)
FROM MaterialCompositions mc WHERE mc.Id = #RecordProceed
)
PRINT 'MaterialCompositionEn2: ' + #MaterialCompositionEn
END
Result:
MaterialCompositionEn2: 20% Cashmere
MaterialCompositionEn1: 20% Cashmere,
MaterialCompositionEn2: 80% Wool
Now when I change to:
SET #MaterialCompositionEn +=
(SELECT COALESCE......
I am expecting 20% Cashmere, 80% Wool
instead my 3 prints are NULL
I tried to CAST but won't help.
Any idea?
Thanks in advance
I'm guessing there is a much simpler way to do what you want. However, I think the problem is that you need to initialize the string. So at the top of the code block put:
SET #MaterialCompositionEn = '';
SET #MaterialCompositionEn =
SELECT
CONCAT(
mp.MaterialPrimaryEn, ' ', MaterialProportion, '% ', --always show primary
', ' + ms.MaterialSecondaryEn + CONCAT(100 - MaterialProportion, '%') --sometimes show secondary
)
FROM
MaterialCompositions mc
INNER JOIN dbo.MaterialPrimaries mp ON mp.Id = mc.MaterialPrimaryId
LEFT JOIN dbo.MaterialSecondaries ms ON ms.Id = mc.MaterialSecondaryId
WHERE mc.Id = #RecordProceed
)
Something like this might be neater.. Note that I'm not clear where MaterialProportion comes from (I suspect MaterialCompositions), so this perhaps isn't a solution, just a note as to how you might use CONCAT/avoid having boatloads of nested selects. The use of INNER/OUTER join links compositions and definitely a primary, possibly a secondary material. If the secondary material is null then the aim is to hide it with a mix and match of CONCAT and +
CONCAT treats nulls as empty strings, where as + causes the whole expression to become null. This can be useful to mix and match e.g. in something like ', ' + ms.MaterialSecondaryEn + CONCAT(100 - MaterialProportion, '%'):
the CONCAT(100 - MaterialProportion, '%') would be 20% (if the primary material was 80%) but
the ', ' + ms.MaterialSecondaryEn + CONCAT(...) as a whole is NULL if MaterialSecondaryEn IS NULL from a left join fail to match, so where there is only a primary material, a the string describing the secondary should disappear entirely as NULL, which the outer CONCAT handles as an empty string

Pivot table giving wrong result

Below code gives me result set as shown in first image
SELECT dbo.tbStudent.Name, dbo.tbStudent.RegNo, dbo.tbFee.PID, dbo.tbFee.Purpose, dbo.tbFee.AmountPaid, dbo.tbFee.StudentID, dbo.tbFee.Date, dbo.tbFee.FeeID,
dbo.tbFee.SemID, dbo.tbFee.CourseID, dbo.tbFee.ModeOfPayment, dbo.tbFee.CheckNo, dbo.tbFee.DDNo, dbo.tbFee.HostelDDNo, dbo.tbFee.FRID,
dbo.tbStudent.Parentage, dbo.tbCourse.Name AS Course, ISNULL(dbo.tbSemester.SemName, ' + #st +') AS Semester
FROM dbo.tbFee INNER JOIN
dbo.tbStudent ON dbo.tbFee.StudentID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbCourse ON dbo.tbFee.CourseID = dbo.tbCourse.CourseID LEFT OUTER JOIN
dbo.tbSemester ON dbo.tbFee.SemID = dbo.tbSemester.SemID Where tbFee.SemID=1
However using Pivot table I need result as below:
My code for pivot table is :
SET #values = '';
If(#SemID=0)
BEGIN
SELECT #values = #values +'['+ CAST(PurPose AS varchar(max))+ ']' + ','
FROM tbFee Where CourseID=#CourseID
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
END
ELSE
BEGIN
SELECT #values = #values +'['+ CAST(PurPose AS varchar(max))+ ']' + ','
FROM tbFee Where SemID=#SemID
SET #values = SUBSTRING(#values, 1, Len(#values) - 1)
END
Declare #st nvarchar(max)
set #st='''Not Available''';
declare #q nvarchar(max)
set #q = '
Select * from(
SELECT dbo.tbStudent.Name, dbo.tbStudent.RegNo, dbo.tbFee.PID, dbo.tbFee.Purpose, dbo.tbFee.AmountPaid, dbo.tbFee.StudentID, dbo.tbFee.Date, dbo.tbFee.FeeID,
dbo.tbFee.SemID, dbo.tbFee.CourseID, dbo.tbFee.ModeOfPayment, dbo.tbFee.CheckNo, dbo.tbFee.DDNo, dbo.tbFee.HostelDDNo, dbo.tbFee.FRID,
dbo.tbStudent.Parentage, dbo.tbCourse.Name AS Course, ISNULL(dbo.tbSemester.SemName, ' + #st +') AS Semester
FROM dbo.tbFee INNER JOIN
dbo.tbStudent ON dbo.tbFee.StudentID = dbo.tbStudent.StudentID INNER JOIN
dbo.tbCourse ON dbo.tbFee.CourseID = dbo.tbCourse.CourseID LEFT OUTER JOIN
dbo.tbSemester ON dbo.tbFee.SemID = dbo.tbSemester.SemID Where tbFee.SemID=1
) as x
pivot (
max(AmountPaid)
for Purpose in (' + #values + ')
) as pvt
'
exec (#q)
I am getting Values of Purpose columns in #values due to the reason that number of rows can change. However instead of getting result as single row for same student having same regNo , I am getting below result :
But what I am getting is below:
In the source query for your PIVOT, you should only specify those columns which are involved in the actual pivot - namely dbo.tbStudent.Name, dbo.tbStudent.RegNo, dbo.tbFee.Purpose, dbo.tbFee.AmountPaid.
SELECT
dbo.tbStudent.Name,
dbo.tbStudent.RegNo,
dbo.tbFee.Purpose,
dbo.tbFee.AmountPaid
FROM
dbo.tbFee
INNER JOIN dbo.tbStudent ON dbo.tbFee.StudentID = dbo.tbStudent.StudentID
INNER JOIN dbo.tbCourse ON dbo.tbFee.CourseID = dbo.tbCourse.CourseID
LEFT OUTER JOIN dbo.tbSemester ON dbo.tbFee.SemID = dbo.tbSemester.SemID
Where tbFee.SemID=1
If any other columns apart from these are present, they will be factored into the pivot computation, and you will get multiple rows accordingly.

Translating MySQL statement to MS SQL (no GROUP_CONCAT)

I'm trying to translate the query from my question in SQL multiple rows as columns (optimizing). It is in MySQL but I need it to also run on a MS SQL Server.
One problem is that there is no GROUP_CONCAT in MS SQL, but there seems to be ways to simulate this however (Simulating group_concat MySQL function in Microsoft SQL Server 2005?).
Also, I can't find a way to to store the first SELECT statement into the #sql variable the same way which troubles me as I don't know how to then reference colkey as I currently do.
The MySQL statement:
SET #sql = NULL;
SELECT
GROUP_CONCAT(DISTINCT
CONCAT('MAX(CASE
WHEN ckm.colkey = ', colkey, ' THEN
(ccdr.value)
END) AS ', CONCAT('`ExtraColumn_', colkey, '`'))
) INTO #sql
FROM test_customkeymapping;
SET #sql = CONCAT('SELECT c.Name, ', #sql, '
FROM customers c
LEFT JOIN customercustomdatarels ccdr
ON c.Id = ccdr.customer
LEFT JOIN customdatas cd
ON cd.Id = ccdr.customdata
LEFT JOIN test_customkeymapping ckm
ON cd.key = ckm.customkey
GROUP BY c.Id');
PREPARE stmt FROM #sql;
EXECUTE stmt;
In SQL Server you need to make the following changes
Explicitly declare your variable with a type
Use + to concatenate strings instead of CONCAT (Unless you are using SQL Server 2012 or later)
Use brackets ([]) for object names/aliases instead of backticks (``) - QUOTENAME will do this for you
Use XML extensions to concatenate rows
Include c.Name in the group by as it is contained in the select
Use SP_EXECUTESQL to actually execute your query
So your query becomes something like:
DECLARE #SQL NVARCHAR(MAX);
SET #SQL = 'SELECT c.Name' + ( SELECT DISTINCT
', MAX(CASE WHEN ckm.colkey = '
+ QUOTENAME(colKey AS VARCHAR(10))
+ ' THEN (ccdr.value) END) AS '
+ QUOTENAME('ExtraColumn_' + CAST(colKey AS VARCHAR(10))
FROM test_customkeymapping
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)') +
'FROM customers c
LEFT JOIN customercustomdatarels ccdr
ON c.Id = ccdr.customer
LEFT JOIN customdatas cd
ON cd.Id = ccdr.customdata
LEFT JOIN test_customkeymapping ckm
ON cd.[key] = ckm.customkey
GROUP BY c.ID, c.Name';
EXECUTE SP_EXECUTESQL #SQL;

Error when using single quote with sp_executesql

I have the following SQL statement so that I can script out view creation using if not exists with sp_executesql but the statement is giving me errors due to the single quotes around 1 in my last where statement. Is there any way around this?
IF NOT EXISTS (SELECT * FROM SYS.objects WHERE NAME = 'vw_JeopardyAlertDetails' AND TYPE = 'V')
EXEC sp_executesql #statement = N'CREATE VIEW [dbo].[vw_JeopardyAlertDetails]
AS
SELECT Main.TicketNumber, TS.TicketStateDesc, Main.ApptEnd, ISNULL(C.FirstName, '') AS FirstName, ISNULL(C.LastName, '') AS LastName, ISNULL(Main.CustomerID, '')
AS CustomerID, Main.ApptID, Main.ID, Main.TicketType
FROM (SELECT s.TicketState, s.TicketID AS ID, s.ApptEnd, dbo.Ticket.TicketNumber, dbo.Ticket.TimeOpened, dbo.Ticket.CreatedBy, dbo.Ticket.ReportedBy,
dbo.Ticket.ModifiedBy, dbo.Ticket.ChangedBy, dbo.Ticket.Priority, dbo.Ticket.ServingArea, dbo.Ticket.StructureLink, dbo.Ticket.CustomerID,
dbo.Ticket.TicketCategory, dbo.Ticket.TicketCode, s.ApptStart, s.TicketType, s.CanReschedule, ISNULL(s.ID, 0) AS ApptID
FROM dbo.Schedule AS s INNER JOIN
dbo.Ticket ON s.TicketID = dbo.Ticket.ID
WHERE (s.TicketState IN
(SELECT DISTINCT TicketState
FROM dbo.AlertJeopardyTicketState
WHERE (IsJeopardyState = '1'))) AND (s.ApptEnd >= CONVERT(DATETIME, CONVERT(VARCHAR(10), GETDATE(), 111) + ' ' + dbo.GetJeopardyStartTime()))
AND (s.ApptEnd <= GETDATE())) AS Main LEFT OUTER JOIN
dbo.Customer AS C ON Main.CustomerID = C.ID LEFT OUTER JOIN
dbo.TicketStatus AS TS ON Main.TicketState = TS.ID
GO' ;
ELSE PRINT N'vw_JeopardyAlertDetails ALREADY EXISTS'
GO
You'll need to double up those quotes in your Sql #statement, e.g.
ISNULL(C.FirstName, '')
needs to be
ISNULL(C.FirstName, '''')
Simplified fiddle here

Operand type clash: varchar is incompatible with User-Defined Table Type

I created a User-Defined Table Type:
CREATE TYPE dbo.ListTableType AS TABLE(
ITEM varchar(500) NULL
)
I leverage it in a function:
CREATE FUNCTION dbo.fn_list_to_string
(
#LIST dbo.ListTableType READONLY
)
RETURNS varchar(max)
AS
BEGIN
DECLARE #RESULT varchar(max)
SET #RESULT = ''
DECLARE #NL AS CHAR(2) = CHAR(13) + CHAR(10)
SELECT #RESULT = #RESULT + ITEM + #NL FROM #LIST
SET #RESULT = SUBSTRING(#RESULT, 1, LEN(#RESULT) - 1)
RETURN #RESULT
END
Finally, I try to use this function in a simple select:
SELECT
P.PROGRAM_ID,
PROGRAM_NAME,
PROGRAM_DESC,
P.STATUS_ID,
STATUS_DESC,
P.CONTACT_SID,
I.FIRST_NAME + ' ' + I.LAST_NAME as CONTACT_NAME,
P.CLARITY_ID,
dbo.fn_list_to_string(
( SELECT CONVERT(varchar,CLARITY_ID) as ITEM
FROM dbo.MUSEUM_PROGRAM_PROJECTS as A
JOIN dbo.MUSEUM_PROJECTS as B on B.PROJECT_ID = A.PROJECT_ID
WHERE PROGRAM_ID = P.PROGRAM_ID )
) as PROJECT_CLARITY_IDS
FROM dbo.MUSEUM_PROGRAMS as P
LEFT JOIN dbo.MUSEUM_PROGRAM_STATUS_TYPES as S on S.STATUS_ID = P.STATUS_ID
LEFT JOIN dbo.v_IDVAULT_ENRICHED_CURRENT_EMPLOYEES as I on I.[SID] = P.CONTACT_SID
But I get this error:
Operand type clash: varchar is incompatible with ListTableType
Any idea why? Also if there's another [more elegant] way to achieve what I'm trying to do I'm open to suggestions as well! Thanks in advance!
Here is a simple demonstration of the FOR XML PATH technique which does all of this with a very simple subquery and no table types or extremely inefficient multi-statement table-valued functions etc.
USE tempdb;
GO
CREATE TABLE dbo.P(Program_ID INT);
CREATE TABLE dbo.M(Clarity_ID INT, Program_ID INT);
INSERT dbo.P VALUES(1),(2),(3),(4);
INSERT dbo.M VALUES(1,1),(1,2),(2,3),(3,2),(1,4),(4,1);
SELECT
P.PROGRAM_ID,
PROJECT_CLARITY_IDS = STUFF((
SELECT CHAR(13)+CHAR(10)+CONVERT(VARCHAR(12),Clarity_ID)
FROM dbo.M WHERE Program_ID = p.Program_ID
FOR XML PATH(''), TYPE).value('.[1]','nvarchar(max)'),1,2,'')
FROM dbo.P AS p;
SQLfiddle demo
The output doesn't look right in SQLfiddle or in results to grid in Management Studio, because they strip out carriage returns/line feeds for display purposes, but you can replace CHAR(13)+CHAR(10) with two commas or semi-colons or something to verify that there are two characters there.
Using STUFF..FOR XML PATH construct for concatanation in combination with CTE will get the results you'd like. Something like this:
WITH CTE_PROJECT_CLARITIES AS
(
SELECT DISTINCT PROGRAM_ID
, STUFF((
SELECT CHAR(13) + CHAR(10) + CONVERT(varchar(11),CLARITY_ID)
FROM dbo.MUSEUM_PROGRAM_PROJECTS as A
JOIN dbo.MUSEUM_PROJECTS as B on B.PROJECT_ID = A.PROJECT_ID
WHERE A.PROGRAM_ID = X.PROGRAM_ID
FOR XML PATH ('')),1,2,'') AS PROJECT_CLARITY_IDS
FROM MUSEUM_PROGRAM_PROJECTS X
)
SELECT
P.PROGRAM_ID,
PROGRAM_NAME,
PROGRAM_DESC,
P.STATUS_ID,
STATUS_DESC,
P.CONTACT_SID,
I.FIRST_NAME + ' ' + I.LAST_NAME as CONTACT_NAME,
P.CLARITY_ID,
X.PROJECT_CLARITY_IDS
FROM dbo.MUSEUM_PROGRAMS as P
LEFT JOIN dbo.MUSEUM_PROGRAM_STATUS_TYPES as S on S.STATUS_ID = P.STATUS_ID
LEFT JOIN dbo.v_IDVAULT_ENRICHED_CURRENT_EMPLOYEES as I on I.[SID] = P.CONTACT_SID
LEFT JOIN CTE_PROJECT_CLARITIES X ON X.PROGRAM_ID = p.PROGRAM_ID
SQLFiddle DEMO (not sure if I got the columns right, but you'll get the idea)