dynamic columns for dates - sql

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

Related

SQL how to select dates between 2 date parameters as columns in a stored procedure

I need to write a stored procedure that takes 2 date parameters, sums up some data for that date, and returns a row with the dates inbetween as columns.
Im not sure where to start.
Lets say my stored procedure looks like this:
spGetAccountBalanceByDay(DateTime startDate, DateTime endDate)
I would like the column name to be formatted like this: F_{0}{1}{2} where {0} = year, {1} = Month, and {2} = Day.
so for the date 13/12/2014, my column would be called f_2014_12_13. I have a datasource that has dynamic properties which match ( as the grid in question can be run for any date range )
So in the SQL stored procedure, I want to loop between the 2 dates, sum the account balance for each date, and put the data in the column for that day.
So my table would look something like this returned by the stored procedure:
Account Ref | F_2014_12_13 | F_2014_12_14 | F_2014_12_15
------------------------------------------
ABB001 100 150 0
These queries can return one or more rows, I just need to know what function In SQL i should be looking to use, I know its possible to select columns dynamically just not sure how to do it.
Any advice would be appreciated.
Combining a few things I have found across the internet this is the solution I have come up with:
DECLARE #Columns VARCHAR(MAX)
DECLARE #StartDate AS DATETIME
DECLARE #EndDate AS DATETIME
DECLARE #Query AS VARCHAR(MAX)
SET #StartDate = '01 Jan 2012'
SET #EndDate = '31 Mar 2012'
;WITH dateRange as
(
SELECT [Date] = DATEADD(dd, 1, DATEADD(dd, -1,#startDate))
WHERE DATEADD(dd, 1, #startDate) < DATEADD(dd, 1, #endDate)
UNION ALL
SELECT DATEADD(dd, 1, [Date])
FROM dateRange
WHERE DATEADD(dd, 1, [Date]) < DATEADD(dd, 1,#endDate)
)
SELECT #Columns = COALESCE(#Columns, '[') + CONVERT(VARCHAR, [Date], 111) + '],['
FROM dateRange
OPTION (maxrecursion 0)
--delete last two chars of string (the ending ',[') and store columns in variable
SET #Columns = SUBSTRING(#Columns, 1, LEN(#Columns)-2)
SELECT #Columns
SET #Query =
'
SELECT *
FROM
(
SELECT
[PLSupplierAccount].[SupplierAccountNumber],
[PLSupplierAccount].[SupplierAccountName],
[PLPostedSupplierTran].[DueDate],
[PLPostedSupplierTran].[GoodsValueInAccountCurrency] * [PLPostedSupplierTran].[DocumentToBaseCurrencyRate] AS [Value]
FROM [PLPostedSupplierTran]
INNER JOIN [PLSupplierAccount]
ON [PLSupplierAccount].[PLSupplierAccountID]
= [PLPostedSupplierTran].[PLSupplierAccountID]
WHERE [PLPostedSupplierTran].[DueDate]>= ''' + CONVERT(VARCHAR(50), #StartDate, 111) + ''' AND [PLPostedSupplierTran].[DueDate]<= ''' + CONVERT(VARCHAR(50), #EndDate, 111) + '''
) src
PIVOT
(
SUM([Value])
FOR src.[DueDate] IN (' + #Columns + ')
) AS PivotView
'
EXEC (#Query)

How to grab data from the last 13 months?

So I have this code, this is for grabbing data for the last 11 month of sale including the current month, make it a whole year. What do I have to do to change it to grabbing data for the last 12 months plus current month? I know I have to change something on the right(select period)... but not sure
In this one, the left function shows how to get the current year (2014 ) minus 1 to give 2013.. but I don't understand the right function, what does 2 mean?
Thanks
period <= (
SELECT Period
FROM dbo.FiscalDates
WHERE (Date = CONVERT(varchar(10), GETDATE(), 102))) and period >= (
convert(varchar, left((
SELECT Period
FROM dbo.FiscalDates
WHERE (Date = CONVERT(varchar(10), GETDATE(), 102))),4)-1)+'-'+
convert(varchar, right((
SELECT Period
FROM dbo.FiscalDates
WHERE (Date = CONVERT(varchar(10), GETDATE(), 102))),2)))
group by prodnum, period, WhseNum
Whoah buddy I think you may be overcomplicating things here. If you want to get data for the past X number of months then just use DATEADD it's a very useful function.
All you need to do then is
select
YourColumns
FROM YourTable
WHERE YourDate >= DATEADD(MONTH, -13, CAST(GETDATE() AS DATE))
and bam there you go.
DECLARE
#FormYear AS INT,
#FormMonth AS INT,
#ToYear AS INT,
#ToMonth AS INT,
#FromDate AS DATE,
#ToDate AS DATE
SET #FormYear=YEAR(DATEADD(DAY, -365, GETDATE()))
SET #FormMonth=MONTH(DATEADD(DAY, -365, GETDATE()))
SET #ToYear=YEAR(GETDATE())
SET #ToMonth=MONTH(GETDATE())
SET #FromDate= CAST(CAST(#FormMonth AS VARCHAR) +'-'+'01'+ '-' +CAST(#FormYear AS VARCHAR) AS DATE)
SET #ToDate= CAST(CAST(#ToMonth AS VARCHAR) +'-' + '01'+'-' + CAST(#ToYear AS VARCHAR) AS DATE)
After that, just select-
YourDateField Between #FormDate AND #ToDate

Update date only in SQL Server [duplicate]

This question already has an answer here:
T-SQL: How to update just date part of datetime field?
(1 answer)
Closed 9 years ago.
I have this row in my database table with a value of 1/5/2013 5:50:00 PM, and I want to update only the date part. Time should be same without any change, need to change only the date in this record.
I have tried the update statement but it change the time as well..but I can do a
UPDATE table1
SET date = '1/10/2013 5:50:00 PM'
WHERE id =1
This not what I'm looking for, different id's have different times, so just need to update the date keeping the time in that record same.
Please give feedback.
Thank you
You can do it this way if you're using SQL Server 2008 or higher
UPDATE table1
SET [date] = cast('1/10/2013' as datetime) + cast(cast([date] as time) as datetime)
WHERE id =1
If you're using SQL Server 2005 or below, you there's no time data type, so you have to do:
UPDATE table1
SET [date] = cast('1/10/2013' as datetime) + ([date] - DATEADD(dd, 0, DATEDIFF(dd, 0, [date])))
WHERE id =1
UPDATE table1
SET date = DATEADD(dd,5,date)-- 5 is the number of days
FROM table1
WHERE id =1
Not the most elegant, but should work. Idea is to extract hours,minutes and seconds from target row and hard code other parts.
update table1 set date = DATEADD(second, DATEPART(second, date), DATEADD(minute, DATEPART(minute, date), DATEADD(hour, DATEPART(hour,date), '2013-01-10')));
Though a bit hackish in appearance, this will do the trick if you ONLY want to change the date part with an arbitary value, and not touch the time part at all:
update table1 set date= '06-dec-2013 ' + cast(datepart(HOUR,date) as varchar(2)) + ':' +
cast(datepart(MINUTE,date) as varchar(2)) + ':' +
cast(DATEPART(SECOND, date) as varchar(2)) + ' ' +
CASE WHEN DATEPART(HOUR, date) < 12 THEN 'AM' ELSE 'PM' END as datetime
where id=1
Replace '06-dec-2013' with the date value you want to replace with.
create table t
(
col1 datetime
)
Insert Into t
values ('1/5/2013 5:50:00 PM')
declare #newDate datetime
set #newDate = '1/10/2013'
update t
Set col1 = convert(datetime,DateAdd(day, DateDiff(day, col1, #newDate), Col1),101)
sql-fiddle demo
Here's the query you want:
declare #TargetDate datetime
set #TargetDate = '20131001' --1st October 2013
update table1 set [date] = DATEADD(day,DATEDIFF(day,[date],#TargetDate),[date])
where id = 1
And here's a complete script to demonstrate it:
declare #t table (dt datetime not null)
insert into #t (dt) values
('2001-01-01T10:53:44.993'),('2012-06-18T15:33:33.333')
declare #TargetDate datetime
set #TargetDate = '20131001' --1st October 2013
update #t set dt = DATEADD(day,DATEDIFF(day,dt,#TargetDate),dt)
select * from #t
Results:
dt
-----------------------
2013-10-01 10:53:44.993
2013-10-01 15:33:33.333
This works by using DATEDIFF to work out how many days different the target date is from each date stored in the table (with appropriate signage) and then adding that difference back onto the date - having the effect of adjusting the date portions of the stored values whilst not affecting the time.

How to sort varchar string properly with numeric values on both ends?

I'm building a Common Table Expression (CTE) in SQL Server 2008 to use in a PIVOT query.
I'm having difficulty sorting the output properly because there are numeric values that sandwich the string data in the middle. Is it possible to do this?
This is a quick and dirty example, the real query will span several years worth of values.
Example:
Declare #startdate as varchar(max);
Declare #enddate as varchar(max);
Set #startdate = cast((DATEPART(yyyy, GetDate())-1) as varchar(4))+'-12-01';
Set #enddate = cast((DATEPART(yyyy, GetDate())) as varchar(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(dd,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT DISTINCT ',' + QUOTENAME((cast(DATEPART(yyyy, dt) as varchar(4)))+'-Week'+(cast(DATEPART(ww, dt) as varchar(2)))) FROM DateRange
Current Output:
,[2012-Week48]
,[2012-Week49]
,[2012-Week50]
,[2012-Week51]
,[2012-Week52]
,[2012-Week53]
,[2013-Week1]
,[2013-Week10]
,[2013-Week11]
,[2013-Week2]
,[2013-Week3]
,[2013-Week4]
,[2013-Week5]
,[2013-Week6]
,[2013-Week7]
,[2013-Week8]
,[2013-Week9]
Desired Output:
,[2012-Week48]
,[2012-Week49]
,[2012-Week50]
,[2012-Week51]
,[2012-Week52]
,[2012-Week53]
,[2013-Week1]
,[2013-Week2]
,[2013-Week3]
,[2013-Week4]
,[2013-Week5]
,[2013-Week6]
,[2013-Week7]
,[2013-Week8]
,[2013-Week9]
,[2013-Week10]
,[2013-Week11]
EDIT
Of course after I post the question my brain started working. I changed the DATEADD to add 1 week instead of 1 day and then took out the DISTINCT in the select and it worked.
DECLARE #startdate AS VARCHAR(MAX);
DECLARE #enddate AS VARCHAR(MAX);
SET #startdate = CAST((DATEPART(yyyy, GetDate())-1) AS VARCHAR(4))+'-12-01';
SET #enddate = CAST((DATEPART(yyyy, GetDate())) AS VARCHAR(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(ww,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT ',' + QUOTENAME((CAST(DATEPART(yyyy, dt) AS VARCHAR(4)))+'-Week'+(CAST(DATEPART(ww, dt) AS VARCHAR(2)))) FROM DateRange
I can't see the sample SQL code (that site is blacklisted where I am).
Here is a trick for sorting that data in the proper order is to use the length first and then the values:
select col
from t
order by left(col, 6), len(col), col;
Have you considered to sort on two temporary columns (year in smallint and week in tinyint to save space … or directly using the datepart integer if space is not a problem to you and you prefer fast run) along with the use of "order by year, week" ?
If you store dates using a more suitable type (what I suggest), it would then become :
WITH [Define the CTE expression name and column list]
AS
(
SELECT CAST(DATEPART(yyyy, dt) as smallint(4)) year, cast(DATEPART(ww, dt) as tinyint(2)) week, [your columns here]
FROM DateRange WHERE dt < #enddate
)
[Define the outer query referencing the CTE name]
ORDER BY year, week;
GO
Also, please note that string operations will slow your queries so avoid them when possible !
I like Gordon's answer, but if you were hell-bent on text manipulation in your order by:
ORDER BY CAST(REPLACE(LEFT('[2012-Week48]',5),'[','')AS INT)
,CAST(REPLACE(RIGHT('[2012-Week48]',CHARINDEX('Week','[2012-Week48]')-4),']','') AS INT)
Here is another option converting the beginning and ending parts of the column to integer.
SELECT *
FROM YourTable
ORDER BY CAST(SUBSTRING(yourcolumn,1,4) as int),
CAST(SUBSTRING(yourcolumn,CHARINDEX('Week',yourcolumn)+4,len(yourcolumn)) as int)
SQL Fiddle Demo
This will work assuming the format of the data is always the same.
Since you are using dt to generate the string, you should sort by using the date's parts:
WITH DateRange(dt) ...
SELECT DISTINCT ',' + QUOTENAM...
ORDER BY DATEPART(yyyy, dt), DATEPART(ww, dt)
I needed to change the DATEADD portion of the query and remove the DISTINCT. Once changed the order sorted properly on it's own
DECLARE #startdate AS VARCHAR(MAX);
DECLARE #enddate AS VARCHAR(MAX);
SET #startdate = CAST((DATEPART(yyyy, GetDate())-1) AS VARCHAR(4))+'-12-01';
SET #enddate = CAST((DATEPART(yyyy, GetDate())) AS VARCHAR(4))+'-03-15';
WITH DateRange(dt) AS
(
SELECT CONVERT(datetime, #startdate) dt
UNION ALL
SELECT DATEADD(ww,1,dt) dt FROM DateRange WHERE dt < CONVERT(datetime, #enddate)
)
SELECT ',' + QUOTENAME((CAST(DATEPART(yyyy, dt) AS VARCHAR(4)))+'-Week'+(CAST(DATEPART(ww, dt) AS VARCHAR(2)))) FROM DateRange

SSIS job including SQL which creates temporary table

Have constructed a query which basically looks at a table which contains all bank holidays and then looks at each month for current financial year, then tells me how many working days are available to work minus the bank hols and weekends. For example this month there are 21. There is also a cumulative filed which basically adds up each month so the cumulative for April-Feb will have all those days added.
Within the query there is a CreateTable #DATA which is dropped at the end, this works fine within Management Studio and runs correctly.
My problem is that I am doing an SSIS job and have saved my query as a SQL file and have selected it using the 'Browse' button. It is not allowing me to continue as I believe it has a problem with the temporary table (See screenshot)
Any suggestions on how I can get this to work while maintaining functionality?
Please see code for reference:
DECLARE #StartDate DATETIME,
#EndDate DATETIME
SELECT #StartDate = (select
case when month(getdate()) >= 4 then
convert(datetime, cast(year(getdate()) as varchar) + '-4-1')
else
convert(datetime, cast(year(getdate())-1 as varchar) + '-4-1')
end),
#EndDate = (select
case when month(getdate()) < 4 then
convert(datetime, cast(year(getdate()) as varchar) + '-3-31')
else
convert(datetime, cast(year(getdate())+1 as varchar) + '-3-31')
end)
CREATE TABLE #data
(
firstday DATETIME NOT NULL PRIMARY KEY,
workingdays INT NOT NULL
);
WITH dayscte ([Date])
AS (SELECT #StartDate
UNION ALL
SELECT Dateadd(DAY, 1, [Date])
FROM dayscte
WHERE [Date] <= #Enddate)
INSERT INTO #data
SELECT MIN([Date]),
COUNT(*) [Day]
FROM table2
LEFT JOIN [dbo].[mydb].[mytable1]
ON [Date] BETWEEN [dbo].[mydb].[mytable1].startdate AND [dbo].[mydb].[mytable1].enddate
where
NOT EXISTS (
SELECT field1,field2 FROM [dbo].[mydb].[mytable1].tscheme_cal WHERE
dayid ='0234572347854234'
AND
[date] <= startdate
AND
[date] >= enddate
)
AND Datename(weekday, [Date]) NOT IN ( 'Saturday', 'Sunday' )
GROUP BY Datepart(MONTH, [Date]),
Datepart(YEAR, [Date])
OPTION (MAXRECURSION 366)
DECLARE #Date DATETIME
SET #Date = (SELECT MIN(firstday)
FROM #data)
SELECT Period,
workingdays [Days_Available] ,
year (firstday) AS [Year]
FROM (SELECT Datename(MONTH, firstday) [Period],
workingdays,
0 [SortField],
firstday
FROM #data
UNION
SELECT Datename(MONTH, #Date) + ' - ' + Datename(MONTH, firstday),
(SELECT SUM(workingdays)
FROM #data b
WHERE b.firstday <= a.firstday) [WorkingDays],
1 [SortField],
firstday
FROM #data a
WHERE
firstday > #Date) data
ORDER BY sortfield,
firstday
DROP TABLE #data
its not easy dealing with temp tables on SSIS.
I suggest this article:
http://www.sqllike.com/using-temporary-tables-with-ssis.html
it is a solution, but I don't like it.
I sometimes i use table variables or create a regular table on the DB and then drop it in the end.
In SSIS I find that table variables work well. You can't use temp tables even in a stored proc if it is the source code for a transformation.
In SQL Server 2012 if you use temporary tables you must specify a results set.
This is an issue with the sp_describe_first_result_set procedure that SSIS uses to returns the output metadata.
E.g.
EXEC dbo.RptResults_StoredProcedure
Becomes
EXEC dbo.RptResults_StoredProcedure
WITH RESULT SETS
((
Date NVARCHAR(10),
Location VARCHAR(12),
Department CHAR(1),
Shift CHAR(1),
ForecastSales DECIMAL(18,2),
ActualSales DECIMAL(18,2)
))
For more information view
http://blog.concentra.co.uk/2014/08/22/column-metadata-determined-correctly-ssis-data-flow-task-stored-procedure-inputs/
http://blog.concentra.co.uk/2014/08/22/column-metadata-determined-correctly-ssis-data-flow-task-stored-procedure-inputs/