date attend date by regular interval - plsql

i have table which contains 4 rows of perticular emp.. and they have attendance of
like : formate(mm/dd/rrrr)
2 column name
atten date : | strtdate
1) 4/02/2016 | 1) 4/02/2016
2) 4/03/2016 | 2) 4/02/2016
3) 4/05/2016 | 3) 4/02/2016
4) 4/ 07/2016 | 4) 4/02/2016
5) 4/08/2016 | 5) 4/02/2016
here i need like this o/p column
fromdt | todate
1) 4/02/2016 | 4/03/2016
2) 4/03/2016 | 4/05/2016
3) 4/05/2016 | 4/05/2016
4) 4/07/2016 | 4/08/2016

You can do this by using the LEAD() analytic function:
with sample_data as (select 1 emp_id, to_date('02/04/2016', 'dd/mm/yyyy') atten_date, to_date('02/04/2016', 'dd/mm/yyyy') strtdate from dual union all
select 1 emp_id, to_date('03/04/2016', 'dd/mm/yyyy') atten_date, to_date('02/04/2016', 'dd/mm/yyyy') strtdate from dual union all
select 1 emp_id, to_date('05/04/2016', 'dd/mm/yyyy') atten_date, to_date('02/04/2016', 'dd/mm/yyyy') strtdate from dual union all
select 1 emp_id, to_date('07/04/2016', 'dd/mm/yyyy') atten_date, to_date('02/04/2016', 'dd/mm/yyyy') strtdate from dual union all
select 1 emp_id, to_date('08/04/2016', 'dd/mm/yyyy') atten_date, to_date('02/04/2016', 'dd/mm/yyyy') strtdate from dual)
-- end of subquery mimicking a table called "sample_data" with data in it. See SQL below:
select emp_id,
fromdt,
todate
from (select emp_id,
atten_date fromdt,
lead(atten_date) over (partition by emp_id order by atten_date) todate
from sample_data)
where todate is not null;
EMP_ID FROMDT TODATE
---------- ---------- ----------
1 04/02/2016 04/03/2016
1 04/03/2016 04/05/2016
1 04/05/2016 04/07/2016
1 04/07/2016 04/08/2016
if you need the row number displayed then you can use the ROW_NUMBER() analytic function to do that, if that information isn't already available as part of the source data.

If well understood your requirements this solution may work for you:
with sample_data as
(
select 10 emp_id, to_date('4/02/2016','mm/dd/yyyy') attendate, to_date('4/02/2016','mm/dd/yyyy') strtdate from dual union all
select 10 emp_id, to_date('4/03/2016','mm/dd/yyyy') attendate, to_date('4/02/2016','mm/dd/yyyy') strtdate from dual union all
select 10 emp_id, to_date('4/05/2016','mm/dd/yyyy') attendate, to_date('4/02/2016','mm/dd/yyyy') strtdate from dual union all
select 10 emp_id, to_date('4/07/2016','mm/dd/yyyy') attendate, to_date('4/02/2016','mm/dd/yyyy') strtdate from dual union all
select 10 emp_id, to_date('4/08/2016','mm/dd/yyyy') attendate, to_date('4/02/2016','mm/dd/yyyy') strtdate from dual
)
, marked_data as
(
select emp_id
, attendate
, strtdate
, row_number() over (partition by emp_id, strtdate order by attendate) as is_from
, row_number() over (partition by emp_id, strtdate order by attendate) - 1 as is_to
from sample_data
)
select md1.emp_id
, md1.attendate as fromdt
, md2.attendate as todt
, md1.strtdate
from marked_data md1
join marked_data md2
on md1.emp_id = md2.emp_id
and md1.strtdate = md2.strtdate
and md1.is_from = md2.is_to
You will have as a result:
EMP_ID FROMDT TODT STRTDATE
1 10 4/2/2016 4/3/2016 4/2/2016
2 10 4/3/2016 4/5/2016 4/2/2016
3 10 4/5/2016 4/7/2016 4/2/2016
4 10 4/7/2016 4/8/2016 4/2/2016

Related

SQLite: create equal date ranges and query data based on them?

