Get users attendance entry and exit in one row SQL Server - sql

I have a table with all entries for employees. I need to get all the working hours and the entry and exit time of the user in one record.
The table is like this:
How can I do that and also in case there is some missing entries or exit. Like one employee will have entry with no exit in some odd cases.

Assuming that the ins and outs line up (that is, are strictly interleaved), you can use lead() and some filtering:
select t.empId, convert(date, datetime) as date, datetime as timein,
next_datetime as timeout,
datediff(minute, datetime, next_datetime) / 60.0 as decimal_hours
from (select t.*,
lead(datetime) over (partition by empid order by datetime) as next_datetime
from t
) t
where entrytype = 'IN';
Note that this formats the duration as decimal hours rather than as a time. That part does not seem relevant to the actual question and just complicates the query.

This adds LEAD entrytype to make sure there is a corresponding OUT row. Also, it divides the date difference in minutes by 60.0 (added decimal)
select t.empId EmpID, cast(datetime as date) [Day], datetime [Timein], next_datetime [Timeout],
datediff(mi, datetime, next_datetime)/60.0 TotalHours
from (select t.*,
lead(datetime) over (partition by empid order by datetime) as next_datetime,
lead(entrytype) over (partition by empid order by datetime) as next_entrytype
from t
) t
where entrytype = 'IN'
and next_entrytype='Out';

Using Row_number to identify IN and OUT related to which employee:
SELECT EMPID, CAST([DATEUPDT] AS DATE) AS Date,
MAX(CASE WHEN ENTRYTYPE = 'IN' THEN CAST([DATEUPDT] AS TIME) END) AS TIMEIN,
MAX(CASE WHEN ENTRYTYPE = 'OUT' THEN CAST([DATEUPDT] AS TIME) END) AS TIMEOUT,
ABS(DATEDIFF(MINUTE, MAX(CASE WHEN ENTRYTYPE = 'OUT' THEN CAST([DATEUPDT] AS TIME) END), MAX(CASE WHEN ENTRYTYPE = 'IN' THEN CAST([DATEUPDT] AS TIME) END)))/60 AS DURATION
FROM
(
SELECT A.*,
ROW_NUMBER() OVER(PARTITION BY EMPID, [ENTRYTYPE] ORDER BY [DATEUPDT]) RN1
FROM EMPLOYEE_LOG A
) X
GROUP BY EMPID, RN1, CAST([DATEUPDT] AS DATE)
ORDER BY EMPID, RN1;

