How can I select the dynamic column in SELECT statement using OPENROWSET - sql-server

Sorry my English is very poor so I just can describe my problem very meager
I have used OPENROWSET to get a raw data table from an excel file as below
SELECT *
FROM OPENROWSET('Microsoft.ACE.OLEDB.12.0',
'Excel 8.0;Database=C:\PP.xlsx',
'SELECT * FROM [Daily$]')
[enter image description here]
The problems is each column is a dynamically date which user who created the Excel file update it every day.
So I need a solution to select the column= current date (or any date).
Hopefully you can understand my point, below is my code:
Select
t1.*, temp.[13-Nov] as [Plan]
from
(select
case
when FinalMaterial = 'IA15_2020' then 'FADIL'
when FinalMaterial = 'ID2U_2020' then 'V8'
when FinalMaterial = 'ID1U_2019' then 'SUV'
when FinalMaterial = 'ID14_2019' then 'Sedan'
else FinalMaterial
end FinalMaterial,
case
when statusto in ('TP_40', 'TP_40B') then 'Paint'
when statusto = 'TP_20' then 'Body'
when statusto = 'TP_80' then 'OKTS'
when statusto = 'TP_F1' then 'F1 out'
else null
end Shop,
count(WorkOrder_NId) Actual
from
vLogicalStatusTrans_1138772611 lo
join
vWorkOrder_Siemens__1008010152 wo on wo.NId = lo.WorkOrder_NId
where
StatusTo in ('TP_40', 'TP_40B', 'TP_20', 'TP_80', 'TP_F1')
and dateadd(hh, 7, lo.TransitionTime) >= SMALLDATETIMEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), DAY(GETDATE()), 00, 00)
group by
wo.FinalMaterial, statusto) t1
left join
(select * from OPENROWSET('Microsoft.ACE.OLEDB.12.0','Excel 8.0;Database=C:\PP.xlsx','SELECT * FROM [Daily$]')) as temp on temp.Model = t1.FinalMaterial and temp.Shop = t1.Shop
where
t1.Shop = 'Paint'

