Converting week date into calendar date - oracle

I have column with dates in format:
year/number_of_week_in_year/number_of_day_of_the_week, for example:
2015015 = 01.01.2015
How to write query which convert this date to RRRRmmdd format?
I use Oracle 10g.

If you are using the ISO standard for weeks then you can do:
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE FUNCTION TO_ISO_WEEK_DATE(
year NUMBER,
week NUMBER,
dayofweek NUMBER DEFAULT 1
) RETURN DATE DETERMINISTIC
IS
BEGIN
RETURN NEXT_DAY(
TO_DATE( TO_CHAR( year, '0000' ) || '0104', 'YYYYMMDD' )
- INTERVAL '7' DAY, 'MONDAY'
)
+ ( week - 1 ) * 7
+ ( dayofweek - 1 );
EXCEPTION
WHEN OTHERS THEN
RETURN NULL;
END TO_ISO_WEEK_DATE;
/
Query 1:
WITH dates (value ) AS (
SELECT DATE '2015-01-01' + LEVEL - 1
FROM DUAL
CONNECT BY LEVEL < 366
),
data ( value, iso_week_day ) AS (
SELECT value,
TO_CHAR( value, 'IYYY/IW/' ) || TO_CHAR( value - TRUNC( value, 'IW' ) + 1 )
FROM dates
)
SELECT value,
iso_week_day,
TO_ISO_WEEK_DATE(
SUBSTR( iso_week_day, 1, 4 ),
SUBSTR( iso_week_day, 6, 2 ),
SUBSTR( iso_week_day, 9, 1 )
) AS CONVERTED_DATE
FROM data
Results:
| VALUE | ISO_WEEK_DAY | CONVERTED_DATE |
|-----------------------------|--------------|-----------------------------|
| January, 01 2015 00:00:00 | 2015/01/4 | January, 01 2015 00:00:00 |
| January, 02 2015 00:00:00 | 2015/01/5 | January, 02 2015 00:00:00 |
| January, 03 2015 00:00:00 | 2015/01/6 | January, 03 2015 00:00:00 |
| January, 04 2015 00:00:00 | 2015/01/7 | January, 04 2015 00:00:00 |
| January, 05 2015 00:00:00 | 2015/02/1 | January, 05 2015 00:00:00 |
...
| August, 23 2015 00:00:00 | 2015/34/7 | August, 23 2015 00:00:00 |
...
| December, 30 2015 00:00:00 | 2015/53/3 | December, 30 2015 00:00:00 |
| December, 31 2015 00:00:00 | 2015/53/4 | December, 31 2015 00:00:00 |

If I understand you right, this is what you want:
SELECT TO_CHAR(NEXT_DAY(
NEXT_DAY( to_date('01.01.'||SUBSTR('2015015',1,4),'dd.mm.yyyy')-7,1) +
(SUBSTR('2015015',5,2)-1)*7,TO_NUMBER(SUBSTR('2015015',7,1))),'rrrrmmdd')
FROM dual;

In case you're trying to reverse Oracle's yyyywwd format mask, the code below might be of use. However - it is not universal and depends on your NLS settings (names for days and which day is considered first day of week SUN/MON).
It is strange though, that to_date() does not accept 'ww' as a format.
select dt,
dt_ywd,
next_day(to_date(substr(dt_ywd,1,4)||'0101','yyyymmdd') +
7*(to_number(substr(dt_ywd,5,2))-1) -
1,
decode(substr(dt_ywd,7,1),'1','SUN',
'2','MON',
'3','TUE',
'4','WED',
'5','THU',
'6','FRI',
'7','SAT')) tst_revert
from(select dt,
to_char(dt,'yyyywwd') dt_ywd
from (select to_date('01/01/2015','dd/mm/yyyy')+level-1 dt
from dual
connect by level < 500))
This gives me the following output:
DT DT_YWD TST_REVERT
----------- ------- -----------
01/01/2015 2015015 01/01/2015
02/01/2015 2015016 02/01/2015
03/01/2015 2015017 03/01/2015
04/01/2015 2015011 04/01/2015
05/01/2015 2015012 05/01/2015
06/01/2015 2015013 06/01/2015
07/01/2015 2015014 07/01/2015
08/01/2015 2015025 08/01/2015
09/01/2015 2015026 09/01/2015
10/01/2015 2015027 10/01/2015

