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.
Related
I have been asked to create two datasets showing 7 days of dates from a two date range.
Example: I have a date range of StartDate = 2022-12-12 and EndDate = 2022-12-25. I need a query to display the individual dates in between these two dates. I was told to use DATEADD, but cannot for the life figure this out.
Any help would be be helpful, thank you.
SELECT DATEADD(DAY, 7, StartDate) AS WeekOne
I was expecting something like this:
2022-12-12
2022-12-13
2022-12-14
2022-12-15
2022-12-16
2022-12-17
2022-12-18
DECLARE #InStartDate DATE='2022-12-12';
DECLARE #InStopDate DATE='2022-12-25';
WITH GEN AS
(
SELECT #InStartDate AS Start_dated
UNION ALL
SELECT DATEADD(DD,1,G.Start_dated)
FROM GEN AS G
WHERE G.Start_dated< #InStopDate
)
SELECT G.*
FROM GEN AS G
You can use something like this
You need to start by generating a numbers table. It needs enough rows to handle each day between your start and end dates. Something like this:
with Numbers as (
select 0 as n
union all
select n + 1
from Numbers
where n < 365
)
select n
from Numbers
option(maxrecursion 0);
Given the example range, I felt like 365 days (one year) was adequate, but it's easy to tweak that range (as we'll see).
Once you have the Numbers table, you can use DateAdd() to add that amount to the start date:
DECLARE #StartDate date = '20221212';
with Numbers as (
select 0 as n
union all
select n + 1
from Numbers
where n < 365
)
select DATEADD(day, n, #StartDate)
from Numbers
option(maxrecursion 0)
From here it's a simple matter to use the EndDate in a WHERE clause to limit the total rows:
DECLARE #StartDate date = '20221212';
DECLARE #EndDate date = '20221231';
with Numbers as (
select 0 as n
union all
select n + 1
from Numbers
where n < DATEDIFF(day, #StartDate, #EndDate)
)
select DATEADD(day, n, #StartDate)
from Numbers
option(maxrecursion 0)
For SQL Server 2022 and later you can use Generate_Series:
declare #StartDate as Date = '20221212', #EndDate as Date = '20221225';
select DateAdd( day, Value, #StartDate ) as TargetDate
from Generate_Series( 0, DateDiff( day, #StartDate, #EndDate ) );
dbfiddle.
Im trying to establish for any given datetime a tag that is purely dependent on the time part.
However because the time part is cyclic I cant make it work with simple greater lower than conditions.
I tried a lot of casting and shift one time to 24hour mark to kinda break the cycle However it just gets more and more complicated and still doesnt work.
Im using SQL-Server, here is the situation:
DECLARE #tagtable TABLE (tag varchar(10),[start] time,[end] time);
DECLARE #datetimestable TABLE ([timestamp] datetime)
Insert Into #tagtable (tag, [start], [end])
values ('tag1','04:00:00.0000000','11:59:59.9999999'),
('tag2','12:00:00.0000000','19:59:59.9999999'),
('tag3','20:00:00.0000000','03:59:59.9999999');
Insert Into #datetimestable ([timestamp])
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
tagtable:
tag
start
end
tag1
04:00:00.0000000
11:59:59.9999999
tag2
12:00:00.0000000
19:59:59.9999999
tag3
20:00:00.0000000
03:59:59.9999999
for given datetimes e.g. 2022-07-24 23:05:23.120, 2022-07-27 13:24:40.650, 2022-07-26 09:00:00.000
the desired result would be:
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
As I wrote i tried to twist this with casts and adding and datediffs
SELECT
If(Datepart(Hour, a.[datetime]) > 19,
Cast(Dateadd(Day,1,a.[datetime]) as Date),
Cast(a.[datetime] as Date)
) as [date],
b.[tag]
FROM #datetimestable a
INNER JOIN #tagtable b
ON SomethingWith(a.[datetime])
between SomethingWith(b.[start]) and SomethingWith(b.[end])
The only tricky bit here is that your tag time ranges can go over midnight, so you need to check that your time is either between start and end, or if it spans midnight its between start and 23:59:59 or between 00:00:00 and end.
The only other piece is splitting your timestamp column into date and time using a CTE, to save having to repeat the cast.
;WITH splitTimes AS
(
SELECT CAST(timestamp AS DATE) as D,
CAST(timestamp AS TIME) AS T
FROM #datetimestable
)
SELECT
DATEADD(
day,
CASE WHEN b.[end]<b.start THEN 1 ELSE 0 END,
a.D) as timestamp,
b.[tag]
FROM [splitTimes] a
INNER JOIN #tagtable b
ON a.T between b.[start] and b.[end]
OR (b.[end]<b.start AND (a.T BETWEEN b.[start] AND '23:59:59.99999'
OR a.T BETWEEN '00:00:00' AND b.[end]))
Live example: https://dbfiddle.uk/?rdbms=sqlserver_2019&fiddle=506aef05b5a761afaf1f67a6d729446c
Since they're all 8-hour shifts, we can essentially ignore the end (though, generally, trying to say an end time is some specific precision of milliseconds will lead to a bad time if you ever use a different data type (see the first section here) - so if the shift length will change, just put the beginning of the next shift and use >= start AND < end instead of BETWEEN).
;WITH d AS
(
SELECT datetime = [timestamp],
date = CONVERT(datetime, CONVERT(date, [timestamp]))
FROM dbo.datetimestable
)
SELECT date = DATEADD(DAY,
CASE WHEN t.start > t.[end] THEN 1 ELSE 0 END,
CONVERT(date, date)),
t.tag
FROM d
INNER JOIN dbo.tagtable AS t
ON d.datetime >= DATEADD(HOUR, DATEPART(HOUR, t.start), d.date)
AND d.datetime < DATEADD(HOUR, 8, DATEADD(HOUR,
DATEPART(HOUR, t.start), d.date));
Example db<>fiddle
Here's a completely different approach that defines the intervals in terms of starts and durations rather than starts and ends.
This allows the creation of tags that can span multiple days, which might seem like an odd capability to have here, but there might be a use for it if we add some more conditions down the line. For example, say we want to be able say "anything from 6pm friday to 9am monday gets the 'out of hours' tag". Then we could add a day of week predicate to the tag definition, and still use the duration-based interval.
I have defined the duration granularity in terms of hours, but of course this can easily be changed
create table #tags
(
tag varchar(10),
startTimeInclusive time,
durationHours int
);
insert #tags
values ('tag1','04:00:00', 8),
('tag2','12:00:00', 8),
('tag3','20:00:00', 8);
create table #dateTimes (dt datetime)
insert #dateTimes
values ('2022-07-24T23:05:23.120'),
('2022-07-27T13:24:40.650'),
('2022-07-26T09:00:00.000');
select dt.dt,
t.tag
from #datetimes dt
join #tags t on cast(dt.dt as time) >= t.startTimeInclusive
and dt.dt < dateadd
(
hour,
t.durationHours,
cast(cast(dt.dt as date) as datetime) -- strip the time from dt
+ cast(t.startTimeInclusive as datetime) -- add back the time from t
);
Maybe I am looking at this to simple, but,
can't you just take the first tag with an hour greater then your hour in table datetimestable.
With an order by desc it should always give you the correct tag.
This will work well as long as you have no gaps in your tagtable
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select top 1
tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) > datepart(hour, tt.tagStart)
order by tt.tagStart desc
) tag
It returns the correct result in this DBFiddle
The result is
date
tag
2022-07-25
tag3
2022-07-27
tag2
2022-07-26
tag1
EDIT
If it is possible that there are gaps in the table,
then I think the most easy and solid solution would be to split that row that passes midnight into 2 rows, and then your query can be very simple
See this DBFiddle
select case when datepart(hour, tag.tagStart) > 19 then dateadd(day, 1, convert(date, dt.timestamp))
else convert(date, dt.timestamp)
end as [date],
tag.tag
from datetimestable dt
outer apply ( select tt.tag,
tt.tagStart
from tagtable tt
where datepart(Hour, dt.timestamp) >= datepart(hour, tt.tagStart)
and datepart(Hour, dt.timestamp) <= datepart(hour, tt.tagEnd)
) tag
I have a query for calculating first and last date in the week, according to given date. It is enough to set #dDate and the query will calculate first (monday) and last date (sunday) for that week.
Problem is, that is calculating wrong and I don't understand why.
Example:
#dDate = 2019-10-03 (year-month-day).
Result:
W_START W_END
2019-09-25 2019-10-01
But it should be:
2019-09-30 2019-10-06
Why is that?
Query:
set datefirst 1
declare #dDate date = cast('2019-10-16' as date)
select #dDAte
declare #year int = (select DATEPART(year, #dDAte))
select #year
declare #StartingDate date = cast(('' + cast(#year as nvarchar(4)) + '-01-01') as date)
select #StartingDate
declare #dateWeekEnd date = (select DATEADD(week, (datepart(week, cast(#dDate as date)) - 1), #StartingDate))
declare #dateWeekStart date = dateadd(day, -6, #dateWeekEnd)
select #dateWeekStart W_START, #dateWeekEnd W_END
Days of the week are so complicated. I find it easier to remember that 2001-01-01 fell on a Monday.
Then, the following date arithmetic does what you want:
select dateadd(day,
7 * (datediff(day, '2001-01-01', #dDate) / 7),
'2001-01-01' -- 2001-01-01 fell on a Monday
)
I admit this is something of a cop-out/hack. But SQL Server -- and other databases -- make such date arithmetic so cumbersome that simple tricks like this are handy to keep in mind.
I am developing a calendaring system whereby events are created. I need to be able to "roll forward" any event (which occurs on a single day) to a user-specified month/year.
For example, March 4, 2013 is a Monday. I need to be able to determine, by the given month/year, what the corresponding date would be - based upon the weekday and its position within the month. So, in this example the corresponding date for April would be April 1, which is a Monday.
Another example: March 13, 2013 is a Wednesday, so the corresponding date in May would be May 8.
If it were not for the fact that user supplied month/year is variable, this would not be so difficult a task; but since it is...
If you had a Dates table containing five columns, FullDate, Month, Day, Year, and DayOfWeek, and populated with dates well into the future you could easily do the following.
Assuming #m and #y are the user-specified month/year to roll forward to, and #d is the event date:
DECLARE #weekNumInMonth int =
(
SELECT COUNT(1)
FROM Dates
WHERE Year = datepart(year #d)
AND Month = datepart(month, #d)
AND DayOfWeek = datepart(weekday, #d)
AND Day <= datepart(day, #d)
)
SELECT MAX(FullDate)
FROM
(
SELECT TOP #weekNumInMonth
FROM Dates
WHERE Year = #y
AND Month = #m
AND DayOfWeek = datepart(weekday, #d)
) x
Without a dates table, you'll just have to do some math:
DECLARE #DOW int = datepart(weekday, #d)
DECLARE #firstDayInMonth date = dateadd(day, 1-datepart(day, #d), #d)
DECLARE #firstDayInMonthDOW int = datepart(weekday, #firstDayInMonth)
DECLARE #firstSameDayInMonth date =
dateadd(day, (7-(#firstDayInMonthDOW-#DOW))%7, #firstDayInMonth)
DECLARE #weekInMonth int = datediff(week, #firstSameDayInMonth, #d)
DECLARE #corr date = datefromparts(#y, #m, 1)
DECLARE #corrDOW int = datepart(weekday, #corr)
DECLARE #corrFirstSameDay date = dateadd(day, (7-(#corrDOW-#DOW))%7, #corr)
SELECT dateadd(week, #weekInMonth, #corrFirstSameDay)
SQL Fiddle example
It's a little ugly, but what it does is:
Get the first day of the month with the same weekday as #d into #firstSameDayInMonth.
Figure out which week # #d is in within its corresponding month, as a 0-based integer #weekInMonth. This is the number of weeks between #firstSameDayInMonth and #d.
Get the first day of month #m, year #y with the same weekday as #d into #corrFirstSameDay.
Add the 0-based number of weeks #weekInMonth to #corrFirstSameDay to get the result.
Can you do it as a one-liner? Sure, just substitute your variables. Be warned though, it's ugly, and there's really nothing to be gained from it except lack of readability IMHO:
SELECT dateadd(week, datediff(week, dateadd(day, (7-(datepart(weekday, dateadd(day,
1-datepart(day, #d), #d))-datepart(weekday, #d)))%7, dateadd(day,
1-datepart(day, #d), #d)), #d), dateadd(day, (7-(datepart(weekday,
datefromparts(#y, #m, 1))-datepart(weekday, #d)))%7, datefromparts(#y, #m, 1)))
How do i get the date for last friday of the month in T-SQL?
I will be passing the year and month as parameter,e.g, 201211.
If I pass '201211' as parameter it should return me '20121130' as answer as it's the date of last friday of month of november'12.
The 5 January 1900 was a Friday. This uses that a base date and calculates the last Friday in any given month though you must give it a date during the month rather than just the month itself. Replace the 2012-12-01 in this with a date in your month
SELECT DATEADD(DY,DATEDIFF(DY,'1900-01-05',DATEADD(MM,DATEDIFF(MM,0,'2012-12-01'),30))/7*7,'1900-01-05')
You can also use this to get the last Saturday by replacing the 1900-01-05 WITH 1900-01-06 etc.
This would be much simpler using a calendar table; after creating the appropriate columns for your own needs you can just write this:
select
max([Date])
from
dbo.Calendar
where
YearAndMonth = 201211 and
DayOfWeek = 'Friday'
A calendar table is generally a much better solution for determining dates than using functions because the code is much more readable and you can use your own definition of things like WeekNumber, FinancialQuarter etc. that vary widely between countries and even companies.
I created a scalar function for this:
create function [dbo].[lastDWMonth]
(
#y int
,#m int
,#dw int
)
returns date
as
begin
declare #d date
;with x as
(
select datefromparts(#y,#m,1) d
union all
select dateadd(day,1,d) from x where d < eomonth(datefromparts(#y,#m,1))
)
select
#d = max(d)
from
x
where
datepart(dw,d) = #dw
return #d
end
Declare #d1 datetime = '2019-12-23'
Declare #searchDay int = 2 -- monday
select DATEADD(DAY, #searchDay-DATEPART(WEEKday, DateADD(day,-1, DATEADD(month, DATEDIFF(MONTH, 0, #d1)+1, 0))),DateADD(day,-1, DATEADD(month, DATEDIFF(MONTH, 0, #d1)+1, 0)))
This will give you Date on last Monday of the month, you can change your search by changing value in #searchDay