Extracting date field - sql

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.

Related

sql query to get the salary from past 3 years

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>

How to get first date and last date of all twelve months for given year in Oracle PLSQL?

If i give input year like '2021' i need result as below
Month Start Date End Date
1 1/1/2021 31/01/2021
2 1/2/2021 28/01/2021
.
.
.
.
.
.
.
.
.
12 1/12/2021 31/12/2021
Basically, it is about the row generator technique; there are plenty of them, pick any you want. (Have a look at OraFAQ).
For example:
SQL> with mon as
2 (select add_months(trunc(to_date(&par_year, 'yyyy'), 'yyyy'), level - 1) val
3 from dual
4 connect by level <= 12
5 )
6 select to_char(val, 'mm') mon,
7 val start_date,
8 last_day(val) end_date
9 from mon
10 order by 1;
Enter value for par_year: 2021
MO START_DATE END_DATE
-- ---------- ----------
01 01/01/2021 31/01/2021
02 01/02/2021 28/02/2021
03 01/03/2021 31/03/2021
04 01/04/2021 30/04/2021
05 01/05/2021 31/05/2021
06 01/06/2021 30/06/2021
07 01/07/2021 31/07/2021
08 01/08/2021 31/08/2021
09 01/09/2021 30/09/2021
10 01/10/2021 31/10/2021
11 01/11/2021 30/11/2021
12 01/12/2021 31/12/2021
12 rows selected.
SQL>
You could also use directly the model clause for that purpose.
SELECT n
, TO_DATE(&the_year||lpad(f, 2, '0'), 'YYYYMM') start_dt
, last_day(TO_DATE(&the_year||lpad(f, 2, '0'), 'YYYYMM')) end_dt
FROM DUAL
MODEL
DIMENSION by (1 as n)
MEASURES (1 as f)
RULES (
f[FOR n FROM 1 TO 12 INCREMENT 1 ] = cv(n)
)
;
The advantage of the model clause is if you later want to get the every other month, you just need to change the increment from 1 to 2. Or if you are looking for the quarter months of the year (January, April, Jully, October), you just need to change increment from 1 to 3, and so on...
Just replace 2021 in the query for your year
with months (m) as (
select 1 from dual union all
select m + 1 from months where m < 12
)
select
to_date('2021' || '-' || to_char(m) || '-01', 'YYYY-MM-DD') as first_day,
last_day(to_date('2021' || '-' || to_char(m) || '-01', 'YYYY-MM-DD')) as last_day
from months
You can try on this db<>fiddle
Try below query
WITH cte_date as(
SELECT
LEVEL Month_No,
to_date(to_char(LEVEL||'-2021'),'MM-YYYY') Start_Date FROM dual
CONNECT BY LEVEL <=12
)
SELECT Month_No, Start_Date, LAST_DAY(Start_Date) End_Date
FROM cte_date;
I'm a fan of recursive CTEs because they are part of Standard SQL. I would phrase this as:
with months (month, startdate) as (
select 1 as month, date '2021-01-01'
from dual
union all
select month + 1, add_months(startdate, 1)
from months
where month < 12
)
select month, startdate, last_day(startdate) as enddate
from months;
If you need an input year, there are different ways to accomplish it. But a simple way is to change the second line to:
select 1 as month, to_date(:year || '0101', 'YYYYMMDD')

Calculate calendar days by month for date ranges