I have datas in a table with schema:
Id INTEGER,
date DATETIME,
value REAL
id is primary key, and I have an index on date column to speed up querying values within a specific date range.
What should I do if I need N equal date ranges between specific start and end dates, and query aggregated datas for each date range?
For example:
Start date: 2015-01-01
End date: 2019-12-31
N: 5
In this case equal date intervals should be:
2015-01-01 ~ 2015-12-31
2016-01-01 ~ 2016-12-31
2017-01-01 ~ 2017-12-31
2018-01-01 ~ 2018-12-31
2019-01-01 ~ 2019-12-31
And the query should aggregate all values (AVG) in between those intervals, so I would like to have 5 total rows after the execution.
Maybe something with CTE?
There are 2 ways to do it.
They both use recursive ctes but return different results.
The 1st one with NTILE():
with
dates as (select '2015-01-01' mindate, '2019-12-31' maxdate),
alldates as (
select mindate date from dates
union all
select date(a.date, '1 day')
from alldates a cross join dates d
where a.date < d.maxdate
),
groups as (
select *, ntile(5) over (order by date) grp
from alldates
),
cte as (
select min(date) date1, max(date) date2
from groups
group by grp
)
select * from cte;
Results:
| date1 | date2 |
| ---------- | ---------- |
| 2015-01-01 | 2016-01-01 |
| 2016-01-02 | 2016-12-31 |
| 2017-01-01 | 2017-12-31 |
| 2018-01-01 | 2018-12-31 |
| 2019-01-01 | 2019-12-31 |
And the 2nd builds the groups with math:
with
dates as (select '2015-01-01' mindate, '2019-12-31' maxdate),
cte1 as (
select mindate date from dates
union all
select date(
c.date,
((strftime('%s', d.maxdate) - strftime('%s', d.mindate)) / 5) || ' second'
)
from cte1 c inner join dates d
on c.date < d.maxdate
),
cte2 as (
select date date1, lead(date) over (order by date) date2
from cte1
),
cte as (
select date1,
case
when date2 = (select maxdate from dates) then date2
else date(date2, '-1 day')
end date2
from cte2
where date2 is not null
)
select * from cte
Results:
| date1 | date2 |
| ---------- | ---------- |
| 2015-01-01 | 2015-12-31 |
| 2016-01-01 | 2016-12-30 |
| 2016-12-31 | 2017-12-30 |
| 2017-12-31 | 2018-12-30 |
| 2018-12-31 | 2019-12-31 |
In both cases you can get the averages by joining the table to the cte:
select c.date1, c.date2, avg(t.value) avg_value
from cte c inner join tablename t
on t.date between c.date1 and c.date2
group by c.date1, c.date2

List all days of week in query

I'm trying to list all days of the next week of given date
Example:
If today is 2019-09-24 then the result should be:
DAY_OF_WEEK
2019-09-24
2019-09-25
2019-09-26
2019-09-27
2019-09-28
2019-09-29
2019-09-30
This is the query I came up with and I wonder if there is more elegant way to achieve the same results:
SELECT date('now') AS DAY_OF_WEEK
UNION
SELECT date('now', '+1 day') AS DAY_OF_WEEK
UNION
SELECT date('now', '+2 day') AS DAY_OF_WEEK
UNION
SELECT date('now', '+3 day') AS DAY_OF_WEEK
UNION
SELECT date('now', '+4 day') AS DAY_OF_WEEK
UNION
SELECT date('now', '+5 day') AS DAY_OF_WEEK
UNION
SELECT date('now', '+6 day') AS DAY_OF_WEEK
Your code is correct.
If you want you can use a CTE which returns only the numbers from 0 to 6 and select from it the number of days to add to the current date:
WITH days as (
SELECT 0 AS day UNION ALL SELECT 1 UNION ALL SELECT 2 UNION ALL
SELECT 3 UNION ALL SELECT 4 UNION ALL SELECT 5 UNION ALL SELECT 6
)
SELECT date('now', '+' || day || ' day') AS DAY_OF_WEEK FROM days
See the demo.
Or with a RECURSIVE CTE:
WITH RECURSIVE days(day) AS (
SELECT 0
UNION ALL
SELECT day + 1 FROM days
LIMIT 7
)
SELECT date('now', '+' || day || ' day') AS DAY_OF_WEEK FROM days;
See the demo.
Results:
| DAY_OF_WEEK |
| ----------- |
| 2019-09-24 |
| 2019-09-25 |
| 2019-09-26 |
| 2019-09-27 |
| 2019-09-28 |
| 2019-09-29 |
| 2019-09-30 |

How to use case statement with group by?