Related

Oracle sql set end date based on previous start date

I have one table where I need to add new column endDate for future implementation but since we have currently only start date for all records I need to set endDate which should be equal to start date from previous record that are connected by userId and if it is only one record for that user than end date will have some value in future.
For example:
Table structure:
ID | USER_ID | START_DATE | END_DATE
-------------------------------------
1 | 1 | 01.01.2015 |
2 | 1 | 01.01.2016 |
3 | 1 | 01.07.2018 |
4 | 1 | 01.08.2021 |
5 | 2 | 01.01.2015 |
6 | 3 | 01.01.2016 |
7 | 3 | 01.07.2018 |
8 | 4 | 01.08.2021 |
Expected result should be like this
ID | USER_ID | START_DATE | END_DATE
-------------------------------------
1 | 1 | 01.01.2015 | 01.01.2016
2 | 1 | 01.01.2016 | 01.07.2018
3 | 1 | 01.07.2018 | 01.08.2021
4 | 1 | 01.08.2021 | 01.01.2050
5 | 2 | 01.01.2015 | 01.01.2050
6 | 3 | 01.01.2016 | 01.07.2018
7 | 3 | 01.07.2018 | 01.01.2050
8 | 4 | 01.08.2021 | 01.01.2050
Can someone help me with how query in oracle databse should look to update it like this?
I've tried something with for loop but not sure how to continue from this step
DECLARE
CURSOR c_contract
IS
SELECT
USER_ID
FROM
CONTRACT
ORDER_BY START_DATE

BEGIN
FOR r_contract IN c_contract
LOOP

dbms_output.put_line( r_contract.USER_ID );
END LOOP;

END;
Use the LEAD analytic function with the default date as the third argument:
SELECT t.*,
LEAD( start_date, 1, DATE '2050-01-01') OVER (
PARTITION BY user_id
ORDER BY start_date
) AS end_date
FROM table_name t
Which, for the sample data:
ALTER SESSION SET NLS_DATE_FORMAT = 'YYYY-MM-DD HH24:MI:SS';
CREATE TABLE table_name ( ID, USER_ID, START_DATE ) AS
SELECT 1, 1, DATE '2015-01-01' FROM DUAL UNION ALL
SELECT 2, 1, DATE '2016-01-01' FROM DUAL UNION ALL
SELECT 3, 1, DATE '2018-07-01' FROM DUAL UNION ALL
SELECT 4, 1, DATE '2021-08-01' FROM DUAL UNION ALL
SELECT 5, 2, DATE '2015-01-01' FROM DUAL UNION ALL
SELECT 6, 3, DATE '2016-01-01' FROM DUAL UNION ALL
SELECT 7, 3, DATE '2018-07-01' FROM DUAL UNION ALL
SELECT 8, 4, DATE '2021-08-01' FROM DUAL;
Outputs:
ID
USER_ID
START_DATE
END_DATE
1
1
2015-01-01 00:00:00
2016-01-01 00:00:00
2
1
2016-01-01 00:00:00
2018-07-01 00:00:00
3
1
2018-07-01 00:00:00
2021-08-01 00:00:00
4
1
2021-08-01 00:00:00
2050-01-01 00:00:00
5
2
2015-01-01 00:00:00
2050-01-01 00:00:00
6
3
2016-01-01 00:00:00
2018-07-01 00:00:00
7
3
2018-07-01 00:00:00
2050-01-01 00:00:00
8
4
2021-08-01 00:00:00
2050-01-01 00:00:00
If you want to add a column then:
ALTER TABLE table_name ADD (end_date DATE);
MERGE INTO table_name dst
USING (
SELECT ROWID AS rid,
LEAD( start_date, 1, DATE '2050-01-01') OVER (
PARTITION BY user_id
ORDER BY start_date
) AS end_date
FROM table_name
) src
ON (dst.ROWID = src.rid)
WHEN MATCHED THEN
UPDATE SET end_date = src.end_date;
fiddle

Month End Date Range where most recent day = system date

