sql query to get the salary from past 3 years - sql

I have a table salary that has salary details for an employee for various years like:
person_number date_from date_to salary RN
---------------------------------------------------------------------------
272 03-Mar-2022 31-dec-4712 109000 1
272 05-Mar-2021 02-Mar-2022 100000 1
272 10-Mar-2020 04-Mar-2020 100000 1
10 10-Mar-2019 31-dec-4712 4678 1
I want to get the latest salary for past 2 years along with current year. I created the below query for the same -
SELECT *
FROM
(SELECT
person_number
sal.salary_amount,
ROW_NUMBER() OVER (PARTITION BY person_number
ORDER BY date_to DESC) rn,
sal.date_from
FROM
cmp_salary sal
WHERE
1 = 1
AND TO_CHAR(sal.date_from, 'YYYY') = (SELECT TO_CHAR(Add_months(SYSDATE, -12), 'yyyy')
FROM dual)
)
WHERE
rn = 1
Like this one above I have created a clause for all 3 years (just replaced the 12 with 24,36).
These separate query is returning the correct data for 2 years back i.e till 2020 but the only case it is not working in when the same salary exists in 2 years.
Eg- For person #10, the salary is the same from 2019, 2021 and 2022.
Because I am using the year comparison in my above query, it will not return an output for 2020, 2021 and 2022, because the date_From is in 2019. Ideally it should give me the same salary for 2020, 2021 and 2022.
How to tweak this? I have to create 3 separate queries for this.

I don't think you need 3 separate queries; one would suffice. Also, according to data you posted, there's only one row per year per person so it is a "fixed" 3 years back. Therefore, I'd think of something like this instead:
Sample data:
SQL> WITH
2 cmp_salary (person_number,
3 date_from,
4 date_to,
5 salary_amount)
6 AS
7 (SELECT 272, DATE '2022-03-03', DATE '4712-12-31', 109000 FROM DUAL
8 UNION ALL
9 SELECT 272, DATE '2021-03-05', DATE '2022-03-02', 100000 FROM DUAL
10 UNION ALL
11 SELECT 272, DATE '2020-03-10', DATE '2020-03-04', 100000 FROM DUAL
12 UNION ALL
13 SELECT 10, DATE '2019-03-10', DATE '4712-12-31', 4678 FROM DUAL)
Query you might be interested in begins here:
14 SELECT person_number, sal.salary_amount, sal.date_from
15 FROM cmp_salary sal
16 CROSS JOIN
17 TABLE (CAST (MULTISET ( SELECT LEVEL
18 FROM DUAL
19 CONNECT BY LEVEL <= 3) AS SYS.odcinumberlist))
20 WHERE EXTRACT (YEAR FROM sal.date_from) =
21 EXTRACT (YEAR FROM SYSDATE) - COLUMN_VALUE + 1
22 ORDER BY person_number, date_from DESC;
PERSON_NUMBER SALARY_AMOUNT DATE_FROM
------------- ------------- ----------
272 109000 03.03.2022
272 100000 05.03.2021
272 100000 10.03.2020
SQL>

Related

Grouping by Date inclusivity

Here is the data I'm working with here
Accountid
Month
123
08/01/2021
123
09/01/2021
123
03/01/2022
123
04/01/2022
123
05/01/2022
123
06/01/2022
I'm trying to insert into a new table where the data is like this
Accountid
Start Month
End Month
123
08/01/2021
09/01/2021
123
03/01/2022
06/01/2022
I'm not sure how to separate them with the gap, and group by the account id in this case.
Thanks in advance
In 12c+ you may also use match_recognize for gaps-and-islands problems to define grouping rules (islands) in a more readable and natural way.
select *
from input_
match_recognize(
partition by accountid
order by month asc
measures
first(month) as start_month,
last(month) as end_month
/*Any month followed by any number of subsequent month */
pattern(any_ next*)
define
/*Next is the month right after the previous one*/
next as months_between(month, prev(month)) = 1
)
ACCOUNTID
START_MONTH
END_MONTH
123
2021-08-01
2021-09-01
123
2022-03-01
2022-06-01
db<>fiddle here
That's a gaps and islands problem; one option to do it is:
Sample data:
SQL> with test (accountid, month) as
2 (select 123, date '2021-01-08' from dual union all
3 select 123, date '2021-01-09' from dual union all
4 select 123, date '2021-01-03' from dual union all
5 select 123, date '2021-01-04' from dual union all
6 select 123, date '2021-01-05' from dual union all
7 select 123, date '2021-01-06' from dual
8 ),
Query begins here:
9 temp as
10 (select accountid, month,
11 to_char(month, 'J') - row_number() Over
12 (partition by accountid order by month) diff
13 from test
14 )
15 select accountid,
16 min(month) as start_month,
17 max(month) as end_Month
18 from temp
19 group by accountid, diff
20 order by accountid, start_month;
ACCOUNTID START_MONT END_MONTH
---------- ---------- ----------
123 03/01/2021 06/01/2021
123 08/01/2021 09/01/2021
SQL>
Although related to MS SQL Server, have a look at Introduction to Gaps and Islands Analysis; should be interesting reading for you, I presume.

