SQL PROBLEM: checking shipments from yesterday, today and tomorrow with exceptions on Monday and Friday - oracle

is it possible to create a data model in BI publisher (OTM) that looks at the shipments of:
yesterdays date
today date
tomorrow date
With the exception that on Friday instead of looking at tomorrow we look at next Monday and on Monday instead of looking at yesterday we look at last Friday.
The code that we have so far to look at the shipments for yesterday, today and tomorrow is, but we would like to put in the exception. Can anyone help us with this? thanks in advance for the help!!!
SELECT
S.SHIPMENT_GID SHIPMENT
FROM
SHIPMENT S,
SHIPMENT_INVOLVED_PARTY INV
WHERE
S.PERSPECTIVE = 'B'
AND
S.SHIPMENT_GID = INV.SHIPMENT_GID
AND
INV.INVOLVED_PARTY_QUAL_GID = 'BILL-TO'
AND
INV.INVOLVED_PARTY_CONTACT_GID = 'RSK.50144'
AND
(TRUNC(sysdate)+ 1 = TRUNC(S.START_TIME) OR TRUNC(sysdate) = TRUNC(S.START_TIME) OR TRUNC (sysdate) -1 = TRUNC(S.START_TIME))

You can use a case statement to look 5 days ahead if today is Thursday or Friday, and 3 days ahead the other days.
As there are not expected to be any rows the weekend this should always return 3 days if records.
with days as(
--create 31 days for a month
select sysdate -1 + level AS dt
from dual
connect by level <= 30
)
select
dt,
TO_CHAR( dt, 'D' ) "dayNo",
TO_CHAR( dt, 'day' )"dayName"
from days
where dt <
sysdate +
case when to_char(sysdate,'D')
in ('5','4')then 5
else 3 end
DT | dayNo | dayName
:-------- | :---- | :--------
15-APR-22 | 5 | friday
16-APR-22 | 6 | saturday
17-APR-22 | 7 | sunday
18-APR-22 | 1 | monday
19-APR-22 | 2 | tuesday
db<>fiddle here

Related

Database Query with 3 Months Current Year and Previous Year

Hope am not over thinking this...
I am currently looking for any pointers on this. I have a table/view
|Cust_ID | CName | Inv_Date | Sales
|01A | A | 2/7/2006 | 20
|02A | B | 2/7/2006 | 10
|01A | A | 11/5/2005 | 15
Each customer has very many invoices on various dates
I would then like to extract each customer with sales for
Three (months) of the current year (e.g. 2006). This is 3 months prior to today's date.
Inv_Date >= add_months(trunc(sysdate, 'month'), -3)
and Inv_Date <= add_months(last_day(sysdate), -1)
The same three (3) months of the previous year (2005)?!
Would assume something like
|Cust_ID | CName | CY_Sum_Sales | PY_Sum_Sales
|01A | A | 20 | 15
|02A | B | 0 | 0
Assuming 'sysdate' is 1/8/2008. The following is my trial
select Customer_No, Customer_Name,
TO_CHAR(SUM(Sales_Colmn), 'fm999G999G999G999G999D0') as "TYear_Sale",
TO_CHAR((SELECT SUM(Sales_Colmn) from cust_table CL
where CL.invoice_date BETWEEN add_months(trunc(to_date('&From_Dt', 'DD/MM/YYYY'), 'mm'), -(3 + 12)) --15 months
and add_months(trunc(to_date('&To_Dt', 'DD/MM/YYYY'), 'mm'), -(1 + 12)) --13 months)
and Customer_No = CL.Customer_No), 'fm999G999G999G999G999D0') as "LYear_Sale"
from cust_table
where invoice_date BETWEEN add_months(trunc(to_date('&From_Dt', 'DD/MM/YYYY'), 'mm'), -3) --3 months
and add_months(trunc(to_date('&To_Dt', 'DD/MM/YYYY'), 'mm'), -1) --1 months)
GROUP BY Customer_No, Customer_Name
ORDER BY 3
But it takes too long to run. Must be doing something wrong
Functions (add_months, sysdate) you use suggest this is Oracle.
As you want to go back to same months previous year, you just have to add (OK, subtract) another 12 months. Something like this:
SQL> select trunc(sysdate) c_today,
2 trunc(sysdate, 'mm') c_trunc_today,
3 --
4 add_months(trunc(sysdate, 'mm'), -3) c_3_months_ago,
5 add_months(trunc(sysdate, 'mm'), -1) c_1_month_ago,
6 --
7 add_months(trunc(sysdate, 'mm'), -(3 + 12)) c_15_months_ago,
8 add_months(trunc(sysdate, 'mm'), -(1 + 12)) c_13_months_ago
9 from dual; ^^
this!
C_TODAY C_TRUNC_TO C_3_MONTHS C_1_MONTH_ C_15_MONTH C_13_MONTH
---------- ---------- ---------- ---------- ---------- ----------
03.11.2021 01.11.2021 01.08.2021 01.10.2021 01.08.2020 01.10.2020
--------------------- =====================
this year previous year, same months
SQL>