I have a table with date ranges:
CREATE TABLE REQUEST (
REQUEST_ID NUMBER(*,0) NOT NULL ENABLE,
EMPLOYEE_ID NUMBER(*,0) NOT NULL ENABLE,
START_DATE DATE NOT NULL ENABLE,
END_DATE DATE NOT NULL ENABLE,
CONSTRAINT REQUEST_PK PRIMARY KEY (REQUEST_ID)
);
The're also a couple of constraints (omitted for brevity) that ensure they're valid (end date cannot be less then start date) and force time to be 00:00:00. (Runnable fiddle with sample data).
Is there a way to split my data set by year/month into a result set based on the date ranges? The rules are:
A request splits in as many rows as months its range covers.
Each month has a column stating how many calendar days the range has in such month.
For example, a request with [2020-12-28, 2021-02-10] would produce three rows:
request_id year month days
========== ==== ===== ====
1 2020 12 4
1 2021 1 31
1 2021 2 10
I've been playing with CONNECT BY but I've been unable to adapt it to my use case. Is that the right tool?
A more direct way to do the computation is to use connect by to generate just the needed months (not every day in every interval) - and then to do the day computation directly, rather than by counting. Something like this:
Adding data for testing:
insert into request (request_id, employee_id, start_date, end_date)
select 1, 1001, date '2020-12-28', date '2021-02-10' from dual union all
select 2, 4002, date '2021-02-10', date '2021-02-20' from dual union all
select 3, 6004, date '2020-12-15', date '2021-03-31' from dual
;
commit;
Query and output:
with
prep (request_id, start_date, end_date, mth) as (
select request_id, start_date, end_date,
add_months(trunc(start_date, 'mm'), level - 1)
from request
connect by level <= months_between(trunc(end_date, 'mm'),
trunc(start_date, 'mm')) + 1
and prior request_id = request_id
and prior sys_guid() is not null
)
select request_id, extract(year from mth) as year_,
extract(month from mth) as month_,
least(last_day(mth), end_date) - greatest(mth, start_date) + 1 as days
from prep
order by request_id, mth -- if needed
;
REQUEST_ID YEAR_ MONTH_ DAYS
---------- ---------- ---------- ----------
1 2020 12 4
1 2021 1 31
1 2021 2 10
2 2021 2 11
3 2020 12 17
3 2021 1 31
3 2021 2 28
3 2021 3 31
For example this one:
WITH t AS (
SELECT DATE '2020-12-28' +LEVEL-1 AS ts
FROM dual
CONNECT BY DATE '2020-12-28' +LEVEL-1 <= DATE '2021-02-10')
SELECT
EXTRACT(YEAR FROM TRUNC(ts, 'Month')) AS YEAR,
EXTRACT(MONTH FROM TRUNC(ts, 'Month')) AS MONTH,
COUNT(ts) AS DAYS
FROM t
GROUP BY TRUNC(ts, 'Month')
ORDER BY YEAR, MONTH;

Filtering out data based effective and Term Data

I have query contains both start and end date, i would like to filter out data based on two dates. I need only 2019 and higher data either based on start or end date, if you can have a look examples. i need ID 1,2,3,6,7 and 4,5 is not required. We can do based on extract year for both start and end date. but looking for better approach Thanks!
CREATE TABLE TEMP
(
ID INT,
SDate DATE,
EDate DATE
)
INSERT INTO TEMP
SELECT 1,'01/01/2014', '01/01/2019' FROM DUAL
UNION ALL
SELECT 2,'01/01/2015', '01/01/2020' FROM DUAL
UNION ALL
SELECT 3,'01/01/2019', '12/31/2019' FROM DUAL
UNION ALL
SELECT 4,'01/01/2012', '12/31/2018' FROM DUAL
UNION ALL
SELECT 5,'01/01/2010', '10/01/2016' FROM DUAL
UNION ALL
SELECT 6,'06/01/2020', '10/01/2020' FROM DUAL
UNION ALL
SELECT 7,'01/01/2021', '03/01/2021' FROM DUAL
Something like this? Sounds like what you said, but - you didn't post how you do it now. If that's not "it", what "better" approach do you need and why? I mean, what's wrong with this?
SQL> select *
2 from temp
3 where extract(year from sdate) >= 2019
4 or extract(year from edate) >= 2019;
ID SDATE EDATE
---------- ---------- ----------
1 01/01/2014 01/01/2019
2 01/01/2015 01/01/2020
3 01/01/2019 12/31/2019
6 06/01/2020 10/01/2020
7 01/01/2021 03/01/2021
SQL>
You can simply use the condition on edate as follows:
select * from emp
where edate >= date '2019-01-01';
This will use the index on edate, if any.