Changing the select field list on the fly means constructing a "dynamic query" where your build the entire query in a string variable (nvarchar type) and pass that to a stored procedure like sp_executesql (documentation).
Sample data
create table exceldata
(
id int,
[14-nov] int,
[15-nov] int,
[16-nov] int
);
insert into exceldata (id, [14-nov], [15-nov], [16-nov]) values
(1, 114, 115, 116),
(2, 214, 215, 216);
Solution
declare #today date = getdate();
declare #stmt nvarchar(500) = 'select id, ['
+ replace(convert(nvarchar(6), #today, 106), ' ', '-')
+ '] from exceldata;'
select #stmt as Statement; -- validation
exec sp_executesql #stmt;
Result
For #today = '2020-11-14':
Statement
-----------------------------------
select id, [14-Nov] from exceldata;
id 14-Nov
--- ------
1 114
2 214
For #today = '2020-11-15':
Statement
-----------------------------------
select id, [15-Nov] from exceldata;
id 15-Nov
--- ------
1 115
2 215
Fiddle to see it in action (includes step by step date format construction).

Related

Conversion failed when converting the varchar value '01-4001' to data type int

My stored procedure is as follows:
DECLARE #LOCATION_ID VARCHAR(MAX) = 3,
#INVENTORY_ITEM_ID VARCHAR(MAX) = '01-4001' ,
#CALLFROM VARCHAR(MAX) = 'FRAMES' --Requested by VIkram Dated 29jan 2020 For MBT :- 25401,25402
BEGIN
SET NOCOUNT ON;
BEGIN TRY
DECLARE #TABLE_LOCATION_ID TABLE
(
ID INT,
LOCATION_ID INT
)
INSERT INTO #TABLE_LOCATION_ID
SELECT *
FROM dbo.SPLIT(#LOCATION_ID, ',')
SELECT * FROM #TABLE_LOCATION_ID
DECLARE #TABLE_INVENTORY_ITEM_ID TABLE
(
ID INT,
INVENTORY_ITEM_ID VARCHAR(MAX) -- 2189
)
INSERT INTO #TABLE_INVENTORY_ITEM_ID
SELECT *
FROM dbo.SPLIT(#INVENTORY_ITEM_ID, ',')
SELECT * FROM #TABLE_INVENTORY_ITEM_ID
SELECT DISTINCT LOCATION_ID
FROM #TABLE_LOCATION_ID
IF('FRAMES' = 'FRAMES')
BEGIN
-- declare #LOCATION_ID int ,
--#INVENTORY_ITEM_ID VARCHAR(MAX);
SELECT DISTINCT
I.[UPC_ID],
I.INVENTORY_ITEM_ID,
IP.LOCATION_ID,
PL.LOCATION_NAME,
F.MANUFACTURER,
(CASE WHEN F.BRAND IS NULL THEN '' ELSE F.BRAND END) BRAND,
F.COLOR_NAME,
F.COLOR_TEMPLE,
F.[FRAME_TEMPLE],
F.FRAME_EYE_SIZE,
F.[FRAME_DBL],
F.[COLLECTION] ,
F.MODEL_NO ,
[DESCRIPTION],
IP.RETAIL_PRICE ,
IP.WHOLE_SALE AS WHOLE_SALE_PRICE
FROM
PMS_INVENTORY_ITEM_DETAILS I
INNER JOIN
PMS_INVENTORY_ITEM_PRICING_DETAILS IP ON I.INVENTORY_ITEM_ID = IP.INVENTORY_ITEM_ID
INNER JOIN
PMS_FRAME_SPECIFICATIONS F ON I.INVENTORY_ITEM_ID = F.INVENTORY_ITEM_ID
LEFT JOIN
PRACTICE_LOCATIONS PL ON PL.PRACTICE_LOCATION_ID = IP.LOCATION_ID
LEFT JOIN
[PMS_PURCHASE_ORDER_ITEMS] P ON P.UPC_ID = I.UPC_ID -- MBT#21084 Added by Trupti
LEFT JOIN
[PMS_PURCHASE_ORDERS] PO ON PO. PURCHASE_ORDER_ID = P.PURCHASE_ORDER_ID
WHERE
(IP.LOCATION_ID IN (3) OR #LOCATION_ID IS NULL)
--AND (I.UPC_ID IN (SELECT DISTINCT INVENTORY_ITEM_ID FROM #TABLE_INVENTORY_ITEM_ID ) OR #INVENTORY_ITEM_ID IS NULL)
AND ((I.UPC_ID IN ('01-4001') OR #INVENTORY_ITEM_ID IS NULL)
OR (PO.PO_NO IN (SELECT CAST(DATA AS INTEGER)
FROM dbo.SPLIT(#INVENTORY_ITEM_ID, ',')) OR #INVENTORY_ITEM_ID IS NULL))
AND (I.INVENTORY_CATEGORY = 'FRAMES')
AND (IP.IS_ACTIVE = 1)
AND (I.IS_ACTIVE = 1)
--ORDER BY IP.LAST_SOLD_DATE
--OFFSET (#PAGE_NO - 1) * #PAGE_SIZE ROWS FETCH NEXT #PAGE_SIZE ROWS ONLY
END
END TRY
BEGIN CATCH
SELECT
ERROR_NUMBER() AS ErrorNumber,
ERROR_MESSAGE() AS ErrorMessage;
END CATCH
END
If I execute the above whole code then I the image below shows output with error
Conversion failed when converting the varchar value '01-4001' to data type int.
But if execute only select query then it works fine. with no error.
My datatype are Varchar(max). Inventory_Item_id is = '01-4001'
If I put inventory_item_id as '2189' then whole execution doesn't throw error.
Response error in network like:
""error":true,"message":"Index was out of range. Must be non-negative and less than the size of the collection.\r\nParameter name: index""
In table PMS_INVENTORY_ITEM_DETAILS description INVENTORY_ITEM_ID is int length 4 and UPC_ID is varchar length -1.
The error occurs here:
INSERT INTO #TABLE_INVENTORY_ITEM_ID
SELECT * FROM DBO.SPLIT(#INVENTORY_ITEM_ID, ',')
The separator you provide for your split function is , rather than - so the result will be 01-4001 again (rather than the expected 01 and 4001), and converting that to an integer then fails.
To fix, use - as separator.
Hi Thanks for your help.
Issue is resolved
Changes made at line
OR (PO.PO_NO IN (SELECT CAST(DATA AS varchar) FROM DBO.SPLIT(#INVENTORY_ITEM_ID, ',')) OR #INVENTORY_ITEM_ID IS NULL))
CAST(DATA AS INTEGER) updated with CAST(DATA AS varchar)
as parameter #INVENTORY_ITEM_ID is coming as '01-4001' [ - is there so error casting to int ]

Assigning DISTINCT value to a variable in SQL

I have a dataset with one of the columns as Transaction_Date in which date varies from the year 2005 to 2018.
I need to assign a distinct year to a variable, later on, I will be using the same variable in SQL Pivot.
-- variable declaration
DECLARE #PCOL VARCHAR(20);
-- assigning values to the variable
SELECT #PCOL += (QUOTENAME (X.TD) + ',')
FROM
(
SELECT DISTINCT YEAR(TRANSACTION_DATE) AS TD
FROM TRANSACTION_INFO
) AS X;
-- Check the result
PRINT #PCOL
It is not resulting me the output as expected. Please suggest.
You need FOR XML PATH() clause :
SELECT #PCOL = STUFF( (SELECT DISTINCT ', '+ QUOTENAME(CAST(YEAR(TRANSACTION_DATE) AS VARCHAR(255)))
FROM TRANSACTION_INFO
FOR XML PATH('')
), 1, 1, ''
)
You're getting NULL because you didn't pre-set your variable to an empty string before you used the += operator.
Since NULL + 'some value' = NULL, your variable never gets changed from NULL to something else.
It looks like this is what you're attempting to do...
IF OBJECT_ID('tempdb..#TRANSACTION_INFO', 'U') IS NOT NULL
BEGIN DROP TABLE #TRANSACTION_INFO; END;
CREATE TABLE #TRANSACTION_INFO (
TRANSACTION_DATE DATE
);
INSERT #TRANSACTION_INFO (TRANSACTION_DATE) VALUES
('20130101'),('20130101'),('20140101'),
('20140102'),('20150102'),('20150102'),
('20160103'),('20160103'),('20170104'),
('20170104'),('20180105'),('20180105');
--================================================
DECLARE #POL VARCHAR(200) = '';
SELECT
#POL = CONCAT(#POL, ',', x.TD)
FROM (
SELECT DISTINCT
TD = YEAR(ti.TRANSACTION_DATE)
FROM
#TRANSACTION_INFO ti
) x;
SET #POL = STUFF(#POL, 1, 1, '');
PRINT(#POL);
Result:
2013,2014,2015,2016,2017,2018

T-SQL - Update first letter in each word of a string that are not 'or', 'of' or 'and' to uppercase. Lowercase 'or', 'of' or 'and' if found

Given the below table and data:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
Code INT,
PDescription VARCHAR(2000)
)
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001,'c and d, together'),
(2,0002,'equals or Exceeds $27.00'),
(3,0003,'Fruit Evaporating Or preserving'),
(4,0004,'Domestics And domestic Maintenance'),
(5,0005,'Bakeries and cracker')
SELECT *
FROM #Temp
DROP TABLE #Temp
Output:
ID Code PDescription
1 1 c and d, together
2 2 equals or Exceeds $27.00
3 3 Fruit Evaporating Or preserving
4 4 Domestics And domestic Maintenance
5 5 Bakeries and cracker
I need a way to achieve the below update to the description field:
ID Code PDescription
1 1 C and D, Together
2 2 Equals or Exceeds $27.00
3 3 Fruit Evaporating or Preserving
4 4 Domestics and Domestic Maintenance
5 5 Bakeries and Cracker
If you fancied going the SQL CLR route the function could look something like
using System.Data.SqlTypes;
using System.Text.RegularExpressions;
public partial class UserDefinedFunctions
{
//One or more "word characters" or apostrophes
private static readonly Regex _regex = new Regex("[\\w']+");
[Microsoft.SqlServer.Server.SqlFunction]
public static SqlString ProperCase(SqlString subjectString)
{
string resultString = null;
if (!subjectString.IsNull)
{
resultString = _regex.Replace(subjectString.ToString().ToLowerInvariant(),
(Match match) =>
{
var word = match.Value;
switch (word)
{
case "or":
case "of":
case "and":
return word;
default:
return char.ToUpper(word[0]) + word.Substring(1);
}
});
}
return new SqlString(resultString);
}
}
Doubtless there may be Globalization issues in the above but it should do the job for English text.
You could also investigate TextInfo.ToTitleCase but that still leaves you needing to handle your exceptions.
The following function is not the most elegant of solutions but should do what you want.
ALTER FUNCTION [dbo].[ToProperCase](#textValue AS NVARCHAR(2000))
RETURNS NVARCHAR(2000)
AS
BEGIN
DECLARE #reset BIT;
DECLARE #properCase NVARCHAR(2000);
DECLARE #index INT;
DECLARE #character NCHAR(1);
SELECT #reset = 1, #index=1, #properCase = '';
WHILE (#index <= len(#textValue))
BEGIN
SELECT #character= substring(#textValue,#index,1),
#properCase = #properCase + CASE WHEN #reset=1 THEN UPPER(#character) ELSE LOWER(#character) END,
#reset = CASE WHEN #character LIKE N'[a-zA-Z\'']' THEN 0 ELSE 1 END,
#index = #index +1
END
SET #properCase = N' ' + #properCase + N' ';
SET #properCase = REPLACE(#properCase, N' And ', N' and ');
SET #properCase = REPLACE(#properCase, N' Or ', N' or ');
SET #properCase = REPLACE(#properCase, N' Of ', N' of ');
RETURN RTRIM(LTRIM(#properCase))
END
Example use:
IF OBJECT_ID('tempdb..#Temp') IS NOT NULL
DROP TABLE #Temp
CREATE TABLE #Temp
(
ID INT,
Code INT,
PDescription VARCHAR(2000)
)
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001, N'c and d, together and'),
(2,0002, N'equals or Exceeds $27.00'),
(3,0003, N'Fruit Evaporating Or preserving'),
(4,0004, N'Domestics And domestic Maintenance'),
(5,0005, N'Bakeries and cracker')
SELECT ID, Code, dbo.ToProperCase(PDescription) AS [Desc]
FROM #Temp
DROP TABLE #Temp
If you want to convert your text to proper case before inserting into table, then simply call function as follow:
INSERT INTO #Temp
(ID,
Code,
PDescription)
VALUES (1,0001, dbo.ToProperCase( N'c and d, together and')),
(2,0002, dbo.ToProperCase( N'equals or Exceeds $27.00')),
(3,0003, dbo.ToProperCase( N'Fruit Evaporating Or preserving')),
(4,0004, dbo.ToProperCase( N'Domestics And domestic Maintenance')),
(5,0005, dbo.ToProperCase( N'Bakeries and cracker'))
This is a dramatically modified version of my Proper UDF. The good news is you may be able to process the entire data-set in ONE SHOT rather than linear.
Take note of #OverR (override)
Declare #Table table (ID int,Code int,PDescription varchar(150))
Insert into #Table values
(1,1,'c and d, together'),
(2,2,'equals or Exceeds $27.00'),
(3,3,'Fruit Evaporating Or preserving'),
(4,4,'Domestics And domestic Maintenance'),
(5,5,'Bakeries and cracker')
-- Generate Base Mapping Table - Can be an Actual Table
Declare #Pattn table (Key_Value varchar(25));Insert into #Pattn values (' '),('-'),('_'),(','),('.'),('&'),('#'),(' Mc'),(' O''') -- ,(' Mac')
Declare #Alpha table (Key_Value varchar(25));Insert Into #Alpha values ('A'),('B'),('C'),('D'),('E'),('F'),('G'),('H'),('I'),('J'),('K'),('L'),('M'),('N'),('O'),('P'),('Q'),('R'),('S'),('T'),('U'),('V'),('W'),('X'),('Y'),('X')
Declare #OverR table (Key_Value varchar(25));Insert Into #OverR values (' and '),(' or '),(' of ')
Declare #Map Table (MapSeq int,MapFrom varchar(25),MapTo varchar(25))
Insert Into #Map
Select MapSeq=1,MapFrom=A.Key_Value+B.Key_Value,MapTo=A.Key_Value+B.Key_Value From #Pattn A Join #Alpha B on 1=1
Union All
Select MapSeq=99,MapFrom=A.Key_Value,MapTo=A.Key_Value From #OverR A
-- Convert Base Data Into XML
Declare #XML xml
Set #XML = (Select KeyID=ID,String=+' '+lower(PDescription)+' ' from #Table For XML RAW)
-- Convert XML to varchar(max) for Global Search & Replace
Declare #String varchar(max)
Select #String = cast(#XML as varchar(max))
Select #String = Replace(#String,MapFrom,MapTo) From #Map Order by MapSeq
-- Convert Back to XML
Select #XML = cast(#String as XML)
-- Generate Final Results
Select KeyID = t.col.value('#KeyID', 'int')
,NewString = ltrim(rtrim(t.col.value('#String', 'varchar(150)')))
From #XML.nodes('/row') AS t (col)
Order By 1
Returns
KeyID NewString
1 C and D, Together
2 Equals or Exceeds $27.00
3 Fruit Evaporating or Preserving
4 Domestics and Domestic Maintenance
5 Bakeries and Cracker
You don't even need functions and temporary objects. Take a look at this query:
WITH Processor AS
(
SELECT ID, Code, 1 step,
CONVERT(nvarchar(MAX),'') done,
LEFT(PDescription, CHARINDEX(' ', PDescription, 0)-1) process,
SUBSTRING(PDescription, CHARINDEX(' ', PDescription, 0)+1, LEN(PDescription)) waiting
FROM #temp
UNION ALL
SELECT ID, Code, step+1,
done+' '+CASE WHEN process IN ('and', 'or', 'of') THEN LOWER(process) ELSE UPPER(LEFT(process, 1))+LOWER(SUBSTRING(process, 2, LEN(process))) END,
CASE WHEN CHARINDEX(' ', waiting, 0)>0 THEN LEFT(waiting, CHARINDEX(' ', waiting, 0)-1) ELSE waiting END,
CASE WHEN CHARINDEX(' ', waiting, 0)>0 THEN SUBSTRING(waiting, CHARINDEX(' ', waiting, 0)+1, LEN(waiting)) ELSE NULL END FROM Processor
WHERE process IS NOT NULL
)
SELECT ID, Code, done PSDescription FROM
(
SELECT *, ROW_NUMBER() OVER (PARTITION BY ID ORDER BY step DESC) RowNum FROM Processor
) Ordered
WHERE RowNum=1
ORDER BY ID
It produces desired result as well. You can SELECT * FROM Processor to see all steps executed.

TSQL - Error in stored procedure due to conversion failure

I have this situation in a stored Procedure:
SET #DATE_RELEASE_START = '2015-01-01';
SET #DATE_RELEASE_END = '2015-05-31'
SELECT #statement = ' SELECT *
FROM (SELECT AFCDENTE, M.ID_MODIFICATION_CODE, COUNT(*) AS Conteggio--, CAST((COUNT(*) * 100/ 15032) AS decimal(10,7)) AS Percentage
FROM CIC_LOG_MODIFICHE AS L INNER JOIN ADM_MODIFICATION_CODE AS M ON L.CD_MODIFICATION_CODE = M.CD_MODIFICATION_CODE
INNER JOIN CIC_PRODUZIONE AS P ON P.CD_CIC_PRODUZIONE = L.CD_CIC_PRODUZIONE
WHERE AFDTMODI BETWEEN '+#DATE_RELEASE_START+' AND '+#DATE_RELEASE_END+' AND P.CD_PLANT = '+#CD_PLANT+' AND AFCDENTE IS NOT NULL
GROUP BY AFCDENTE, M.ID_MODIFICATION_CODE) as tbl
PIVOT (SUM(tbl.Conteggio) for tbl.ID_MODIFICATION_CODE in (' + #columns + ')) as pvt'
I get this error:
Conversion failed when converting date and/or time from character
string.
I tried casting those dates but no changes. I get the same error.
What should I do?
I'd suggest doing it like that:
SET #DATE_RELEASE_START = '2015-01-01';
SET #DATE_RELEASE_END = '2015-05-31'
SELECT #statement = ' SELECT *
FROM (SELECT AFCDENTE, M.ID_MODIFICATION_CODE, COUNT(*) AS Conteggio--, CAST((COUNT(*) * 100/ 15032) AS decimal(10,7)) AS Percentage
FROM CIC_LOG_MODIFICHE AS L INNER JOIN ADM_MODIFICATION_CODE AS M ON L.CD_MODIFICATION_CODE = M.CD_MODIFICATION_CODE
INNER JOIN CIC_PRODUZIONE AS P ON P.CD_CIC_PRODUZIONE = L.CD_CIC_PRODUZIONE
WHERE AFDTMODI BETWEEN #p0 AND #p1 AND P.CD_PLANT = #p2 AND AFCDENTE IS NOT NULL
GROUP BY AFCDENTE, M.ID_MODIFICATION_CODE) as tbl
PIVOT (SUM(tbl.Conteggio) for tbl.ID_MODIFICATION_CODE in (' + #columns + ')) as pvt'
EXECUTE sp_executesql #statement, N'#p0 DATETIME2, #p1 DATETIME2, #p0 NVARCHAR(1000)', #p0 = #DATE_RELEASE_START, #p1 = #DATE_RELEASE_END, #p2 = #CD_PLANT;
Instead casting them as VARCHAR, just pass them as variables and then use sp_executesql to do the right job.
You must quote the date strings in your SQL code properly:
WHERE AFDTMODI BETWEEN '''+#DATE_RELEASE_START+''' AND '''+#DATE_RELEASE_END+'''
Otherwise the SQL will read
WHERE AFDTMODI BETWEEN 2015-01-01 AND 2015-05-31
instead of
WHERE AFDTMODI BETWEEN '2015-01-01' AND '2015-05-31'
We've also had problems before with passing date/time values to stored procedures in Management Studio. We needed to give the date as 20150101 or 20150531.

SQL Server 2005 - column names based on variable passed in pivot query

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)

Resources