Assume our company has multiple marketing campaigns for one specific product, which might boost the sales of it in certain way. The results of the campaign are shown in the following data table:
Date CampaignID QtySold
2017-01-05 1 20
2017-01-18 2 35
2017-01-23 1 15
…
For modeling purposes, the desired output table looks like this:
CampaignID JanQtySold FebQtySold … DecQtySold
1 55 30
2 45 20
…
N
I have tried to get the month of each transaction for each campaign, and then group by campaignID and month.
select
campaignid,
strftime('%m',date) as Month,
sum(qtysold) as Sum_Qty
from campaign
group by campaignid, month
;
The return should include a unique CampaignID but I don't know how to proceed.
I believe that you want something like :-
select
campaignid,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 1
)
,0) AS JanQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 2
)
,0) As FebQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 3
)
,0) As MarQtySold
/* .............. and so on ..........*/
from campaign
group by campaignid
;
That is introducing more groups is not really what you want for more derived columns (introducing more grouping components will introduce more rows, so you only want the 1 grouping component i.e. the campaign id). Rather you want to use sub-queries to generate the data for the derived columns.
Note for brevity only 3 months (Jan-Mar) have been shown, the other months is just a matter of copying one of the months and then amending the test value and the column name respectively.
Example :-
Using :-
DROP TABLE IF EXISTS campaign;
CREATE TABLE IF NOT EXISTS campaign (Date TEXT, CampaignID INTEGER, QtySold INTEGER);
INSERT INTO campaign VALUES
('2017-01-05',1,20),('2017-01-23',1,15),('2017-02-01',1,5),
('2017-01-18',2,35)
;
select
campaignid,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 1
)
,0) AS JanQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 2
)
,0) As FebQtySold,
coalesce(
(
SELECT sum(qtysold) FROM campaign AS c2 WHERE campaign.campaignid = c2.campaignid AND CAST(strftime("%m",date) AS INTEGER) = 3
)
,0) As MarQtySold
from campaign
group by campaignid
;
results in :-
You need conditional aggregation with a CASE statement inside SUM():
select
CampaignID,
sum(case strftime('%m',Date) when '01' then QtySold else 0 end) as JanQtySold,
sum(case strftime('%m',Date) when '02' then QtySold else 0 end) as FebQtySold,
sum(case strftime('%m',Date) when '03' then QtySold else 0 end) as MarQtySold,
sum(case strftime('%m',Date) when '04' then QtySold else 0 end) as AprQtySold,
sum(case strftime('%m',Date) when '05' then QtySold else 0 end) as MayQtySold,
sum(case strftime('%m',Date) when '06' then QtySold else 0 end) as JunQtySold,
sum(case strftime('%m',Date) when '07' then QtySold else 0 end) as JulQtySold,
sum(case strftime('%m',Date) when '08' then QtySold else 0 end) as AugQtySold,
sum(case strftime('%m',Date) when '09' then QtySold else 0 end) as SepQtySold,
sum(case strftime('%m',Date) when '10' then QtySold else 0 end) as OctQtySold,
sum(case strftime('%m',Date) when '11' then QtySold else 0 end) as NovQtySold,
sum(case strftime('%m',Date) when '12' then QtySold else 0 end) as DecQtySold
from campaign
where strftime('%Y',Date) = '2017'
group by CampaignID
I added the condition:
where strftime('%Y',Date) = '2017'
just in case there are rows in the table for multiple years.
If you don't want zeros in the results just remove all else 0 from the case statements.
See the demo.
Just to make it more efficient, you can a use a CTE:
with cte as (
select
CampaignID,
strftime('%Y',Date) as year,
strftime('%m',Date) as month,
QtySold
from campaign
)
select
CampaignID,
sum(case month when '01' then QtySold else 0 end) as JanQtySold,
sum(case month when '02' then QtySold else 0 end) as FebQtySold,
sum(case month when '03' then QtySold else 0 end) as MarQtySold,
sum(case month when '04' then QtySold else 0 end) as AprQtySold,
sum(case month when '05' then QtySold else 0 end) as MayQtySold,
sum(case month when '06' then QtySold else 0 end) as JunQtySold,
sum(case month when '07' then QtySold else 0 end) as JulQtySold,
sum(case month when '08' then QtySold else 0 end) as AugQtySold,
sum(case month when '09' then QtySold else 0 end) as SepQtySold,
sum(case month when '10' then QtySold else 0 end) as OctQtySold,
sum(case month when '11' then QtySold else 0 end) as NovQtySold,
sum(case month when '12' then QtySold else 0 end) as DecQtySold
from cte
where year = '2017'
group by CampaignID
See the demo.
Results:
| CampaignID | JanQtySold | FebQtySold | MarQtySold | AprQtySold | MayQtySold | JunQtySold | JulQtySold | AugQtySold | SepQtySold | OctQtySold | NovQtySold | DecQtySold |
| ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- | ---------- |
| 1 | 35 | 65 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 2 | 35 | 75 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |

ORACLE: Range between Weeks

How would one create my expected results. Any help would be appreciated, Thanks in advance!
Master Calendar:
SELECT DISTINCT
--CA.CALENDAR_DATE,
TO_CHAR(CALENDAR_DATE,'MM/DD/YYYY') AS CALENDAR_DATE
TO_CHAR(NEXT_DAY(CALENDAR_DATE, 'Monday') - 7, 'MM/DD/YY-') ||
TO_CHAR(NEXT_DAY(CALENDAR_DATE, 'Monday') - 1, 'MM/DD/YY') AS WEEK_OF_YEAR,
ROW_NUMBER () OVER ( ORDER BY CALENDAR_DATE) AS MasterCalendar_RNK
FROM CALENDAR CA
WHERE 1=1
--AND CA.CALENDAR_DATE BETWEEN ADD_MONTHS(TRUNC(SYSDATE), -12) AND TRUNC(SYSDATE)
--AND CA.CALENDAR_DATE BETWEEN TRUNC(SYSDATE) -5 AND TRUNC(SYSDATE)
ORDER BY TO_DATE(CALENDAR_DATE,'MM/DD/YYYY') DESC
Input:
Member StartDate EndDate
A 1/31/17
B 2/1/17 2/15/17
Expected Results:
Member StartDate EndDate Week_Of_Year Active
A 1/31/17 1/30/17-2/5/17 1
A 1/31/17 2/6/17-2/12/17 1
A 1/31/17 2/13/17-2/19/17 1
B 2/1/17 2/15/17 1/30/17/2/5/17 1
B 2/1/17 2/15/17 2/6/17-2/12/17 1
B 2/1/17 2/15/17 2/13/17-2/19/17 1

Oracle: Creating a range on month between two dates

I have the below table: I also have a calendar table if needed.
ID Start_dt End_dt
1 1/9/2016 3/10/2016
Expected Output:
ID Start_dt End_dt Month ActiveCustomerPerMonth
1 1/9/16 3/10/2016 201601 1
1 1/9/16 3/10/2016 201602 1
1 1/9/16 3/10/2016 201603 0 (Not Active end of Month)
I need this as I'm working on a current query that will utilize a case statement to count if the customer was active for that month. If the member was active on the last day of the month, the member would be considred active for that month. But I need to be able to count for al months for that customer.
CASE
WHEN LAST_DAY(x.END_DT) = x.END_DT
THEN '1'
WHEN TO_CHAR(X.END_DT,'MM/DD/YYYY') != '01/01/3000'
OR X.DISCHARGE_REASON IS NOT NULL
THEN '0'
WHEN X.FIRST_ASSGN_DT IS NULL
THEN '0'
ELSE '1'
END ActiveMemberForMonth
I'm new to Oracle and was reading about connect by but did not understand the process and not sure if this would be the proper place to use.
Something like this.
with
test_data ( id, start_dt, end_dt ) as (
select 1, to_date('1/9/2016' , 'mm/dd/yyyy'), to_date('3/10/2016', 'mm/dd/yyyy')
from dual union all
select 2, to_date('1/23/2016', 'mm/dd/yyyy'), to_date('5/31/2016', 'mm/dd/yyyy')
from dual
)
-- end of test data; solution (SQL query) begins below this line
select id, start_dt, end_dt,
to_char(add_months(trunc(start_dt, 'mm'), level - 1), 'yyyymm') as mth,
case when end_dt < last_day(end_dt)
and level = 1 + months_between(trunc(end_dt, 'mm'), trunc(start_dt, 'mm'))
then 0 else 1 end as active_at_month_end
from test_data
connect by level <= 1 + months_between(trunc(end_dt, 'mm'), trunc(start_dt, 'mm'))
and prior id = id
and prior sys_guid() is not null
order by id, mth -- optional
;
ID START_DT END_DT MTH ACTIVE_AT_MONTH_END
--- ---------- ---------- ------ -------------------
1 2016-01-09 2016-03-10 201601 1
1 2016-01-09 2016-03-10 201602 1
1 2016-01-09 2016-03-10 201603 0
2 2016-01-23 2016-05-31 201601 1
2 2016-01-23 2016-05-31 201602 1
2 2016-01-23 2016-05-31 201603 1
2 2016-01-23 2016-05-31 201604 1
2 2016-01-23 2016-05-31 201605 1
8 rows selected.

Resources