How to split record in multiple records from start/end date record

I'm trying to split record to multiple record from start/end date in Oracle
I have data like this
MachineID | start date | end date | running time |
WC01 | 2019/09/05 07:00 | 2019/09/07 09:00 | 26:00 |
and I want to split record to each day from 08:00 to 08:00
MachineID | running date | running time |
WC01 | 2019/09/05 | 1:00 |
WC01 | 2019/09/06 | 24:00 |
WC01 | 2019/09/07 | 1:00 |
Thank you for your help!
We can handle this via the help from a calendar table which contains all dates you expect to appear in your data set, along with a separate record for each minute:
WITH dates AS (
SELECT TIMESTAMP '2019-09-05 00:00:00' + NUMTODSINTERVAL(rownum, 'MINUTE') AS dt
FROM dual
CONNECT BY level <= 5000
)
SELECT
m.MachineID,
TRUNC(d.dt) AS running_date,
COUNT(t.MachineID) / 60 AS running_hours
FROM dates d
CROSS JOIN (SELECT DISTINCT MachineID FROM yourTable) m
LEFT JOIN yourTable t
ON d.dt >= t.start_date AND d.dt < t.end_date
WHERE
TO_CHAR(d.dt, 'HH24') >= '08' AND TO_CHAR(d.dt, 'HH24') < '21'
GROUP BY
m.MachineID,
TRUNC(d.dt)
ORDER BY
TRUNC(d.dt);
Demo
You can try below query:
SELECT
MACHINEID,
RUNNING_DATE,
DECODE(RUNNING_DATE, TRUNC(START_DATE), CASE
WHEN DIFF_START < 0 THEN 0
WHEN DIFF_START > 12 THEN 12
ELSE DIFF_START
END, TRUNC(END_DATE), CASE
WHEN DIFF_END < 0 THEN 0
WHEN DIFF_END > 12 THEN 12
ELSE DIFF_END
END, 24) AS RUNNING_HOURS
FROM
(
SELECT
MACHINEID,
RUNNING_DATE,
ROUND(24 *((TRUNC(START_DATE + LVL - 1) + 8 / 24) - START_DATE)) AS DIFF_START,
ROUND(24 *(END_DATE -(TRUNC(START_DATE + LVL - 1) + 8 / 24))) AS DIFF_END,
START_DATE,
END_DATE
FROM
(
SELECT
DISTINCT MACHINEID,
LEVEL AS LVL,
START_DATE,
END_DATE,
TRUNC(START_DATE + LEVEL - 1) AS RUNNING_DATE
FROM
YOURTABLE
CONNECT BY
LEVEL <= TRUNC(END_DATE) - TRUNC(START_DATE) + 1
)
);
db<>fiddle demo
Change the logic wherever it is not meeting your requirement. I have created the query taking sample data and expected output into consideration.
Cheers!!

How to convert week number to date range in Oracle?