You can also "sessionize" in SQL Server - by using OLAP queries: With a counter that is at 1 when a new session begins and at 0 otherwise
WITH
input(id,empid,dttime,entrytype) AS (
SELECT 1,125,CAST('2020-08-13 08:10:00.000' AS DATETIME),'IN'
UNION ALL SELECT 1,157,CAST('2020-08-13 08:01:00.000' AS DATETIME),'IN'
UNION ALL SELECT 1,125,CAST('2020-08-13 15:21:00.000' AS DATETIME),'OUT'
UNION ALL SELECT 1,125,CAST('2020-08-13 15:24:00.000' AS DATETIME),'IN'
UNION ALL SELECT 1,125,CAST('2020-08-13 17:24:00.000' AS DATETIME),'OUT'
UNION ALL SELECT 1,157,CAST('2020-08-13 15:01:00.000' AS DATETIME),'OUT'
UNION ALL SELECT 1,125,CAST('2020-08-14 08:10:00.000' AS DATETIME),'IN'
UNION ALL SELECT 1,157,CAST('2020-08-14 08:01:00.000' AS DATETIME),'IN'
UNION ALL SELECT 1,125,CAST('2020-08-14 15:21:00.000' AS DATETIME),'OUT'
UNION ALL SELECT 1,125,CAST('2020-08-14 15:24:00.000' AS DATETIME),'IN'
UNION ALL SELECT 1,125,CAST('2020-08-14 17:24:00.000' AS DATETIME),'OUT'
UNION ALL SELECT 1,157,CAST('2020-08-14 15:01:00.000' AS DATETIME),'OUT'
)
,
with_session AS (
SELECT
*
, SUM(CASE entrytype WHEN 'IN' THEN 1 ELSE 0 END) OVER(
PARTITION BY empid ORDER BY dttime
) AS sessid
FROM input
)
SELECT
id
, empid
, sessid
, CAST(MAX(CASE entrytype WHEN 'IN' THEN dttime END) AS DATE) AS day
, CAST(MAX(CASE entrytype WHEN 'IN' THEN dttime END) AS TIME) AS indtm
, CAST(MAX(CASE entrytype WHEN 'OUT' THEN dttime END) AS TIME) AS outdtm
, CAST(
MAX(CASE entrytype WHEN 'OUT' THEN dttime END)
- MAX(CASE entrytype WHEN 'IN' THEN dttime END)
AS TIME
) AS totalhours
FROM with_session
GROUP BY
id
, empid
, sessid
ORDER BY
id
, 4
, empid
, sessid
;
-- out id | empid | sessid | day | indtm | outdtm | totalhours
-- out ----+-------+--------+------------+----------+----------+------------
-- out 1 | 125 | 1 | 2020-08-13 | 08:10:00 | 15:21:00 | 07:11:00
-- out 1 | 125 | 2 | 2020-08-13 | 15:24:00 | 17:24:00 | 02:00:00
-- out 1 | 157 | 1 | 2020-08-13 | 08:01:00 | 15:01:00 | 07:00:00
-- out 1 | 125 | 3 | 2020-08-14 | 08:10:00 | 15:21:00 | 07:11:00
-- out 1 | 125 | 4 | 2020-08-14 | 15:24:00 | 17:24:00 | 02:00:00
-- out 1 | 157 | 2 | 2020-08-14 | 08:01:00 | 15:01:00 | 07:00:00

Related

How to bucket data based on timestamps within a certain period or previous record?

