I an new to the pivot function and I am trying to use it to create new columns with existing data. In this case the columns should be based on the data from the current month and each month for an entire year. The field Months contains the month in YYYYMM format.
I am using the following query:
SELECT *
FROM (
SELECT
[Num]
,[Channel]
,[Months]
,[Currency]
,[Value]
FROM [Test].[dbo].[Decision_Details]
WHERE
(Key_Name = 'Decision'
OR Key_Name = 'LastCycle')
) as s
PIVOT
(
SUM([Value])
FOR [Months] IN ([201705], [201706], [201708], [201709], [201710], [201711], [201712])
)AS pvt
And receive the following output:
This is the correct output but I need the columns to be based on the data from current month (201705) and onward, instead of being hard-coded.
I have tried adding LEFT(CONVERT(varchar, GetDate(),112),6) to the pivot query but it doesn't seem to work the way that I want it to.
Any suggestions on how to incorporate this into my query?
Update:
Managed to get every month using Dynamic Pivot:
If anyone is interested, I managed to solve my problem by using the following query. (Note that there is probably room for improvement here but this works for me):
DECLARE #cols2 AS NVARCHAR(MAX),
#query2 AS NVARCHAR(MAX);
select #cols2 = STUFF((SELECT distinct ',' + QUOTENAME(c.Months)
FROM [Test].[dbo].[Decision_Details] c
WHERE (Months >= LEFT(CONVERT(varchar, (DATEADD(MONTH, -1, GETDATE())), 112),6)
AND Months <= LEFT(CONVERT(varchar, (DATEADD(MONTH, +6, GETDATE())), 112),6))
AND Key_Name = 'Decision'
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query2 = 'SELECT Num, Channel, Currency, ' + #cols2 + ' from
(
select Key_name,
Num,
Channel,
Months,
Currency,
Value
from [Test].[dbo].[Decision_Details]
WHERE (Months >= LEFT(CONVERT(varchar, (DATEADD(MONTH, -1, GETDATE())), 112),6)
AND Months <= LEFT(CONVERT(varchar, (DATEADD(MONTH, +6, GETDATE())), 112),6))
AND Key_Name = ''Decision''
) x
pivot
(
MAX([Value])
for Months in (' + #cols2 + ')
) p '
execute(#query2)
Related
I have a query when I'm using static Dynamic pivot it is working fine
Static Script :
Select Name, tableName,MAX([2017-07-10])[2017-07-10],MAX([2017-07-09])[2017-07-09]
from (
SELECT Target_Db_Name,
Target_Tbl_Name,
Cnt
FROM Table1 l
WHERE Name='Employee' AND
dt >= CAST(dateadd(day, -1, getdate()) as date))T
PIVOT (MAX(cnt)FOR dt IN ([2017-07-10],[2017-07-09]) )PVT
GROUP BY Name, tableName
when I'm trying to implement dynamic query in the same query I'm confused to get max values for multiple comma separated data values
Dynamic Script :
DECLARE #cols AS NVARCHAR(MAX)='';
DECLARE #query AS NVARCHAR(MAX)='';
SELECT #cols = #cols + QUOTENAME(dt) + ',' FROM (select distinct CONVERT(DATE,t)Start_dt from Events
WHERE Start_dt >= CAST(dateadd(day, -1, getdate()) as date) ) as tmp ORDER BY Start_dt desc
select #cols = substring(#cols, 0, len(#cols))
Select #query = '
Select Name, tableName,'+#cols+'
from (
SELECT Name,
tableName,
Cnt
FROM Table1 l
WHERE Name=''Employee'' AND
Start_dt >= CAST(dateadd(day, -1, getdate()) as date))T
PIVOT (MAX(Cnt)FOR Start_dt IN ('+#cols+') )PVT
GROUP BY Name, tableName
'
EXEC (#query)
So here date values are coming like this
([2017-07-10]),([2017-07-09]) in dynamic how to apply
MAX([2017-07-10]),MAX([2017-07-09])
how to get max for each date in Dynamic Pivot
Here is one way to get the Max aggregate in column list
SET #cols = stuff((SELECT DISTINCT ','+Quotename(CONVERT(DATE, Start_dt))
FROM Events
WHERE Start_dt >= Cast(Dateadd(day, -1, Getdate()) AS DATE)
ORDER BY Start_dt DESC
FOR xml path('')) ,1,1,'')
--Print #cols
SET #select_cols = stuff((SELECT DISTINCT ',Max('+Quotename(CONVERT(DATE, Start_dt))+') as '+Quotename(CONVERT(DATE, t))
FROM Events
WHERE Start_dt >= Cast(Dateadd(day, -1, Getdate()) AS DATE)
ORDER BY Start_dt DESC
FOR xml path('')) ,1,1,'')
--Print #select_cols
Select #query = '
Select Name, tableName,'+#select_cols+'
from (
SELECT Name,
tableName,
Cnt
FROM Table1 l
WHERE Name=''Employee'' AND
Start_dt >= CAST(dateadd(day, -1, getdate()) as date))T
PIVOT (MAX(Cnt)FOR Start_dt IN ('+#cols+') )PVT
GROUP BY Name, tableName
'
EXEC (#query)
Note : Your current method to convert rows into csv's isn't guaranteed to work all the time. I have used for xml path() method to do the same
I have this query segment below where I'm trying to build a string of "month-year" from a date field in this table. It's very important that it comes in the right order starting from current month going forward 12 months.
DECLARE #cols AS NVARCHAR(MAX)
SELECT #cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT DISTINCT CONVERT(char(3), StartDate, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(StartDate)), 2) AS y
FROM Products2
) AS Y
--ORDER BY y desc
FOR XML PATH('')),
1, 1, N'')
This query isn't pulling the dates in the right order and I wanted to see if you guys know of any neat tricks to pull the dates in the correct order. I can bring in the startDate column and sort it by that but it brings in duplicates as it may have several entries for the same month. I've created a sample table here http://sqlfiddle.com/#!6/3a500/5
You could use
DECLARE #cols AS NVARCHAR(MAX);
SELECT #cols = STUFF((SELECT N',' + QUOTENAME(y) AS [text()]
FROM (SELECT CONVERT(CHAR(3), StartDate, 0) + '-'
+ RIGHT(CONVERT(VARCHAR, YEAR(StartDate)), 2) AS y,
MIN(StartDate) AS z
FROM Products2
GROUP BY CONVERT(CHAR(3), StartDate, 0) + '-'
+ RIGHT(CONVERT(VARCHAR, YEAR(StartDate)), 2)) AS Y
ORDER BY z
FOR XML PATH('')), 1, 1, N'');
SELECT #cols;
SQL Fiddle
It looks like you just getting the month/year so we can truncate to the first day of the month and include that in the query.
DATEADD(month, DATEDIFF(month, 0, StartDate), 0) monthStart
Now we can order by it:
DECLARE #cols AS NVARCHAR(MAX);
SELECT #cols = STUFF(
(SELECT N',' + QUOTENAME(y) AS [text()]
FROM (
SELECT DISTINCT CONVERT(char(3), StartDate, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(StartDate)), 2) AS y,
DATEADD(month, DATEDIFF(month, 0, StartDate), 0) monthStart
FROM Products2
) AS Y
ORDER BY monthStart
FOR XML PATH('')),
1, 1, N'');
select #cols;
This is the output:
[Dec-15],[Jan-16],[Feb-16],[Mar-16],[Apr-16],[May-16],[Jun-16],[Jul-16],[Aug-16],[Sep-16],[Oct-16],[Nov-16],[Dec-16]
Is that what you are looking for? Here is a fiddle:
http://sqlfiddle.com/#!6/3a500/67
Better yet, just select the distinct month start dates and then only do the string conversion on that.
DECLARE #cols AS NVARCHAR(MAX);
SELECT #cols = STUFF(
(SELECT N',' + QUOTENAME(CONVERT(char(3), monthStart, 0) + '-' +
RIGHT(CONVERT(varchar, YEAR(monthStart)), 2)) AS [text()]
FROM (
SELECT DISTINCT
DATEADD(month, DATEDIFF(month, 0, StartDate), 0) monthStart
FROM Products2
) AS Y
ORDER BY monthStart
FOR XML PATH('')),
1, 1, N'');
select #cols;
Here is that fiddle:
http://sqlfiddle.com/#!6/3a500/72
If you are using SQL Server 2012+ you could use FORMAT function:
DECLARE #cols AS NVARCHAR(MAX);
;WITH cte AS -- get only one date per month/year
(
SELECT MIN(StartDate) AS StartDate
FROM #Products2
GROUP BY YEAR(StartDate),MONTH(StartDate)
)
SELECT #cols = STUFF((SELECT ',' + QUOTENAME(FORMAT(StartDate, 'MMM-yy'))
FROM cte
ORDER BY StartDate
FOR XML PATH('')),
1, 1, N'');
SELECT #cols;
LiveDemo
Output:
╔══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╗
║ result ║
╠══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╣
║ [Dec-15],[Jan-16],[Feb-16],[Mar-16],[Apr-16],[May-16],[Jun-16],[Jul-16],[Aug-16],[Sep-16],[Oct-16],[Nov-16],[Dec-16] ║
╚══════════════════════════════════════════════════════════════════════════════════════════════════════════════════════╝
I have a stored procedure that accepts two Dates. In my stored procedure, I need to create a temp table with the months in between the two dates as columns.
For example,
If the user passes in
1/1/2016 , 8/1/2016
I need a temp table with the columns:
January February March April May June July August
How could I create this type of temp table with columns created in this manner? With the columns being based on the two dates passed in?
The following script should get you started (and almost there):
declare #start_date DATE = '20160101'
declare #end_date DATE = '20160801'
;WITH CTE AS
(
SELECT #start_date AS cte_start_date, DATENAME(month, #start_date) AS Name,
CAST(' ALTER TABLE #myTemp ADD ' + DATENAME(month, #start_date) + ' INT ' + CHAR(13) + CHAR(10) AS VARCHAR(8000)) AS SqlStr
UNION ALL
SELECT DATEADD(MONTH, 1, cte_start_date), DATENAME(month, DATEADD(MONTH, 1, cte_start_date)) AS Name,
CAST(SqlStr + ' ALTER TABLE #myTemp ADD ' + DATENAME(month, DATEADD(MONTH, 1, cte_start_date)) + ' INT ' + CHAR(13) + CHAR(10) AS VARCHAR(8000))
FROM CTE
WHERE DATEADD(MONTH, 1, cte_start_date) <= #end_date
)
SELECT cte_start_date, Name, SqlStr
FROM CTE
Using a recursive-CTE it generates a loop between start and end date, and for each month it computes its string representation and also creates a alter script to add the columns to a temporary table.
The CTE computes the SQL script gradually, so that the final script is on the last line.
Try This ....
declare #start_date DATE = '20160101'
declare #end_date DATE = '20160801'
;WITH CTE AS
(
SELECT #start_date AS cte_start_date, DATENAME(month, #start_date) AS NAME , 0 AS Coun
UNION ALL
SELECT DATEADD(MONTH, 1, cte_start_date), DATENAME(month, DATEADD(MONTH, 1, cte_start_date)) AS NAME , 0 AS Coun
FROM CTE
WHERE DATEADD(MONTH, 1, cte_start_date) <= #end_date
)
SELECT Coun,Name
INTO #tmp1
FROM CTE
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
select #cols = STUFF((SELECT ',' + Name
from #tmp1
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT ' + #cols + ' from
(
select Coun,Name
from #tmp1
) x
pivot
(
MAX(Coun)
for Name in (' + #cols + ')
) p '
execute(#query);
DROP TABLE #tmp1
It Will Return OutPut Like your expected output .......
Columns [today], [30days ago], [60days ago], [90 days ago]
I have a table the will provide me with the sales number fro today and 30days ago, 60 days ago and 90 days ago,
But the issue I have is to figure out what the actual date was 60 days ago.
I wanted to update my script not show me 60days ago but to show me the actual date 60days ago. I want to make my columns dynamic so i get the actual date 90days ago.
Can anyone help me here?
Please remember this is a long scripts and 10 columns and I want to change each column to show me the actual date and not the 90 days ago.
You can use GETDATE() and DATEADD() to get these values:
SELECT CONVERT(DATE, GETDATE()) AS Today,
CONVERT(DATE, DATEADD(DAY, -30, GETDATE())) Minus30
Converting to DATE simply takes off the time portion.
Produces:
Today Minus30
2015-06-08 2015-05-09
To use these values you can assign a few variables and set the values to be used later in your code:
DECLARE #today DATE, #Minus30 DATE
SELECT #today = CONVERT(DATE, GETDATE()),
#Minus30 = CONVERT(DATE, DATEADD(DAY, -30, GETDATE()))
PRINT #today
PRINT #Minus30
To use the values as column names, you'll need to use some dynamic SQL:
DECLARE #today NVARCHAR(15), #Minus30 NVARCHAR(15)
SELECT #today = CONVERT(NVARCHAR(15), CONVERT(DATE, GETDATE())),
#Minus30 = CONVERT(NVARCHAR(15), CONVERT(DATE, DATEADD(DAY, -30, GETDATE())))
EXEC ('SELECT ''30'' AS ''' + #today + ''', ''123'' AS ''' + #Minus30 + '''')
Produces:
2015-06-08 2015-05-09
30 123
This is best handled in the client code that retrieves the sql results. Using c# as an example:
string sql = String.Format(
"SELECT [today] as [{0}], [30days ago] as [{1}], [60days ago] as [{2}], [90 days ago] as [{3}] FROM [MyTable]",
DateTime.Today.ToShortDateString(),
DateTime.Today.AddDays(-30).ToShortDateString(),
DateTime.Today.AddDays(-60).ToShortDateString(),
DateTime.Today.AddDays(-90).ToShortDateString());
If you really need to, you could put the same string logic into procedural sql, and save that to a stored procedure. But don't do that.
I haven't actually tried doing this, but you can try to use an sp_rename statement using the variable names that #Tanner suggested:
DECLARE #Minus30 DATE
SELECT #Minus30 = CONVERT(DATE, DATEADD(DAY, -30, GETDATE()))
EXEC sp_rename 'Table.[30days ago]', #Minus30, 'COLUMN'
If you are trying to build a list of dynamic date columns, you could do this using dynamic pivot method something like this:
Creating a Test Table
create table pvtTbl(DateColumns varchar(50),sales money,employeeid int);
insert into pvtTbl
select cast(getdate() as date) datecol,1249.00 as sales,123 employeeid
UNION
select cast(dateadd(day,-30,getdate()) as date) datecol,15615.00 as sales,456 employeeid
UNION
select cast(dateadd(day,-60,getdate()) as date) datecol,125583.00 sales,356 employeeid
UNION
select cast(dateadd(day,-90,getdate()) as date) datecol,25571.00 sales,859 employeeid
This part, I am just creating as an example to build the pivot table. But, you could actually create this step as an insert statement to a temp table in your stored proc, so that it will be updated each time to dynamically build your date columns using this query:
Query to build a list of needed date values
select cast(getdate() as date) datecol
UNION
select cast(dateadd(day,-30,getdate()) as date) datecol
UNION
select cast(dateadd(day,-60,getdate()) as date) datecol
UNION
select cast(dateadd(day,-90,getdate()) as date) datecol
After that, all you will need to do is build a dynamic pivot using STUFF function and XML path to first build a comma separated list of date values and then pivot it dynamically to output it as your columns like this:
Dynamic Pivot to build date columns
DECLARE #cols AS NVARCHAR(MAX),
#query AS NVARCHAR(MAX)
SET #cols = STUFF((SELECT distinct ',' + QUOTENAME(c.DateColumns)
FROM pvtTbl c
FOR XML PATH(''), TYPE
).value('.', 'NVARCHAR(MAX)')
,1,1,'')
set #query = 'SELECT employeeid,' + #cols + ' from
(
select sales,employeeid,DateColumns
from pvtTbl
) x
pivot
(
sum(sales)
for DateColumns in (' + #cols + ')
) p '
execute(#query)
Anyway, this would be a good idea if you build this as a stored procedure. Let me know if you need any more help.
Demo
At the end of my sql, I am using the following code. Is there any way of replacing the fixed strings [2011/07/14], [2011/07/16], etc, to GetDate() value?
PIVOT
(
count([AppointmentsBooked])
FOR [date] IN ([2011/07/14], [2011/07/16], [2011/07/17],[2011/07/18],[2011/07/21])
) as pivottable
Can you try to use BETWEEN and DATEADD in following manner:
DECLARE #dates TABLE(value DateTime)
INSERT INTO #dates VALUES
(GETDATE()),
('2011/07/9'),
('2011/07/17'),
('2011/07/18'),
('2011/07/21')
SELECT value FROM #dates
WHERE value BETWEEN GETDATE() AND DATEADD(day, 5, GETDATE())
You can create a string of all dates which is comma seperated with '[' and ']' before and after each date. assign this string to a string variable (#dates) and use the string spit method to split all dates inside the pivot query.
this question was posted about a year ago. i don't care. i have some code that might be exactly what the OP wanted.... i'm sure he'll never come back and chose an answer but still.... all i want to do is count the records by month with a pivot for a few tables and ultimately compare the number of records for each month for each table. however... in this code there is only one table (rt_taco_15m) but that doesn't matter. i just haven't written the rest to completely fit my needs. but i think it fits the needs of the OP or at least gets him on a good start.... if he truly has been waiting a year on this problem. lol.
if object_id('tempdb..#temp') is not null drop table #temp
if object_id('tempdb..#temp2') is not null drop table #temp2
if object_id('tempdb..#temp3') is not null drop table #temp3
declare #start_date as datetime
set #start_date = cast('1-1-2012' as datetime)
declare #end_date as datetime
set #end_date = cast('9-1-2012' as datetime)
;with cte as (
select #start_date as [start],
dateadd(month, 1, #start_date) as [end]
union all
select dateadd(month, 1, [start]) as [start],
dateadd(month, 1, dateadd(month, 1, [start])) as [end]
from cte
where dateadd(month, 1, [start]) <= #end_date
)
(select 'rt_taco_15m' as table_name,
convert(varchar(10), [start], 101) as [start],
convert(varchar(10), [end], 101) as [end],
datename(month, [start]) as month_name,
cast([start] as integer) as orderby,
count(taco.taco_record_id) as [range_count]
into #temp
from cte
left outer join rt_taco_15m as taco
on taco.period >= cte.[start] and
taco.period < cte.[end]
group by cte.[start], cte.[end])
select table_name as table_name,
convert(varchar(10), getdate(), 101) as [start],
convert(varchar(10), getdate(), 101) as [end],
'Total' as month_name,
cast(dateadd(month,2,#end_date) as integer) as orderby,
range_sum as [range_count]
into #temp2
from (select table_name, sum([range_count]) as range_sum
from #temp group by table_name) as summed_up
select *
into #temp3
from (select * from #temp
union all
select * from #temp2) as x
order by orderby
select * from #temp3
declare #cols nvarchar(2000)
select #cols = stuff(
(select '],[' + month_name
from #temp3
order by orderby
for xml path('') )
, 1, 2, '') + ']'
print #cols
if object_id('tempdb..#temp2') is not null drop table #temp2
declare #query varchar(max)
set #query = N'
select table_name, ' + #cols + N'
from (select table_name, month_name, range_count
from #temp3) p
pivot ( sum(range_count) for month_name in ( '+ #cols +' ) ) as pvt'
execute(#query