SQL Carryover from previous month - sql

I have some data that I am trying to get some counts on. There are dates for when the record was entered and when it was closed, if it has been closed yet. I want to be able to get a count of how many records were still open from the previous month as of the first of the month. Here is an example. First table is the data, second table is the results I am looking for. In the second table, ignore the parenthesis, they are just the IDs of the records that make up that count.
Position DateEntered DateClosed
1 12/15/2017 12/20/2017
11 12/20/2017 1/7/2018
2 1/23/2018 2/3/2018
3 1/24/2018
4 2/15/2018
5 2/20/2018 5/16/2018
6 3/3/2018 3/15/2018
7 3/23/2018 4/12/2018
8 4/11/2018 5/10/2018
9 4/12/2018 4/25/2018
10 5/4/2018
Year Month Carried Over
2018 January 1 (11)
2018 February 2 (2,3)
2018 March 3 (3,4,5)
2018 April 4 (3,4,5,7)
2018 May 4 (3,4,5,8)
2018 June 3 (3,4,10)
2018 July 3 (3,4,10)
2018 August 3 (3,4,10)
Is this possible, and if so, how? Been racking my brain on this one for a few hours.

For each month, you want the number of rows that start before that month and end after. I'm thinking:
with dates as (
select cast('2018-01-01' as date) as dte
union all
select dateadd(month, 1, dte)
from dates
where dte < '2018-08-01'
)
select d.dte,
(select count(*)
from t
where t.dateentered < d.dte and
(t.dateclosed > d.dte or t.dateClosed is null)
) as carriedover
from dates d;
Note that this puts the date in a single column, rather than splitting the year and month into separate columns. That is easily arranged, but I prefer to keep date components together.

Related

SQL Server query to get the data from previous month if the column value is not present in next month

I have a table that contains a number of menu items in restaurants with respect to each month.
and the table gets updated whenever there is a change in the number of items.
I want to change the table or make a new table to get the value of items for each month.
For example.
If latest month is April
Restaurant
Number of items
Month
A
20
1/1/2021
A
15
1/21/2021
B
12
1/1/2021
C
30
2/1/2021
A
22
3/31/2021
B
15
4/1/2021
I want the new table to have the above rows plus the missing months with same data as last month
Restaurant
Number of items
Month
A
20
1/1/2021
A
15
1/21/2021
A
15
2/21/2021
A
22
3/1/2021
A
22
4/1/2021
B
12
1/1/2021
B
12
2/1/2021
B
12
3/1/2021
B
15
4/1/2021
C
30
2/1/2021
C
30
3/31/2021
C
30
4/1/2021
Thanks for the help guys appreciated
This answers the original version of the question.
Assuming that month is stored as a date with the first first day of the month, then a simple method uses recursive CTEs:
with cte as (
select restaurant, num_items, month,
dateadd(month, -1,
coalesce(lead(month) over (partition by restaurant order by month),
max(month) over ()
)
) as end_month
from t
union all
select restaurant, num_items, dateadd(month, 1, month), end_month
from cte
where month < end_month
)
select *
from cte
order by restaurant, month;
Here is a db<>fiddle.

Can I calculate an aggregate duration over multiple rows with a single row per day?

