Database Query with 3 Months Current Year and Previous Year - oracle

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>

Related

Oracle: Return the specific records based on one column date

I have a database structure as below.
period
month
start_date
1
April
2022-04-01
2
May
2022-05-07
3
June
2022-06-04
4
July
2022-07-02
5
August
2022-08-06
6
September
2022-09-03
7
October
2022-10-01
8
November
2022-11-05
9
December
2022-12-03
10
January
2023-01-01
11
February
2023-02-04
12
March
2023-03-04
End date of the year is 2023-03-31.
Based on current_date, how do I select the query to return where the current date falls under Period 6.
My current query as below.
SELECT period FROM table1 as a
WHERE
a.start_date = (SELECT MAX(start_date) FROM table1 as b WHERE
b.start_date <=current_date) and ROWNUM <= 1
Is there anyway to improve the current query which to avoid using subquery?
Today is September 22nd, so - would this do?
Some sample data:
SQL> with test (period, month, start_date) as
2 (select 1, 'april' , date '2022-04-01' from dual union all
3 select 5, 'august' , date '2022-08-06' from dual union all
4 select 6, 'september', date '2022-09-03' from dual union all
5 select 7, 'october' , date '2022-10-01' from dual union all
6 select 10, 'january' , date '2023-01-01' from dual union all
7 select 12, 'march' , date '2023-03-04' from dual
8 ),
Query begins here:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= sysdate
14 )
15 select period
16 from temp
17 where rn = 1
18 /
PERIOD
----------
6
SQL>
It still uses a subquery (or a CTE, as in my example), but - as opposed to your approach, it selects from the source table only once, so performance should be improved.
A few more tests: instead of sysdate (line #13), presume that today is September 2nd (which means that it is in period #5):
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-09-02'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Or, if today were August 7th:
9 temp as
10 (select period, month, start_date,
11 row_number() over (order by start_date desc) rn
12 from test
13 where start_date <= date '2022-08-07'
14 )
15 select period
16 from temp
17 where rn = 1;
PERIOD
----------
5
SQL>
Your rule for the start_date appears to be:
If the month is January (first month of the calendar year) or April (typically, first month of the financial year) then use the 1st of that month;
Otherwise use the 1st Saturday of the month.
If that is the case then you can calculate the start date of the next month and use the query:
SELECT *
FROM table1
WHERE start_date <= SYSDATE
AND SYSDATE < CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(start_date, 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN TRUNC(ADD_MONTHS(start_date, 1), 'MM')
ELSE NEXT_DAY(TRUNC(ADD_MONTHS(start_date, 1), 'MM') - 1, 'SATURDAY')
END
Then, for your sample data:
CREATE TABLE table1 (
period NUMBER(2,0),
month VARCHAR2(9)
GENERATED ALWAYS AS (
CAST(
TO_CHAR(start_date, 'FXMonth', 'NLS_DATE_LANGUAGE=English')
AS VARCHAR2(9)
)
),
start_date DATE
);
INSERT INTO table1 (period, start_date)
SELECT LEVEL,
CASE
WHEN EXTRACT(MONTH FROM ADD_MONTHS(DATE '2022-04-01', LEVEL - 1))
IN (1, 4) -- 1st month of calendar or financial year
THEN ADD_MONTHS(DATE '2022-04-01', LEVEL - 1)
ELSE NEXT_DAY(ADD_MONTHS(DATE '2022-04-01', LEVEL - 1) - 1, 'SATURDAY')
END
FROM DUAL
CONNECT BY LEVEL <= 12;
Outputs:
PERIOD
MONTH
START_DATE
6
September
2022-09-03 00:00:00
fiddle

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!!

Query for a Specific Time

How can i return data from 2 days ago at 11:00:00 PM to all of yesterday ending at 11:59:59 PM?
I currently have only yesterdays date query:
SELECT *
FROM table
WHERE code = '00'
AND to_char(RQST_TMSTMP, 'yyyy-mm-dd') = to_char(sysdate-1, 'yyyy-mm-dd')
How about
select *
from table
where code = '00'
and rqst_tmstmp >= trunc(sysdate - 2) + 11/24
and rqst_tmstmp <= trunc(sysdate);
Here's what all those TRUNCs represent (so that you could follow what's going on):
SQL> select sysdate, -- today, right now
2 trunc(sysdate) ts, -- today at midnight
3 trunc(sysdate - 2) ts_2, -- 2 days ago at midnight
4 trunc(sysdate - 2) + 11/24 ts_2_11 -- 2 days ago at midnight + 11 hours
5 from dual;
SYSDATE TS TS_2 TS_2_11
---------------- ---------------- ---------------- ----------------
29.11.2018 17:07 29.11.2018 00:00 27.11.2018 00:00 27.11.2018 11:00
SQL>
If the column is capturing hours & minutes then use,
TO_CHAR(RQST_TMSTMP,'DD-MM-YY HH24:MI')

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

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