I wrote a query that should select the last record of each month in a year. I'd like to create a View based on this select, that I could run later in my project, but unfortunately I can't use any while loops or variables in a view command. Is there a way to select all these records - last days of a month in a View that I can use later?
My desired effect of the view:
The query that I'm trying to implement in a view:
DECLARE #var_day01 DATETIME;
DECLARE #month int;
SET #month = 1;
DROP TABLE IF EXISTS #TempTable2;
CREATE TABLE #TempTable2 (ID int, date datetime, INP2D float, INP3D float, ID_device varchar(max));
WHILE #month < 13
BEGIN
SELECT #var_day01 = CONVERT(nvarchar, date) FROM (SELECT TOP 1 * FROM data
WHERE DATEPART(MINUTE, CONVERT(nvarchar, date)) = '59'
AND
MONTH(CONVERT(nvarchar, date)) = (CONVERT(nvarchar, #month))
ORDER BY date DESC
) results
ORDER BY date DESC;
INSERT INTO #TempTable2 (ID, date, INP2D,INP3D,ID_device)
SELECT * FROM data
WHERE DATEPART(MINUTE, CONVERT(nvarchar, date)) = '59'
AND
MONTH(CONVERT(nvarchar, date)) = (CONVERT(nvarchar, #month))
AND
DAY(CONVERT(nvarchar, date)) = CONVERT(datetime, DATEPART(DAY, #var_day01))
ORDER BY date DESC
PRINT #var_day01
SET #month = #month +1;
END
SELECT * FROM #TempTable2;
If you are actually just after the single most recent row for each month, there is no need for a while loop to achieve this. You just need to identify the max date value for each month and then filter your source data for those for those rows.
One way to achieve this is via a row_number window function:
declare #t table(id int,dt datetime2);
insert into #t values(1,getdate()-40),(2,getdate()-35),(3,getdate()-25),(4,getdate()-10),(5,getdate());
select id
,id_device
,dt
from(select id
,id_device
,dt
,row_number() over (partition by id_device, year(dt), month(dt) order by dt desc) as rn
from #t
) as d
where rn = 1;
You can add a simple where to your select statement, in where clause you will add one day to the date field and then select the day from the resultant date. If the result date is 1 then only you will select that record
the where clause for your query will be : Where Day(DATEADD(d,1,[date])) = 1
I have two tables, one is Period and the other is UserTarget.
Period table is:
QuarterNo StartDate EndDate
-----------------------------------------
1 2018-04-01 2018-06-30
2 2018-07-01 2018-09-30
3 2018-10-01 2018-12-31
4 2019-01-01 2019-03-31
UserTarget table is :
USERID YEAR QTR AMOUNT
---------------------------------
akshay 2019 1 200
Right now I am taking the qtr no from period table. Now I don't need take qtr no from period table. I want it from stored procedure based on year is entered in usertarget table
Existing stored procedure :
ALTER PROCEDURE [dbo].[GetQuarterlyTargetData]
#Userid VARCHAR(50)
AS
BEGIN
DECLARE #QuarterNumber VARCHAR(50)
DECLARE #SetTarget DECIMAL(10);
DECLARE #StartDate DATE
DECLARE #EndDate DATE
SELECT
#QuarterNumber = p.QuarterNo,
#SetTarget = AMOUNT
FROM
PERIOD p
LEFT OUTER JOIN
USERTARGETS s ON p.QuarterNo = s.QTR
WHERE
StartDate <= GETDATE() AND EndDate >= GETDATE()
SELECT
#StartDate = StartDate,
#EndDate = EndDate
FROM
PERIOD
WHERE
QuarterNo = #QuarterNumber
From this procedure I am getting the start date and end date for quarter but I don't want to modify in period table every time when I want to check previous years data.
I believe that the term you are looking for is fiscal year. It's where the company year is different than the calendar year.
Note that many people recommend using a lookup table instead of calculating it. Date matches can be difficult for SQL to optimize.
Here's one way to do it. Finding the fiscal year and quarter would probably be good to put in a table function.
DECLARE #userTarget TABLE (UserId VARCHAR(20), Year INT, Quarter INT, Amount INT)
INSERT INTO #userTarget
VALUES
('akshay', 2018, 4, 150)
,('akshay', 2019, 1, 200)
SELECT
s.UserId
,s.Amount
,FY.FiscalYear
,FQ.FiscalQuarter
FROM
(
SELECT
--DATEFROMPARTS(2019, 2, 23)
GETDATE()
AS reportDate
) AS ReportDate
CROSS APPLY (
SELECT
CASE WHEN MONTH(ReportDate.reportDate) < 4 THEN YEAR(ReportDate.reportDate) - 1 -- Fiscal Year begins in April
ELSE YEAR(ReportDate.reportDate)
END AS FiscalYear
) AS FY
CROSS APPLY (
SELECT
DATEDIFF(QUARTER, DATEFROMPARTS(FY.FiscalYear, 4, 1), ReportDate.reportDate) + 1 AS FiscalQuarter
) AS FQ
INNER JOIN #userTarget s
ON s.Year = FY.FiscalYear
AND s.Quarter = FQ.FiscalQuarter
Also, be careful with end dates. last_day >= GETDATE() does not include the last day. Take as an example the end of last quarter, it would calculate it as '2019-03-31 00:00' >= '2019-03-31 08:20' which is false when you want it to be true.
After thinking I come up to this solutions
ALTER PROCEDURE [dbo].[Dashboard_GetQuarterlyTargetData]
#Userid varchar(50)
AS
Begin
DECLARE #QuarterNumber varchar(50)
DECLARE #SetTarget decimal(10);
DECLARE #AchievedTarget decimal(10);
DECLARE #TargetProgress decimal(10);
DECLARE #RemainingTarget decimal(10);
DECLARE #StartDate Date
DECLARE #EndDate Date
DECLARE #Year as int
Select #QuarterNumber = QTR,#Year=YEAR,#SetTarget=AMOUNT from USERTARGETS where USERID=#Userid
if(#QuarterNumber = 1)
begin
SELECT #StartDate = DATEFROMPARTS(#year,4,1), #EndDate=DATEFROMPARTS(#year,6,30)
End
else if(#QuarterNumber = 2)
begin
SELECT #StartDate = DATEFROMPARTS(#year,7,1), #EndDate=DATEFROMPARTS(#year,9,30)
End
else if(#QuarterNumber = 3)
begin
SELECT #StartDate = DATEFROMPARTS(#year,10,1), #EndDate=DATEFROMPARTS(#year,12,31)
End
else if(#QuarterNumber = 4)
begin
SELECT #StartDate = DATEFROMPARTS(#year,1,1), #EndDate=DATEFROMPARTS(#year,3,31)
End
I'm working on a query that deals with a frequency value (i.e. Mondays, Tuesdays, etc. - Think assignments).
So in my query I currently have a result of
jobId:1, personId:100, frequencyVal: 'Mondays'
jobId:2, personId:101, frequencyVal: 'Saturdays'
What I need is the next the 4 future(or current) dates for the frequencyVal.
So if today is 1/3/2015
I would need my result set to be
jobId:1, personId:100, frequencyVal: 'Mondays', futureDates: '1/5,1/12,1/19,1/26'
jobId:2, personId:102, frequencyVal: 'Saturdays', futureDates: '1/3,1/10,1/17,1/24'
I was looking at the following post:
How to find the Nearest (day of the week) for a given date
But that sets it for a specific date. And I'm looking at this being a web application and I want the dates for the current date. So if I try to run this query next Tuesday the future dates for jobId:1 would remove the 1/5 and add the 2/2.
Is there a way to pass in a weekday value to get the next nearest date?
I prefer a calendar table for this kind of query. Actually, I prefer a calendar table over date functions for most queries. Here's a minimal one. The one I use in production has more columns and more rows. (100 years of data is only 37k rows.)
create table calendar (
cal_date date not null primary key,
day_of_week varchar(15)
);
insert into calendar (cal_date) values
('2015-01-01'), ('2015-01-02'), ('2015-01-03'), ('2015-01-04'),
('2015-01-05'), ('2015-01-06'), ('2015-01-07'), ('2015-01-08'),
('2015-01-09'), ('2015-01-10'), ('2015-01-11'), ('2015-01-12'),
('2015-01-13'), ('2015-01-14'), ('2015-01-15'), ('2015-01-16'),
('2015-01-17'), ('2015-01-18'), ('2015-01-19'), ('2015-01-20'),
('2015-01-21'), ('2015-01-22'), ('2015-01-23'), ('2015-01-24'),
('2015-01-25'), ('2015-01-26'), ('2015-01-27'), ('2015-01-28'),
('2015-01-29'), ('2015-01-30'), ('2015-01-31'),
('2015-02-01'), ('2015-02-02'), ('2015-02-03'), ('2015-02-04'),
('2015-02-05'), ('2015-02-06'), ('2015-02-07'), ('2015-02-08'),
('2015-02-09'), ('2015-02-10'), ('2015-02-11'), ('2015-02-12'),
('2015-02-13'), ('2015-02-14'), ('2015-02-15'), ('2015-02-16'),
('2015-02-17'), ('2015-02-18'), ('2015-02-19'), ('2015-02-20'),
('2015-02-21'), ('2015-02-22'), ('2015-02-23'), ('2015-02-24'),
('2015-02-25'), ('2015-02-26'), ('2015-02-27'), ('2015-02-28')
;
update calendar
set day_of_week = datename(weekday, cal_date);
alter table calendar
alter column day_of_week varchar(15) not null;
alter table calendar
add constraint cal_date_matches_dow
check (datename(weekday, cal_date) = day_of_week);
create index day_of_week_ix on calendar (day_of_week);
Set the privileges so that
everyone can select, but
almost nobody can insert new rows, and
even fewer people can delete rows.
(Or write a constraint that can guarantee there are no gaps. I think you can do that in SQL Server.)
You can select the next four Mondays after today with a very simple SQL statement. (The current date is 2015-01-05, which is a Monday.)
select top 4 cal_date
from calendar
where cal_date > convert(date, getdate())
and day_of_week = 'Monday'
order by cal_date;
CAL_DATE
--
2015-01-12
2015-01-19
2015-01-26
2015-02-02
For me, this is a huge advantage. No procedural code. Simple SQL that is obviously right. Big win.
Your sample table
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
QUERY 1 : Select nearest 4 week of days in current month for particular week day
-- Gets first day of month
DECLARE #FIRSTDAY DATE=DATEADD(month, DATEDIFF(month, 0, GETDATE()), 0)
;WITH CTE as
(
-- Will find all dates in current month
SELECT CAST(#FIRSTDAY AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(MONTH,1,#FIRSTDAY)
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal,DATES,
-- Get week difference for each weekday
DATEDIFF(WEEK,DATES,GETDATE()) WEEKDIFF,
-- Count the number of weekdays in a month
COUNT(DATES) OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES)) WEEKCOUNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
WHERE MONTH(DATES)= MONTH(GETDATE())
)
-- Converts to CSV and make sure that only nearest 4 week of days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal AND
((WEEKDIFF<3 AND WEEKDIFF>-3 AND WEEKCOUNT = 5) OR WEEKCOUNT <= 4)
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
For example, in Query2 the nearest date(here we take example as Saturday) of
2015-Jan-10 will be 01/03,01/10,01/17,01/24
2015-Jan-24 will be 01/10,01/17,01/24,01/31
QUERY 2 : Select next 4 week's dates for particular week day irrelevant of month
;WITH CTE as
(
-- Will find the next 4 week details
SELECT CAST(GETDATE() AS DATE) as DATES
UNION ALL
SELECT DATEADD(DAY,1,DATES)
FROM CTE
WHERE DATES < DATEADD(DAY,28,GETDATE())
)
,CTE2 AS
(
-- Join the #t table with CTE on the datename+'s'
SELECT jobId,personId,frequencyVal, DATES,
ROW_NUMBER() OVER(PARTITION BY DATENAME(WEEKDAY,CTE.DATES) ORDER BY CTE.DATES) DATECNT
FROM CTE
JOIN #t ON DATENAME(WEEKDAY,CTE.DATES)+'s' = #t.frequencyVal
)
-- Converts to CSV and make sure that only 4 days are generated for month
SELECT DISTINCT C2.jobId,C2.personId,frequencyVal,
SUBSTRING(
(SELECT ', ' + CAST(DATEPART(MONTH,DATES) AS VARCHAR(2)) + '/' +
CAST(DATEPART(DAY,DATES) AS VARCHAR(2))
FROM CTE2
WHERE C2.jobId=jobId AND C2.personId=personId AND C2.frequencyVal=frequencyVal
AND DATECNT < 5
ORDER BY CTE2.DATES
FOR XML PATH('')),2,200000) futureDates
FROM CTE2 C2
SQL FIDDLE
The following would be the output if the GETDATE() (if its Saturday) is
2015-01-05 - 1/10, 1/17, 1/24, 1/31
2015-01-24 - 1/24, 1/31, 2/7, 2/14
There's no built-in function to do it. But you can try this, you may place it inside a Scalar-Valued Function:
DECLARE #WeekDay VARCHAR(10) = 'Monday';
DECLARE #WeekDayInt INT;
SELECT #WeekDayInt = CASE #WeekDay
WHEN 'SUNDAY' THEN 1
WHEN 'MONDAY' THEN 2
WHEN 'TUESDAY' THEN 3
WHEN 'WEDNESDAY' THEN 4
WHEN 'THURSDAY' THEN 5
WHEN 'FRIDAY' THEN 6
WHEN 'SATURDAY' THEN 7 END
SELECT CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + #WeekDayInt) % 7, GETDATE())) AS NearestDate
UPDATE:
Looks like radar was right, here's the solution:
DECLARE #WeekDay VARCHAR(10) = 'Monday';
DECLARE #WeekDayInt INT;
DECLARE #Date DATETIME = GETDATE();
SELECT #WeekDayInt = CASE #WeekDay
WHEN 'SUNDAY' THEN 1
WHEN 'MONDAY' THEN 2
WHEN 'TUESDAY' THEN 3
WHEN 'WEDNESDAY' THEN 4
WHEN 'THURSDAY' THEN 5
WHEN 'FRIDAY' THEN 6
WHEN 'SATURDAY' THEN 7 END
DECLARE #Diff INT = DATEPART(WEEKDAY, #Date) - #WeekDayInt;
SELECT CONVERT(DATE, DATEADD(DAY, CASE WHEN #Diff >= 0 THEN 7 - #Diff ELSE ABS(#Diff) END, #Date)) AS NearestDate
Try this - based on king.code's answer to get the nearest date.
create table #t
(
jobId int,
personId int,
frequencyVal varchar(10)
);
insert into #t values (1,100,'Mondays'),(2,101,'Saturdays');
WITH cte(n) AS
(
SELECT 0
UNION ALL
SELECT n+1 FROM cte WHERE n < 3
)
select #t.jobId, #t.personId, #t.frequencyVal, STUFF(a.d, 1, 1, '') AS FutureDates
from #t
cross apply (SELECT CASE #t.frequencyVal
WHEN 'SUNDAYS' THEN 1
WHEN 'MONDAYS' THEN 2
WHEN 'TUESDAYS' THEN 3
WHEN 'WEDNESDAYS' THEN 4
WHEN 'THURSDAYS' THEN 5
WHEN 'FRIDAYS' THEN 6
WHEN 'SATURDAYS' THEN 7
END)tranlationWeekdays(n)
cross apply (select ',' + CONVERT(varchar(10), CONVERT(date,dateadd(WEEK, cte.n,CONVERT(DATE, DATEADD(DAY, (DATEPART(WEEKDAY, GETDATE()) + tranlationWeekdays.n) % 7, GETDATE()))))) from cte FOR XML PATH('')) a(d);
drop table #t;
Try this,
DECLARE #YEAR INT=2015
DECLARE #MONTH INT=1
DECLARE #DAY INT=1
DECLARE #DATE DATE = (SELECT DateFromParts(#Year, #Month, #Day))
DECLARE #TOTAL_DAYS INT =(SELECT DatePart(DY, #DATE));
WITH CTE1
AS (SELECT T_DAY=(SELECT DateName(DW, #DATE)),
#DATE AS T_DATE,
#DAY AS T_DDAY
UNION ALL
SELECT T_DAY=(SELECT DateName(DW, DateAdd(DAY, T_DDAY + 1, #DATE))),
DateAdd(DAY, T_DDAY + 1, #DATE) AS T_DATE,
T_DDAY + 1
FROM CTE1
WHERE T_DDAY + 1 <= 364)
SELECT DISTINCT T_DAY,
Stuff((SELECT ',' + CONVERT(VARCHAR(30), T_DATE)
FROM CTE1 A
WHERE A.T_DAY=CTE1.T_DAY AND A.T_DATE > GetDate() AND A.T_DATE<(DATEADD(WEEK,4,GETDATE()))
FOR XML PATH('')), 1, 1, '') AS FUTURE
FROM CTE1
ORDER BY T_DAY
OPTION (MAXRECURSION 365)
This is a simpler way I think, and I think it fits your requirements.
Note that I have changed your frequency_val column to an integer that represents the day of the week from SQL servers perspective and added a calculated column to illustrate how you can easily derive the day name from that.
declare #t table
(
jobId int,
personId int,
--frequencyVal varchar(10)
frequency_val int,
frequency_day as datename(weekday,frequency_val -1) + 's'
);
declare #num_occurances int = 4
declare #from_date date = dateadd(dd,3,getdate()) -- this will allow you to play with the date simply by changing the increment value
insert into #t
values
(1,100,1),--'Mondays'),
(2,101,6),--'Saturdays');
(3,101,7),--'Saturdays');
(4,100,2)--'Mondays'),
--select * from #t
;with r_cte (days_ahead, occurance_date)
as (select 0, convert(date,#from_date,121)
union all
select r_cte.days_ahead +1, convert(date,dateadd(DD, r_cte.days_ahead+1, #from_date),121)
from r_cte
where r_cte.days_ahead < 7 * #num_occurances
)
select t.*, r_cte.occurance_date
from
#t t
inner join r_cte
on DATEPART(WEEKDAY, dateadd(dd,##DATEFIRST - 1 ,r_cte.occurance_date)) = t.frequency_val
Having seen the use of DATENAME in some of the answers already given, I'd like to point out that return values of DATENAME might vary depending on your current language setting, but you can save the current language setting and ensure usage of us_english so you can be confident to use English weekday names.
Now here is my slightly different approach to get the 4 next dates that fall on a certain (known) weekday, using a user defined table valued function that allows to create a number sequence table (yes this is a pretty dull function, you have to pass MaxValue greater MinValue, but that could be easily enhanced, if needed, but hey, it does the job). Using that function span a table over 28 values (next 28 days should indeed include the next 4 relevant weekdays ;)), apply DATEADD on GETDATE and reduce the result set with WHERE to only those values that have the right weekday:
CREATE FUNCTION GetIntSequence(#MinValue INT, #MaxValue INT)
RETURNS #retSequence TABLE
(
IntValue INT NOT NULL
)
BEGIN
DECLARE #i INT = (SELECT #MinValue)
WHILE #i <= #MaxValue
BEGIN
INSERT INTO #retSequence (IntValue) SELECT #i
SELECT #i = #i + 1
END
RETURN
END
GO
DECLARE #weekDay NVARCHAR(MAX) = 'Monday' --(or Tuesday, wednesday, ...)
--save current language setting
DECLARE #languageBackup NVARCHAR(MAX) = (SELECT ##LANGUAGE)
--ensure us english language setting for reliable weekday names
SET LANGUAGE us_english;
SELECT FourWeeks.SomeDay FROM
(
SELECT
DATEADD(DAY, IntValue, GETDATE()) AS SomeDay
FROM dbo.GetIntSequence(1, 28)
) AS FourWeeks
WHERE DATENAME(WEEKDAY, SomeDay) = #weekDay
--restore old language setting
SET LANGUAGE #languageBackup;
GO
DROP FUNCTION dbo.GetIntSequence
I have a table in which joining dates are give in datetime format.
I have to calculate how many employees joined each financial year resp. ie for eg from
1-04-2002 to 31-03-2003.this should work for each year..from 2003 to 2004,2004 to 2005...n so on.
can anybdy help?
thanxx.
You can map start date to financial year using YEAR(DATEADD(M,-3,JoinDate) I think and you can count records with a CTE, e.g.
with EmployeeStartFinYear(FinYear, EmployeeId)
as
(
select year(dateadd(M,-3,JoinDate)), EmployeeId
from Employees
where JoinDate is not null
)
select FinYear, count(EmployeeId)
from EmployeeStartFinYear
group by FinYear
order by FinYear;
Here's my answer. I think it looks horrid, but i think it works. I'll explain the logic behind it.
I declared the Start and End dates just because it's easier to write than a date.
The #Years variable is the difference in years between the start and end date.
#Counter is used to loop through the number of years stored in #Years. #Diff is always one more than #Counter because each time we go through the loop, we want to increment the date range so it's always 1 year, rather than be counting employees that joined in 1 year, then 2 years etc.
#TempTable stores the info we get each time we go through the query.
All the query does is get the count of employees between the Start Date and a year from that start date and puts it into a temp table. Then it looks through again, and gets the employees that started between Start Date + 1 and Start Date + 2.
Sorry if it's horrible and ugly and doesn't work.
DECLARE #StartDate DATETIME
DECLARE #EndDate DATETIME
DECLARE #Years TINYINT
DECLARE #Counter TINYINT
DECLARE #Diff TINYINT
DECLARE #TempTable TABLE
(
FinancialYear VARCHAR(9)
,Employees TINYINT
)
SET #Count = 0
SET #Diff = 1
SET #Years = DATEDIFF(yyyy, #StartDate, #EndDate)
WHILE #Count < #Years - 1
BEGIN
SELECT
CAST(DATEPART(yyyy, DATEADD(yyyy, #Count, #StartDate) AS VARCHAR(4)) + '-' + CAST(DATEPART(yyyy, DATEADD(yyyy, #Diff, #StartDate)) AS VARCHAR(4) AS FinancialYear
,COUNT(employee_id) AS Employees
INTO #TempTable
FROM
Employees
WHERE
join_date >= #StartDate AND join_date < DATEADD(yyyy, 1, #StartDate)
GROUP BY
CAST(DATEPART(yyyy, DATEADD(yyyy, #Count, #StartDate) AS VARCHAR(4)) + '-' + CAST(DATEPART(yyyy, DATEADD(yyyy, #Diff, #StartDate)) AS VARCHAR(4)
SET #Count = #Count + 1
SET #Diff = #Diff + 1
END
SELECT * FROM #TempTable