Splitting full date into day/month/quarter/year columns - sql

I have a simple table structure, where my DATE column has yyyy-mm-dd format. I'd like to split all the dates and INSERT them in to my time dimension table, that contains day, month, quarter and year columns. I'm not sure how to get going with this (trying with query as insert into).
EXAMPLE: SPLIT 2010-03-01 AND INSERT 2010 into year column, 03 into month column, 01 into day column and 1 into quarter column.
Additionally I'd like to assign the names to specific date part (for example month_name for 1 is January), is it good practice to store date-name values inside same table?
Edit: I've just realized that I probably made big mistake? Is TIME dimension in DATA WAREHOUSE is supposed to store unique data only (for description purposes?)

Typically a time dimension implies time (hours, minutes, seconds) or datetime.
It sounds like you just need a date dimension. Most of the work is already done for you here: http://smehrozalam.wordpress.com/2009/06/09/t-sql-using-common-table-expressions-cte-to-generate-sequences/
If you are still in the design phase of the fact table, I'd recommend going with date rather than the YYYYMMDD ID format in the tutorial for your PK on the dimension. It's a byte cheaper per row and enables date math. Before SQL 2008, the int dateid format made sense. Now that date is available, it's a more appropriate choice.
As for uniqueness, for hierarchies and associated attribute relationships in ssas I'll typically combine the necessary columns to uniquely identify the period. For example:
SELECT
CAST(YEAR(GETDATE()) as char(4)) + ' ' + DATENAME(MONTH,GETDATE()) MonthUniqueName
, CAST(YEAR(GETDATE()) as char(4)) + ' Q' + CAST(DATEPART(QUARTER,GETDATE()) as char(1)) QuarterUniqueName
returns
MonthUniqueName QuarterUniqueName
2013 March 2013 Q1

Is this what you want?
select Datename(yy,yourDateColumn) as year, Datename(quarter,yourDateColumn), Datename(month, yourDateColumn),Datename(day, yourDatecolumn),CONVERT(varchar(3), yourDateColumn, 100) from yourTable
Assuming you have column type as DateTime
Insert into yourTable (yearColumn, quarterColumn, monthColumn, dayColumn) values
Datename(yy,yourDateColumn) , Datename(quarter,yourDateColumn), Datename(month, yourDateColumn),Datename(day, yourDatecolumn)

-- First solution: date is stored and day, month, year and quarter are computed
DECLARE #Table1 TABLE (
Dt DATE NOT NULL,
DD AS CONVERT(TINYINT, DATEPART(DAY, Dt)) PERSISTED,
MM AS CONVERT(TINYINT, DATEPART(MONTH, Dt)) PERSISTED,
YYYY AS CONVERT(SMALLINT, DATEPART(YEAR, Dt)) PERSISTED,
QQ AS CONVERT(TINYINT, DATEPART(QUARTER, Dt)) PERSISTED
);
INSERT #Table1
VALUES (GETDATE());
SELECT * FROM #Table1;
-- Second solution: day, month and year are stored and date and quarter are stored
DECLARE #Table2 TABLE (
Dt AS CONVERT(DATE, DATEADD(DAY, DD-1, DATEADD(MONTH, MM-1, DATEADD(YEAR, YYYY-1, CONVERT(DATE, '00010101',112))))) PERSISTED,
DD TINYINT NOT NULL CHECK(DD BETWEEN 1 AND 31),
MM TINYINT NOT NULL CHECK(MM BETWEEN 1 AND 12),
YYYY SMALLINT NOT NULL CHECK(YYYY BETWEEN 1 AND 9999),
QQ AS CONVERT(TINYINT, ((MM-1)/3)+1) PERSISTED
);
INSERT #Table2 (DD, MM, YYYY)
VALUES (9,3,2013);
SELECT * FROM #Table2;
Results:
Dt DD MM YYYY QQ
---------- ---- ---- ------ ----
2013-03-09 9 3 2013 1
Dt DD MM YYYY QQ
---------- ---- ---- ------ ----
2013-03-09 9 3 2013 1

Related

Setting SQL Date field value based off Week Number and Day Number Field