I have some data that I'm trying to bucket. Let's say the data has an user and timestamp. I want to define a session as any rows that has a timestamp within 10 minutes of the previous timestamp by user.
How would I go about this in SQL?
Example
+------+---------------------+---------+
| user | timestamp | session |
+------+---------------------+---------+
| 1 | 2021-05-09 15:12:52 | 1 |
| 1 | 2021-05-09 15:18:52 | 1 | within 10 min of previous timestamp
| 1 | 2021-05-09 15:32:52 | 2 | over 10 min, new session
| 2 | 2021-05-09 16:00:00 | 1 | different user
| 1 | 2021-05-09 17:00:00 | 3 | new session
| 1 | 2021-05-09 17:02:00 | 3 |
+------+---------------------+---------+
This will give me records within 10 minutes but how would I bucket them like above?
with cte as (
select user,
timestamp,
lag(timestamp) over (partition by user order by timestamp) as last_timestamp
from table
)
select *
from cte
where datediff(mm, last_timestamp, timestamp) <= 10
Try this one. It's basically an edge problem.
Working test case for SQL Server
The SQL:
with cte as (
select user1
, timestamp1
, session1 AS session_expected
, lag(timestamp1) over (partition by user1 order by timestamp1) as last_timestamp
, CASE WHEN datediff(n, lag(timestamp1) over (partition by user1 order by timestamp1), timestamp1) <= 10 THEN 0 ELSE 1 END AS edge
from table1
)
select *, SUM(edge) OVER (PARTITION BY user1 ORDER BY timestamp1) AS session_actual
from cte
ORDER BY timestamp1
;
Additional suggestion, see ROWS UNBOUNDED PRECEDING (thanks to #Charlieface):
with cte as (
select user1
, timestamp1
, session1 AS session_expected
, lag(timestamp1) over (partition by user1 order by timestamp1) as last_timestamp
, CASE WHEN datediff(n, lag(timestamp1) over (partition by user1 order by timestamp1), timestamp1) <= 10 THEN 0 ELSE 1 END AS edge
from table1
)
select *
, SUM(edge) OVER (PARTITION BY user1 ORDER BY timestamp1 ROWS UNBOUNDED PRECEDING) AS session_actual
from cte
ORDER BY timestamp1
;
Result:
Setup:
CREATE TABLE table1 (user1 int, timestamp1 datetime, session1 int);
INSERT INTO table1 VALUES
( 1 , '2021-05-09 15:12:52' , 1 )
, ( 1 , '2021-05-09 15:18:52' , 1 ) -- within 10 min of previous timestamp
, ( 1 , '2021-05-09 15:32:52' , 2 ) -- over 10 min, new session
, ( 2 , '2021-05-09 16:00:00' , 1 ) -- different user
, ( 1 , '2021-05-09 17:00:00' , 3 ) -- new session
, ( 1 , '2021-05-09 17:02:00' , 3 )
;

Count distinct values for day in oracle sql

I have a table that has the next values:
sta_datetime | calling_number |called_number
01/08/2019 | 999999 | 9345435
01/08/2019 | 999999 | 5657657
02/08/2019 | 999999 | 5657657
03/08/2019 | 999999 | 9844566
I want a query that counts the uniques values for each date in all the month , for example:
sta_datetime | calling_number | quantity_calls
01/08/2019 | 999999 | 2
02/08/2019 | 999999 | 0
03/08/2019 | 999999 | 1
In date 02/08/2019 is 0 because the called_numbers are repited in date 01/08/2019.
Assuming you have records on each day, you can just count the first in a series of days with a given called number by using lag():
select sta_datetime, calling_number,
sum(case when prev_sta_datetime = sta_datetime - 1 then 0 else 1 end) as cnt
from (select t.*,
lag(sta_datetime) over (partition by calling_number, called_number order by sta_datetime) as prev_sta_datetime
from t
) t
group by sta_datetime, calling_number
order by sta_datetime, calling_number;
If you only want to count the first date called_number was called, then:
select sta_datetime, calling_number,
sum(case when first_sta_datetime = sta_datetime then 1 else 0 end) as cnt
from (select t.*,
min(sta_datetime) over (partition by calling_number, called_number) as first_sta_datetime
from t
) t
group by sta_datetime, calling_number
order by sta_datetime, calling_number;
I think you can use not exists and then group by as following:
Select t1.sta_datetime, t1.calling_number, count(1) as quantity_calls
from your_table t1
Where not exists
(select 1 from
your_table t2
Where t2.sta_datetime < t1.sta_datetime
and t1.calling_number = t2.calling_number
and t1.called_number = t2.called_number
and trunc(t1.sta_datetime, 'month') = trunc(t2.sta_datetime, 'month'))
Group by t1.sta_datetime, t1.calling_number
Order by t1.calling_number, t1.sta_datetime;
Cheers!!

SQL Server : remove duplicates and add columns

I have a table which has duplicate record this is how the table looks like.
ID Date Status ModifiedBy
------------------------------------------
1 1/2/2019 10:29 Assigned(0) xyz
1 1/2/2019 12:21 Pending(1) abc
1 1/4/2019 11:42 Completed(5)abc
1 1/20/2019 2:45 Closed(8) pqr
2 9/18/2018 10:05 Assigned(0) xyz
2 9/18/2018 11:15 Pending(1) abc
2 9/21/2018 11:15 Completed(5)abc
2 10/7/2018 2:46 Closed(8) pqr
What I want to do is take the minimum date value but also I want to add additional column which is PendingStartDate and PendingEndDate.
PendingStartDate: date when ID went into pending status
PendingEndDate: date when ID went from pending status to any other status
So my final output should look like this
ID AuditDate Status ModifiedBy PendingStartDate PendingEndDate
---------------------------------------------------------------------------
1 1/2/2019 10:29 Assigned(0) xyz 1/2/2019 12:21 1/4/2019 11:42
2 9/18/2018 10:05 Assigned(0) abc 9/18/2018 11:15 9/21/2018 11:15
Any help as to how to do this is appreciated.
Thanks
I think you want conditional aggregation:
select id, min(date) as auditdate,
max(case when seqnum = 1 then status end) as status,
max(case when seqnum = 1 then modifiedBy end) as modifiedBy,
min(case when status like 'Pending%' then date end) as pendingStartDate,
max(case when status like 'Pending%' then next_date end) as pendingEndDate
from (select t.*,
row_number() over (partition by id order by date) as seqnum,
lead(date) over (partition by id order by date) as next_date
from t
) t
group by id;
please try this:
Declare #Tab Table(Id int, [Date] DATETIME,[Status] Varchar(25),ModifiedBy varchar(10))
Insert into #Tab
SELECT 1,'1/2/2019 10:29','Assigned(0)','xyz' Union All
SELECT 1,'1/2/2019 11:29','Started(0)','xyz' Union All
SELECT 1,'1/2/2019 12:21','Pending(1)','abc' Union All
SELECT 1,'1/2/2019 12:21','In-Progress(1)','abc' Union All
SELECT 1,'1/4/2019 11:42','Completed(5)','abc'Union All
SELECT 1,'1/20/2019 2:45','Closed(8)','pqr' Union All
SELECT 2,'9/18/2018 10:05','Assigned(0)','xyz'Union All
SELECT 2,'9/18/2018 11:15','Pending(1)','abc' Union All
SELECT 2,'9/21/2018 11:15','Completed(5)','abc' Union All
SELECT 2,'10/7/2018 2:46','Closed(8)','pqr'
;with cte As
(
Select * ,lead(date) over (partition by id order by date) as pendingStartDate
from #Tab
Where Status in ('Assigned(0)','Pending(1)','Completed(5)')
)
,cte2 As
(
Select * , lead(pendingStartDate) over (partition by id order by date) As pendingEndDate
from cte
)
Select * from cte2 where Status ='Assigned(0)'
As you mentioned in comment, i have included few states between Assigned,pending and completed.

Determining price for discreet periods from a list of trades

I have a discreet list of historic trades from a commodity exchange, with a datetime and a value e.g.
tradeTime | price
-----------------------------------
2014-06-11T00:21:12+02:00 | 647.24
2014-06-11T00:23:12+02:00 | 700.18
2014-06-11T00:28:12+02:00 | 750.23
2014-06-11T00:40:00+02:00 | 767.81
2014-06-11T00:51:12+02:00 | 711.46
Now I want to be able to create a list of the price every hour. So the three logical possibilities are:
There was 1 trade in that period - great, that's the price (opening and closing)
There were multiple trades - the first is the opening price and the last is the closing
There were no trades - the opening & closing price should be the most recent trade prior to the period.
So with the above example, looking at 10min increments, the output should be something like:
period | open | close
--------------------------------
00:20 -> 00:30 | 648.24 | 750.23
00:30 -> 00:40 | 750.23 | 767.81
00:40 -> 00:50 | 767.81 | 711.46
-
SELECT * FROM sales BETWEEN From_date AND To_date
possibly with a MIN(), MAX() for the dates to select the price? I am not sure how this would all come together with the
Try this query:
WITH qq AS (
SELECT cast('2014-06-11T00:00:00' as datetime) as startTime,
dateadd( minute, 10, cast('2014-06-11T00:00:00' as datetime)) as endTime
UNION ALL
SELECT endTime, dateadd( minute, 10, endTime )
FROM qq
WHERE endTime < cast('2014-06-12T00:00:00' as datetime)
)
SELECT startTime, endTime,
coalesce( opening, opening1 ) as opening,
coalesce( closing, opening, opening1 ) as closing
FROM (
SELECT qq.startTime, qq.endTime,
(SELECT TOP 1 price FROM table1 t1
WHERE t1.tradeTime >= qq.startTime AND t1.tradeTime < qq.endTime
ORDER BY t1.tradeTime ) As opening,
(SELECT TOP 1 price FROM table1 t1
WHERE t1.tradeTime >= qq.startTime AND t1.tradeTime < qq.endTime
ORDER BY t1.tradeTime DESC ) As closing,
(SELECT TOP 1 price FROM table1 t1
WHERE t1.tradeTime < qq.startTime
ORDER BY t1.tradeTime DESC ) as opening1
FROM qq
) x
option (maxrecursion 0)
Demo: http://sqlfiddle.com/#!18/b9363/6
| startTime | endTime | opening | closing |
|----------------------|----------------------|---------|---------|
| 2014-06-11T00:00:00Z | 2014-06-11T00:10:00Z | (null) | (null) |
| 2014-06-11T00:10:00Z | 2014-06-11T00:20:00Z | (null) | (null) |
| 2014-06-11T00:20:00Z | 2014-06-11T00:30:00Z | 647.24 | 750.23 |
| 2014-06-11T00:30:00Z | 2014-06-11T00:40:00Z | 750.23 | 750.23 |
| 2014-06-11T00:40:00Z | 2014-06-11T00:50:00Z | 767.81 | 767.81 |
| 2014-06-11T00:50:00Z | 2014-06-11T01:00:00Z | 711.46 | 711.46 |
| 2014-06-11T01:00:00Z | 2014-06-11T01:10:00Z | 711.46 | 711.46 |
| 2014-06-11T01:10:00Z | 2014-06-11T01:20:00Z | 711.46 | 711.46 |
| 2014-06-11T01:20:00Z | 2014-06-11T01:30:00Z | 711.46 | 711.46 |
| 2014-06-11T01:30:00Z | 2014-06-11T01:40:00Z | 711.46 | 711.46 |
| 2014-06-11T01:40:00Z | 2014-06-11T01:50:00Z | 711.46 | 711.46 |
| 2014-06-11T01:50:00Z | 2014-06-11T02:00:00Z | 711.46 | 711.46 |
...
...
...
Following query should give you a start.
SELECT DISTINCT Disp,
(
CASE WHEN [Open] IS NOT NULL THEN [Open]
ELSE (SELECT TOP 1 PRICE FROM #Table
WHERE Datepart(MINUTE, TradeTime) / 10 < O.Diff )
END
) AS [Open]
,(
CASE WHEN O.[Open] IS NOT NULL THEN [Open]
ELSE (SELECT TOP 1 PRICE FROM #Table
WHERE Datepart(MINUTE, TradeTime) / 10 < O.Diff )
END
) AS [Close]
FROM
(
SELECT
D.Disp
,D.Diff
,FIRST_VALUE(Price) OVER (PARTITION BY GRP
ORDER BY TradeTime ASC) AS [Open]
,FIRST_VALUE(Price) OVER (PARTITION BY GRP
ORDER BY TradeTime DESC) AS [Close],
TradeTime
FROM (VALUES (0,'00:01->00:10'),
(1,'00:10->00:20'),
(2,'00:20->00:30'),
(3,'00:30->00:40'),
(4,'00:40->00:50'),
(5,'00:50->00:59')
) D(Diff,Disp)
LEFT JOIN (SELECT Datepart(MINUTE, TradeTime) / 10 AS GRP,
TradeTime,
Price
FROM #Table
GROUP BY Datepart(MINUTE, TradeTime) / 10,
TradeTime,
Price) T
ON T.GRP = D.Diff
) O
Complete Example :
DECLARE #Table TABLE(TradeTime DATETIME, Price DECIMAL(15,2))
INSERT INTO #Table
SELECT * FROM
(VALUES
(CAST('2018-02-18 13:04:46.920' AS DATETIME) , 647.24)
,(CAST('2018-02-18 13:05:46.920' AS DATETIME) , 700.18)
,(CAST('2018-02-18 13:15:46.920' AS DATETIME) , 750.23)
,(CAST('2018-02-18 13:24:46.920' AS DATETIME) , 767.81)
,(CAST('2018-02-18 14:26:46.920' AS DATETIME) , 711.46)
) TAB(TradeTime, Price)
SELECT DISTINCT Disp,
(
CASE WHEN [Open] IS NOT NULL THEN [Open]
ELSE (SELECT TOP 1 PRICE FROM #Table
WHERE Datepart(MINUTE, TradeTime) / 10 < O.Diff )
END
) AS [Open]
,(
CASE WHEN O.[Open] IS NOT NULL THEN [Open]
ELSE (SELECT TOP 1 PRICE FROM #Table
WHERE Datepart(MINUTE, TradeTime) / 10 < O.Diff )
END
) AS [Close]
FROM
(
SELECT
D.Disp
,D.Diff
,FIRST_VALUE(Price) OVER (PARTITION BY GRP
ORDER BY TradeTime ASC) AS [Open]
,FIRST_VALUE(Price) OVER (PARTITION BY GRP
ORDER BY TradeTime DESC) AS [Close],
TradeTime
FROM (VALUES (0,'00:00->00:10'),
(1,'00:10->00:20'),
(2,'00:20->00:30'),
(3,'00:30->00:40'),
(4,'00:40->00:50'),
(5,'00:50->00:59')
) D(Diff,Disp)
LEFT JOIN (SELECT Datepart(MINUTE, TradeTime) / 10 AS GRP,
TradeTime,
Price
FROM #Table
GROUP BY Datepart(MINUTE, TradeTime) / 10,
TradeTime,
Price) T
ON T.GRP = D.Diff
) O
Sample Output
Disp Open Close
--------------------------------------
00:00->00:10 647.24 647.24
00:10->00:20 750.23 750.23
00:20->00:30 767.81 767.81
00:30->00:40 647.24 47.24
00:40->00:50 647.24 647.24
00:50->00:59 647.24 647.24
You need to start with a list of hours. Because you may not have trades, you need to generate this. Assuming you only want a handful of hours -- say all hours in one day -- a recursive CTE is sufficient:
with hours as (
select cast('2014-06-11' as datetime) as yyyymmddhh
union all
select dateadd(hour, 1, yyyymmddhh)
from hours
where dateadd(hour, 1, yyyymmddhh) < '2014-06-12'
)
Then, you can do what you want using apply:
with hours as (
select cast('2014-06-11' as datetime) as yyyymmddhh
union all
select dateadd(hour, 1, yyyymmddhh)
from cte
where dateadd(hour, 1, yyyymmddhh) < '2014-06-12'
)
select h.*, opening.price, closing.price
from hours h outer apply
(select top 1 t.*
from t
where tradetime < dateadd(hour, 1, yyyymmddhh)
order by (case when cast(tradetime as date) = cast(yyyymmddhh as date) and
datepart(hour, tradetime) = datepart(hour, yyyymddhh)
then 1
else 0
end),
(case when cast(tradetime as date) = cast(yyyymmddhh as date) and
datepart(hour, tradetime) = datepart(hour, yyyymddhh)
then tradetime
end) asc,
tradetime desc
) opening outer apply
(select top 1 t.*
from t
where tradetime < dateadd(hour, 1, yyyymmddhh)
order by (case when cast(tradetime as date) = cast(yyyymmddhh as date) and
datepart(hour, tradetime) = datepart(hour, yyyymddhh)
then 1
else 0
end),
tradetime desc
) closing;
Good afternoon,
I'm not sure if my solution actually helps you, since i'm not in a situation where I can check if the code works or why it doesn't, I would try something along these lines:
SELECT prices FROM trades WHERE(SELECT MIN(DATEPART(MINUTE, trades.dates)),
MAX(DATEPART(MINUTE, trades.dates)) WHERE(SELECT DISTINCT DATEPART(HOUR,
trades.dates));
The idea is to sub-query the minimum and maximum values for the minutes in each distinct hour, and then query the related prices for minute 1 and minute 60, so that you obtain both the opening and closing prices.
I hope this helps you to move forward on your problem, or at least gives you a hint on a possible solution.
Best wishes,
M.S.

Count and pivot a table by date

I would like to identify the returning customers from an Oracle(11g) table like this:
CustID | Date
-------|----------
XC321 | 2016-04-28
AV626 | 2016-05-18
DX970 | 2016-06-23
XC321 | 2016-05-28
XC321 | 2016-06-02
So I can see which customers returned within various windows, for example within 10, 20, 30, 40 or 50 days. For example:
CustID | 10_day | 20_day | 30_day | 40_day | 50_day
-------|--------|--------|--------|--------|--------
XC321 | | | 1 | |
XC321 | | | | 1 |
I would even accept a result like this:
CustID | Date | days_from_last_visit
-------|------------|---------------------
XC321 | 2016-05-28 | 30
XC321 | 2016-06-02 | 5
I guess it would use a partition by windowing clause with unbounded following and preceding clauses... but I cannot find any suitable examples.
Any ideas...?
Thanks
No need for window functions here, you can simply do it with conditional aggregation using CASE EXPRESSION :
SELECT t.custID,
COUNT(CASE WHEN (last_visit- t.date) <= 10 THEN 1 END) as 10_day,
COUNT(CASE WHEN (last_visit- t.date) between 11 and 20 THEN 1 END) as 20_day,
COUNT(CASE WHEN (last_visit- t.date) between 21 and 30 THEN 1 END) as 30_day,
.....
FROM (SELECT s.custID,
LEAD(s.date) OVER(PARTITION BY s.custID ORDER BY s.date DESC) as last_visit
FROM YourTable s) t
GROUP BY t.custID
Oracle Setup:
CREATE TABLE customers ( CustID, Activity_Date ) AS
SELECT 'XC321', DATE '2016-04-28' FROM DUAL UNION ALL
SELECT 'AV626', DATE '2016-05-18' FROM DUAL UNION ALL
SELECT 'DX970', DATE '2016-06-23' FROM DUAL UNION ALL
SELECT 'XC321', DATE '2016-05-28' FROM DUAL UNION ALL
SELECT 'XC321', DATE '2016-06-02' FROM DUAL;
Query:
SELECT *
FROM (
SELECT CustID,
Activity_Date AS First_Date,
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '10' DAY FOLLOWING )
- 1 AS "10_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '20' DAY FOLLOWING )
- 1 AS "20_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '30' DAY FOLLOWING )
- 1 AS "30_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '40' DAY FOLLOWING )
- 1 AS "40_Day",
COUNT(1) OVER ( PARTITION BY CustID
ORDER BY Activity_Date
RANGE BETWEEN CURRENT ROW AND INTERVAL '50' DAY FOLLOWING )
- 1 AS "50_Day",
ROW_NUMBER() OVER ( PARTITION BY CustID ORDER BY Activity_Date ) AS rn
FROM Customers
)
WHERE rn = 1;
Output
USTID FIRST_DATE 10_Day 20_Day 30_Day 40_Day 50_Day RN
------ ------------------- ---------- ---------- ---------- ---------- ---------- ----------
AV626 2016-05-18 00:00:00 0 0 0 0 0 1
DX970 2016-06-23 00:00:00 0 0 0 0 0 1
XC321 2016-04-28 00:00:00 0 0 1 2 2 1
Here is an answer that works for me, I have based it on your answers above, thanks for contributions from MT0 and Sagi:
SELECT CustID,
visit_date,
Prev_Visit ,
COUNT( CASE WHEN (Days_between_visits) <=10 THEN 1 END) AS "0-10_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 11 AND 20 THEN 1 END) AS "11-20_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 21 AND 30 THEN 1 END) AS "21-30_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 31 AND 40 THEN 1 END) AS "31-40_day" ,
COUNT( CASE WHEN (Days_between_visits) BETWEEN 41 AND 50 THEN 1 END) AS "41-50_day" ,
COUNT( CASE WHEN (Days_between_visits) >50 THEN 1 END) AS "51+_day"
FROM
(SELECT CustID,
visit_date,
Lead(T1.visit_date) over (partition BY T1.CustID order by T1.visit_date DESC) AS Prev_visit,
visit_date - Lead(T1.visit_date) over (
partition BY T1.CustID order by T1.visit_date DESC) AS Days_between_visits
FROM T1
) T2
WHERE Days_between_visits >0
GROUP BY T2.CustID ,
T2.visit_date ,
T2.Prev_visit ,
T2.Days_between_visits;
This returns:
CUSTID | VISIT_DATE | PREV_VISIT | DAYS_BETWEEN_VISIT | 0-10_DAY | 11-20_DAY | 21-30_DAY | 31-40_DAY | 41-50_DAY | 51+DAY
XC321 | 2016-05-28 | 2016-04-28 | 30 | | | 1 | | |
XC321 | 2016-06-02 | 2016-05-28 | 5 | 1 | | | | |