Calculate age as of end of pay period - oracle

I have been tasked to calculate age based on date of birth as of end of every two weeks (pay period). Is that possible?
I can calculate as of sysdate but cant think of how I can achieve this task.
TO_CHAR(SYSDATE,'yyyy')-TO_CHAR(PAE.BIRTHDATE,'yyyy') > 50
Hire Date
Birth Date
Term Date
Employee Status
BSI Group
Age Years
Months
Days
04/05/2021
12/20/1971
01/01/1700
A1
50
49
3
13

When looking at your example
TO_CHAR(SYSDATE,'yyyy')-TO_CHAR(PAE.BIRTHDATE,'yyyy') > 50
it seems that you just want to know whether a person is over 50 years old
(there may be more age brackets).
You could use the ADD_MONTHS() function for just adding 600 months to the
individual DOB values, and then check whether this "calculated date" is after the payment date. The following queries are just a rough illustration.
Test data: person & DOB
-- person 100 will be 50 years old on 16 April 2021 -> must be in the "over 50" bracket thereafter
create table persondetails( id primary key, dob )
as
select 1, date '1955-01-01' from dual union all
select 2, date '1965-01-01' from dual union all
select 100, date '1971-04-16' from dual union all
select 3, date '1975-01-01' from dual union all
select 4, date '1985-01-01' from dual union all
select 5, date '1995-01-01' from dual union all
select 6, date '2005-01-01' from dual
;
-- sysdate ie today's date is: 2021-04-03
select id
, case
when add_months( dob, 600 ) > sysdate then 'under 50'
else 'over 50'
end age_bracket
from persondetails
;
ID AGE_BRACKET
1 over 50
2 over 50
100 under 50
3 under 50
4 under 50
5 under 50
6 under 50
Test data: paydates and payroll (every person gets paid on every "paydate")
-- payments every fortnight
create table paydates
as
select date '2021-02-12' + ( level * 14 ) paydate
from dual
connect by level <= 12 ;
create table payroll
as
select P1.id, P2.paydate
from (
select id from persondetails
) P1
cross join (
select paydate from paydates
) P2
;
View
create or replace view paa -- payment dates and age
as
select P1.id
, P1.dob
, P2.paydate
, case
when add_months( P1.dob, 600 ) > P2.paydate then 'under 50'
else 'over 50'
end age_bracket
from persondetails P1 join payroll P2 on P1.id = P2.id
;
Final query & result
select * from paa where id = 100 order by paydate ;
ID DOB PAYDATE AGE_BRACKET
100 16-APR-71 26-FEB-21 under 50
100 16-APR-71 12-MAR-21 under 50
100 16-APR-71 26-MAR-21 under 50
100 16-APR-71 09-APR-21 under 50
100 16-APR-71 23-APR-21 over 50
100 16-APR-71 07-MAY-21 over 50
100 16-APR-71 21-MAY-21 over 50
100 16-APR-71 04-JUN-21 over 50
100 16-APR-71 18-JUN-21 over 50
100 16-APR-71 02-JUL-21 over 50
100 16-APR-71 16-JUL-21 over 50
100 16-APR-71 30-JUL-21 over 50
DBfiddle
ADDENDUM
In an Oracle Community discussion, user Blue Shadow has posted a solution for the "Years Months Days" problem (under "Accepted Answer"):
> select c_start_date, c_end_date
> ,trunc(months_between(c_end_date, c_start_date) / 12) as yrs
> ,trunc(mod(months_between(c_end_date, c_start_date), 12)) as mnths
> ,trunc(c_end_date - add_months(c_start_date, trunc(months_between(c_end_date, c_start_date)))) as dys from t
You can add this to the "view code", and get the years/months/days values. Example see dbfiddle.
-- eg results for id 100
ID DOB PAYDATE AGE_BRACKET YRS MNTHS DYS
100 16-APR-71 26-FEB-21 under 50 49 10 10
100 16-APR-71 12-MAR-21 under 50 49 10 24
100 16-APR-71 26-MAR-21 under 50 49 11 10
100 16-APR-71 09-APR-21 under 50 49 11 24
100 16-APR-71 23-APR-21 over 50 50 0 7
100 16-APR-71 07-MAY-21 over 50 50 0 21
100 16-APR-71 21-MAY-21 over 50 50 1 5
100 16-APR-71 04-JUN-21 over 50 50 1 19