Is there a procedure or means of setting the Date value based off a week value and day value.
To elaborate further using this calendar year:
January 1st-7th is week 1,
January 8th-14th is week 2 etc.
As for Day number value;
Monday is 1
Tuesday is 2
Wednesday is 3
Thursday is 4
Friday is 5.
Using this logic I wish to create and populate a date field that uses these values.
For instance, I want Week: 2 - Day: 3 to populate my new date field as 10/1/2018
Any help and guidance will be greatly appreciated.
Some things can be done with the datetime functions to calculate a date from just the week number and the weekday number.
However, beware that ##DATEFIRST can be different on a server.
Which defines which weekday is the first day in the week.
So it's best change it in your session to 1 to make Monday the first weekday.
Without it there might be an adjustment needed in the calculation used below.
For example [Weekday]+1 when select ##DATEFIRST returns 7
Example snippet:
declare #Table table ([Week] int, [Weekday] int);
insert into #Table ([Week], [Weekday]) values
(1, 1)
,(1, 7)
,(2, 1)
,(2, 7)
;
-- The US default for ##DATEFIRST is 7, which would make Sunday weekday 1.
-- By setting ##DATEFIRST to 1 then Monday will be weekday 1
SET DATEFIRST 1;
SELECT [Week], [Weekday],
DATEADD(WEEKDAY, 1-DATEPART(WEEKDAY, DATEFROMPARTS(DATEPART(YEAR,GetDate()),1,1)), DATEADD(WEEK,[Week]-1, DATEFROMPARTS(DATEPART(YEAR,GetDate()),1,[Weekday]))) AS CalcDate
FROM #Table
ORDER BY [Week], [Weekday];
Result:
Week Weekday CalcDate
---- ------- ----------
1 1 2018-01-01
1 7 2018-01-07
2 1 2018-01-08
2 7 2018-01-14
Note that the year is based on the current date.
But if it's to update an existing table?
Then you could look at it in another way.
Create a temp table with all the dates in the current year, and derive the week & weekday from those dates.
Then you can use that temp table to update your existing table.
Example snippet:
-- Create a temporary table
IF OBJECT_ID('tempdb..#tmpDates') IS NOT NULL DROP TABLE #tmpDates;
CREATE TABLE #tmpDates ([Week] INT NOT NULL, [WeekDay] INT NOT NULL, [Date] DATE NOT NULL, PRIMARY KEY ([Week], [WeekDay]));
-- Make Monday the first weekday
SET DATEFIRST 1;
-- Filling the temp table with data
WITH DATES2018 AS
(
SELECT CAST('2018-01-01' AS DATE) AS [Date]
UNION ALL
SELECT DATEADD(day, 1, [Date])
FROM DATES2018
WHERE [Date] < CAST('2018-12-31' AS DATE)
)
INSERT INTO #tmpDates ([Date], [Week], [WeekDay])
SELECT [Date], DATEPART(WEEK, [Date]) AS [Week], DATEPART(WEEKDAY, [Date]) as [WeekDay]
FROM DATES2018
ORDER BY [Week], [WeekDay]
OPTION (MAXRECURSION 366);
-- Just using a table variable per demonstration
declare #YourTable table ([Week] int, [Weekday] int, [Date] DATE);
-- Sample data
insert into #YourTable ([Week], [Weekday]) values
(1, 1)
,(1, 7)
,(2, 1)
,(2, 7)
;
-- update based on the temp table
update t
set [Date] = tmp.[Date]
from #YourTable t
join #tmpDates tmp on (tmp.[Week] = t.[Week] AND tmp.[WeekDay] = t.[Weekday])
where (t.[Date] is null OR t.[Date] <> tmp.[Date]);
select * from #YourTable;
You can refer this link which has a calendar table creation statement
https://www.sqlshack.com/designing-a-calendar-table/
What you need to do is map your available columns like year, week and day to the calendar table and extract the date
Maybe something like this can get you started.
It will return the date from your sample, but I did not tested in all situations...
So I am sure it will need some finetuning and using a Calender Table as #Ajan-Balakumaran suggests would be far better
declare #year int = datepart(year, getdate())
declare #month int
declare #week int = 2
declare #day int = 3
SELECT #month = DATEPART(MM,CAST(CONVERT(CHAR(3),
DATEADD(WW, #WEEK - 1,
CONVERT(datetime, '01/01/' + CONVERT(char(4) ,#Year)))
, 100) + ' 1900' AS DATETIME))
select datefromparts(2018, #month, #day + (#week - 1) * 7)

SQL date difference ignoring years

I have two date columns and I would like to calculate their differences in Months/Days and exclude years.
Assuming the first date is 10/30/2017, comparing it to the current date, its difference should one. If the current date is 10/30/2018, the difference should also be one.
A few examples:
Schedule Date:10/30/2017 Current Date 10/29/2017 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2017 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2018 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2018 Diff 30
Schedule Date:10/30/2017 Current Date 10/29/2019 Diff 1
Schedule Date:10/30/2017 Current Date 11/30/2019 Diff 30
Try This
SELECT ABS(365 * DATEDIFF(year, '10/30/2017', '11/30/2018')
- DATEDIFF(day, '10/30/2017', '11/30/2018')) AS DateDiff;
Note that the difference between 10/30/ and 11/30/ can't be 30 days as you have shown.It is 31 days.
You can use Common Table Expression to get your results like below :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2019'
;WITH usingCTE AS
(
SELECT CAST(STUFF(CONVERT(varchar, #Schedule_Date, 102), 1, 4, CAST(YEAR(#Current_Date) AS varchar)) AS datetime) AS Schedule_Date
)
SELECT abs(DATEDIFF(day, #Current_Date, Schedule_Date)) FROM usingCTE
Another approach of the query :
DECLARE #Schedule_Date datetime = '10/30/2017'
DECLARE #Current_Date datetime = '11/30/2017'
SELECT ABS(DATEDIFF(day,
REPLACE(#Schedule_Date, DATEPART(year, #Schedule_Date),
DATEPART(year, #Current_Date)), -- replace the year with current year
#Current_Date))
This might work for you, first adjust the date to be in the same year, then calculate the number of days different between the two dates:
DECLARE #ScheduleDate DATE = '2017-10-30';
DECLARE #Dates TABLE (CurrentDate DATE);
INSERT INTO #Dates VALUES ('2017-10-29'),('2017-11-30'),('2018-10-29'),('2018-11-30'),('2019-10-29'),('2019-11-30');
SELECT #ScheduleDate ScheduleDate, *
FROM #Dates a
CROSS APPLY (SELECT AdjustedDate=DATEADD(YEAR, YEAR(#ScheduleDate) - YEAR(a.CurrentDate), a.CurrentDate)) b
CROSS APPLY (SELECT Diff=ABS(DATEDIFF(DAY, #ScheduleDate, b.AdjustedDate))) c

SQL Server: get all previous values that are within half year of a month (not in future)?

I have values such as
201401
201411
201501
201504
201508
201606
If I select values last six months from 201501, I want to get 201411. If last six months from 201606, then nothing. If from 201508, then 201504.
I have a month column of the varchar form 201601.
How can I get the last six months relative to each month with some datatype objects such as datepart functions?
Another option which will reduce record level processing/conversions
Declare #YourTable table (SomeCol varchar(6))
Insert Into #YourTable values
(201401),
(201411),
(201501),
(201504),
(201508),
(201606)
Declare #Target varchar(6) = '201508'
Select *
From #YourTable
Where SomeCol >= format(dateadd(MONTH,-6,cast(#Target+'01' as date)),'yyyyMM')
and SomeCol < #Target
Returns
201504
Here is one method:
where left(val, 4) * 12 + right(val, 2) >= left('201501', 4) * 12 + right('201501', 2) - 6
This is rather inelegant. Basically is converts the values to months since a date.
Alternatively, you can use date arithmetic:
where cast(val + '01' as date) >= dateadd(month, -6, cast('201501' + '01' as date)
In both cases, you can add a computed column and then an index on the computed column to make the queries run faster.

Need SQL Server Query help, Newbie

I have a table that has the following columns.
id,
compid(used to identify a piece of equipment),
startalarmdate,
endalarmdate
when a piece of equipment goes into alarm, I insert the compid and startalarmdate(with the current time) into the table and when the equipment comes out of alarm I fill in the null in that same row in the endalarmdate column with the current time.
so I end up with something like this in the table
417,6,Sun Oct 30 18:48:17 CDT 2011,Mon Oct 31 09:49:21 CDT 2011
422,6,Mon Oct 31 10:19:19 CDT 2011,Mon Oct 31 12:19:22 CDT 2011
427,6,Mon Oct 31 20:19:56 CDT 2011,Tue Nov 01 09:50:59 CDT 2011
429,6,Tue Nov 01 21:51:41 CDT 2011,Wed Nov 02 09:52:37 CDT 2011
432,6,Wed Nov 02 21:23:23 CDT 2011,Fri Nov 04 16:26:29 CDT 2011
I was able to build a query that gives me a total downtime in hours for each event, but what I would like to do now is build a query that gives me a total hours in downtime for each day of a month. Id like it to have the compid all the way to the left, then have each day of the month to the right of the compid in a column on the same row. Id like the days with no downtime to be null. Is it possible to do that with the way this table is setup?
Step 1: set up a temp table containing the desired "time blocks" that you want to total for. These blocks could be for any range of time; in your example, it would be one entry for ever day (24-hour period) in the month.
CREATE TABLE #TimeRanges
(
RangeStart datetime not null
,RangeEnd datetime not null
)
Left-outer-joining this table on your data ensures you get at least one row per time block (day), even if there were no alarms occuring that day:
SELECT
tr.RangeStart -- Use start of each time block to identify the block
,md.CompId -- With left outer join, will be 0 or more rows for each block
,sum(datediff(hh
,case
when tr.RangeStart > md.StartAlarmDate then tr.RangeStart
else md.StartAlarmDate
end
,case
when tr.RangeEnd > md.EndAlarmDate then tr.RangeEnd
else md.EndAlarmDate
end)) HoursInRange
from #TimeRanges tr
left outer join MyData md
on md.StartAlarmDate < tr.RangeEnd
and md.EndAlarmDate > tr.From
group by
tr.RangeStart
,md.CompId
(I can't test this code, some debugging may be required--but the concept is solid. I'll let you worry about rounding partial hours, and whether you want > and <, or >= and <= (things may get tricky if an alarm starts and/or ends at the exact same point in time as a block boundary).
Edit/Addenda
Here's a fairly basic way to set up the temp table used in the routine (this code, I tested):
-- Set up and initialize some variables
DECLARE
#FirstDay datetime
,#NumberOfDays int
SET #FirstDay = 'Oct 1, 2011' -- Without time, this makes it "midnight the morning of" that day
SET #NumberOfDays = 91 -- Through Dec 31
-- Creates a temporary table that will persist until it is dropped or the connection is closed
CREATE TABLE #TimeRanges
(
RangeStart datetime not null
,RangeEnd datetime not null
)
-- The order in which you add rows to the table is irrelevant. By adding from last to first, I
-- only have to fuss around with one variable, instead of two (the counter and the end-point)
WHILE #NumberOfDays >= 0
BEGIN
INSERT #TimeRanges (RangeStart, RangeEnd)
values ( dateadd(dd, #NumberOfDays, #FirstDay) -- Start of day
,dateadd(dd, #NumberOfDays + 1, #FirstDay)) -- Start of the next day
SET #NumberOfDays = #NumberOfDays - 1
END
-- Review results
SELECT *
from #TimeRanges
order by RangeStart
-- Not necessary, but doesn't hurt, especially when testing code
DROP TABLE #TimeRanges
Note that by making RangeEnd the start of the next day, you have to be careful with your greaterthans and lessthans. The details can get very finicky and fussy there, and you'll want to do a lot of edge-case testing (what if alarm starts, or ends, exactly at Dec 16 2011 00:00.000). I'd go with that, because overall it's simpler to code for than for junk like 'Dec 16, 2011 23:59.997'
As mentionned by #paulbailey, you want to use the DATEDIFF function to get the amount of downtime.
To extract the dates and downtime period (I'm adding a bit more columns that you might need)..
SELECT compid,
YEAR(startalarmdate) AS [Year],
MONTH(startalarmdate) AS [Month],
DAY(startalarmdate) AS [Day],
DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use
FROM YourTable
/* WHERE CLAUSE - Most probably a date range */
Now this gives you the downtime in seconds for each days that had a downtime.
To get the amount of downtime per day is easy as grouping by day and SUMing up the downtimes (again adding more columns that you might need)..
SELECT compid,
[Year],
[Month],
[Day],
SUM(DowntimeInSeconds) AS TotalDowntimeInSeconds
FROM (SELECT compid,
YEAR(startalarmdate) AS [Year],
MONTH(startalarmdate) AS [Month],
DAY(startalarmdate) AS [Day],
DATEDIFF(ss, startalarmdate, endalarmdate) AS DowntimeInSeconds --You will need to convert thid later to the format you wish to use
FROM YourTable
/* WHERE CLAUSE - Most probably a date range */) AS GetDowntimes
GROUP BY compid, [Year], [Month], [Day]
ORDER BY [Year], [Month], [Day], compid
And I believe this should help you get where you want to.
Edit:
To have the days that have no downtime included in this result, you need to first have a list of ALL days present in a month. You take this list and you LEFT OUTER JOIN the result from the above query (you will have to remove the ORDER BY first).
The case statement in Philip Kelley's answer does not work, although the main principal of filling a temp table with dates and left joining stands true. For my version I've used the same variable to start - an input date and the number of days to report on.
DECLARE #StartDate DATETIME, #Days INT
SELECT #StartDate = GETDATE(),
#Days = 5
-- REMOVE ANY TIME FROM THE STARTDATE
SET #StartDate = DATEADD(DAY, 0, DATEDIFF(DAY, 0, #StartDate))
-- CREATE THE TEMP TABLE OF DATES USING THE SAME METHODOLOGY
DECLARE #Dates TABLE (AlarmDate SMALLDATETIME NOT NULL PRIMARY KEY)
WHILE (#Days > 0)
BEGIN
INSERT #Dates VALUES (DATEADD(DAY, #Days, #StartDate))
SET #Days = #Days - 1
END
-- NOW SELECT THE DATA
SELECT AlarmDate,
CompID,
CONVERT(DECIMAL(10, 2), ISNULL(SUM(DownTime), 0) / 3600.0) [DownTime]
FROM #Dates
LEFT JOIN
( SELECT DATEADD(DAY, 0, DATEDIFF(DAY, 0, StartAlarmDate)) [StartAlarmDate],
CompID,
DATEDIFF(SECOND, StartAlarmDate, CASE WHEN EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) THEN DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)) ELSE EndAlarmDate END) [DownTime]
FROM [yourTable]
UNION ALL
SELECT DATEADD(DAY, 0, DATEDIFF(DAY, 0, EndAlarmDate)) [Date],
CompID,
DATEDIFF(SECOND, DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate)), EndAlarmDate) [DownTime]
FROM [yourTable]
WHERE EndAlarmDate >= DATEADD(DAY, 1, DATEDIFF(DAY, 0, StartAlarmDate))
) data
ON StartAlarmDate = AlarmDate
GROUP BY AlarmDate, CompID
I have used seconds for the date diff and divided by 3600.0 after the seconds have been summed up as 60 rows each with a difference of a minute would sum to 0 when using hours for a datediff.

How to generate week ranges for a year in SQL server 2005?

I have to insert YEAR WEEKNUMBER STARTDATE ENDDATE values to a datatable (say weekrange), if I pass 2011 as year.
Week range starts at 2011-03-28 to 2011-04-03 (because in my database 2010 last week range ends with 2011-03-27)
Like this I have to generate for 52 weeks.
I want to write a stored procedure, that takes only year as parameter. with this year I have to generate week ranges and insert into my table as shown above.
How can I do this ?
Regards,
jn
The following will generate the data you need based on SQL server's week number definition (see the notes on DATEPART(ww,...) here). Note that this will mean some years have 53 weeks.
DECLARE #year AS CHAR(4)
SET #year = '2011'
DECLARE #firstDay DATETIME
SET #firstDay = CAST(#year + '0101' AS DATETIME)
;WITH dayCTE
AS
(
SELECT DATEADD(dd,ROW_NUMBER() OVER (ORDER BY name) - 1, #firstDay) AS yearday
FROM master.dbo.spt_values
)
,weekCTE
AS
(
SELECT yearday,
DATEPART(WW,yearday) AS weekno
FROM dayCTE
WHERE YEAR(yearday) = #year
)
SELECT #year AS [YEAR],
weekno AS WEEKNUMBER,
MIN(yearday) AS STARTDATE,
MAX(yearday) AS ENDDATE
FROM weekCTE
GROUP BY weekno
ORDER BY weekno
It's a side point, but I'd recommend renaming the YEAR column of your target table to something which isn't a T-SQL keyword.