I'm using MS-SQLServer-2016 and got a requirement to transpose rows to columns.
I'm using Pivot and dynamic SQL option to do it because the number of rows is dynamic.
Figure1 is the output I'm currently getting. However the client doesn't want those NULLs displayed. He only wants the Not-Null dates to be displayed.
Is there way to get rid of those Null Values and display only distinct dates?
Figure1-Transpose_Output
With Regards,
Tanuja
DECLARE #columns AS NVARCHAR(MAX), #sql AS NVARCHAR(MAX), #var1 AS
VARCHAR(MAX);
SELECT #columns = stuff((select DISTINCT ',' + quotename(replace(replace(replace(n.action_note,' ','<>'),'><',''),'<>',' ') )
from engagement_action n, action_party m, personal p
where n.action_id = m.action_id
and p.party_id = m.party_id
and n.action_note like 'XXX'
and m.system_name = 'XXXXXXXXXXXX'
and p.customer_number = 'XXXXXXX' FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
SELECT #sql =
'select "RANK_IN_PROP" ,
customer_number,
customer_shortname,
system_name,
'+ #columns + '
from
(SELECT RANK() OVER (PARTITION BY convert(date,m.created_when) ORDER BY convert(date,m.created_when))"RANK_IN_PROP" ,
p.customer_number,
p.customer_shortname,
m.system_name,
m.created_when ,
convert(date,m.created_when) as created_when,
replace(replace(replace(n.action_note,'' '',''<>''),''><'',''''),''<>'','' '') as action_note1
FROM engagement_action n, action_party m, personal p
WHERE n.action_id = m.action_id
AND p.party_id = m.party_id
AND n.action_note like ''%XXX%''
AND m.system_name = ''XXXXXXXXXXX''
AND p.customer_number = ''XXXXXXXX'' ) d
PIVOT
(max(created_when) for action_note1 in ( ' + #columns + ' ))p order by created_when desc'
execute(#sql);`
Can you try this , just added (select distinct action_note engagement_action where action_note is not null) in where clause. As i can't create test data so could not really execute the query
SELECT #columns = stuff((select DISTINCT ',' +
quotename(replace(replace(replace(n.action_note,' ','<>'),'><',''),'<>',' ')
)
from
(select distinct action_note engagement_action where action_note is not null)
n, action_party m, personal p
where n.action_id = m.action_id
and p.party_id = m.party_id
and n.action_note like 'XXX'
and m.system_name = 'XXXXXXXXXXXX'
and p.customer_number = 'XXXXXXX' FOR XML PATH(''),TYPE).value('.','NVARCHAR(MAX)'),1,1,'')
Related
(SQL Server 2012 - Web Edition)
I have a parent/child (one to many) relationship in a query like so:
SELECT a.a, a.b, b.c
FROM tablea INNER JOIN
tableb ON b.pk = a.fk
I have a huge pagination query that encompasses this using the standard (psuedo-code):
WITH C as (SELECT top(#perpage*#pagenum) rowID = row_number() OVER (somefield)),
SELECT c.* FROM C (query) WHERE DT_RowId > (#pagenum-1)*#perpage
The question I have is in this scenario is it possible to paginate off the parent table (a), instead of the entire query? Can I modify my pagination query (not the sql that pulls the query itself) so that when I ask for 10 rows, it gives me 10 rows from the parent, with 'x' number of children attached?
I know I'm not giving the bigger picture here, but the bigger picture is ugly. If need be, we can go there, but it's out there. Here's a small taste of where we're going with this:
IF UPPER(LEFT(#rSQL, 6)) = 'SELECT'
BEGIN
SET #rSQL = 'SELECT * FROM (' + #rSQL + ')' + ' as rTBL';
SET #rSQL = RIGHT(#rSQL, LEN(#rSQL)-7);
IF (LEN(LTRIM(#search)) > 0)
BEGIN
SET #rPaging =
'IF (#schemaonly=1) SET FMTONLY ON;
SELECT #ttlrows = COUNT(*) FROM (SELECT ' + #rSQL + #rWhere + ') AS TBL;
WITH C as (select top(#perpage*#pagenum) DT_RowId = ROW_NUMBER() OVER (' + #rOrder + '), ';
SET #rPaging = #rPaging + #rSQL + #rWhere + ')
SELECT C.*' + #rcols + ', (#perpage-1) * #pagenum as pagenum, #ttlrows as ct, CEILING(#ttlrows / CAST(#perpage AS FLOAT)) as pages
FROM C '+ #query + ' WHERE DT_RowId > (#pagenum-1) * #perpage ';
END
ELSE
BEGIN
SET #rPaging =
'IF (#schemaonly=1) SET FMTONLY ON;
SELECT #ttlrows = COUNT(*) FROM (' + #oSQL + ') AS SUBQUERY;
WITH C as (select top(#perpage*#pagenum) DT_RowId = ROW_NUMBER() OVER (' + #rOrder + '), ';
SET #rPaging = #rPaging + #rSQL + ')
SELECT C.*' + #rcols + ',(#perpage-1) * #pagenum as pagenum, #ttlrows as ct, CEILING(#ttlrows / CAST(#perpage AS FLOAT)) as pages
FROM C '+ #query + ' WHERE DT_RowId > (#pagenum-1) * #perpage ';
END
PRINT #rPaging;
EXECUTE SP_EXECUTESQL #rPaging, #parms, #ttlrows out, #schemaonly, #perpage, #pagenum, #fksiteID, #filter1, #filter2, #filter3, #filter4, #intfilter1, #intfilter2, #intfilter3, #intfilter4, #datefilter1, #datefilter2, #search;
SET FMTONLY OFF;
END
ELSE
BEGIN
SET #rSQL = LTRIM(REPLACE(UPPER(#rSQL), 'EXEC',''));
EXECUTE SP_EXECUTESQL #rSQL, #parms, #ttlrows out, #schemaonly, #perpage, #pagenum, #fksiteID, #filter1, #filter2, #filter3, #filter4, #intfilter1, #intfilter2, #intfilter3, #intfilter4, #datefilter1, #datefilter2;
END
You could do the pagination in a CTE that only gets the parent rows, and then join the child rows in a subsequent CTE or in the main query.
Due to the dynamic way you are using this, this might have to involve building your pagination query from the same building blocks you use to build #query. Without seeing the code that builds #query I can't be much more specific than that.
You could add
,DENSE_RANK() OVER (ORDER BY table_a.primary_key)
This would indirectly provide the same result as
,ROW_NUMBER() OVER(ORDER BY table_a.primary_key)
but the former would be on the final result set instead going back to table a for the latter code snippet.
But please be aware of the disadvantage: any additional ranking function will force an additional sort operation on the result set! This might significantly influence the query performance. If this is the case in your scenario, I'd recommend to follow Tab Allemans solution and use a cte.
I have a temp table called #temp, and I need to get all the CDate column from that table, to build a string.
The CDate list in that table is (20171209, 20171210....20171223)
I expected to see
'A.[20171209] as [20171209], A.[20171210] as [20171210],
A.[20171211] as [20171211], A.[20171212] as [20171212],
A.[20171213] as [20171213], A.[20171214] as [20171214],
A.[20171215] as [20171215], A.[20171216] as [20171216],
A.[20171217] as [20171217], A.[20171218] as [20171218],
A.[20171219] as [20171219], A.[20171220] as [20171220],
A.[20171221] as [20171221], A.[20171222] as [20171222],
A.[20171223] as [20171223], '
however the result I got is missing the first date , ie 'A.[20171209] as [20171209]'
Here is my code:
SELECT
#col2 = ISNULL(#col2 + 'A.' + QUOTENAME(CDate) + ' as ' + QUOTENAME(CDate) + ', ' , '')
FROM
(SELECT DISTINCT CDate FROM #temp) AS tmp;
Your current approach will not work in some cases, it is an undocumented feature, always use For Xml path to concatenating the rows into csv.
SET #col2 = stuff((SELECT ', A.' + Quotename(CDate) + ' as '
+ Quotename(CDate)
FROM (SELECT DISTINCT CDate
FROM #temp) a
FOR xml path('')),1,1,'')
[Edit] Due to time constraints I gave up on using a CTE and created a function that returns the concatenated string:
CREATE FUNCTION fn_GetCategoryNamesAsString
(
#lawID INT
)
RETURNS NVARCHAR(MAX)
AS
BEGIN
DECLARE #categoryNames NVARCHAR(MAX)
SET #categoryNames = ''
DECLARE #categoryID INT
DECLARE CUR CURSOR LOCAL FORWARD_ONLY READ_ONLY FOR
SELECT t1.LawCategoryID FROM [GWS].[dbo].[GWSMasterLawsLawCategories] t1 WHERE t1.LawID = #lawID
OPEN CUR
FETCH FROM CUR INTO #categoryID
WHILE ##FETCH_STATUS = 0
BEGIN
SET #categoryNames = #categoryNames + (SELECT t2.Name
FROM [GWS].[dbo].GWSMasterLawCategories t2
WHERE t2.LawCategoryID = #categoryID) + ', '
FETCH NEXT FROM CUR INTO #categoryID
END
CLOSE CUR
DEALLOCATE CUR
RETURN #categoryNames
END
GO
This does the job but I don't really like it. If anyone has a better solution I'd love to know.
[End edit]
I have seen several questions the deal roughly with the same topic but none cover the inclusion of null values.
I am writing a query that should return the full contents of one table with a couple of columns added with relevant data from other tables. These columns can include 0 - n values.
Null values need to be stored as an empty string and sets that do have the extra data should display it separated by commas.
Some approaches delivered all the names strung together, some only returned the values separately, some no values at all and, most often, the recursion went to deep (which means I fouled up as the dataset is small).
This is my current approach:
DECLARE #categoryNames NVARCHAR(MAX);
SET #categoryNames = '';
WITH sources (sourcesLawSourceID, sourcesName) AS (
SELECT DISTINCT [LawSourceID], [name]
FROM [GWS].[dbo].[GWSMasterLawSources]
),
categories AS(
SELECT GWSCategories.LawCategoryID AS categoryID, GWSLawCategories.LawID AS lawID,
categoryNames = #categoryNames
--CAST(LEFT( GWSCategories.name, CHARINDEX(',', GWSCategories.name + ',') -1) AS NVARCHAR(MAX)) categoryName,
--STUFF(GWSCategories.name, 1, CHARINDEX(',', GWSCategories.name + ','), '') categoryNames
FROM [GWS].[dbo].[GWSMasterLawCategories] GWSCategories
JOIN [GWS].[dbo].[GWSMasterLawsLawCategories] GWSLawCategories
ON GWSCategories.LawCategoryID = GWSLawCategories.LawCategoryID
UNION ALL
SELECT categories.categoryID, categories.lawID,
CAST(LEFT( #categoryNames, CHARINDEX(',', #categoryNames + ',') -1) AS NVARCHAR(MAX)) + GWSCategories.Name
FROM categories
JOIN [GWS].[dbo].[GWSMasterLawCategories] GWSCategories
ON categories.categoryID = GWSCategories.LawCategoryID
WHERE #categoryNames > ''
)
SELECT DISTINCT GWSMaster.[LawID]
,[Name]
,sources.sourcesName LawSourceName
,(SELECT STUFF((SELECT DISTINCT ', ' + RTRIM(LTRIM(categories.CategoryNames))
FROM categories
FOR XML PATH ('')), 1, 1, '')) Categories
FROM [GWS].[dbo].[GWSMasterLaws] GWSMaster
JOIN sources
ON sources.sourcesLawSourceID = GWSMaster.LawSourceID
JOIN categories
ON categories.lawID = GWSMaster.LawID
This leaves the category name field completely empty.
If I can give any more information or I have missed a question that answers my problem please let me know.
I have a dynamic pivot/unpivot script that transposes a table. This is dynamic enough to return certain columns that I want and using dynamic columns.
What I am looking for is rather to convert this into either a UDF or a VIEW so that I can join it to other tables.
Please help.
ALTER PROC [dbo].[uspGetUserByValues]
(
#Select NVARCHAR(4000) = '*',
#Where NVARCHAR(4000) = NULL,
#OrderBy NVARCHAR(4000) = NULL
)
AS
BEGIN
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',P.' + QUOTENAME(PropertyDescription)
from System_Properties
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ', M.Email, C.Company_Name, C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles,0) SMSProfiles, U.UserID
from
(
select PropertyDescription, UP.UserID, PropertyValue
from User_Properties UP
JOIN System_Properties SP ON UP.PropertyID = SP.PropertyID
JOIN aspnet_Membership M ON UP.UserID = M.UserID
) X
pivot
(
min(PropertyValue)
for PropertyDescription in (' + REPLACE(#cols,'P.','') + ')
) P
JOIN aspnet_Membership M ON P.UserID = M.UserID
JOIN aspnet_Users U on P.UserID = U.UserID
JOIN Companies C ON C.Company_ID = P.Company_ID
LEFT JOIN (SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile GROUP BY UserID ) SMS ON SMS.UserID = P.UserID
'
SET #query = 'SELECT ' + #Select + ' FROM ('+ #query +') A'
IF ISNULL(#Where,'NULL') != 'NULL'
BEGIN
SET #query = #query + ' WHERE ' + #Where
END
IF ISNULL(#OrderBy,'NULL') != 'NULL'
BEGIN
SET #query = #query + ' ORDER BY ' + #OrderBy
END
execute(#query)
--PRINT(#query)
END
OH wow I made it.
I know this is with "known" column names but actually I didn't have to know them.
Firstly, this is the query I used to create the View. I will need to drop the view at least every I add a new Property or I can actually write a job that checks if all the properties from System_Properties are represented in the view, if not then drop the view and run this code.
CREATE PROC [dbo].[uspCreateViewUsers]
AS
BEGIN
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT distinct ',P.' + QUOTENAME(PropertyDescription)
from System_Properties
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'CREATE VIEW vwUsers AS SELECT ' + #cols + ', M.Email, C.Company_Name, C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles,0) SMSProfiles, U.UserID
from
(
select PropertyDescription, UP.UserID, PropertyValue
from User_Properties UP
JOIN System_Properties SP ON UP.PropertyID = SP.PropertyID
JOIN aspnet_Membership M ON UP.UserID = M.UserID
) X
pivot
(
min(PropertyValue)
for PropertyDescription in (' + REPLACE(#cols,'P.','') + ')
) P
JOIN aspnet_Membership M ON P.UserID = M.UserID
JOIN aspnet_Users U on P.UserID = U.UserID
JOIN Companies C ON C.Company_ID = P.Company_ID
LEFT JOIN (SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile GROUP BY UserID ) SMS ON SMS.UserID = P.UserID
'
execute(#query)
END
Them the View, which can't be represented graphically by table joins looks like this:
SELECT P.[Company_ID], P.[Created_Date], P.[Created_User], P.[Cust_ID], P.[FirstName], P.[IPCheck], P.[JobTitle], P.[LastLogin], P.[LastModified_Date], P.[LastModified_User],
P.[LastName], P.[Newsletter_OptIn], P.[Password_Change], P.[SupAdmin], P.[SysAccess], P.[SysAdmin], P.[User_Cat_1], P.[User_Cat_10], P.[User_Cat_2],
P.[User_Cat_3], P.[User_Cat_4], P.[User_Cat_5], P.[User_Cat_6], P.[User_Cat_7], P.[User_Cat_8], P.[User_Cat_9], P.[UserClient_ID], M.Email, C.Company_Name,
C.Company_Type_ID, U.UserName, ISNULL(SMS.SMSProfiles, 0) SMSProfiles, U.UserID
FROM (SELECT PropertyDescription, UP.UserID, PropertyValue
FROM User_Properties UP JOIN
System_Properties SP ON UP.PropertyID = SP.PropertyID JOIN
aspnet_Membership M ON UP.UserID = M.UserID) X PIVOT (min(PropertyValue) FOR PropertyDescription IN ([Company_ID], [Created_Date], [Created_User],
[Cust_ID], [FirstName], [IPCheck], [JobTitle], [LastLogin], [LastModified_Date], [LastModified_User], [LastName], [Newsletter_OptIn], [Password_Change], [SupAdmin],
[SysAccess], [SysAdmin], [User_Cat_1], [User_Cat_10], [User_Cat_2], [User_Cat_3], [User_Cat_4], [User_Cat_5], [User_Cat_6], [User_Cat_7], [User_Cat_8],
[User_Cat_9], [UserClient_ID])) P JOIN
aspnet_Membership M ON P.UserID = M.UserID JOIN
aspnet_Users U ON P.UserID = U.UserID JOIN
Companies C ON C.Company_ID = P.Company_ID LEFT JOIN
(SELECT UserId, COUNT(Users_SMS_Profile_ID) SMSProfiles
FROM Users_SMS_Profile
GROUP BY UserID) SMS ON SMS.UserID = P.UserID
This now allows me to query the View as if it was a table.
I hope this helps someone else in the future.
Simply said: you cant
At least you can't do it using conventional TSQL programming. Which means you would have to use some hack. Let me explain.
Closest thing to your SP would be UDF. However, UDFs are rather restricted. One thing UDF expect is for data to stay the same while and after executing it. Of course that this means EXEC() is forbidden in that scope.
Another possibility would be a view. However, you have a number of parameters and view's schema depends on these parameters. Functionality to change view's schema based on input parameters doesn't exist in SQL server.
And now for the hacks.
One hack I can think of is:
create a CLR UDF
create new connection based on context connection (same server, same db)
exec your SP there
return result to your original pipe
But it may or may not work (it's a hack after all).
If the hack doesn't work, you can try playing it by the book. This means creating a CLR UDF, assemble select statement in there and execute it, which means that you will have to throw away your original SP. However, it is not a hack since SQL CLR UDF's are made for such (and other) situations. Only thing you will have to take care about is using SqlMetaData because UDF doesn't have a predefined resultset. See this.
In my previous answer I stated that it could be done using CLR UDF, but that was wrong. One thing I forgot is that Microsoft insists on providing a finite number of columns for UDF. This may not be obvious while developing in .NET - after all, you can return any number of columns to SqlPipe. See this (untested) code...
[SqlFunction(DataAccess = DataAccessKind.Read)]
public static void DynOutFunc(SqlString select, SqlString where, SqlString orderBy)
{
// 1: Create SQL query
string query = "select db_id(), db_name()";
// 2: Find out which colums and their types are part of the output
SqlMetaData[] metaData =
{
new SqlMetaData("ID", System.Data.SqlDbType.Int),
new SqlMetaData("Database", System.Data.SqlDbType.NVarChar, 256)
};
using (SqlConnection connection = new SqlConnection("context connection=true"))
{
connection.Open();
SqlCommand command = new SqlCommand(query, connection);
SqlDataReader reader = command.ExecuteReader();
using (reader)
{
while (reader.Read())
{
SqlDataRecord record = new SqlDataRecord(metaData);
SqlContext.Pipe.SendResultsStart(record);
for(int i = 0; i < metaData.Length; i++)
{
if(metaData[i].DbType == DbType.String)
record.SetString(i, reader.GetString(i));
else if(metaData[i].DbType == DbType.Int32)
record.SetInt32(i, reader.GetInt32(i));
// else if's should cover all supported data taypes
}
SqlContext.Pipe.SendResultsRow(record);
}
}
SqlContext.Pipe.SendResultsEnd();
}
}
Notice SqlMetaData collection that holds information about columns. What stops you from appending just another column to it?
But(!) when it comes to registering that function in the SQL Server itself, you HAVE to provide arguments, like:
CREATE FUNCTION DynOutFunc
#select [nvarchar](4000),
#where [nvarchar](4000),
#orderBy [nvarchar](4000)
RETURNS TABLE (p1 type1, p2 type2, ... pN typeN)
AS EXTERNAL NAME SqlClrTest.UserDefinedFunctions.DynOutFunc;
It turns out there are no hacks for this I can think of. Or there are just no hacks here at all.
I have following query which takes 2 parameters.
YearNumber
MonthNumber
In my pivot query, I am trying to select columns based on #Year_Rtl variable. I need to select data for the year passed, last year and last last year. Since the data being displayed on UI is table format divided by #Year_Rtl, I decided to write a pivot query for that as below.
In the query, it works fine if I hard code [#Year_Rtl], [#Year_Rtl - 1], [#Year_Rtl - 2] to [2012], [2011], [2010]. But since the year passed can be anything, I want columns to be named dynamically.
DECLARE #Month_Rtl int
DECLARE #Year_Rtl int
SET #Year_Rtl = 2012
SET #Month_Rtl = 1
SELECT
'Data 1', [#Year_Rtl], [#Year_Rtl - 1], [#Year_Rtl - 2]
FROM
(SELECT [Yr_No], Qty
FROM dbo.Table1 t
WHERE (t.Col1 = 10) AND
(t.Col2 = '673') AND
((t.Mth_No = #Month_Rtl AND t.Yr_No = #Year_Rtl) OR
(t.Mth_No = 12 AND t.Yr_No IN (#Year_Rtl - 1, #Year_Rtl - 2)))
) p PIVOT (SUM(Qty)
FOR [Yr_No] IN ([#Year_Rtl], [#Year_Rtl-1], [#Year_Rtl-2])
) AS pvt
Above query throws following errors:
Error converting data type nvarchar to smallint.
The incorrect value "#Year_Rtl" is supplied in the PIVOT operator.
Invalid column name '#Year_Rtl - 1'.
Invalid column name '#Year_Rtl - 2'.
Since you can use dynamic SQL, I'd go with a macro-replacement approach. You're identifying areas of the query that must be dynamically replaced with placeholders (e.g. $$Year_Rtl) and then calculating their replacement values below. I find that it keeps the SQL statement easy to follow.
DECLARE #SQL NVarChar(2000);
SELECT #SQL = N'
SELECT
''Data 1'', [$$Year_Rtl], [$$Year_RtlM1], [$$Year_RtlM2]
FROM
(SELECT [Yr_No], Qty
FROM dbo.Table1 t
WHERE (t.Col1 = 10) AND
(t.Col2 = ''673'') AND
((t.Mth_No = $$Month_Rtl AND t.Yr_No = $$Year_Rtl) OR
(t.Mth_No = 12 AND t.Yr_No IN ($$Year_RtlM1, $$Year_RtlM2)))
) p PIVOT (SUM(Qty)
FOR [Yr_No] IN ([$$Year_Rtl], [$$Year_RtlM1], [$$Year_RtlM2])
) AS pvt';
SELECT #SQL = REPLACE(#SQL, '$$Year_RtlM2', #Year_Rtl - 2);
SELECT #SQL = REPLACE(#SQL, '$$Year_RtlM1', #Year_Rtl - 1);
SELECT #SQL = REPLACE(#SQL, '$$Year_Rtl', #Year_Rtl);
SELECT #SQL = REPLACE(#SQL, '$$Month_Rtl', #Month_Rtl);
PRINT #SQL;
-- Uncomment the next line to allow the built query to execute...
--EXECUTE sp_ExecuteSQL #SQL;
Since consuming code will also have to be flaky under this scheme (e.g. selecting columns based on "position" rather than name) - why not normalize the columns by performing a DATEDIFF(year,Yr_No,#Year_Rtl), and work from there? Those columns will always be 0, -1 and -2...
You need to look into Dynamic SQL Pivoting.
I recommend reading Itzik Ben-Gan's T-SQL Fundamentals where he goes over how to do this.
Alternatively try this article if you don't want to buy the book.
Maybe this will help:
First getting the columns with a tally function like this:
DECLARE #Month_Rtl int,
#Year_Rtl int,
#Year_Rtl_Start INT,
#cols VARCHAR(MAX),
#values VARCHAR(MAX)
SET #Year_Rtl = 2012
SET #Month_Rtl = 1
SET #Year_Rtl_Start=2009
;WITH Years ( n ) AS (
SELECT #Year_Rtl_Start UNION ALL
SELECT 1 + n FROM Years WHERE n < #Year_Rtl )
SELECT
#cols = COALESCE(#cols + ','+QUOTENAME(n),
QUOTENAME(n)),
#values = COALESCE(#values + ','+CAST(n AS VARCHAR(100)),
CAST(n AS VARCHAR(100)))
FROM
Years
ORDER BY n DESC
The variable #cols contains the columns that is in the pivot and the variable #values contains the years for the IN. The #Year_Rtl is the end year and the #Year_Rtl_Start is the start for you range.
Then declaring and executing the dynamic pivot like this:
DECLARE #query NVARCHAR(4000)=
N'SELECT
''Data 1'', '+#cols+'
FROM
(
SELECT
[Yr_No], Qty
FROM
dbo.Table1 t
WHERE
t.Col1 = 10
AND t.Col2 = ''673''
AND
(
(
t.Mth_No = '+CAST(#Month_Rtl AS VARCHAR(10))+'
AND t.Yr_No = '+CAST(#Year_Rtl AS VARCHAR(10))+'
)
OR
(
t.Mth_No = 12
AND t.Yr_No IN ('+#values+'))
)
) p
PIVOT
(
SUM(Qty)
FOR [Yr_No] IN ('+#cols+')
) AS pvt'
EXECUTE(#query)