I am trying to generate a range of 'last day of the month' dates from a given start date (01/01/2019) to the system date (sysdate).
I am able to generate the date range for this using the below query:
select last_day(add_months (trunc (to_date('01/01/2019','MM/DD/YYYY'), 'MM'), Level - 1))
Month FROM Dual
CONNECT BY Level <= MONTHS_BETWEEN(sysdate, to_date('01/01/2019','MM/DD/YYYY')) + 1
order by month
I am trying to get the last record to equal the sysdate so I can calculate running balances - how can I go about adding this to the above query?
Example output of the solution:
+------------------------+
| MONTH |
+------------------------+
| 1/31/2019 12:00:00 AM |
| 2/28/2019 12:00:00 AM |
| 3/31/2019 12:00:00 AM |
| 4/30/2019 12:00:00 AM |
| 5/31/2019 12:00:00 AM |
| 6/30/2019 12:00:00 AM |
| 7/31/2019 12:00:00 AM |
| 8/31/2019 12:00:00 AM |
| 9/30/2019 12:00:00 AM |
| 10/31/2019 12:00:00 AM |
| 11/30/2019 12:00:00 AM |
| 12/31/2019 12:00:00 AM |
| 1/31/2020 12:00:00 AM |
| 2/25/2020 12:00:00 AM |
+------------------------+
If I understood you correctly, you'd want today's date in the last line. If so, use CASE (lines #13 - 19).
Lines #1 - 12 represent your current query.
SQL> alter session set nls_date_format = 'dd.mm.yyyy hh24:mi:ss';
Session altered.
SQL> WITH your_data
2 AS ( SELECT LAST_DAY (
3 ADD_MONTHS (
4 TRUNC (TO_DATE ('01/01/2019', 'MM/DD/YYYY'), 'MM'),
5 LEVEL - 1))
6 Month
7 FROM DUAL
8 CONNECT BY LEVEL <=
9 MONTHS_BETWEEN (
10 SYSDATE,
11 TO_DATE ('01/01/2019', 'MM/DD/YYYY'))
12 + 1)
13 SELECT CASE
14 WHEN TRUNC (month, 'mm') = TRUNC (SYSDATE, 'mm')
15 THEN
16 TRUNC (SYSDATE)
17 ELSE
18 month
19 END
20 month
21 FROM your_data
22 ORDER BY month;
MONTH
-------------------
31.01.2019 00:00:00
28.02.2019 00:00:00
31.03.2019 00:00:00
30.04.2019 00:00:00
31.05.2019 00:00:00
30.06.2019 00:00:00
31.07.2019 00:00:00
31.08.2019 00:00:00
30.09.2019 00:00:00
31.10.2019 00:00:00
30.11.2019 00:00:00
31.12.2019 00:00:00
31.01.2020 00:00:00
18.02.2020 00:00:00
14 rows selected.
SQL>
When I first saw this I immediately thought a recursive CTE would be perfect. The problem I kept getting a type mismatch on the recursion that I could not resolve. It turned out Oracle 11g has a bug in recursive CTE with recurson with dates. This finally got me to updating at long last. So thanks for the question. Anyway for future viewers a recursive CTE does work.
alter session set nls_date_format = 'yyyy-mm-dd';
with date_list (dte) as
( select last_day(date '2019-01-01') from dual
union all
select least(add_months(dte,1), trunc(sysdate))
from date_list
where dte<trunc(sysdate)
)
select dte from date_list;

Oracle NUMBER to TIME

Im writing some ETL to get data out of a legacy Oracle db and into SQL Server db for Analysis services etc.
The legacy oracle database is storing several time columns as Number(4,2).
9 = 09:00
1.2 = 01:20
11.53 = 11:53
Are there any built in functions that will convert this to a Time data type? Has anyone come across this before? How did you solve it?
Thanks
Oracle does not have a TIME datatype - it has DATE or TIMESTAMP both of which have a date and a time component.
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE times ( time ) AS
SELECT 9 FROM DUAL
UNION ALL SELECT 1.2 FROM DUAL
UNION ALL SELECT 15.53 FROM DUAL
UNION ALL SELECT 24.62 FROM DUAL;
Query 1 - Get the correct time component for numbers which are valid times:
SELECT time,
TO_DATE( TO_CHAR( time, '00.00' ), 'HH24.MI' ) AS parsed_time
FROM times
WHERE REGEXP_LIKE( TO_CHAR( time, '00.00' ), '([01]\d|2[0-3]).[0-5]\d' )
Results:
| TIME | PARSED_TIME |
|-------|-----------------------------|
| 9 | September, 01 2015 09:00:00 |
| 1.2 | September, 01 2015 01:20:00 |
| 15.53 | September, 01 2015 15:53:00 |
Query 2 - Get the correct time component for numbers which are valid times with today's date:
SELECT time,
TO_DATE( TO_CHAR( SYSDATE, 'YYYY-MM-DD' ) || ' ' || TO_CHAR( time, '00.00' ), 'YYYY-MM-DD HH24.MI' ) AS parsed_time
FROM times
WHERE REGEXP_LIKE( TO_CHAR( time, '00.00' ), '([01]\d|2[0-3]).[0-5]\d' )
Results:
| TIME | PARSED_TIME |
|-------|-----------------------------|
| 9 | September, 28 2015 09:00:00 |
| 1.2 | September, 28 2015 01:20:00 |
| 15.53 | September, 28 2015 15:53:00 |
Query 3 - Get the correct time component for any number of hours (unit part) and minutes (decimal part):
SELECT time,
TRUNC( SYSDATE ) + TRUNC(time)/24 + (time - TRUNC(time)) * 100 / 60/ 24 AS parsed_time
FROM times
Results:
| TIME | PARSED_TIME |
|-------|-----------------------------|
| 9 | September, 28 2015 09:00:00 |
| 1.2 | September, 28 2015 01:20:00 |
| 15.53 | September, 28 2015 15:53:00 |
| 24.62 | September, 29 2015 01:02:00 |
EDIT2: Corrected the query as per MT0. This will also take care of scenario when you have : in between instead of .
with tbl as(
select 9 as tf from dual union all
select 1.2 from dual union all
select 11.53 as tf from dual
)
select TO_DATE( TO_CHAR( replace(to_char(tf),':','.'), '00.00' ), 'HH24.MI' ) AS time from tbl
EDIT1: It will only work for the values you gave. If you have something which is not a valid time, then it will fail saying
ORA-01850: hour must be between 0 and 23
or
ORA-01851: minutes must be between 0 and 59

List all days between two dates in Oracle

I am converting a postgres app to an Oracle app.
I came across this query:
WITH cost AS (SELECT
well_schedules.id,
generate_series(well_schedules.start_date::timestamp, well_schedules.end_date, '1 Day') AS "Date",
(well_schedules.drilling_engineering_estimate * well_schedules.well_estimated_working_interest)/((well_schedules.end_date - well_schedules.start_date) + 1) AS "Cost Per Day"
FROM
well_schedules
)
SELECT date_trunc('quarter', "Date"), COUNT("Cost Per Day"), id
FROM cost
GROUP BY id, date_trunc('quarter', "Date")
ORDER BY date_trunc('quarter', "Date")
The part I am struggling with is the generate_series line.
That line takes a start_date and end_date and lists all days between those two dates. We need that information to compile per day/week/month/quarter/year reports (or at least we assume we need that info).
Our data looks like this:
well_schedules
| id | start_date | end_date | cost |
| 1 | '2015-01-01' | '2015-03-20' | 100 |
We assume cost_per_day is equal across all days, so we'd like to generate a report that lets us look at cost_per_day, cost_per_week, cost_per_month, cost_per_year, and cost_per_quarter. cost_per_week/month/quarter/year is calculated by grouping the days by week/month/quarter/year and summing the associated cost_per_days
SQL Fiddle
Oracle 11g R2 Schema Setup:
CREATE TABLE well_schedules ( id, start_date, end_date, cost ) AS
SELECT 1 , DATE '2015-01-01', DATE '2015-01-20', 100 FROM DUAL;
Query 1:
SELECT ID,
COLUMN_VALUE AS Day,
COST / ( end_date - start_date + 1 ) AS Cost_per_day
FROM well_schedules,
TABLE (
CAST(
MULTISET(
SELECT start_date + LEVEL - 1
FROM DUAL
CONNECT BY start_date + LEVEL - 1 <= end_date
)
AS SYS.ODCIDATELIST
)
)
Results:
| ID | DAY | COST_PER_DAY |
|----|---------------------------|--------------|
| 1 | January, 01 2015 00:00:00 | 5 |
| 1 | January, 02 2015 00:00:00 | 5 |
| 1 | January, 03 2015 00:00:00 | 5 |
| 1 | January, 04 2015 00:00:00 | 5 |
| 1 | January, 05 2015 00:00:00 | 5 |
| 1 | January, 06 2015 00:00:00 | 5 |
| 1 | January, 07 2015 00:00:00 | 5 |
| 1 | January, 08 2015 00:00:00 | 5 |
| 1 | January, 09 2015 00:00:00 | 5 |
| 1 | January, 10 2015 00:00:00 | 5 |
| 1 | January, 11 2015 00:00:00 | 5 |
| 1 | January, 12 2015 00:00:00 | 5 |
| 1 | January, 13 2015 00:00:00 | 5 |
| 1 | January, 14 2015 00:00:00 | 5 |
| 1 | January, 15 2015 00:00:00 | 5 |
| 1 | January, 16 2015 00:00:00 | 5 |
| 1 | January, 17 2015 00:00:00 | 5 |
| 1 | January, 18 2015 00:00:00 | 5 |
| 1 | January, 19 2015 00:00:00 | 5 |
| 1 | January, 20 2015 00:00:00 | 5 |
I will suggest the code below that consider the first and last day of the month from two dates:
Example:
Date Initial: 01/10/2014
Date Final: 12/21/2018
The code will return:
01/01/2014
02/01/2014
03/01/2014
04/01/2014
...
12/28/2018
12/29/2018
12/30/2018
12/31/2018
The Code:
SELECT
CAL.DT AS "Date"
,TO_NUMBER(TO_CHAR(CAL.DT,'DD')) AS "Day"
,TO_NUMBER(TO_CHAR(CAL.DT,'MM')) AS "Month"
,TO_NUMBER(TO_CHAR(CAL.DT,'YY')) AS "YearYY"
,TO_NUMBER(TO_CHAR(CAL.DT,'YYYY')) AS "YearYYYY"
,TO_CHAR(CAL.DT,'day') AS "Description_Day"
,TO_CHAR(CAL.DT,'dy') AS "Description_Day_Abrev"
,TO_CHAR(CAL.DT,'Month') AS "Description_Month"
,TO_CHAR(CAL.DT,'Mon') AS "Description_Month_Abrev"
,TO_CHAR(CAL.DT,'dd month yyyy') AS "Date_Text"
FROM (
SELECT
(
TO_DATE(SEQ.MM || SEQ.YYYY, 'MM/YYYY')-1
) + SEQ.NUM AS "DT"
FROM
(
SELECT RESULT NUM,
TO_CHAR(( -- Minimum Date
TO_DATE('01/01/2014', 'DD/MM/YYYY')
) , 'MM') AS "MM",
TO_CHAR(( -- Minimum Date
TO_DATE('01/01/2014', 'DD/MM/YYYY')
) , 'YYYY') AS "YYYY"
FROM
(
SELECT ROWNUM RESULT FROM DUAL CONNECT BY LEVEL <= (
(
-- Maximum Date
LAST_DAY(TO_DATE('31/12/2018', 'DD/MM/YYYY')) -- Always Last Day
-
-- Maximum Date
TRUNC(TO_DATE('01/01/2014', 'DD/MM/YYYY')) -- Always First Day of Month
) + 1 -- Because the First Day (RESULT) don't begin at zero
)
) -- How many sequences (RESULT) to generate
) SEQ
) CAL
;

Pl/SQL and building out monthly data from Start and Stop Dates

PL/SQL is not my strong suite. I am decent with SQL, but I have a challenge that I could really use your help with, if possible. I am using SQL Developer, if that helps.
I have a table that is a join from two other tables, but suffice it to say, it has the following applicaple columns:
FTE_NAME (VARCHAR2)
PRIMARY_BILLALBE_ROLE (VARCHAR2)
INVOICABLE_ALLOCATION (NUMBER)
CONTRACTED_FTE (NUMBER)
FTE_COUNTRY (VARCHAR2)
BILLING_START_DATE (DATE)
BILLING_END_DATE (DATE)
Here is an example of what I am trying to do:
I have actually done this using VBA and excel and it works great, but now the data is on an Oracle Server and its time for an update.
Example Rows:
| FTE_NAME | PRIMARY_BILLABLE_ROLE | INVOICEABLE_ALLOCATION | CONTRACTED_FTE | FTE_COUNTRY | BILLING_START_DATE | BILLING_END_DATE |
|------------|-----------------------|------------------------|----------------|-------------|--------------------|------------------|
| John Smith | Associate Manager | 1 | 1 | USA | January, 01 2013 | May, 01 2013 |
| John Smith | Manager | 1 | 1 | USA | May, 02 2013 | (null) |
What I would need to happen is that the PL/SQL code would build a monthly table and row by row include or exclude the row in that month, so from 01-JAN-2013 to 05-MAY-2013, the monthly table might look like this now with a MONTH COLUMN up front:
| MONTHLY | FTE_NAME | PRIMARY_BILLABLE_ROLE | INVOICEABLE_ALLOCATION | CONTRACTED_FTE | FTE_COUNTRY | BILLING_START_DATE | BILLING_END_DATE |
|-------------------|------------|-----------------------|------------------------|----------------|-------------|--------------------|------------------|
| January, 01 2013 | John Smith | Associate Manager | 1 | 1 | USA | January, 01 2013 | May, 01 2013 |
| February, 01 2013 | John Smith | Associate Manager | 1 | 1 | USA | January, 01 2013 | May, 01 2013 |
| March, 01 2013 | John Smith | Associate Manager | 1 | 1 | USA | January, 01 2013 | May, 01 2013 |
| April, 01 2013 | John Smith | Associate Manager | 1 | 1 | USA | January, 01 2013 | May, 01 2013 |
| May, 01 2013 | John Smith | Associate Manager | 1 | 1 | USA | January, 01 2013 | May, 01 2013 |
| May, 01 2013 | John Smith | Manager | 1 | 1 | USA | May, 02 2013 | (null) |
The lines for MAY would both be included in the 01-MAY-2013 rows, because that manager still worked as an Associate Manager for those few days. I used the start and end dates to calculate how many days.
The big part I need help with is how to get the table to build with a MONTHLY column using the first day of the month. There will 1000s of lines and each building everyday. I would have this code running in a view which will feed report and dashboards.
I really appreciate any help you can provide.
David
You can populate desired dates from scratch like in this example query:
with report_params as (
-- just to introduce variables
select
2013 year_value,
4 start_month,
6 end_month
from dual
),
report_months as (
-- list of first dates of all required months
select
add_months( -- add months to
trunc( -- January, 1st of year from parameters
to_date(
(select year_value from report_params),
'yyyy'
),
'yyyy'
),
(select start_month from report_params) + level - 2
)
from
dual
connect by
-- select number of rows (levels) from start_month to end_month
level <= (select end_month - start_month + 1 from report_params)
)
select * from report_months;
Another possibility is to analyze a table and generate diapason between minimum and maximum values:
with bound_months as (
select
min(trunc(billing_start_date,'mm')) as first_month,
max(trunc(billing_start_date,'mm')) as last_month
from
applicable_columns
),
report_months as (
select
add_months(
(select first_month from bound_months),
level - 1
)
as first_date
from
dual
connect by
-- select number of rows (levels) from start_month to end_month
level <= (select months_between(last_month,first_month)+1 from bound_months)
)
select * from report_months;
After that you can join list of months to data table/view with applicable columns:
with bound_months as (
select
min(trunc(billing_start_date,'mm')) as first_month,
max(trunc(billing_start_date,'mm')) as last_month
from
applicable_columns
),
report_months as (
select
add_months(
(select first_month from bound_months),
level - 1
)
as first_date
from
dual
connect by
-- select number of rows (levels) from start_month to end_month
level <= (select months_between(last_month,first_month)+1 from bound_months)
)
select
months.first_date,
data.*
from
report_months months,
applicable_columns data
where
data.billing_start_date < add_months(months.first_date,1)
and
nvl(data.billing_end_date, months.first_date) >= months.first_date
order by
first_date, fte_name, primary_billable_role
SQLFiddle test

Resources