Finding simultaneous events in a database between times - sql

I have a database that stores phone call records. Each phone call record has a start time and an end time. I want to find out what is the maximum amount of phone calls that are simultaneously happening in order to know if we have exceed the amount of available phone lines in our phone bank. How could I go about solving this problem?

Disclaimer: I'm writing my answer based on the (excelent) following post:
https://www.itprotoday.com/sql-server/calculating-concurrent-sessions-part-3 (Part1 and 2 are recomended also)
The first thing to understand here with that problem is that most of the current solutions found in the internet can have basically two issues
The result is not the correct answer (for example if range A overlaps with B and C but B dosen't overlaps with C they count as 3 overlapping ranges).
The way to compute it is very innefficient (because is O(n^2) and / or they cicle for each second in the period)
The common performance problem in solutions like the proposed by Unreasons is a cuadratic solution, for each call you need to check all the other calls if they are overlaped.
there is an algoritmical linear common solution that is list all the "events" (start call and end call) ordered by date, and add 1 for a start and substract 1 for a hang-up, and remember the max. That can be implemented easily with a cursor (solution proposed by Hafhor seems to be in that way) but cursors are not the most efficient ways to solve problems.
The referenced article has excelent examples, differnt solutions, performance comparison of them. The proposed solution is:
WITH C1 AS
(
SELECT starttime AS ts, +1 AS TYPE,
ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
FROM Calls
UNION ALL
SELECT endtime, -1, NULL
FROM Calls
),
C2 AS
(
SELECT *,
ROW_NUMBER() OVER( ORDER BY ts, TYPE) AS start_or_end_ordinal
FROM C1
)
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
Explanation
suppose this set of data
+-------------------------+-------------------------+
| starttime | endtime |
+-------------------------+-------------------------+
| 2009-01-01 00:02:10.000 | 2009-01-01 00:05:24.000 |
| 2009-01-01 00:02:19.000 | 2009-01-01 00:02:35.000 |
| 2009-01-01 00:02:57.000 | 2009-01-01 00:04:04.000 |
| 2009-01-01 00:04:12.000 | 2009-01-01 00:04:52.000 |
+-------------------------+-------------------------+
This is a way to implement with a query the same idea, adding 1 for each starting of a call and substracting 1 for each ending.
SELECT starttime AS ts, +1 AS TYPE,
ROW_NUMBER() OVER(ORDER BY starttime) AS start_ordinal
FROM Calls
this part of the C1 CTE will take each starttime of each call and number it
+-------------------------+------+---------------+
| ts | TYPE | start_ordinal |
+-------------------------+------+---------------+
| 2009-01-01 00:02:10.000 | 1 | 1 |
| 2009-01-01 00:02:19.000 | 1 | 2 |
| 2009-01-01 00:02:57.000 | 1 | 3 |
| 2009-01-01 00:04:12.000 | 1 | 4 |
+-------------------------+------+---------------+
Now this code
SELECT endtime, -1, NULL
FROM Calls
Will generate all the "endtimes" without row numbering
+-------------------------+----+------+
| endtime | | |
+-------------------------+----+------+
| 2009-01-01 00:02:35.000 | -1 | NULL |
| 2009-01-01 00:04:04.000 | -1 | NULL |
| 2009-01-01 00:04:52.000 | -1 | NULL |
| 2009-01-01 00:05:24.000 | -1 | NULL |
+-------------------------+----+------+
Now making the UNION to have the full C1 CTE definition, you will have both tables mixed
+-------------------------+------+---------------+
| ts | TYPE | start_ordinal |
+-------------------------+------+---------------+
| 2009-01-01 00:02:10.000 | 1 | 1 |
| 2009-01-01 00:02:19.000 | 1 | 2 |
| 2009-01-01 00:02:57.000 | 1 | 3 |
| 2009-01-01 00:04:12.000 | 1 | 4 |
| 2009-01-01 00:02:35.000 | -1 | NULL |
| 2009-01-01 00:04:04.000 | -1 | NULL |
| 2009-01-01 00:04:52.000 | -1 | NULL |
| 2009-01-01 00:05:24.000 | -1 | NULL |
+-------------------------+------+---------------+
C2 is computed sorting and numbering C1 with a new column
C2 AS
(
SELECT *,
ROW_NUMBER() OVER( ORDER BY ts, TYPE) AS start_or_end_ordinal
FROM C1
)
+-------------------------+------+-------+--------------+
| ts | TYPE | start | start_or_end |
+-------------------------+------+-------+--------------+
| 2009-01-01 00:02:10.000 | 1 | 1 | 1 |
| 2009-01-01 00:02:19.000 | 1 | 2 | 2 |
| 2009-01-01 00:02:35.000 | -1 | NULL | 3 |
| 2009-01-01 00:02:57.000 | 1 | 3 | 4 |
| 2009-01-01 00:04:04.000 | -1 | NULL | 5 |
| 2009-01-01 00:04:12.000 | 1 | 4 | 6 |
| 2009-01-01 00:04:52.000 | -1 | NULL | 7 |
| 2009-01-01 00:05:24.000 | -1 | NULL | 8 |
+-------------------------+------+-------+--------------+
And there is where the magic occurs, at any time the result of #start - #ends is the amount of cocurrent calls at this moment.
for each Type = 1 (start event) we have the #start value in the 3rd column. and we also have the #start + #end (in the 4th column)
#start_or_end = #start + #end
#end = (#start_or_end - #start)
#start - #end = #start - (#start_or_end - #start)
#start - #end = 2 * #start - #start_or_end
so in SQL:
SELECT MAX(2 * start_ordinal - start_or_end_ordinal) AS mx
FROM C2
WHERE TYPE = 1
In this case with the prposed set of calls, the result is 2.
In the proposed article, there is a little improvment to have a grouped result by for example a service or a "phone company" or "phone central" and this idea can also be used to group for example by time slot and have the maximum concurrency hour by hour in a given day.

Given the fact that the maximum number of connections is going to be a StartTime points, you can
SELECT TOP 1 count(*) as CountSimultaneous
FROM PhoneCalls T1, PhoneCalls T2
WHERE
T1.StartTime between T2.StartTime and T2.EndTime
GROUP BY
T1.CallID
ORDER BY CountSimultaneous DESC
The query will return for each call the number of simultaneous calls. Either order them descending and select first one or SELECT MAX(CountSimultaneous) from the above (as subquery without ordering and without TOP).

try this:
DECLARE #Calls table (callid int identity(1,1), starttime datetime, endtime datetime)
INSERT #Calls (starttime,endtime) values ('6/12/2010 10:10am','6/12/2010 10:15am')
INSERT #Calls (starttime,endtime) values ('6/12/2010 11:10am','6/12/2010 10:25am')
INSERT #Calls (starttime,endtime) values ('6/12/2010 12:10am','6/12/2010 01:15pm')
INSERT #Calls (starttime,endtime) values ('6/12/2010 11:10am','6/12/2010 10:35am')
INSERT #Calls (starttime,endtime) values ('6/12/2010 12:10am','6/12/2010 12:15am')
INSERT #Calls (starttime,endtime) values ('6/12/2010 10:10am','6/12/2010 10:15am')
DECLARE #StartDate datetime
,#EndDate datetime
SELECT #StartDate='6/12/2010'
,#EndDate='6/13/2010'
;with AllDates AS
(
SELECT #StartDate AS DateOf
UNION ALL
SELECT DATEADD(second,1,DateOf) AS DateOf
FROM AllDates
WHERE DateOf<#EndDate
)
SELECT
a.DateOf,COUNT(c.callid) AS CountOfCalls
FROM AllDates a
INNER JOIN #Calls c ON a.DateOf>=c.starttime and a.DateOf<=c.endtime
GROUP BY a.DateOf
ORDER BY 2 DESC
OPTION (MAXRECURSION 0)
OUTPUT:
DateOf CountOfCalls
----------------------- ------------
2010-06-12 10:10:00.000 3
2010-06-12 10:10:01.000 3
2010-06-12 10:10:02.000 3
2010-06-12 10:10:03.000 3
2010-06-12 10:10:04.000 3
2010-06-12 10:10:05.000 3
2010-06-12 10:10:06.000 3
2010-06-12 10:10:07.000 3
2010-06-12 10:10:08.000 3
2010-06-12 10:10:09.000 3
2010-06-12 10:10:10.000 3
2010-06-12 10:10:11.000 3
2010-06-12 10:10:12.000 3
2010-06-12 10:10:13.000 3
2010-06-12 10:10:14.000 3
2010-06-12 10:10:15.000 3
2010-06-12 10:10:16.000 3
2010-06-12 10:10:17.000 3
2010-06-12 10:10:18.000 3
2010-06-12 10:10:19.000 3
2010-06-12 10:10:20.000 3
2010-06-12 10:10:21.000 3
2010-06-12 10:10:22.000 3
2010-06-12 10:10:23.000 3
2010-06-12 10:10:24.000 3
2010-06-12 10:10:25.000 3
2010-06-12 10:10:26.000 3
2010-06-12 10:10:27.000 3
....
add a TOP 1 or put this query in a derived table and further aggergate it if necessary.

SELECT COUNT(*) FROM calls
WHERE '2010-06-15 15:00:00' BETWEEN calls.starttime AND calls.endtime
and repeat this for every second.

The only practical method I can think of is as follows:
Split the period you want to analyze in arbitrary "buckets", say, 24 1-hour buckets over the day. For each Bucket count how many calls either started or finished between the start or the end of the interval
Note that the 1-hour limit is not a hard-and-fast rule. You could make this shorter or longer, depending on how precise you want the calculation to be.
You could make the actual "length" of the bucket a function of the average call duration.
So, let's assume that your average call is 3 minutes. If it is not too expensive in terms of calculations, use buckets that are 3 times longer than your average call (9 minutes) this should be granular enough to give precise results.

-- assuming calls table with columns starttime and endtime
declare #s datetime, #e datetime;
declare #t table(d datetime);
declare c cursor for select starttime,endtime from calls order by starttime;
open c
while(1=1) begin
fetch next from c into #s,#e
if ##FETCH_STATUS<>0 break;
update top(1) #t set d=#e where d<=#s;
if ##ROWCOUNT=0 insert #t(d) values(#e);
end
close c
deallocate c
select COUNT(*) as MaxConcurrentCalls from #t

Related

SQL interpolating missing values for a specific date range - with some conditions

There are some similar questions on the site, but I believe mine warrants a new post because there are specific conditions that need to be incorporated.
I have a table with monthly intervals, structured like this:
+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
| 1 | 10 | 12/17/2017 | 1/17/2018 |
| 1 | 10 | 1/18/2018 | 2/18/2018 |
| 1 | 10 | 2/19/2018 | 3/19/2018 |
| 1 | 10 | 3/20/2018 | 4/20/2018 |
| 1 | 10 | 4/21/2018 | 5/21/2018 |
+----+--------+--------------+--------------+
I've found that sometimes there is a month of data missing around the end/beginning of the year where I know it should exist, like this:
+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
| 2 | 10 | 10/14/2018 | 11/14/2018 |
| 2 | 10 | 11/15/2018 | 12/15/2018 |
| 2 | 10 | 1/17/2019 | 2/17/2019 |
| 2 | 10 | 2/18/2019 | 3/18/2019 |
| 2 | 10 | 3/19/2019 | 4/19/2019 |
+----+--------+--------------+--------------+
What I need is a statement that will:
Identify where this year-end period is missing (but not find missing
months that aren't at the beginning/end of the year).
Create this interval by using the length of an existing interval for
that ID (maybe using the mean interval length for the ID to do it?). I could create the interval from the "gap" between the previous and next interval, except that won't work if I'm missing an interval at the beginning or end of the ID's record (i.e. if the record starts at say 1/16/2015, I need the amount for 12/15/2014-1/15/2015
Interpolate an 'amount' for this interval using the mean daily
'amount' from the closest existing interval.
The end result for the sample above should look like:
+----+--------+--------------+--------------+
| ID | amount | interval_beg | interval_end |
+----+--------+--------------+--------------+
| 2 | 10 | 10/14/2018 | 11/14/2018 |
| 2 | 10 | 11/15/2018 | 12/15/2018 |
| 2 | 10 | 12/16/2018 | 1/16/2018 |
| 2 | 10 | 1/17/2019 | 2/17/2019 |
| 2 | 10 | 2/18/2019 | 3/18/2019 |
+----+--------+--------------+--------------+
A 'nice to have' would be a flag indicating that this value is interpolated.
Is there a way to do this efficiently in SQL? I have written a solution in SAS, but have a need to move it to SQL, and my SAS solution is very inefficient (optimization isn't a goal, so any statement that does what I need is fantastic).
EDIT: I've made an SQLFiddle with my example table here:
http://sqlfiddle.com/#!18/8b16d
You can use a sequence of CTEs to build up the data for the missing periods. In this query, the first CTE (EOYS) generates all the end-of-year dates (YYYY-12-31) relevant to the table; the second (INTERVALS) the average interval length for each ID and the third (MISSING) attempts to find start (from t2) and end (from t3) dates of adjoining intervals for any missing (indicated by t1.ID IS NULL) end-of-year interval. The output of this CTE is then used in an INSERT ... SELECT query to add missing interval records to the table, generating missing dates by adding/subtracting the interval length to the end/start date of the adjacent interval as necessary.
First though we add the interp column to indicate if a row was interpolated:
ALTER TABLE Table1 ADD interp TINYINT NOT NULL DEFAULT 0;
This sets interp to 0 for all existing rows. Then we can do the INSERT, setting interp for all those rows to 1:
WITH EOYS AS (
SELECT DISTINCT DATEFROMPARTS(DATEPART(YEAR, interval_beg), 12, 31) AS eoy
FROM Table1
),
INTERVALS AS (
SELECT ID, AVG(DATEDIFF(DAY, interval_beg, interval_end)) AS interval_len
FROM Table1
GROUP BY ID
),
MISSING AS (
SELECT e.eoy,
ids.ID,
i.interval_len,
COALESCE(t2.amount, t3.amount) AS amount,
DATEADD(DAY, 1, t2.interval_end) AS interval_beg,
DATEADD(DAY, -1, t3.interval_beg) AS interval_end
FROM EOYS e
CROSS JOIN (SELECT DISTINCT ID FROM Table1) ids
JOIN INTERVALS i ON i.ID = ids.ID
LEFT JOIN Table1 t1 ON ids.ID = t1.ID
AND e.eoy BETWEEN t1.interval_beg AND t1.interval_end
LEFT JOIN Table1 t2 ON ids.ID = t2.ID
AND DATEADD(MONTH, -1, e.eoy) BETWEEN t2.interval_beg AND t2.interval_end
LEFT JOIN Table1 t3 ON ids.ID = t3.ID
AND DATEADD(MONTH, 1, e.eoy) BETWEEN t3.interval_beg AND t3.interval_end
WHERE t1.ID IS NULL
)
INSERT INTO Table1 (ID, amount, interval_beg, interval_end, interp)
SELECT ID,
amount,
COALESCE(interval_beg, DATEADD(DAY, -interval_len, interval_end)) AS interval_beg,
COALESCE(interval_end, DATEADD(DAY, interval_len, interval_beg)) AS interval_end,
1 AS interp
FROM MISSING
This adds the following rows to the table:
ID amount interval_beg interval_end interp
2 10 2017-12-05 2018-01-04 1
2 10 2018-12-16 2019-01-16 1
2 10 2019-12-28 2020-01-27 1
Demo on SQLFiddle

How to Duplicate Records According to Start and End Dates

I have records in a SQL database that have a startDate and endDate that I need to expand.
| userName | startDate | endDate | weekDay |
| :---------: | :--------: | :--------: | :-----: |
| Test User 1 | 2011-03-30 | 2011-04-05 | 1 |
| Test User 2 | 2016-10-05 | 2016-10-07 | 5 |
| Test User 3 | 2018-05-22 | 2018-05-26 | 4 |
In the table above, each record has information that covers more than one date. What I need is one record per one date per user. An example of what I'm looking for:
| userName | startDate | weekDay |
| :---------: | :--------: | :--------: |
| Test User 1 | 2011-03-30 | 1 |
| Test User 1 | 2011-03-31 | 1 |
| Test User 1 | 2011-04-01 | 1 |
| Test User 1 | 2011-04-02 | 1 |
| Test User 1 | 2011-04-03 | 1 |
| Test User 1 | 2011-04-04 | 1 |
| Test User 1 | 2011-04-05 | 1 |
| Test User 2 | 2016-10-05 | 5 |
| Test User 2 | 2016-10-06 | 5 |
| Test User 2 | 2016-10-07 | 5 |
| Test User 3 | 2018-05-22 | 4 |
| Test User 3 | 2018-05-23 | 4 |
| Test User 3 | 2018-05-24 | 4 |
| Test User 3 | 2018-05-25 | 4 |
| Test User 3 | 2018-05-26 | 4 |
This answer has gotten me a step closer, specifying how to generate a sequence of dates in SQL. How can I duplicate tabular records according to start and end dates in SQL?
As a note, I need this solution to work in both MSSQL and PostgreSQL.
You can use a recursive CTE in both SQL Server and Postgres, but the syntax is slightly different. And, there is a simpler method in Postgres. So, in SQL Server, you can do:
with cte as (
select username, startdate, weekday, enddate
from t
union all
select username, dateadd(day, 1, startdate) weekday, enddate
from cte
where startdate < enddate
)
select username, startdate, weekday
from cte
order by username, startdate;
You can adjust the date arithmetic and add the recursive keyword for Postgres.
The simpler method in Postgres is a lateral join:
select t.username, g.startdate, t.weekday
from t, lateral
generate_series(start_date, end_date, interval '1 day') g(startdate);
If you need the same code to work in both, you need to generate a numbers table. Here is one (unpleasant) method:
with digits as (
select v.n
from (values (0), (1), (2), (3), (4), (5), (6), (7), (8), (9)) v(n)
),
n as (
select d1.n * 100 + d2.n * 10 + d3.n as n
from digits d1 cross join digits d2 cross join digits d3
)
select t.username, t.startdate + n.n, t.weekday
from t join
n
on t.startdate + n.n <= t.enddate;
Note that for this to work startdate needs to be a datetime in SQL Server, but a date in Postgres.
try below code. I used recursive common table expression.
;with cte
AS
(
SELECT userName,startDate,startDate AS endDate,weekDay FROM tab1
Union all
SELECT t1.userName,DATEADD(d,1,t1.startdate) AS startDate,
DATEADD(d,1,t1.startdate) AS startDate,t1.weekDay
FROM cte t1
JOIN tab1 t2 on t1.userName=t2.userName
WHERE t2.endDate>t1.endDate
)
Select userName,startDate,weekDay from cte order by userName
SQL Fiddle: http://sqlfiddle.com/#!18/fa22a/3

Identify two rows with 1 year or more of difference

I have a table called finance that I store all payment of the customer. The main columns are: ID,COSTUMERID,DATEPAID,AMOUNTPAID.
What I need is a list of dates by COSTUMERID with dates of its first payment and any other payment that is grater than 1 year of the last one. Example:
+----+------------+------------+------------+
| ID | COSTUMERID | DATEPAID | AMOUNTPAID |
+----+------------+------------+------------+
| 1 | 1 | 2015-01-10 | 10 |
| 2 | 1 | 2016-01-05 | 30 |
| 2 | 1 | 2017-02-20 | 30 |
| 3 | 2 | 2016-03-15 | 100 |
| 4 | 2 | 2017-02-15 | 100 |
| 5 | 3 | 2017-05-01 | 25 |
+----+------------+------------+------------+
What I expect as result:
+------------+------------+
| COSTUMERID | DATEPAID |
+------------+------------+
| 1 | 2015-01-01 |
| 1 | 2017-02-20 |
| 2 | 2016-03-15 |
| 3 | 2017-05-01 |
+------------+------------+
Costumer 1 have 2 dates: the first one + one more that have more then 1 year after the last one.
I hope I make my self clear.
I think you just want lag():
select t.*
from (select t.*,
lag(datepaid) over (partition by customerid order by datepaid) as prev_datepaid
from t
) t
where prev_datepaid is null or
datepaid > dateadd(year, 1, prev_datepaid);
Gordon's solution is correct, as long as you are only looking at the previous row (previous payment) diff, but I wonder if Antonio is looking for payments greater than one year from the last 1 year payment, in which case this becomes a more complex problem to solve. Take the following example:
CREATE TABLE #Test (
CustomerID smallint
,DatePaid date
,AmountPaid smallint )
INSERT INTO #Test
SELECT 1, '2015-1-10', 10
INSERT INTO #Test
SELECT 1, '2016-1-05', 30
INSERT INTO #Test
SELECT 1, '2017-2-20', 30
INSERT INTO #Test
SELECT 1, '2017-6-30', 50
INSERT INTO #Test
SELECT 1, '2018-3-5', 50
INSERT INTO #Test
SELECT 1, '2018-5-15', 50
INSERT INTO #Test
SELECT 2, '2016-3-15', 100
INSERT INTO #Test
SELECT 2, '2017-6-15', 100
WITH CTE AS (
SELECT
CustomerID
,DatePaid
,LAG(DatePaid) OVER (PARTITION BY CustomerID ORDER BY DatePaid) AS PreviousPaidDate
,AmountPaid
FROM #Test )
SELECT
*
,-DATEDIFF(DAY, DatePaid, PreviousPaidDate) AS DayDiff
,CASE WHEN DATEDIFF(DAY, PreviousPaidDate, DatePaid) >= 365 THEN 1 ELSE 0 END AS Paid
FROM CTE
Row number 5 is > 1 year from the last 1 year payment, but subtracting from previous row doesn't address this. This may or may not matter but I wanted to point it out in case that is what he means.

TSQL: Loop through a table by given time intervals and count

Source table
dbo.sourcetable
The table has more columns than shown here:
| ID | TrackingID | TrackingTime |....
|--------|----------------|-----------------------|
| 001 | 10 |2017-03-08 10:12:20.240|
| 003 | 50 |2017-03-08 12:30:23.240|
| 001 | 10 |2017-03-03 09:10:23.240|
| 002 | 10 |2017-03-06 10:12:23.240|
| 001 | 15 |2017-03-05 10:12:23.240|
| 001 | 20 |2017-03-08 17:12:23.240|
| 002 | 15 |2017-03-04 00:12:23.240|
| 003 | 10 |2017-03-06 01:18:23.240|
....
I also have a table which provides all possible TrackingIDs and their description. If this is of any use.
Query
I count the last given TrackingIDs für a time range with:
--Initializing---------
DECLARE #Begin datetime,
DECLARE #End datetime,
SET #Begin = '2017-03-05 00:00:00';
SET #End = '2017-03-06 00:00:00';
--Coding CTE----------
WITH CTE AS
(
SELECT *
,ROW_NUMBER() OVER (PARTITION BY ID ORDER BY TrackingTime DEC) rn
FROM dbo.sourcetable
WHERE (1 = 1)
AND (TrackingTime BETWEEN #Begin AND #End)
)
--Select CTE----------
SELECT TrackingID
,COUNT(TrackingID) AS TotalIDs
FROM CTE
WHERE (1 = 1)
AND rn = 1
GROUP BY TrackingID
ORDER BY Tracking ID ASC
I receive a table which shows the total of IDs for each TrackingID:
| TrackingID | TotalIDs |
|------------------|----------------|
| 10 | 12 |
| 15 | 3 |
| 20 | 10 |
...
What I want
In order to create a chart in SSRS I want to split up a time range in many intervals. Then use the beginning and end of each interval as the variable input of my query and in the end receive a table which shows the total IDs for each TrackingID in that interval.
| TrackingTime | 10 | 15 | 20 |...
|-----------------------|----------|----------|----------|
|2017-03-05 00:00:00.000| 13 | 10 | 3 |
|2017-03-05 00:00:02.000| 11 | 8 | 5 |
....
|2017-03-06 00:00:00.000| 20 | 11 | 7 |
Query
DECLARE #TotalTable Table
(
TrackingTime datetime PRIMARY KEY CLUSTERED,
----------------------------
Alle the columns for
the TrackingIDs
----------------------------
)
WHILE #Begin <= #End
BEGIN
INSERT INTO #TotalTable (TrackingTime) VALUES (#Begin)
----------------------------------------
I don't know, so maybe insert
magic code here?!
----------------------------------------
SET #Begin = DATEADD(SS, 120, #Begin)
END;
SELECT *
FROM #TotalTable AS TT
I think I have to work with dynamic SQL somehow as not for every interval something can be counted. I am sorry if this is to much to ask for. Thank you for your help.

Count concurrent dates in user-input date range using SQL

The user will input a date range, and I want to output in SQL every date between and including that range in the number of concurrent uses of said equipment.
In this example, the user date range is 03/08/2016 to 03/09/2016, so you can see below I include anything on or between those dates (grouped by category, but I've simplified here by only using 'powerchair')
The table schema is as follows;
trans_date | trans_end_date | eq_category
17/03/2016 | 16/10/2016 | POWERCHAIR
08/08/2016 | 08/08/2016 | POWERCHAIR
12/08/2016 | 12/08/2016 | POWERCHAIR
17/08/2016 | 18/08/2016 | POWERCHAIR
22/08/2016 | 22/08/2016 | POWERCHAIR
26/08/2016 | 26/08/2016 | POWERCHAIR
02/09/2016 | 02/09/2016 | POWERCHAIR
And I would like to output;
date | concurrent_use
03-08-2016 | 1
04-08-2016 | 1
05-08-2016 | 1
06-08-2016 | 1
07-08-2016 | 1
08-08-2016 | 2
09-08-2016 | 1
10-08-2016 | 1
11-08-2016 | 1
12-08-2016 | 2
13-08-2016 | 1
14-08-2016 | 1
15-08-2016 | 1
16-08-2016 | 1
17-08-2016 | 2
18-08-2016 | 2
19-08-2016 | 1
20-08-2016 | 1
21-08-2016 | 1
22-08-2016 | 2
23-08-2016 | 1
24-08-2016 | 1
25-08-2016 | 1
26-08-2016 | 2
27-08-2016 | 1
28-08-2016 | 1
29-08-2016 | 1
30-08-2016 | 1
31-08-2016 | 1
01-09-2016 | 1
02-09-2016 | 2
03-09-2016 | 1
Anything 1 or 0, I can then filter out as there mustn't have been any equipment out concurrently that day.
I don't think this is a gaps/islands problem, but I'm drawing a blank trying to get this in an SQL statement.
Try like below. You need to generate dates using recursive cte. Then we need to count the no of occurrences of each date falling in range.
;WITH CTE
AS (SELECT CONVERT(DATE, '2016-08-03', 103) DATE1
UNION ALL
SELECT Dateadd(DAY, 1, DATE1) AS DATE1
FROM CTE
WHERE Dateadd(DD, 1, DATE1) <= '2016-09-03')
SELECT C.DATE1,
Count(1) OCCURENCES
FROM CTE C
JOIN #TABLE1 T
ON C.DATE1 BETWEEN [TRANS_DATE] AN [TRANS_END_DATE]
GROUP BY C.DATE1
You need a set of numbers or dates. So, if you want everything in that range:
with d as (
select cast('2016-08-03' as date) as d
union all
select dateadd(day, 1, d.d)
from d
where d < '2016-09-03'
)
select d.d, count(s.trans_date)
from d left join
schema s
on d.d between s.trans_date and s.trans_date_end
group by d.d;
I'm not sure if both the start and end dates are included in the range.