Related
I have a database of employees and I am trying to count the number of employees I have between specific dates in 10 year increments. I have my syntax incorrect but I am not very proficient at identifying why this will not run.
I need the query to produce the following:
12 employees were hired between 1970 and 1980
19 employees were hired between 1980 and 1990
etc.
I keep getting this error:
Why are you looping?
DECLARE #StartYear date = '19700101',
#EndYear date = '20500101';
;WITH Years(y) AS
(
SELECT #StartYear
UNION ALL
SELECT DATEADD(YEAR, 10, y)
FROM Years
WHERE y < #EndYear
)
SELECT Years.y, EmployeesHired = COUNT(e.emp_num)
FROM Years
LEFT OUTER JOIN dbo.lgemployee AS e
ON e.emp_hire_date >= Years.y
AND e.emp_hire_date < DATEADD(YEAR, 10, Years.y)
GROUP BY Years.y;
If you really want to start with ints, you could say:
DECLARE #StartYear int = 1970,
#EndYear int = 2050;
;WITH Years(y) AS
(
SELECT DATEFROMPARTS(#StartYear, 1, 1)
UNION ALL
SELECT DATEADD(YEAR, 10, y)
FROM Years
WHERE y < DATEFROMPARTS(#EndYear, 1, 1)
)
...
Example db<>fiddle
Lots of general date tips here, especially why you want to avoid BETWEEN:
Dating Responsibly
First, you're using malformed date literals.
1970 isn't a date, '1970-01-01' is a date.
2050-01-01 isn't a date, '2050-01-01' is a date.
Second, you don't need a loop at all. Instead round the emp_hiredate down to the preceding decade start, and then group by it...
SELECT
DATEADD(YEAR, (DATEDIFF(YEAR, '1900-01-01', emp_hiredate)/10)*10, '1900-01-01') AS decade_start,
COUNT(*)
FROM
lgemployee
GROUP BY
DATEADD(YEAR, (DATEDIFF(YEAR, '1900-01-01', emp_hiredate)/10)*10, '1900-01-01')
Or even just use (DATEDIFF(YEAR, '1900-01-01', emp_hiredate)/10)*10 to identify the decade.
If a user selects a range such as:
Start: November 2016
End: September 2017,
I want to include all results that fall within the range of 2016-11-01 to 2017-09-30.
I tried concatenating together the year, month, and day, however the issue comes that not all months have the same last day. While I know all months start on day 01, a month's end day can be 28, 29, 30, or 31.
Is there a way to do this without constructing the date? SqlServer 2008 doesn't have the EOMONTH function, and I feel like anything more complex than that is not the right solution. I would like to avoid this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol <= '2017' + '-' + '09' + '-30'
It really seems to me that the easiest and best answer is to go from the first of the beginning month to the first of the month after the ending month, and make the second comparison not inclusive.
In other words, instead of this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol <= '2017' + '-' + '09' + '-30'
simply this:
WHERE
DateCol >= '2016' + '-' + '11' + '-01' AND
DateCol < '2017' + '-' + '10' + '-01'
There is a faster way to do so :
DECLARE #minDate DATE
DECLARE #maxDate DATE
SET #minDate = XXXXX
SET #maxDate = YYYYY
-- Get the first day of the month minDate.
SET #minDate = CONVERT(datetime,CONVERT(varchar(6),#minDate,112)+'01',112)
-- Get the last day of the month minDate.
SET #maxDate = CONVERT(datetime,CONVERT(varchar(6),#maxDate,112)+'01',112)
SET #maxDate = DATEADD(day, -1, DATEADD(month, 1, #maxDate))
SELECT * FROM myTABLE WHERE DateCol >= #minDate AND DateCol <= #maxDate
Or :
SELECT * FROM myTABLE
WHERE DateCol >= CONVERT(datetime,CONVERT(varchar(6),XXXXX,112)+'01',112)
AND DateCol <= DATEADD(day, -1, DATEADD(month, 1, CONVERT(datetime,CONVERT(varchar(6),YYYYY,112)+'01',112)))
Use syntax like CONVERT(datetime,'20170930',112) or CONVERT(datetime,'09-30-2017',110) for XXXXX and YYYYY rather than '2017-09-30' that use SQL Server implicit convertion from char to datetime (rely on the server configuration : can be hazardous!!!)).
Using this syntax is faster because #minDate and #maxDate do not need any evaluation. So that indexes can be used directly...
Otherwise a scalar function that will simulate the eomonth() behaviour could be usefull...
You could use following select statement to get last date of any month (and any year) by passing a field or date to it:
DECLARE #dtDate DATE
SET #dtDate = '09/25/2016'
SELECT CAST(DATEADD(s,-1,DATEADD(mm, DATEDIFF(m,0,#dtDate)+1,0)) AS DATE) AS LastDay_AnyMonth
Please provide some example data and desired result and I will update my answer further.
Here you go:
DECLARE #YourTable TABLE (YourData DATE);
INSERT INTO #YourTable VALUES
('2016-11-01'),
('2016-09-05'),
('2017-03-03'),
('2017-11-11'),
('2017-12-14'),
('2017-09-30');
WITH CTE AS (
SELECT YourData
FROM #YourTable
WHERE YEAR(YourData) =2016 AND MONTH (YourData) >= 11
)
SELECT YourData
FROM #YourTable
WHERE YEAR(YourData) =2017 AND MONTH (YourData) <= 9
UNION ALL
SELECT YourData
FROM CTE;
There is no need to know the end of the month (28 or 30 or 31).
For 2008, you can simply convert the string
Example
Select Date1=convert(date,'November 2016')
,Date2=dateadd(DAY,-1,dateadd(MONTH,1,convert(date,'September 2017')))
Returns
Date1 Date2
2016-11-01 2017-09-30
So the WHERE would be somthing like this
...
Where DateCol between convert(date,'November 2016')
and dateadd(DAY,-1,dateadd(MONTH,1,convert(date,'September 2017')))
A useful construct for performing date-based operations is to make use of a Date Dimension table. You are creating a lookup table that is populated with a lot of information about dates over a large span of time. You can then query the table based on the information that you do have. The table is small enough so that it does not impose significant performance concerns.
In your particular case, you have the month and year. You would plug that into the date dimension table to get the first of the month from the beginning month and the last of the month from the ending month. You now have a time range to search over without any complex logic or calculations on the fly.
Aaron Bertrand explains it in depth here: https://www.mssqltips.com/sqlservertip/4054/creating-a-date-dimension-or-calendar-table-in-sql-server/
This may be the dumbest question, but I have a column called [Start Time] which is (datetime, not null) and outputs data in the form '2016-05-25 00:12:01.977'
What I want is to snip everything except the Month and Year and group data into months. What I used is
select format([Start Time],'MMM/yyyy') as [Month]
Which worked fine except that it seems now no longer to be a datetime field and when I add
order by format([Start Time],'MMM/yyyy')
It puts the months in alphabetical order rather than Jan Feb etc. I tried using
convert(date,format([Start Time],'MMM/yyyy'))
but that brought back the error
Msg 241, Level 16, State 1, Line 1 Conversion failed when converting
date and/or time from character string.
Can anyone tell me if it is possible to maintain the date data type when formatting and grouping by month and if so how?
Thanks
Handling dates in SQL Server require a little bit work, but you can get around your problem by changing the dates first to the 1st of the month, grouping with that, and then finally format it, something like this:
select
format(Month2,'MMM/yyyy') as Month,
amount
from
(
select
DATEADD(month, DATEDIFF(month, 0, [Start Time]), 0) as Month2,
sum(amount) as amount
from
yourtable
group by
DATEADD(month, DATEDIFF(month, 0, [Start Time]), 0)
) X
order by
Month2
Format is also quite heavy operation, you should consider using convert or datepart if you have a lot of rows.
select format([Start Time],'MMM/yyyy') as [Month]
order by month([Start Time]), year([Start Time])
Can you check this :
Declare #t table(dtvalue datetime)
insert into #t values ('2016-05-01 00:12:01.977'), ('2016-05-06 00:12:01.977'), ('2016-05-10 00:12:01.977'), ('2016-05-15 00:12:01.977'),
('2016-05-25 00:12:01.977'), ('2016-06-25 00:12:01.977'), ('2016-06-28 00:12:01.977')
--select * from #t
select dtvalue,
format(dtvalue,'MMM/yyyy')
from #t
group by dtvalue
insert into #t values ('2016-05-02 00:12:01.977')
select dtvalue,
format(dtvalue,'MMM/yyyy')
from #t
group by dtvalue
/*Count the record in a month via grouping*/
select count (format(dtvalue,'MMM/yyyy')),
format(dtvalue,'MMM/yyyy')
from #t
group by format(dtvalue,'MMM/yyyy')
I'd like to get 4:30 PM of the current day. Hard-coding this way doesn't work:
SELECT '07242012 16:30:00.000'
This is proving to be more difficult than I thought it would be. How do I approach this?
SQL Server 2000 / 2005:
SELECT DATEADD(MINUTE, 30, DATEADD(HOUR, 16, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP)));
-- or
SELECT DATEADD(MINUTE, (16*60) + 30, DATEDIFF(DAY, 0, CURRENT_TIMESTAMP))
-- or
SELECT CONVERT(DATETIME, CONVERT(CHAR(9), CURRENT_TIMESTAMP, 112) + '16:30');
SQL Server 2008+:
SELECT CONVERT(DATETIME, CONVERT(DATE, CURRENT_TIMESTAMP)) + '16:30';
SQL Server 2012:
SELECT SMALLDATETIMEFROMPARTS(YEAR(GETDATE()), MONTH(GETDATE()), DAY(GETDATE()), 16, 30);
Probably the easiest thing to do is to cast the current date/time to a date (stripping the time off), cast it back to a datetime to allow use of datetime's overloaded + (plus) and, finally cast your desired textual time to a datetime. As follows:
select cast(cast(sysutcdatetime() as date) as datetime) + cast('16:30' as datetime)
returns (when run on 11th Jan 2018):
2018-01-11 16:30:00.000
You can construct this as you like with day, hour, minute etc.
SELECT CURDATE() - interval 1 DAY + interval 2
select(dateadd(day, datediff(day, 0, getdate()), 0) + '20:00') as specified_date
specified_date - Output Column name
20:00 - Specified time(24 hr Format -Default)
getdate() - To get Today's date.
SQL Server, trying to get day of week via a deterministic UDF.
Im sure this must be possible, but cant figure it out.
UPDATE: SAMPLE CODE..
CREATE VIEW V_Stuff WITH SCHEMABINDING AS
SELECT
MD.ID,
MD.[DateTime]
...
dbo.FN_DayNumeric_DateTime(MD.DateTime) AS [Day],
dbo.FN_TimeNumeric_DateTime(MD.DateTime) AS [Time],
...
FROM {SOMEWHERE}
GO
CREATE UNIQUE CLUSTERED INDEX V_Stuff_Index ON V_Stuff (ID, [DateTime])
GO
Ok, i figured it..
CREATE FUNCTION [dbo].[FN_DayNumeric_DateTime]
(#DT DateTime)
RETURNS INT WITH SCHEMABINDING
AS
BEGIN
DECLARE #Result int
DECLARE #FIRST_DATE DATETIME
SELECT #FIRST_DATE = convert(DATETIME,-53690+((7+5)%7),112)
SET #Result = datediff(dd,dateadd(dd,(datediff(dd,#FIRST_DATE,#DT)/7)*7,#FIRST_DATE), #DT)
RETURN (#Result)
END
GO
Slightly similar approach to aforementioned solution, but just a one-liner that could be used inside a function or inline for computed column.
Assumptions:
You don't have dates before
1899-12-31 (which is a Sunday)
You want to imitate ##datefirst = 7
#dt is smalldatetime, datetime,
date, or datetime2 data type
If you'd rather it be different, change the date '18991231' to a date with the weekday that you'd like to equal 1. The convert() function is key to making the whole thing work - cast does NOT do the trick:
((datediff(day, convert(datetime,
'18991231', 112), #dt) % 7)
+ 1)
I know this post is way-super-old, but I was trying to do a similar thing and came up with a different solution and figured I'd post for posterity. Plus I did some searching around and did not find much content on this question.
In my case, I was trying to use a computed column PERSISTED, which requires the calculation to be deterministic. The calculation I used is:
datediff(dd,'2010-01-03',[DateColumn]) % 7 + 1
The idea is to figure out a known Sunday that you know will occur before any possible date in your table (in this case, Jan 3 2010), then calculate the modulo 7 + 1 of the number of days since that Sunday.
The problem is that including a literal date in the function call is enough to mark it as non-deterministic. You can work around that by using the integer 0 to represent the epoch, which for SQL Server is Jan 1st, 1900, a Sunday.
datediff(dd,0,[DateColumn]) % 7 + 1
The +1 just makes the result work the same as datepart(dw,[datecolumn]) when datefirst is set to 7 (default for US), which sets Sunday to 1, Monday to 2, etc
I can also use this in conjunction with case [thatComputedColumn] when 1 then 'Sunday' when 2 then 'Monday' ... etc. Wordier, but deterministic, which was a requirement in my environs.
Taken from Deterministic scalar function to get week of year for a date
;
with
Dates(DateValue) as
(
select cast('2000-01-01' as date)
union all
select dateadd(day, 1, DateValue) from Dates where DateValue < '2050-01-01'
)
select
year(DateValue) * 10000 + month(DateValue) * 100 + day(DateValue) as DateKey, DateValue,
datediff(day, dateadd(week, datediff(week, 0, DateValue), 0), DateValue) + 2 as DayOfWeek,
datediff(week, dateadd(month, datediff(month, 0, DateValue), 0), DateValue) + 1 as WeekOfMonth,
datediff(week, dateadd(year, datediff(year, 0, DateValue), 0), DateValue) + 1 as WeekOfYear
from Dates option (maxrecursion 0)
There is an already built-in function in sql to do it:
SELECT DATEPART(weekday, '2009-11-11')
EDIT:
If you really need deterministic UDF:
CREATE FUNCTION DayOfWeek(#myDate DATETIME )
RETURNS int
AS
BEGIN
RETURN DATEPART(weekday, #myDate)
END
GO
SELECT dbo.DayOfWeek('2009-11-11')
EDIT again: this is actually wrong, as DATEPART(weekday) is not deterministic.
UPDATE:
DATEPART(weekday) is non-deterministic because it relies on DATEFIRST (source).
You can change it with SET DATEFIRST but you can't call it inside a stored function.
I think the next step is to make your own implementation, using your preferred DATEFIRST inside it (and not considering it at all, using for example Monday as first day).
The proposed solution has one problem - it returns 0 for Saturdays. Assuming that we're looking for something compatible with DATEPART(WEEKDAY) this is an issue.
Nothing a simple CASE statement won't fix, though.
Make a function, and have #dbdate varchar(8) as your input variable.
Have it return the following:
RETURN (DATEDIFF(dd, -1, convert(datetime, #dbdate, 112)) % 7)+1;
The value 112 is the sql style YYYYMMDD.
This is deterministic because the datediff does not receive a string input, if it were to receive a string it would no longer work because it internally converts it to a datetime object. Which is not deterministic.
Not sure what you are looking for, but if this is part of a website, try this php function from http://php.net/manual/en/function.date.php
function weekday($fyear, $fmonth, $fday) //0 is monday
{
return (((mktime ( 0, 0, 0, $fmonth, $fday, $fyear) - mktime ( 0, 0, 0, 7, 17, 2006))/(60*60*24))+700000) % 7;
}
The day of the week? Why don't you just use DATEPART?
DATEPART(weekday, YEAR_DATE)
Can't you just select it with something like:
SELECT DATENAME(dw, GETDATE());