Related

Retrieving profiles from a table based on specific logic

I have a requirement to extract profiles from a table for the below criteria:
There are 5 IDs in total. 100, 200, 300, 400 and 500
The profiles to have atleast one ID of each value 100, 300 and 400. IDs with value 200 and 500 should not be present even once.
Profile can have multiple number of IDs of value 300 and 400 but will have only one ID of value 100.
The profiles will have IDs 300 and 400 equally. i.e For every ID will the value 300, there will be an ID of value 400.
Eg:
TABLE A:
-------------------------
PROFILE_ID ID
-------------------------
12345 100
12345 300
12345 400
23456 100
23456 300
23456 400
23456 300
23456 400
34567 100
34567 200
-------------------------
The result should fetch PROFILE_IDs 12345 and 23456 and not 34567.
I am pretty stuck and blank in getting a clear idea on how to frame a query for this. Please help.
For sample data you posted:
SQL> select * From test;
PROFILE_ID ID
---------- ----------
12345 100
12345 300
12345 400
23456 100
23456 300
23456 400
23456 300
23456 400
34567 100
34567 200
10 rows selected.
one option is to do it rule-by-rule, each of CTEs retrieving data which satisfy certain rule. The final result is intersection of these PROFILE_IDs.
SQL> with
2 rule2 as -- don't allow IDs 200 nor 500
3 (select profile_id
4 from test
5 where profile_id not in (select profile_id
6 from test
7 where id in (200, 500)
8 )
9 ),
10 rule3 as -- there can be only one ID = 100 for each PROFILE_ID
11 (select profile_id
12 from (select profile_id,
13 sum(case when id = 100 then 1 else 0 end) cnt_100
14 from test
15 group by profile_id
16 )
17 where cnt_100 = 1
18 ),
19 rule4 as -- number of IDs 300 and 400 has to be equal and greater than 0
20 (select profile_id
21 from (select profile_id,
22 sum(case when id = 300 then 1 else 0 end) cnt_300,
23 sum(case when id = 400 then 1 else 0 end) cnt_400
24 from test
25 group by profile_id
26 )
27 where cnt_300 = cnt_400
28 and cnt_300 > 0
29 )
30 select profile_id from rule2
31 intersect
32 select profile_id from rule3
33 intersect
34 select profile_id from rule4;
PROFILE_ID
----------
12345
23456
SQL>
Personally, since all your rules rely on counting the different ids, I'd do it more like this. Some of this is redundant, but it makes explicit which part comes from which of your criteria.
with rules as (
select profile_id,
sum(case when id = 100 then 1 else 0 end) as cnt_100,
sum(case when id = 200 then 1 else 0 end) as cnt_200,
sum(case when id = 300 then 1 else 0 end) as cnt_300,
sum(case when id = 400 then 1 else 0 end) as cnt_400,
sum(case when id = 500 then 1 else 0 end) as cnt_500
from table1
group by profile_id
)
select profile_id
from rules
-- rule 1
where cnt_100 > 0
and cnt_300 > 0
and cnt_400 > 0
-- rule 2
and cnt_200 = 0
and cnt_500 = 0
-- rule 3
and cnt_100 = 1
-- rule 4
and cnt_300 = cnt_400
SQL Fiddle

Query to find before and after values of a given value

If we have table Employees
EMP_ID ENAME SALARY DEPT_ID
1 abc 1000 10
2 bca 1050 10
3 dsa 2000 20
4 zxc 3000 30
5 bnm 5000 30
6 rty 5050 30
I want to get the rank of the salary with before 2 values and after 2 values including the given rank
Like if I give rank 4 it should give ranks 2,3,4,5,6 details.
output should be
5 bnm 5000 30
4 zxc 3000 30
3 dsa 2000 20
3 dsa 2000 20
2 bca 1050 10
1 abc 1000 10
I have a query
WITH dept_count AS (
SELECT
e.*,
dense_rank() over( ORDER BY salary DESC) AS rk
FROM employees e
)
SELECT
*
FROM dept_count dc
WHERE dc.rk BETWEEN (
SELECT
c.rk-2
FROM dept_count c
WHERE c.rk =4
)
AND (
SELECT
c.rk + 2
FROM dept_count c
WHERE c.rk = 4
)
but I need a query which can be simplified.
Could someone help me with this query?
You just need to use ROW_NUMBER() along with a substitution parameter :
WITH dept_count AS (
SELECT
e.*,
ROW_NUMBER() OVER( ORDER BY salary DESC) AS rk
FROM employees e
)
SELECT *
FROM dept_count
WHERE rk BETWEEN &prm - 2 AND &prm + 2