In Oracle we get week number from following query:
select to_char(TO_DATE(SYSDATE,'DD-MM-YY'),'IW') from dual
I want to get date range of given week number, for example for week no:1 date range is 01-01-2017 to 08-01-2017.
is there any way to get the date range for given week number?
"week no:1 date range is 01-01-2017 to 08-01-2017"
No it isn't. You're confusing 'IW' (which runs MON - SUN) with 'WW' which runs from the first day of the year:
SQL> with dts as (
2 select date '2017-01-01' + (level-1) as dt
3 from dual
4 connect by level <= 8
5 )
6 select dt
7 , to_char(dt, 'DY') as dy_dt
8 , to_char(dt, 'IW') as iw_dt
9 , to_char(dt, 'WW') as ww_dt
10 from dts
11 order by 1;
DT DY_DT IW WW
--------- ------------ -- --
01-JAN-17 SUN 52 01
02-JAN-17 MON 01 01
03-JAN-17 TUE 01 01
04-JAN-17 WED 01 01
05-JAN-17 THU 01 01
06-JAN-17 FRI 01 01
07-JAN-17 SAT 01 01
08-JAN-17 SUN 01 02
8 rows selected.
SQL>
However, it's easy enough to generate a range for the the IW week number. You need to multiple the IW number by 7 which you can convert to a date with the day of year mask. Then you can use next_day() function to get the previous Monday and the next Sunday relative to that date:
SQL> with tgt as (
2 select to_date( &iw *7, 'DDD') as dt from dual
3 )
4 select next_day(dt-8, 'mon') as start_date
5 , next_day(dt, 'sun') as end_date
6* from tgt;
Enter value for iw: 23
old 2: select to_date( &iw *7, 'DDD') as dt from dual
new 2: select to_date( 23 *7, 'DDD') as dt from dual
START_DAT END_DATE
--------- ---------
05-JUN-17 11-JUN-17
SQL>
Obvious this solution uses my NLS Settings (English): you may need to tweak the solution if you use different settings.
These kinds of problems are easy to solve with calendar tables.
The following query builds on the assumption (ISO 8601) that the 4th of January is present in the first week in a year. Therefore I can generate a valid date in the first week of any year by constructing the 4th of January like: to_date(year || '-01-04', 'yyyy-mm-dd'). Oracle will tell me the day of week (sun=1, sat=7) for any date using to_char(date, 'D'). The 4th of JAN 2017 happens to be a wednesday (day 4). Subtracting 3 days will give me the first day (sunday) of the first week of the year.
Now it is easy to find the start day in any given week in the year by simply adding 7 days for each week (not counting the first week).
with weeks as(
select 2017 as year, 39 as week from dual union all
select 2017 as year, 40 as week from dual union all
select 2018 as year, 35 as week from dual
)
select a.*
,to_date(year || '-01-04', 'yyyy-mm-dd') - to_number(to_char(to_date(year || '-01-04', 'yyyy-mm-dd'), 'D')) + 1 + (7 * (week-1)) as start_day
,to_date(year || '-01-04', 'yyyy-mm-dd') + 7 - to_number(to_char(to_date(year || '-01-04', 'yyyy-mm-dd'), 'D')) + (7 * (week-1)) as end_day
from weeks a;
Edit: These are the "convert" expressions you need to convert from week to date range. Note that 2017 and 39 are variable...
start date = to_date(2017 || '-01-04', 'yyyy-mm-dd') - to_number(to_char(to_date(2017 || '-01-04', 'yyyy-mm-dd'), 'D')) + 1 + (7 * (39-1))
end date = to_date(2017 || '-01-04', 'yyyy-mm-dd') + 7 - to_number(to_char(to_date(2017 || '-01-04', 'yyyy-mm-dd'), 'D')) + (7 * (39-1))
Here's a query to list all ISO weeks from 2001 to 2099
SELECT TO_CHAR(TRUNC(dt, 'IW') + 6, 'IYYY-IW') AS week,
TRUNC(dt, 'IW') AS start_date,
TRUNC(dt, 'IW') + 6 AS end_date
FROM (SELECT DATE '2001-01-01' + ((LEVEL - 1) * 7) dt
FROM DUAL
CONNECT BY LEVEL <= 5165);
For the first and last week of year this query needs some CASE logic, but for other weeks works good. This solution use current NLS settings.
select to_char( start_of_week, 'day dd.mm.yyyy' ) start_of_week,
to_char( start_of_week + 6, 'day dd.mm.yyyy' ) end_of_week
from
(
select trunc( date '2017-01-01' + 38*7 , 'day') start_of_week
from dual
)
1) date '2017-01-01' - in what year we look for weeks
or it may be trunc (sysdate, 'YEAR') to take first day of current year
2) date '2017-01-01' + 38*7 - jump to 38th week
3) trunc ( ... , 'day' ) - gives date of first day of the week
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions201.htm
https://docs.oracle.com/cd/B19306_01/server.102/b14200/functions230.htm
I use this function:
FUNCTION ISOWeekDate(WEEK INTEGER, YEAR INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF WEEK > 53 OR WEEK < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( YEAR || '0104', 'YYYYMMDD' ) - 7, 'MONDAY') + ( WEEK - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = YEAR THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeekDate;
Please note, according to my comment it is ambiguous if you only provide a week number without a year. The function returns the first day of given ISO Week.

Round off time value to before midnight in oracle sql

I am writing a query that will allow me to workout dates for when particular jobs need to be completed.
The following is a list of my jobs and codes.
JOB_NO | CODES | RCVD_TIME
ABC1 | 1 | 07-JAN-17 09:44:07
DEF2 | 3 | 20-MAR-17 14:32:49
GHI3 | 3 | 27-MAR-17 10:00:03
JKL4 | 1 | 12-JAN-17 12:59:05
Now I have a few conditions in order to workout the end date/time for these jobs.
Code 1 - Add 1 day + anytime until 23:59:59
Code 3 - If rcvd time is before 1pm then job to be completed same day until 23:59:59, if rcvd time is 1pm or after then job to be completed before 1pm next day.
My simple query:
SELECT
JOB_NO, CODES, RCVD_TIME,
CASE
WHEN CODES = '1'
THEN RCVD_TIME + 1
ELSE RCVD_TIME
END AS TARGET
FROM...
This gives me (not sure how to write query for codes 3):
JOB_NO | CODES | RCVD_TIME | TARGET
ABC1 | 1 | 07-JAN-17 09:44:07 | 08-JAN-17 09:44:07
DEF2 | 3 | 20-MAR-17 14:32:49 | 20-MAR-17 14:32:49
GHI3 | 3 | 27-MAR-17 10:00:03 | 27-MAR-17 10:00:03
JKL4 | 1 | 12-JAN-17 12:59:05 | 13-JAN-17 12:59:05
This is what I would like:
JOB_NO | CODES | RCVD_TIME | TARGET
ABC1 | 1 | 07-JAN-17 09:44:07 | 08-JAN-17 23:59:59
DEF2 | 3 | 20-MAR-17 14:32:49 | 21-MAR-17 13:00:00
GHI3 | 3 | 27-MAR-17 10:00:03 | 27-MAR-17 23:59:59
JKL4 | 1 | 12-JAN-17 12:59:05 | 13-JAN-17 23:59:59
Would really appreciate if somebody could advise on how I can get my required answer.
Thanks in advance.
EDIT
bit frustrating but been told to add one more condition to consider working day of the week is between 1 and 6 so have to make sure the target time does not fall into a sunday (day 7). sorry guys for the double, just thought I would add it here in case it gets missed.
You can use a CASE statement in your SQL to handle your complex if-then logic.
-- TEST_DATA is not part of the solution
with test_data ( job_no, code, rcvd_time, target ) AS (
SELECT 'ABC1',1,to_date('07-JAN-17 09:44:07','DD-MON-YY HH24:MI:SS'),to_date('08-JAN-17 09:44:07','DD-MON-YY HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'DEF2',3,to_date('20-MAR-17 14:32:49','DD-MON-YY HH24:MI:SS'),to_date('20-MAR-17 14:32:49','DD-MON-YY HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'GHI3',3,to_date('27-MAR-17 10:00:03','DD-MON-YY HH24:MI:SS'),to_date('27-MAR-17 10:00:03','DD-MON-YY HH24:MI:SS') FROM DUAL UNION ALL
SELECT 'JKL4',1,to_date('12-JAN-17 12:59:05','DD-MON-YY HH24:MI:SS'),to_date('13-JAN-17 12:59:05','DD-MON-YY HH24:MI:SS') FROM DUAL)
-- Actual solution begins here...
SELECT job_no, code, rcvd_time,
case
when rcvd_time - trunc(rcvd_time) < 13/24 and code = 1 then
trunc(rcvd_time) + (86399/86400) + 1
when rcvd_time - trunc(rcvd_time) < 13/24 and code = 3 then
trunc(rcvd_time) + (86399/86400)
when rcvd_time - trunc(rcvd_time) >= 13/24 then
trunc(rcvd_time) + 1 + (13/24)
end target
from test_data;
You can use interval literals (so you do not need to sprinkle magic numbers throughout your code):
CASE
WHEN code = 1
THEN TRUNC( rcvd_time ) + INTERVAL '2' DAY - INTERVAL '1' SECOND
WHEN code = 3 AND TO_CHAR( rcvd_time, 'HH24' ) < '13'
THEN TRUNC( rcvd_time ) + INTERVAL '1' DAY - INTERVAL '1' SECOND
WHEN code = 3 -- AND TO_CHAR( rcvd_time, 'HH24' ) >= '13'
THEN TRUNC( rcvd_time ) + INTERVAL '1' DAY + INTERVAL '13' HOUR
END
To compare the time you can use various different techniques:
TO_CHAR( rcvd_time, 'HH24' ) < '13'
EXTRACT( HOUR FROM CAST( rcvd_time AS TIMESTAMP ) ) < 13
rcvd_time < TRUNC( rcvd_time ) + INTERVAL '13' HOUR
In the solution below, I assume that the "special" handling applies only when CODES is 3, otherwise the TARGET is the end of the next day. (This allows CODES values other than 1 and 3... you can adapt if you have more CODES values and additional rules.)
Since all TARGET date-times are offset from trunc(rcvd_time), I use a CASE expression just to determine the offset.
select job_no, codes, rcvd_time,
trunc(rcvd_time) +
case codes when 3 then
case when extract (hour from cast (rcvd_time as timestamp)) < 13
then interval '23:59:59' hour to second
else interval '1 13:00:00' day to second
end
else interval '1 23:59:59' day to second
end as target
from your_table;
NOTE: Thanks to MT0 for showing the ways to extract hour from a date; I had forgotten that extract(hour from...) works only for timestamps. I edited my answer accordingly.
Try this
select
t.*
,case when codes=3
and to_date(to_char(rcvd_time,'HH24:MI:SS'),'HH24:MI:SS')
>=to_date('13:00:00','HH24:MI:SS')
then trunc(rcvd_time)+1+(13/24) -- add 1 day and then add 13 hours
when codes=1
then trunc(rcvd_time)+2-(1/(24*60*60)) --add 2 days and subtract 1 second
else
trunc(rcvd_time)+1-(1/(24*60*60)) -- add 1 day and subtract 1 second
end as target
from tbl t;
Working sample
with tbl (JOB_NO,CODES,RCVD_TIME) as (
select 'ABC1',1,to_date('07-JAN-17 09:44:07','DD-MON-YY HH24:MI:SS') from dual union all
select 'DEF2',3,to_date('20-MAR-17 14:32:49','DD-MON-YY HH24:MI:SS') from dual union all
select 'GHI3',3,to_date('27-MAR-17 10:00:03','DD-MON-YY HH24:MI:SS') from dual union all
select 'JKL4',1,to_date('12-JAN-17 12:59:05','DD-MON-YY HH24:MI:SS') from dual)
select
t.*
,case when codes=3
and to_date(to_char(rcvd_time,'HH24:MI:SS'),'HH24:MI:SS')
>=to_date('13:00:00','HH24:MI:SS')
then trunc(rcvd_time)+1+(13/24) -- add 1 day and then add 13 hours
when codes=1
then trunc(rcvd_time)+2-(1/(24*60*60)) --add 2 days and subtract 1 second
else
trunc(rcvd_timeJOB_NO,CODES,RCVD_TIME)+1-(1/(24*60*60)) -- add 1 day and subtract 1 second
end as target
from tbl t;
Output
JOB_NO CODES RCVD_TIME TARGET
ABC1 1 07-JAN-2017 09:44:07 08-JAN-2017 23:59:59
DEF2 3 20-MAR-2017 14:32:49 21-MAR-2017 13:00:00
GHI3 3 27-MAR-2017 10:00:03 27-MAR-2017 23:59:59
JKL4 1 12-JAN-2017 12:59:05 13-JAN-2017 23:59:59

Oracle Week Number From Date ISO Week But From December 1

Ok, I've had a query that has been working fine that calculates the week number from December 1 (the start of our Sales Fiscal Year).
Now the requirements have changed. I still need to calculate the week number based on the field (Invoice_Date). However, instead of starting to count from December 1 (Dec 1-7, Week 1, etc.) now I need to start counting on the nearest Monday to December 1st. As I understand it, the ISO week is kind of what I'm looking for but it starts January 1. How do I modify this to work from December 1?
Any help would be greatly appreciated.
select next_day(to_date('0112' || to_char(sysdate, 'YYYY'),'ddmmyyyy') - 1, 'MONDAY') dec_mon from dual; gives you first Monday of December current year
Number of week is just ceil((sysdate - dec_mon)/7).
If you want last Monday before 1st Dec you can get it by:
select next_day(to_date('2511' || to_char(sysdate, 'YYYY'),'ddmmyyyy') - 1, 'MONDAY')
from dual;
In this proposed solution, I build a "helper table" first, showing the Monday_from and Monday_to for each fiscal year (in the third CTE, named ranges). Then I build a few test dates - I was lazy, I should have used to_date() so I can include time-of-day component as well. The join condition in the actual solution (at the end of the code) is written so it works without modification for dates with non-zero "time-of-day" component.
I used the nice feature of Oracle 11.2 which allows us to give column aliases in the declaration of CTEs - otherwise the column aliases would need to be moved inside the respective SELECTs. Otherwise the solution should work at least for Oracle 9 and above (I think).
with
y ( dt ) as (
select add_months(date '2000-12-01', 12 * level )
from dual
connect by level <= 30
),
m ( dt ) as (
select trunc(dt, 'iw') + case when dt - trunc(dt, 'iw') <= 3 then 0 else 7 end
from y
),
ranges ( monday_from, monday_to ) as (
select dt, lead(dt) over (order by dt) - 1
from m
),
test_dates ( t_date ) as (
select date '2013-02-23' from dual union all
select date '2008-12-01' from dual union all
select date '2008-04-28' from dual union all
select date '2016-11-29' from dual
)
select t_date, monday_from, 1 + trunc((t_date - monday_from)/7) as week_no
from test_dates t inner join ranges r
on t.t_date >= r.monday_from and t.t_date < r.monday_to
;
T_DATE MONDAY_FROM WEEK_NO
------------------- ------------------- ----------
2008-04-28 00:00:00 2007-12-03 00:00:00 22
2008-12-01 00:00:00 2008-12-01 00:00:00 1
2013-02-23 00:00:00 2012-12-03 00:00:00 12
2016-11-29 00:00:00 2016-11-28 00:00:00 1
The nearest Monday to any any given date is returned with the following function:
NEXT_DAY(some_date-4,'Monday')
as shown by this query:
with dts(some_date) as (
select date '2006-12-1' from dual
union all
select add_months(some_date,12)
from dts
where some_date <= date '2014-12-1'
)
select some_date
, next_day(some_date-4,'monday') nearest
, some_date - next_day(some_date-4,'monday') dist
from dts;
SOME_DATE NEAREST DIST
----------- ----------- ----------
01-DEC-2006 04-DEC-2006 -3
01-DEC-2007 03-DEC-2007 -2
01-DEC-2008 01-DEC-2008 0
01-DEC-2009 30-NOV-2009 1
01-DEC-2010 29-NOV-2010 2
01-DEC-2011 28-NOV-2011 3
01-DEC-2012 03-DEC-2012 -2
01-DEC-2013 02-DEC-2013 -1
01-DEC-2014 01-DEC-2014 0
01-DEC-2015 30-NOV-2015 1
10 rows selected

Resources