How to count number of records for each week, from last month activity on a table?

I'm working with Oracle and I have a table with a column of type TIMESTAMP. I was wondering how can I extract the records from last 4 weeks of activity on the database, partitioned by week.
Following rows are inserted on week 1
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
Following rows are inserted on week2
cv 1 12-10-2021
ck 5 14-10-2021
Following rows are inserted on week3
vv 7 19-10-2021
Following rows are inserted on week4
vx 7 29-10-2021
Table now has
SQL>select * from tab;
NAME VALUE TIMESTAMP
-------------------- ----------
kc 2 04-10-2021
vc 3 06-10-2021
vk 4 07-10-2021
cv 1 12-10-2021
ck 5 14-10-2021
vv 7 19-10-2021
vx 7 29-10-2021
I would like a query which would give me the number of rows added each week, in the last 4 weeks.
This is what I would like to see
numofrows week
--------- -----
3 1
2 2
1 3
1 4
One option is to use to_char function and its iw parameter:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 )
10 select to_char(datum, 'iw') week,
11 count(*)
12 from test
13 where datum >= add_months(sysdate, -1) --> the last month
14 group by to_char(datum, 'iw');
WE COUNT(*)
-- ----------
42 1
43 1
40 3
41 2
SQL>
Line #13: I intentionally used "one month" instead of "4 weeks" as I thought (maybe wrongly) that you, actually, want that (you know, "a month has 4 weeks" - not exactly, but close, sometimes not close enough).
If you want 4 weeks, what is that, then? Sysdate minus 28 days (as every week has 7 days)? Then you'd modify line #13 to
where datum >= trunc(sysdate - 4*7)
Or, maybe it is really the last 4 weeks:
SQL> with test (name, datum) as
2 (select 'kc', date '2021-10-04' from dual union all
3 select 'vc', date '2021-10-06' from dual union all
4 select 'vk', date '2021-10-07' from dual union all
5 select 'cv', date '2021-10-12' from dual union all
6 select 'ck', date '2021-10-14' from dual union all
7 select 'vv', date '2021-10-19' from dual union all
8 select 'vx', DATE '2021-10-29' from dual
9 ),
10 temp as
11 (select to_char(datum, 'iw') week,
12 count(*) cnt,
13 row_number() over (order by to_char(datum, 'iw') desc) rn
14 from test
15 group by to_char(datum, 'iw')
16 )
17 select week, cnt
18 from temp
19 where rn <= 4
20 order by week;
WE CNT
-- ----------
40 3
41 2
42 1
43 1
SQL>
Now you have several options, see which one fits the best (if any).
I "simulated" missing data (see TEST CTE), created a calendar (calend) and ... did the job. Read comments within code:
SQL> with test (name, datum) as
2 -- sample data
3 (select 'vv', date '2021-10-19' from dual union all
4 select 'vx', DATE '2021-10-29' from dual
5 ),
6 calend as
7 -- the last 31 days; 4 weeks are included, obviously
8 (select max_datum - level + 1 datum
9 from (select max(a.datum) max_datum from test a)
10 connect by level <= 31
11 ),
12 joined as
13 -- joined TEST and CALEND data
14 (select to_char(c.datum, 'iw') week,
15 t.name
16 from calend c left join test t on t.datum = c.datum
17 ),
18 last4 as
19 -- last 4 weeks
20 (select week, count(name) cnt,
21 row_number() over (order by week desc) rn
22 from joined
23 group by week
24 )
25 select week, cnt
26 from last4
27 where rn <= 4
28 order by week;
WE CNT
-- ----------
40 0
41 0
42 1
43 1
SQL>

Extracting date field