Count if date in date column is between start and end date [ Oracle SQL ]

This is my first post, so I hope I've posted this one correctly.
My problem:
I want to count the number of active customers per day, the last 30 days.
What I have so far:
In the first column I want to print today, and the last 29 days. This I have done with
select distinct trunc(sysdate-dayincrement, 'DD') AS DATES
from (
select level as dayincrement
from dual
connect by level <= 30
)
I've picked it up here at stackoverflow, and it works perfectly. I can even extend the number of days returned to ex. 365 days. Perfect!
I also have a table that looks like this
|Cust# | Start date | End date |
| 1000 | 01.01.2015 | 31.12.2015|
| 1001 | 02.01.2015 | 31.12.2016|
| 1002 | 02.01.2015 | 31.03.2015|
| 1003 | 03.01.2015 | 31.08.2015|
This is where I feel the problem starts
I would like to get this result:
| Dates | # of cust |
|04.01.2015| 4 |
|03.01.2015| 4 |
|02.01.2015| 3 |
|01.01.2015| 1 |
Here the query would count 1 if:
Start date <= DATES
End date >= DATES
Else count 0.
I just don't know how to structure the query.
I tried this, but it didn't work.
count(
IF ENDDATE <= DATES THEN
IF STARTDATE >= DATES THEN 1 ELSE 0 END IF
ELSE
0
END IF
) AS CUST
Any ideas?
The following produces the results you're looking for. I had change the date generator to start on 04-JAN-2015 instead of SYSDATE (which is, of course, in the year 2016), and to use LEVEL-1 to include 'current' day:
WITH CUSTS AS (SELECT 1000 AS CUST_NO, TO_DATE('01-JAN-2015', 'DD-MON-YYYY') AS START_DATE, TO_DATE('31-DEC-2015', 'DD-MON-YYYY') AS END_DATE FROM DUAL UNION ALL
SELECT 1001 AS CUST_NO, TO_DATE('02-JAN-2015', 'DD-MON-YYYY') AS START_DATE, TO_DATE('31-DEC-2016', 'DD-MON-YYYY') AS END_DATE FROM DUAL UNION ALL
SELECT 1002 AS CUST_NO, TO_DATE('02-JAN-2015', 'DD-MON-YYYY') AS START_DATE, TO_DATE('31-MAR-2015', 'DD-MON-YYYY') AS END_DATE FROM DUAL UNION ALL
SELECT 1003 AS CUST_NO, TO_DATE('03-JAN-2015', 'DD-MON-YYYY') AS START_DATE, TO_DATE('31-AUG-2015', 'DD-MON-YYYY') AS END_DATE FROM DUAL ),
DATES AS (SELECT DISTINCT TRUNC(TO_DATE('04-JAN-2015', 'DD-MON-YYYY') - DAYINCREMENT, 'DD') AS DT
FROM (SELECT LEVEL-1 AS DAYINCREMENT
FROM DUAL
CONNECT BY LEVEL <= 30))
SELECT d.DT, COUNT(*)
FROM CUSTS c
CROSS JOIN DATES d
WHERE d.DT BETWEEN c.START_DATE AND c.END_DATE
GROUP BY d.DT
ORDER BY DT DESC
Best of luck.
You could write a CASE expression equivalent to your IF-ELSE construct.
For example,
SQL> SELECT COUNT(
2 CASE
3 WHEN hiredate <= sysdate
4 THEN 1
5 ELSE 0
6 END ) AS CUST
7 FROM emp;
CUST
----------
14
SQL>
However, looking at your desired output, it seems, you just need to use COUNT and GROUP BY. The date conditions should be in the filter predicate.
For example,
SELECT dates, COUNT(*)
FROM table_name
WHERE dates BETWEEN start_date AND end_date
GROUP BY dates;