how to do a query based on a shifting schedule (day/mids/swings)

I need to pull the total number of tickets created per shift together with the running total. I have an existing query which I thought was correct but after checking, it seems that it is pulling the wrong numbers.
SELECT
TO_CHAR(TRUNC(DTTM,'Y'),'YYYY') as "DATE"
,COUNT(CASE WHEN TO_CHAR(DTTM, 'HH24:MI') BETWEEN '14:00' AND '22:00' THEN TKTNUM ELSE NULL END) AS "DAYS"
,COUNT(CASE WHEN TO_CHAR(DTTM, 'HH24:MI') BETWEEN '06:00' AND '14:00' THEN TKTNUM ELSE NULL END) AS "MIDS"
,COUNT(CASE WHEN TO_CHAR(DTTM, 'HH24:MI') NOT BETWEEN '06:00' AND '22:00' THEN TKTNUM ELSE NULL END) AS "SWINGS"
,COUNT(TKTNUM) "TOTAL"
,SUM(COUNT(TKTNUM)) OVER (ORDER BY (TRUNC(E.ESCDTTM,'Y'),'YYYY')) -- c/o Littlefoot and Stew Ashton
FROM TKTCHISTORY
GROUP BY TRUNC(E.ESCDTTM,'Y')
ORDER BY TRUNC(E.ESCDTTM,'Y')
SAMPLE DATA:
TKTNUM TKT_CREATED
INC0001 01/10/2019 1:00
INC0002 01/10/2019 23:00
INC0003 03/10/2019 5:00
INC0004 03/10/2019 9:20
INC0005 05/11/2019 15:00
DESIRED OUTPUT:
DATE DAYS MIDS SWINGS TOTAL
2019-08-01 8 13 1 22 22
2019-08-02 19 5 3 27 49
2019-08-03 23 6 6 35 84
2019-08-04 7 9 13 29 113
2019-08-05 4 17 2 23 136
2019-08-06 10 5 16 31 167
2019-08-07 3 12 11 26 193
"SWINGS" would pull tickets between 00:00 and 06:00 or 22:00 and 24:00 on the same date. For example, a ticket was generated on 02-Nov 01:00... when I pull the report it would be counted on 02-Nov for SWINGS when it should be for the 01-Nov duty.
I've come up with something that would probably help with the logic but am not 100% sure.
WITH Shift_Sched (shiftdate,shiftsched) as
(
SELECT
--sysdate
CASE
WHEN TO_CHAR(TRUNC(sysdate,'MI'),'HH24:MI') BETWEEN '06:00' AND '23:59' THEN TRUNC(sysdate,'DD')
WHEN TO_CHAR(TRUNC(sysdate,'MI'),'HH24:MI') BETWEEN '00:00' AND '05:59' THEN TRUNC(sysdate -1,'DD')
END as "SHIFT DATE",
CASE
WHEN TO_CHAR(TRUNC(sysdate,'MI'),'HH24:MI') BETWEEN '06:00' AND '14:00' THEN 'MIDS'
WHEN TO_CHAR(TRUNC(sysdate,'MI'),'HH24:MI') BETWEEN '14:00' AND '22:00' THEN 'DAYS'
ELSE 'SWINGS'
END as "SHIFT SCHED"
FROM DUAL
)
SELECT shiftdate,shiftsched,COUNT(shiftsched)
FROM shift_sched
GROUP by shiftdate,shiftsched
Any help would be greatly appreciated
If I followed you correctly, you want days from 6 AM to 6 AM the next day. If so, you can substract 6 hours to tkt_created, truncate it to day, and group by that. The rest is just conditional aggregation.
select
trunc(tkt_created - 6/24) "DATE",
sum(case when extract(hour from tkt_created) between 6 and 13 then 1 end) days,
sum(case when extract(hour from tkt_created) between 14 and 21 then 1 end) mids,
sum(case
when extract(hour from tkt_created) between 22 and 23
or extract(hour from tkt_created) between 0 and 5
then 1 end
) swings,
count(*) total
from tktchistory
group by trunc(tkt_created - 6/24)
order by "DATE"
Note: I used standard SQL extract() instead of to_char(), which is Oracle specific; apart from being standard, another advantage is that it returns an integer rather than a string.
This can also be phrased as:
select
trunc(tkt_created - 6/24) "DATE",
sum(case when extract(hour from (tkt_created) between 6 and 13 then 1 end) days,
sum(case when extract(hour from tkt_created) between 14 and 21 then 1 end) mids,
sum(case when extract(hour from (tkt_created - 6/24)) between 16 and 23 then 1 end) swings,
count(*) total
from tktchistory
group by trunc(tkt_created - 6/24)
order by "DATE"

How to get one query about sales set on default date?

I have two tables, T_TEST and T_DEFAULT_DATE. T_TEST contains date and amount, and T_DEFAULT_DATE contains just P_DATE.
First table T_TEST:
DATE AMOUNT
-------- ----------
01.01.99 77
16.02.99 59
01.01.00 12
15.01.00 32
01.02.00 144
15.02.00 320
16.02.00 521
01.03.00 98
15.03.00 76
16.03.00 33
01.01.01 65
15.01.01 78
01.02.01 95
15.02.01 39
16.02.01 97
02.02.02 63
07.03.02 75
And second table T_DEFAULT_DATE:
P_DATE
--------
16.02.01
What I want to get is two queries established in a single query :
1. what is the amount of sale achieved on the same day last year (- 12 mounths)
2. amount of sales for whole past year (based on table T_DEFAULT_DATE)
3. the amount (sum) for whole mounth (default mounth : 1.2. 2001 - 28.2.2001)
Expected output is :
P_SDLY P_LY P_MS
-----------
521 1236 231
I tryed with add_months(t_default_date.p_date, -12) , but I didn't get expected result. Please help
You can try something like this, assuming that your fields are stored in date columns.
SQL> with t_test(date_, amount) as
2 (
3 select to_date('01.01.99', 'dd.mm.rr'), 77 from dual union all
4 select to_date('16.02.99', 'dd.mm.rr'), 59 from dual union all
5 select to_date('01.01.00', 'dd.mm.rr'), 12 from dual union all
6 select to_date('15.01.00', 'dd.mm.rr'), 32 from dual union all
7 select to_date('01.02.00', 'dd.mm.rr'), 144 from dual union all
8 select to_date('15.02.00', 'dd.mm.rr'), 320 from dual union all
9 select to_date('16.02.00', 'dd.mm.rr'), 521 from dual union all
10 select to_date('01.03.00', 'dd.mm.rr'), 98 from dual union all
11 select to_date('15.03.00', 'dd.mm.rr'), 76 from dual union all
12 select to_date('16.03.00', 'dd.mm.rr'), 33 from dual union all
13 select to_date('01.01.01', 'dd.mm.rr'), 65 from dual union all
14 select to_date('15.01.01', 'dd.mm.rr'), 78 from dual union all
15 select to_date('01.02.01', 'dd.mm.rr'), 95 from dual union all
16 select to_date('15.02.01', 'dd.mm.rr'), 39 from dual union all
17 select to_date('16.02.01', 'dd.mm.rr'), 97 from dual union all
18 select to_date('02.02.02', 'dd.mm.rr'), 63 from dual union all
19 select to_date('07.03.02', 'dd.mm.rr'), 75 from dual
20 ),
21 t_default_date(p_date) as
22 (
23 select to_date('16.02.01', 'dd.mm.rr') from dual
24 )
25 select sum(
26 case
27 when date_ between add_months(trunc(p_date, 'yyyy'), -12)
28 and trunc(p_date, 'yyyy')-1
29 then amount
30 else 0
31 end
32 ) as year,
33 sum( decode (date_, add_months(p_date, -12), amount, 0) ) as day,
34 sum( case
35 when date_ between
36 trunc(p_date, 'MM') and
37 last_day(p_date)
38 then amount
39 else
40 0
41 end
42 ) as month
43 from t_test
44 inner join t_default_date on (date_ between add_months(trunc(p_date, 'yyyy'), -12) and last_day(p_date) );
YEAR DAY MONTH
---------- ---------- ----------
1236 521 231
SQL>
This makes use of add_months to get exactly "one year ago"; if you need "365 days ago" (think of leap years), consider using something like date - 365

How to select data from a single column in Oracle and display results in multiple columns?

my table;
Date | Cost
01.01.2010 | 100
02.01.2010 | 200
03.01.2010 | 300
04.01.2010 | 400
10.01.2010 | 800
11.01.2010 | 800
12.01.2010 | 800
25.01.2010 | 500
26.01.2010 | 500
05.02.2010 | 600
13.02.2010 | 700
15.02.2010 | 700
ı want to make "date between '01.01.2010' and '28.02.2010' " weekly view
Week 1 | Week 2 | week 3 | week . .. .
1000 | 2400 | 0 | 32432.... . .
How to make pls help thank you ?
SQL> create table mytable (the_date,cost)
2 as
3 select date '2010-01-01', 100 from dual union all
4 select date '2010-01-02', 200 from dual union all
5 select date '2010-01-03', 300 from dual union all
6 select date '2010-01-04', 400 from dual union all
7 select date '2010-01-10', 800 from dual union all
8 select date '2010-01-11', 800 from dual union all
9 select date '2010-01-12', 800 from dual union all
10 select date '2010-01-25', 500 from dual union all
11 select date '2010-01-26', 500 from dual union all
12 select date '2010-02-05', 600 from dual union all
13 select date '2010-02-13', 700 from dual union all
14 select date '2010-02-15', 700 from dual
15 /
Table created.
This query uses MAX-DECODE as a standard pivot technique. If you are on version 11, you can also use the PIVOT operator. The below version will work on any version.
SQL> select nvl(max(decode(the_week,'01',cost)),0) "Week 1"
2 , nvl(max(decode(the_week,'02',cost)),0) "Week 2"
3 , nvl(max(decode(the_week,'03',cost)),0) "Week 3"
4 , nvl(max(decode(the_week,'04',cost)),0) "Week 4"
5 , nvl(max(decode(the_week,'05',cost)),0) "Week 5"
6 , nvl(max(decode(the_week,'06',cost)),0) "Week 6"
7 , nvl(max(decode(the_week,'07',cost)),0) "Week 7"
8 , nvl(max(decode(the_week,'08',cost)),0) "Week 8"
9 , nvl(max(decode(the_week,'09',cost)),0) "Week 9"
10 from ( select to_char(the_date,'ww') the_week
11 , sum(cost) cost
12 from mytable
13 where the_date between date '2010-01-01' and date '2010-02-28'
14 group by to_char(the_date,'ww')
15 )
16 /
Week 1 Week 2 Week 3 Week 4 Week 5 Week 6 Week 7 Week 8 Week 9
---------- ---------- ---------- ---------- ---------- ---------- ---------- ---------- ----------
1000 2400 0 1000 0 600 1400 0 0
1 row selected.
Regards,
Rob.
select to_char(date, 'ww'), sum(cost)
from table
group by to_char(date, 'ww');
Or something along those lines should bring sums by week with the week number in the result. Link to Oracle 11g to_char syntax and link to format values. If that doesn't do it and you don't need the week number trunc(date, 'DAY') might be what you're looking for.
Not elegant solution, but its works...
SELECT SUM(Week1) Week1, SUM(Week2) Week2 ... SUM(Week36) Week36,
SUM(Week36) Week37
FROM (SELECT DECODE(WeekNo, 1, Cost, 0) Week1,
DECODE(WeekNo, 2, Cost, 0) Week2,
...
DECODE(WeekNo, 36, Cost, 0) Week36,
DECODE(WeekNo, 37, Cost, 0) Week37
FROM (SELECT to_char(DateFrom, 'IW') WeekNo, SUM(cost) Cost
FROM (SELECT trunc(SYSDATE) + LEVEL - 1 DateFrom,
LEVEL * 100 Cost
FROM dual
CONNECT BY LEVEL < 40)
GROUP BY to_char(DateFrom, 'IW')))

Resources