I need to pull the month field from the first table and I would like the results to look like the second table.
I know to use EXTRACT(MONTH FROM BEGIN_DATE) to get the month but I'm unsure how to show all of the date fields. Any ideas? Using Oracle SQL.
Here is one way to do this. Note that I changed the column name from Month to mth ("month" is an Oracle keyword, it shouldn't be used as a table or column name). I also show the full month description (that is, including the year), so that there is no confusion when you have the same month (September for example) for the same person_id more than once. You can change that to show just the month by adjusting the format model in TO_CHAR, but that doesn't make much sense to me.
The test data is in the WITH clause which is NOT part of the SQL query; remove it, and use the actual table name in the SELECT query (instead of the made-up name I created in the WITH clause).
with
inputs ( person_id, begin_date, end_date ) as (
select 123, date '2017-11-01', date '2199-12-31' from dual union all
select 654, date '2017-09-01', date '2017-10-31' from dual union all
select 789, date '2017-12-01', date '2199-12-31' from dual
)
select person_id,
to_char(add_months(trunc(begin_date, 'mm'), level - 1), 'mm-yyyy') as mth,
case when add_months(trunc(begin_date, 'mm'), level - 1) <= end_date
then 'Y' else 'N' end as yn
from inputs
connect by add_months(trunc(begin_date, 'mm'), level - 1) <= sysdate
and prior person_id = person_id
and prior sys_guid() is not null
order by person_id, level
;
PERSON_ID MTH YN
---------- ------- --
123 11-2017 Y
123 12-2017 Y
123 01-2018 Y
654 09-2017 Y
654 10-2017 Y
654 11-2017 N
654 12-2017 N
654 01-2018 N
789 12-2017 Y
789 01-2018 Y
10 rows selected
One more thing - in the sample data, your begin_date is always the beginning of a month. If this is guaranteed, then you could simplify the code - there would be no need for trunc( ..., 'mm') - but I would still not change it; you don't know if this special property of the column will continue into the future, and if it ever changes, you will be glad the code addresses it already.
I think I finally understood what you want; it is SYSDATE you're running the query on. Based on it, you want to get all months that are between BEGIN_DATE and SYSDATE, for each PERSON_ID.
If that's so, here you go:
SQL> with test (person_id, begin_date) as
2 (select 123, date '2017-11-01' from dual union
3 select 654, date '2017-09-01' from dual
4 )
5 select person_id,
6 to_char(add_months(trunc(begin_date, 'mm'), column_value - 1), 'mm') mon
7 from test,
8 table(cast(multiset
9 (select level lvl from dual
10 connect by level <= round(months_Between (sysdate, begin_date)) + 1)
11 as sys.odcinumberlist))
12 order by 1, 2;
PERSON_ID MO
---------- --
123 01
123 11
123 12
654 01
654 09
654 10
654 11
654 12
8 rows selected.
SQL>
A variation on mathguy's answer:
SELECT
person_id
, TO_CHAR(ADD_MONTHS(begin_date, LEVEL-1), 'mm') m
, TO_CHAR(ADD_MONTHS(begin_date, LEVEL-1), 'yyyy') y
, CASE WHEN ADD_MONTHS(begin_date, LEVEL-1) BETWEEN begin_date and end_date THEN 'Y' ELSE 'N' END AS yn
FROM
person_table
CONNECT BY
LEVEL <= MONTHS_BETWEEN(TRUNC(SYSDATE, 'MON'), TRUNC(begin_date, 'MON')) + 1
AND prior person_id = person_id
AND prior sys_guid() IS NOT NULL
ORDER BY
person_id, y, m;
SQLFiddle for reference.

sql oracle ignore holidays

I am using this code to calculate the difference between two dates ignoring weekends:
SELECT To_date(SYSDATE) -
To_date('01.07.2014', 'DD.MM.YYYY')
- 2 * ( TRUNC(Next_day(To_date(SYSDATE) - 1, 'FRI'))
- TRUNC( Next_day(To_date('01.07.2014' , 'DD.MM.YYYY')
- 1, 'FRI')) ) / 7 AS DAYS_BETWEEN
FROM dual
I have another table called table1 in which the column "date" exists (its type is "DATE") in which all dates where a holiday is are written down.
Example table 1:
DATES
12.06.2011
19.06.2014
09.05.2013
...
I am trying to make my code check this table and that if one date is between the two dates above it makes -1 day in the output.
It should be easy if you divide it into following tasks:
Generate all the dates between the two given dates using Row Generator method as shown here.
Ignore the dates which are weekend, i.e. Saturdays and Sundays
Check whether the dates in the range are having any match in the holiday table.
The following row generator query will give you the total count of weekdays, i.e. not including Saturdays and Sundays:
SQL> WITH dates AS
2 (SELECT to_date('01/01/2014', 'DD/MM/YYYY') date1,
3 to_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual
5 )
6 SELECT SUM(weekday) weekday_count
7 FROM
8 (SELECT
9 CASE
10 WHEN TO_CHAR(date1+LEVEL-1, 'DY','NLS_DATE_LANGUAGE=AMERICAN')
11 NOT IN ('SAT', 'SUN')
12 THEN 1
13 ELSE 0
14 END weekday
15 FROM dates
16 CONNECT BY LEVEL <= date2-date1+1
17 )
18 /
WEEKDAY_COUNT
-------------
261
SQL>
Now, based on above row generator query, let's see a test case.
The following query will calculate the count of working days between 1st Jan 2014 and 31st Dec 2014 excluding the holidays as mentioned in the table.
The WITH clause is only to use it as tables, in your case you can simply use your holiday table.
SQL> WITH dates
2 AS (SELECT To_date('01/01/2014', 'DD/MM/YYYY') date1,
3 To_date('31/12/2014', 'DD/MM/YYYY') date2
4 FROM dual),
5 holidays
6 AS (SELECT To_date('12.06.2011', 'DD.MM.YYYY') holiday FROM dual UNION ALL
7 SELECT To_date('19.06.2014', 'DD.MM.YYYY') holiday FROM dual UNION ALL
8 SELECT To_date('09.05.2013', 'DD.MM.YYYY') holiday FROM dual),
9 count_of_weekdays
10 AS (SELECT SUM(weekday) weekday_count
11 FROM (SELECT CASE
12 WHEN To_char(date1 + LEVEL - 1, 'DY',
13 'NLS_DATE_LANGUAGE=AMERICAN')
14 NOT IN (
15 'SAT',
16 'SUN' ) THEN 1
17 ELSE 0
18 END weekday
19 FROM dates
20 CONNECT BY LEVEL <= date2 - date1 + 1)),
21 count_of_holidays
22 AS (SELECT Count(*) holiday_count
23 FROM holidays
24 WHERE holiday NOT BETWEEN To_date('01/01/2015', 'DD/MM/YYYY') AND
25 To_date('31/03/2015', 'DD/MM/YYYY'))
26 SELECT weekday_count - holiday_count as working_day_count
27 FROM count_of_weekdays,
28 count_of_holidays
29 /
WORKING_DAY_COUNT
-----------------
258
SQL>
There were total 261 weekdays, out of which there were 3 holidays in holiday table. So, total count of working days in the output is 261 - 3 = 258.
SELECT To_date(sysdate)- To_date('01.07.2014','DD.MM.YYYY')
- (2 * (to_char(To_date(sysdate), 'WW') - to_char(To_date('01.07.2014','DD.MM.YYYY'), 'WW'))) AS DAYS_BETWEEN
FROM dual

oracle count based on month

I am attempting to write Oracle SQL.
I am looking for solution something similar. Please find below data I have
start_date end_date customer
01-01-2012 31-06-2012 a
01-01-2012 31-01-2012 b
01-02-2012 31-03-2012 c
I want the count of customer in that date period. My result should look like below
Month : Customer Count
JAN-12 : 2
FEB-12 : 2
MAR-12 : 2
APR-12 : 1
MAY-12 : 1
JUN-12 : 1
One option would be to generate the months separately in another query and join that to your data table (note that I'm assuming that you intended customer A to have an end-date of June 30, 2012 since there is no June 31).
SQL> ed
Wrote file afiedt.buf
1 with mnths as(
2 select add_months( date '2012-01-01', level - 1 ) mnth
3 from dual
4 connect by level <= 6 ),
5 data as (
6 select date '2012-01-01' start_date, date '2012-06-30' end_date, 'a' customer from dual union all
7 select date '2012-01-01', date '2012-01-31', 'b' from dual union all
8 select date '2012-02-01', date '2012-03-31', 'c' from dual
9 )
10 select mnths.mnth, count(*)
11 from data,
12 mnths
13 where mnths.mnth between data.start_date and data.end_date
14 group by mnths.mnth
15* order by mnths.mnth
SQL> /
MNTH COUNT(*)
--------- ----------
01-JAN-12 2
01-FEB-12 2
01-MAR-12 2
01-APR-12 1
01-MAY-12 1
01-JUN-12 1
6 rows selected.
WITH TMP(monthyear,start_date,end_date,customer) AS (
select LAST_DAY(start_date),
CAST(ADD_MONTHS(start_date, 1) AS DATE),
end_date,
customer
from data
union all
select LAST_DAY(start_date),
CAST(ADD_MONTHS(start_date, 1) AS DATE),
end_date,
customer
from TMP
where LAST_DAY(end_date) >= LAST_DAY(start_date)
)
SELECT TO_CHAR(MonthYear, 'MON-YY') TheMonth,
Count(Customer) Customers
FROM TMP
GROUP BY MonthYear
ORDER BY MonthYear;
SQLFiddle