I'm creating an Absence Report for HR. The Absence Data is stored in the database as a single row per day (the columns are EmployeeId, Absence Date, Duration). So if I'm off work from Tuesday 11 February 2020 to Friday 21 February 2020 inclusive, there will be 9 rows in the table:
11 February 2020 - 1 day
12 February 2020 - 1 day
13 February 2020 - 1 day
14 February 2020 - 1 day
17 February 2020 - 1 day
18 February 2020 - 1 day
19 February 2020 - 1 day
20 February 2020 - 1 day
21 February 2020 - 1 day
(see screenshot below)
HR would like to see a single entry in the report for a contiguous period of absence:
My question is - without using a cursor, how can I calculate the is in SQL (even more complicated because I have to do this using Linq to SQL, but I might be able to swap this out for a stored procedure. Note that the criterion for contiguous data is adjacent working days EXCLUDING weekends and bank holidays. I hope I've made myself clear ... apologies if not.
This is a form of gaps-and-islands. In this case, use lag() to see if two vacations overlap and then a cumulative sum:
select employee, min(absent_from), max(absent_to)
from (select t.*,
sum(case when prev_absent_to = dateadd(day, -1, absent_from) then 0 else 1
end) over (partition by employee order by absent_to) as grp
from (select t.*,
lag(absent_to) over (partition by employee order by absent_from) as prev_absent_to
from t
) t
) t
group by employee, grp;
If you need to deal with holidays and weekends, then you need a calendar table.

Dynamic 12 month rolling total for each previous month, going back 12 months

I have the following sql Code (where clause just to limit rows currently)
select
month,
monthname,
year,
count(distinct case when a.dim_service_type_id_desc like '%Direct Payment%' then a.DIM_PERSON_ID else null end) as No_dp,
count(distinct a.DIM_PERSON_ID) as no_ppl
from
SERVICE_PROVISIONS a
inner join date_tbl d on CONVERT(VARCHAR(35),a.start_dttm,112) = d.dim_date_id
where
a.dim_person_id >0
and year = 2018
group by
month,
monthname,
year
my output is this
month monthname year No_dp no_ppl
1 January 2018 142 1604
2 February 2018 111 1526
3 March 2018 133 1636
4 April 2018 1107 3829
5 May 2018 140 1575
6 June 2018 131 1389
7 July 2018 200 893
8 August 2018 2 73
9 September 2018 1 32
10 October 2018 2 21
11 November 2018 2 21
12 December 2018 2 19
So my question is - the customer wants to see how many services were open (using start date and end date) during the previous 12 months (not how many were started, but how many were current and not ended). This is fine when using the current month, however they want to show this also for the previous 12 months as a rolling dynamic figure.
So for example this month in July they want to see how many services were open during the last 12 months. Last month June, they want to see how many services were open during the 12 months previous to June and so on for the previous 12 months.
The table needs to have the month name for the last 12 months and in a column show the number of services that were open in the previous 12 months next to that month.
I hope that makes sense, sorry if it doesn't, feel free to ask questions and I will try to clarify.
The output needs to look something like the current output table, but it is currently only showing how many services were started within that month, which isn't what we want.
The date table is a reference table which has different date formats etc. It can be used or added to if needed.
I've had to make several assumptions about your data. Hopefully the query I'll show in a minute will be easy for you to adjust if any of these are wrong:
I am guessing by its name that start_dttm is a datetime or datetime2 column.
I assume there is a corresponding column called end_dttm that gives the end date/time of a service, and that a null in this column would indicate that a service has not yet ended.
My best guess as to what it means for a service to be "open" in a given month is that it began sometime either within or prior to that month, and has not ended by the time that month is over.
I assume from your original query that multiple services having the same dim_person_id do not represent distinct services.
Since I don't know what's in your date_tbl, I'll show an example that doesn't require it. Consider the following query:
select
BeginDate = dateadd(month, -1, dateadd(day, 1, eomonth(getdate(), -Offset.X))),
EndDate = dateadd(day, 1, eomonth(getdate(), -Offset.X))
from
(values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) Offset(X)
This will give you 12 records, representing the current month and each of the 11 preceding months. Note that my EndDate here is not actually the last day of the month, but the first day of the following month. I've done this because of assumption 1 above; since your service dates may have time components, I'll determine whether they fall in a given month by checking if their dates are strictly earlier than the start of the following month. Here's what that query gives me:
BeginDate EndDate
2018-07-01 2018-08-01
2018-06-01 2018-07-01
2018-05-01 2018-06-01
2018-04-01 2018-05-01
2018-03-01 2018-04-01
2018-02-01 2018-03-01
2018-01-01 2018-02-01
2017-12-01 2018-01-01
2017-11-01 2017-12-01
2017-10-01 2017-11-01
2017-09-01 2017-10-01
2017-08-01 2017-09-01
Now I'll join the above result set to your SERVICE_PROVISIONS data, looking for records in each month that have dim_person_id > 0 (from your original query) and which satisfy assumption 3 above.
-- Some sample data (assumptions 1 & 2)
declare #SERVICE_PROVISIONS table (dim_person_id bigint, start_dttm datetime, end_dttm datetime);
insert #SERVICE_PROVISIONS values
(1, '20180101', '20180315'),
(1, '20180101', '20180315'),
(2, '20171215', '20180520');
-- The CTE defines the months we'll report on, as described earlier.
with MonthsCTE as
(
select
BeginDate = dateadd(month, -1, dateadd(day, 1, eomonth(getdate(), -Offset.X))),
EndDate = dateadd(day, 1, eomonth(getdate(), -Offset.X))
from
(values (0),(1),(2),(3),(4),(5),(6),(7),(8),(9),(10),(11)) Offset(X)
)
-- This query matches the months from the CTE against the applicable services.
select
[Month] = datepart(month, M.BeginDate),
[MonthName] = datename(month, M.BeginDate),
[Year] = datepart(year, M.BeginDate),
ServicesOpen = count(distinct S.dim_person_id) -- Assumption 4
from
MonthsCTE M
left join #SERVICE_PROVISIONS S on
S.dim_person_id > 0 and
S.start_dttm < M.EndDate and -- Assumption 3
(
S.end_dttm >= M.EndDate or
S.end_dttm is null -- Assumption 2
)
group by
M.BeginDate,
M.EndDate
order by
M.BeginDate;
Note that I moved the dim_person_id > 0 from the WHERE clause to the JOIN so that each of the 12 months will still appear in the result set even if there were no services open during that time. Results:
Month MonthName Year ServicesOpen
8 August 2017 0
9 September 2017 0
10 October 2017 0
11 November 2017 0
12 December 2017 1
1 January 2018 2
2 February 2018 2
3 March 2018 1
4 April 2018 1
5 May 2018 0
6 June 2018 0
7 July 2018 0
something a bit like this - if you can write a query to get the value you want for a row in your ootput, then use cross apply to link to that query. Counting records that have an open record before the month, but no close record before the month seems feasible
SELECT IQ. *, OA.SERVICE_PROVISIONS FROM (select
month,
monthname,
year,
a.dim_person_id dim_person_id,
count(distinct case when a.dim_service_type_id_desc like '%Direct Payment%' then a.DIM_PERSON_ID else null end) as No_dp,
count(distinct a.DIM_PERSON_ID) as no_ppl
from
SERVICE_PROVISIONS a
inner join date_tbl d on CONVERT(VARCHAR(35),a.start_dttm,112) = d.dim_date_id
where
a.dim_person_id >0
and year = 2018
group by
month,
monthname,
year) IQ
CROSS APPLY
(SELECT count(0) OpenThings FROM SERVICE_PROVISIONS SP1 WHERE
(sp1.startdate < DATEFROMPARTS(IQ.year,iq.month,1)
AND
sp1.enddate is null or sp1.enddate > DATEFROMPARTS(IQ.year,iq.month,1)) and sp1.dim_person_id = iq.dim_person_id
) AS OA

T-SQL Running Monthly Totals Including Missing Months

I know this sounds really simple but I just cannot seem to get my head around it.
I have a temporary table that holds for example, Handler, MonthName, MonthNumber and MTD, which is a total for that month. What I need to do with that data is then create a running total for each Handler, from April to March. Now, here is the bit I am struggling with. Not all Handlers will have data for all months.
For example.
Handler MonthName MonthNo MTD
Julian Slaughter April 1 10000
Julian Slaughter June 3 12000
Julian Slaughter July 4 10000
Julian Slaughter September 6 12000
Bob Monkhouse April 1 5000
Bob Monkhouse July 4 5000
So I want the results to look like this
Julian Slaughter April 1 10000
Julian Slaughter May 2 10000
Julian Slaughter June 3 22000
Julian Slaughter July 4 32000
Julian Slaughter August 5 32000
Julian Slaughter September 6 44000
...and so on until March
Bob Monkhouse April 1 5000
Bob Monkhouse May 2 5000
Bob Monkhouse June 3 5000
Bob Monkhouse July 4 10000
...and so on until March
I have tried LEFT JOIN onto a table of the Month Names\Numbers and I have had an attempt at
OVER(PARTITION ..... ORDER BY ..... RANGE\ROWS)
but can't get the missing months.
Thanks in advance, sorry for the poor formatting, not sure how to do tables on here.
EDIT - Here is my LEFT JOIN attempt
SELECT
Months.MonthNo,
Department,
Executive,
#8.MonthNo,
MTD = SUM([TY MTD Prem]) OVER (PARTITION BY Department, Executive, [Exec Code] ORDER BY #8.MonthNo RANGE UNBOUNDED PRECEDING)
FROM Months
LEFT JOIN #8 ON Months.MonthNo = #8.MonthNo
For one Executive, I only get 4 rows, not the 12 I need. Can't show you the results for Data Protection purposes.
DECLARE #start_date date, #end_date date
SELECT #start_date='2012-04-01',#end_date='2013-03-31'
;WITH xo AS
(
SELECT #start_date AS cte_start_date
UNION ALL
SELECT DATEADD(MONTH, 1, cte_start_date)
FROM xo
WHERE DATEADD(MONTH, 1, cte_start_date) <= #end_date
), x as (
select *,row_number() over (order by cte_start_date) monthno
from xo
)
, y as (
select distinct handler from test
)
SELECT y.handler, datename(mm,x.cte_start_date), x.monthno
,(select sum(mtd) from test a where a.handler=y.handler and a.monthno<=x.monthno) mtd
FROM y
cross join x
order by 1,3
see example on SQLFiddle http://sqlfiddle.com/#!3/7d483/15
Sorry for the delay. The proposed solution worked a treat. I had to use the same code several times in various other parts of my giant query but it worked great.

Using sum function with a condition based on a returned value

I have a set of given month with a number of hours related to each of it
DATE HOURS
8/1/2013 3
9/1/2013 8
10/1/2013 2
11/1/2013 4
12/1/2013 1
I need to return the sum of hours for everything that is in the past including current month, in the example below, starting in august, sum would be august only. For september, I'd need august + september
DATE HOURS SUM
8/1/2013 3 3
9/1/2013 8 11
10/1/2013 2 13
11/1/2013 4 17
12/1/2013 1 18
I am not sure how to proceed, since the date condition is different for each line.
If anyone can help on this, it'd be greatly appreciated
You can do this in most SQL dialects using a correlated subquery (or a non-equijoin, but I find the subquery cleaner):
select date, hours,
(select sum(t2.hours)
from t t2
where t2.date <= t.date
) as cum
from t;
Many SQL engines also support the cumulative sum function, which would typically look like this:
select date, hours sum(hours) over (order by date) as cum
from t