Count total students actively studying during date range - sql

I am looking to get one output showing the total count of students actively studying in each year in a user defined range.
for eg DB Structure
StudentId Course StartDate EndDate
1 BSc Maths 2012-01-01 2015-01-01
2 BSc English 2014-01-01 2017-01-01
If the user defines actively studying between '2013' and '2016' the output i would like to get is this;
YEAR Student_Count
2013 1
2014 2
2015 2
2016 1
Thanks for your time :)

You need to have a years table to do this.
Here am generating years table through Recursive CTE. You can create a physical years table and use it instead of recursive CTE.
DECLARE #max_yr INT = (SELECT Max(Year(EndDate)) yr FROM yourtable);
WITH cte
AS (SELECT Min(Year(StartDate)) yr
FROM yourtable
UNION ALL
SELECT yr + 1
FROM cte
WHERE yr < #max_yr)
SELECT a.yr as [YEAR],
Count(1) as [Student_Count]
FROM cte a
JOIN yourtable b
ON a.yr BETWEEN Year(b.StartDate) AND Year(b.EndDate)
AND a.yr BETWEEN 2013 AND 2016
GROUP BY a.yr

You could try this, it certainly worked for me - obviously #startYear and #endYear would be replaced by your own variables but I've left them in so you understand how the code is working:
Create Table #Temp2 ([YEAR] int, Student_Count int)
Declare #startYear int = '2013',
#endYear int = '2016'
while (#startYear <= #endYear)
Begin
Insert into #Temp2
Select #startYear [YEAR], count(studentId)
from table
where Cast(Cast(#startYear as varchar) as date) between StartDate and EndDate
Set #startYear = #startYear + 1
End
Select * from #Temp2

Related

SQL sum a particular row based on condition?

I am using MS SQL Server. This is the table I have:
Create table tblVal
(
Id int identity(1,1),
Val NVARCHAR(100),
startdate datetime,
enddate datetime
)
--Inserting Records--
Insert into tblVal values(500,'20180907','20191212')
Insert into tblVal values(720,'20190407','20191212')
Insert into tblVal values(539,'20190708','20201212')
Insert into tblVal values(341,'20190221','20190712')
Table as this:
Id |Val |startdate |enddate
--- ----------------------------------------------
1 |500 |2018-09-07 |2019-12-12
2 |720 |2019-04-07 |2019-12-12
3 |539 |2019-07-08 |2020-12-12
4 |341 |2019-02-21 |2019-07-12
This is what I want:
Mon | Total
------------------
Jan | 500
Feb | 841
March | 841
April | 1561
May | 1561
June | 1561
July | 2100
........|.........
I want to sum Val column if it lies in that particular month. For ex. in case of April month it lies between two of the rows. I have to check both the condition start date and end date. and then sum the values.
This is what I have tried:
select *
into #ControlTable
from dbo.tblVal
DECLARE #cnt INT = 0;
while #cnt<12
begin
select sum(CASE
WHEN MONTH(startdate) BETWEEN #cnt and MONTH(enddate) THEN 0
ELSE 0
END)
from #ControlTable
SET #cnt = #cnt + 1;
end
drop table #ControlTable
but from above I was unable to achieve the result.
How do I solve this? Thanks.
I believe you want something like this:
with dates as (
select min(datefromparts(year(startdate), month(startdate), 1)) as dte,
max(datefromparts(year(enddate), month(enddate), 1)) as enddte
from tblVal
union all
select dateadd(month, 1, dte), enddte
from dates
where dte < enddte
)
select d.dte, sum(val)
from dates d left join
tblval t
on t.startdate <= eomonth(dte) and
t.enddate >= dte
group by d.dte
order by d.dte;
This does the calculation for all months in the data.
The results are a bit different from your sample results, but seem more consistent with the data provided.
Here is a db<>fiddle.
Hi if i understand your wall query i think this query can respond :
Create table #tblVal
(
Id int identity(1,1),
Val NVARCHAR(100),
startdate datetime,
enddate datetime
)
--Inserting Records--
Insert into #tblVal values(500,'20180907','20191212')
Insert into #tblVal values(720,'20190407','20191212')
Insert into #tblVal values(539,'20190708','20201212')
Insert into #tblVal values(341,'20190221','20190712')
Create table #tblMonth ( iMonth int)
Insert into #tblMonth values(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12);
select * from #tblVal
select * from #tblMonth
SELECT *, SUM(case when Val is null then 0 else cast (Val as int) end) OVER(ORDER BY iMonth
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW) as 'Totaltime'
FROM #tblMonth
LEFT JOIN #tblVal ON MONTH(startdate) = iMonth
ORDER BY iMonth
drop table #tblVal
drop table #tblMonth
Not you have to use SQL Server version 2008 min for use OVER(ORDER BY iMonth
ROWS BETWEEN UNBOUNDED PRECEDING AND CURRENT ROW)
Link :
https://learn.microsoft.com/en-us/sql/t-sql/queries/select-over-clause-transact-sql?view=sql-server-2017
If you have older version you can use CTE or JOIN ON select .
DECLARE #outpuTable Table(
MOn INT,
Total nvarchar(MAX)
)
DECLARE #cnt INT = 1;
while (#cnt<=12)
begin
INSERT INTo #outpuTable VALUES(#cnt,
(select ISNULL(sum(CONVERT(INT,Val)),0)
from tblVal
WHERE #cnt BETWEEN MONTH(startdate) and MONTH(enddate) ))
SET #cnt = #cnt + 1;
end
Select * from #outpuTable

How to list all months in specific period of time which doesn't have any order at that month

I want to know how to list all months in specific period of time which doesn't have any order. If you can help me.
I have Order Table has OrderDate column
I just make this:
select distinct month(Order.OrderDate) from Order where year(Order.OrderDate) = 1997
the result will show me the months that have order in specific year only
what should i do to complete this query
You need to retrieve the months in which no orders are placed for that we can use below query
;WITH months(MonthNumber) AS
(
SELECT 1
UNION ALL
SELECT MonthNumber+1
FROM months
WHERE MonthNumber < 12
)
SELECT DATENAME( month , DATEADD( month ,MonthNumber , 0 ) )
FROM months
EXCEPT
SELECT DISTINCT month([Order].OrderDate)
FROM [Order]
WHERE YEAR([Order].OrderDate) = 1997
You can try using left join like below
DEMO
select * from
(
VALUES (1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11),(12)
) AS M(val)
left join t1 on month(OrderDate)=val
and year(orderdate)=1997
where month(OrderDate) is null
The problem here is that if we we have a date range, then we may go beyond single year, for example 01-Jul-2017 to 30-Jun-2018 has 2 years, therefore creating a month range may NOT work in this scenario. The possible solution is to have a list of all the months in range along with the year, so that when we search an order, we'll search by the month and year both.
-- this is test order table, just to test the output
declare #order table(OrderDate date);
insert into #order(OrderDate) values('2018-01-01')
declare #dateRange table(d datetime not null primary key);
-- date range input parameter
declare #startDate date = '2017-06-01';
declare #endDate date = '2018-06-30';
-- modifying date range so that we go from start
-- of the month to the end of the month in the range
set #startDate = cast(year(#startDate) as varchar(100)) + '-' + cast(month(#startDate) as varchar(100)) + '-1';
set #endDate = dateadd(day, -1, dateadd(month, 1, cast(year(#endDate) as varchar(100)) + '-' + cast(month(#endDate) as varchar(100)) + '-1'));
-- creating dates for every month
declare #d date = #startDate;
while(#d <= #endDate)
begin
insert into #dateRange(d) values(#d);
set #d = dateadd(month, 1, #d);
end
-- selecting all the months in the range where
-- order does not exists
select cast(year(t.d) as varchar(100)) + '-' + DATENAME(month, t.d) as [Month]
from #dateRange as t
where not exists(
select 1
from #order as x
where month(x.OrderDate) = month(t.d) and year(x.OrderDate) = year(t.d)
)
order by t.d
Output: (notice that 2018-January is missing from result because it has an order)
Month
------------------
2017-June
2017-July
2017-August
2017-September
2017-October
2017-November
2017-December
2018-February
2018-March
2018-April
2018-May
2018-June
You are looking for a LEFT JOIN
CREATE TABLE Orders
(
OrderDate DATE
);
INSERT INTO Orders VALUES
('2018-01-01'),
('2018-03-01'),
('2018-05-15');
DECLARE #MND DATE = (SELECT MIN(OrderDate) FROM Orders);
DECLARE #MXD DATE = (SELECT MAX(OrderDate) FROM Orders);
WITH CTE AS
(
SELECT #MND OrderDate
UNION ALL
SELECT DATEADD(Month, 1, CTE.OrderDate)
FROM CTE
WHERE CTE.OrderDate <= DATEADD(Month, -1, #MXD)
)
SELECT MONTH(CTE.OrderDate) [Months]
FROM CTE LEFT JOIN Orders O ON MONTH(CTE.OrderDate) = MONTH(O.OrderDate)
AND
YEAR(CTE.OrderDate) = YEAR(O.OrderDate)
WHERE O.OrderDate IS NULL;
-- Add extra conditions here to filter the period needed
Returns:
+--------+
| Months |
+--------+
| 2 |
| 4 |
+--------+
Demo

Years separated by comma CTE in SQL Server

I have a field called StartYear, the value of my field is 2000. I have another field called EndYear, the value of my field is 2005.
I want to create a field called YearsInTheProgram that has the values 2005,2004, 2003,2002,2001,2000.
Each of my rows have different values, so in essence I would like this field to have the difference of my fields separated my commas.
I was able to find something that would work, but this would give me a value in different rows. However, I want all of them in one row.
with CTE as
(
select datepart(year, '2006-12-25') as yr
union all
select yr + 1
from CTE
where yr < datepart(year, '2013-11-14')
)
select yr
from CTE
declare #tmp varchar(250)
SET #tmp = ''
;with CTE as
(
select datepart(year, '2006-12-25') as yr
union all
select yr + 1
from CTE
where yr < datepart(year, '2013-11-14')
)
select #tmp = #tmp + convert(varchar(500), yr) + ', ' from CTE
select SUBSTRING(#tmp, 0, LEN(#tmp)) as yr
You can bulid up your string of years using this CTE.
DECLARE #STARTYEAR varchar(10) = '2006-12-25'
DECLARE #ENDYEAR varchar(10) = '2013-11-14'
;with CTE as
(
select datepart(year, #STARTYEAR) as yr, CAST(datepart(year, #STARTYEAR) AS VARCHAR(MAX)) as c
union all
select yr + 1 as yr, CAST(concat(c, ',', yr+1) AS VARCHAR(MAX)) as c
from CTE
where yr < datepart(year, #ENDYEAR)
)
select yr, c
from CTE
where (yr = datepart(year, #ENDYEAR))
And here it is with StartYear and EndYear taken from DB table.
create table #yr (id int, start varchar(20), stop varchar(20))
insert into #yr values(1,'2005-01-01','2010-12-10'), (2,'2008-01-01','2011-12-10'), (3,'2007-01-01','2013-12-10'), (4,'2009-01-01','2012-10-10')
;with CTE as
(
select start as start, datepart(year, start) as yr, CAST(datepart(year, start) AS VARCHAR(MAX)) as c from #yr
union all
select CTE.start as start, yr + 1 as yr, CAST(concat(c, ',', yr+1) AS VARCHAR(MAX)) as c
from CTE join #yr on #yr.start = CTE.start
where yr < datepart(year, stop)
)
select id, #yr.start, #yr.stop, c
from CTE join #yr on #yr.start = CTE.start
where (yr = datepart(year, stop))
With following result
4 2009-01-01 2012-10-10 2009,2010,2011,2012
3 2007-01-01 2013-12-10 2007,2008,2009,2010,2011,2012,2013
2 2008-01-01 2011-12-10 2008,2009,2010,2011
1 2005-01-01 2010-12-10 2005,2006,2007,2008,2009,2010

Count active items/year without a temporary year table

With the following record structure:
<item> | <start> | <stop>
Item-A | 2013-04-05 | 2014-06-07
Item-B | 2012-06-07 | 2015-03-07
Is it possible to query using SQL (in MS-SQL >=2008 and Firebird >= 2.5) how many items are active per year? The result should be:
2012 | 1
2013 | 2
2014 | 2
2015 | 1
I've used a temorary table containing series (1900..2100) and join the origin table and temporary table using BETWEEN and extract(year..). But I'm searching for a better solution without using an extra table.
This meets your requirement.
select Years.[Year],
count(1) [Count]
from MyTable mt
join (select distinct(year(start)) as [Year]
from MyTable
union
select distinct(year(stop))
from MyTable
union
select year(getdate())
from MyTable
where exists
(select 1
from MyTable
where stop is null)) as Years
on Years.[Year] between Year(mt.start) and Year(isnull(mt.stop, getdate()))
group by Years.[Year]
order by Years.[Year]
This is how I would do it using a helper function.
declare #StartMin datetime
declare #StopMax datetime
select #StartMin = min(start),
#StopMax = max(isnull(stop, getdate()))
from MyTable
select Years.[Year],
count(1) [Count]
from MyTable mt
join [dbo].[YearsBetween](#StartMin, #StopMax) as Years
on Years.[Year] between Year(mt.start) and Year(isnull(mt.stop, getdate()))
group by Years.[Year]
order by Years.[Year]
This is the helper function, I have a lot of them, DatesBetween, MonthsBetween, NumbersBetween, etc.
create function [dbo].[YearsBetween](#Start datetime, #Stop datetime)
returns #Years TABLE
(
[Year] int
)
begin
declare #StartYear int
declare #StopYear int
set #StartYear = year(#Start)
set #StopYear = year(#Stop)
while(#StartYear <= #StopYear)
begin
insert into #Years
values(#StartYear)
set #StartYear = #StartYear + 1
end
return;
end

get best sales rep weekly SQL

I need a bit of help with a SQL Server issue.
I have 2 tables:
complete_sales_raw
(
Id int Identity(1,1) PK,
RepId int FK in sale_reps,
Revenue decimal(15,2),
Sale_date datetime2(7)
)
and
sale_reps
(
Id int Identity(1,1) PK,
RepName nvarchar(50)
)
What I need to do is get best sales rep based on the total revenue for each week, starting with 2014-06-01 and ending at current date.
Each week has 7 days and the first day is 2014-06-01.
So far I got to here:
SELECT TOP(1)
sr.RepName as RepName,
SUM(csr.Revenue) as Revenue
INTO #tmp1
FROM complete_sales_raw csr
JOIN sale_reps sr on csr.RepId = sr.Id
WHERE DATEDIFF( d,'2014-06-01', Sale_date ) BETWEEN 0 and 6
GROUP BY sr.RepName
ORDER BY 2 desc
But this only returns the best sale rep for the first week and I need it for each week.
All help is appreciated.
ok so, I created a week table like so
IF ( OBJECT_ID('dbo.tmp4') IS NOT NULL )
DROP TABLE dbo.tmp4
GO
Create Table tmp4(
StartDate datetime,Enddate datetime,WeekNo varchar(20)
)
DECLARE
#start_date DATETIME,
#end_date DATETIME,
#start_date1 DATETIME,
#end_date1 DATETIME
DECLARE #Table table(StartDate datetime,Enddate datetime,WeekNo varchar(20))
Declare #WeekDt as varchar(10)
SET #start_date = '2014-06-01'
SET #end_date = '2015-01-03'
Set #WeekDt = DATEPART(WEEK,#start_date)
SET #start_date1 = #start_date
While #start_date<=#end_date
Begin
--Select #start_date,#start_date+1
IF #WeekDt<>DATEPART(WEEK,#start_date)
BEGIN
Set #WeekDt = DATEPART(WEEK,#start_date)
SET #end_date1=#start_date-1
INSERT INTO tmp4 Values(#start_date1,#end_date1,DATEPART(WEEK,#start_date1))
SET #start_date1 = #start_date
END
set #start_date = #start_date+1
END
GO
and then I used Gordon's answer and made this:
SELECT t.StartDate as StartDate, sr.RepName as RepName, SUM(csr.Revenue) as Revenue,
RANK() OVER (PARTITION BY (t.StartDate) ORDER BY SUM(csr.Revenue) desc) as seqnum into tmp1
FROM tmp4 t,
complete_sales_raw csr
JOIN sale_reps sr on csr.RepId = sr.Id
WHERE DATEDIFF( d,t.StartDate, MAS_PostDate ) BETWEEN 0 and 6
GROUP BY sr.RepName, t.StartDate
SELECT * FROM tmp1
WHERE seqnum = 1
ORDER BY StartDate
which returns the best sales_rep for each week
You can do an aggregation to get the total sales by week. This requires some manipulation of the dates to calculate the number of weeks -- basically dividing the days by 7.
Then, use rank() (or row_number() if you only want one when there are ties) to get the top value:
SELECT s.*
FROM (SELECT tsr.RepName as RepName,
(DATEDIFF(day, '2014-06-01', MAS_PostDate ) - 1) / 7 as weeknum,
SUM(csr.Revenue) as Revenue,
RANK() OVER (PARTITION BY (DATEDIFF(day, '2014-06-01', MAS_PostDate ) - 1) / 7 ORDER BY SUM(csr.Revenue)) as seqnum
FROM complete_sales_raw csr JOIN
sale_reps sr
on csr.RepId = sr.Id
WHERE DATEDIFF(day, '2014-06-01', MAS_PostDate ) BETWEEN 0 and 6
GROUP BY sr.RepName, (DATEDIFF(day, '2014-06-01', MAS_PostDate ) - 1) / 7
) s
WHERE seqnum = 1;