Question for the SQL gurus. I have a table with 3 columns. [Date, Meter, Quality], where there will only be one line per date for each meter. As an example:
SELECT * FROM MyDB WHERE Meter = 'MeterX' AND Date > '1-AUG-2022' AND Date <= '5-AUG-2022' ORDER BY Date;
I would query much larger date ranges so would usually miss if there is a date missing. Is there a way that I can have a value returned in the Quality column like "Missing" if that partiqular day is missing from the database? This means that I also need the missing date in the Date column. I also only have read access, so no creating temp tables to join with.
Thank you.
Use a PARTITIONed OUTER JOIN to a row-generator:
SELECT c.day,
m.meter,
COALESCE(m.quality, 0) AS quality
FROM (
SELECT DATE '2022-08-01' + (LEVEL - 1) AS day
FROM DUAL
CONNECT BY DATE '2022-08-01' + (LEVEL - 1) <= DATE '2022-08-05'
) c
LEFT OUTER JOIN MyDB m
PARTITION BY (m.meter)
ON (c.day <= m."DATE" and m."DATE" < c.day + 1)
WHERE m.Meter = 'MeterX'
ORDER BY c.day;
Which, for the sample data:
CREATE TABLE mydb ("DATE", meter, quality) AS
SELECT DATE '2022-08-01', 'MeterX', 42 FROM DUAL UNION ALL
SELECT DATE '2022-08-02', 'MeterX', 23 FROM DUAL UNION ALL
SELECT DATE '2022-08-04', 'MeterX', 7 FROM DUAL UNION ALL
SELECT DATE '2022-08-05', 'MeterX', 99 FROM DUAL;
Outputs:
DAY
METER
QUALITY
01-AUG-22
MeterX
42
02-AUG-22
MeterX
23
03-AUG-22
MeterX
0
04-AUG-22
MeterX
7
05-AUG-22
MeterX
99
db<>fiddle here
for postgres this could work for you
with date_dimension as (
SELECT dd::DATE
FROM generate_series
( '2022-08-01'::timestamp
, '2022-08-05'::timestamp
, '1 day'::interval) dd
)
select *
from my_table
left join date_dimension on date_dimension.dd = my_table.Date
where Meter = 'MeterX'
and Date > '2022-08-01'
and Date <= '2022-08-05'
order by Date;
I have a table with operations,which looks like this:
date value
01.12.20 1100
04.12.20 920
09.12.20 350
18.12.20 5000
and i need to find peak value of every week for last n-weeks. So expected result should looks like:
week value_sum
week 1 1100
week 2 350
week 3 5000
i've tried it to separete for groups:
select
trunc(mydate, 'ww') week,
sum(myvalue) value_sum
from myTABLE
where 1=1
and mydate >=date'2020-12-01'
and mydate < date'2021-01-01'
group by
trunc(mydate, 'ww')
having max(sum(myvalue))
and this to get days for last 7-weeks for ex, which i can use as:
WITH date_range as
(select trunc(sysdate,'WW')-(rownum-1)*7 week
from (select rownum from dual)
connect by level <=13
order by 1)
select * from date_range
so how can i solve my problem?
I think you're looking for the max value - it represents the peak, your first query with a modification of the sum->max will do the job, with a reduction to the having clause.
select
trunc(mydate, 'ww') week,
max(myvalue) value_sum
from myTABLE
where 1=1
and mydate >=date'2020-12-01'
and mydate < date'2021-01-01'
group by
trunc(mydate, 'ww')
Here are the dates available in my Record_Date (Date) column in Attendance table in Oracle 10g.You can find the dates 04/06/2016 08/06/2016 16/06/2016 23/06/2016 29/06/2016 are missing in the sequence.
**Record_Date**
01/06/2016
02/06/2016
03/06/2016
05/06/2016
06/06/2016
07/06/2016
09/06/2016
10/06/2016
12/06/2016
13/06/2016
14/06/2016
15/06/2016
17/06/2016
18/06/2016
19/06/2016
20/06/2016
21/06/2016
22/06/2016
24/06/2016
25/06/2016
26/06/2016
27/06/2016
28/06/2016
30/06/2016
01/07/2016
I just need a query to find the missing dates in the specific month (and later also in the Year).
Kindly show me an approach
You can use this one:
WITH all_days AS
(SELECT DATE '2016-06-01' + LEVEL-1 AS the_day
FROM dual
CONNECT BY DATE '2016-06-01' + LEVEL-1 <= DATE '2016-06-30')
SELECT the_day
FROM all_days
WHERE the_day <>ALL (SELECT Record_Date FROM Attendance);
Or, if you like to have it more dynamically:
WITH all_days AS
(SELECT START_DATE + LEVEL AS the_day
FROM dual
CROSS JOIN
(SELECT
TRUNC(MIN(Record_Date), 'MM') -1 AS START_DATE,
TRUNC(LAST_DAY(MAX(Record_Date))) AS END_DATE
FROM Attendance)
CONNECT BY START_DATE + LEVEL <= END_DATE)
SELECT the_day
FROM all_days
WHERE the_day <>ALL (SELECT Record_Date FROM Attendance);
Note, <>ALL is the same as NOT IN - it's just my personal preference.
with
nums(num )
as
(select 0 from dual
union all
select num + 1 from nums
where num < (select max(col) from qtable)- (select min(col) from qtable)
),
date_btwn(dat)
as(select num + (select min(col) from qtable) from nums)
select dat from date_btwn
minus
select col from qtable;
Inline views nums will generate all numbers to add from start date. date_btwn contains all dates between start date and end date in table. We are excluding the dates in our table using minus.
You need to generate all dates and you have to find missing ones. Below with cte i have done it
Using CTE and not inQuery :
with calendar as (
select rownum - 1 as daynum,
to_date('1-jun-2016') + (rownum - 1) as monthdate
from dual )
select monthdate as monthdate
from calendar
where monthdate not in (
SELECT
Record_Date
FROM Attendance )
and monthdate >= to_date('1-jun-2016') /* CHANGE as per need */
and monthdate < to_date('1-jul-2016') /* CHANGE as per need */
Or Using CTE and left join Query :
with calendar as (
select rownum - 1 as daynum,
to_date('1-jun-2016') + (rownum - 1) as monthdate
from dual )
select monthdate as monthdate
from calendar C left join Attendance A on A.Record_Date=c.monthdate
where A.Record_Date is null
and monthdate >= to_date('1-jun-2016') /* CHANGE as per need */
and monthdate < to_date('1-jul-2016') /* CHANGE as per need */
as per datecolumn format change it to validformat into selection query first then use this query.
My question is similar to following question:
http://asktom.oracle.com/pls/asktom/f?p=100:11:0::::P11_QUESTION_ID:14582643282111
The difference is my inner query returns two records and I have outer query.
I need to write inner query something like this which will give me a list of dates between two date ranges (I am trying this query whcih does not execute).
Select * from outerTable where my_date in
(
select to_date(r.REQ_DATE) + rownum -1
from all_objects, (MY_INNER_QUERY Where ID =100) r
where rownum <= to_date(r.DUE_DATE,'dd-mon-yyyy')-to_date(r.REQ_DATE,'dd-mon-yyyy')+1;
)
My inner query returns following 2 rows:
Select * from innerTable Where ID =100
Start date end date
3/19/2013 3/21/2013
3/8/2013 3/8/2013
So I am need inner query which will return following dates to outer query:
3/19/2013
3/20/2013
3/21/2013
3/8/2013
Great question - this one really drew me in! The answer was more or less buried further down on Tom's post. Here's the short version, using a table called TestDR to define the ranges. First the TestDR contents:
SELECT * FROM TestDR;
STARTDATE ENDDATE
--------- ---------
19-MAR-13 21-MAR-13
08-MAR-13 08-MAR-13
Now for the query to create one row for each date in the range:
WITH NUMS AS (
SELECT LEVEL-1 DaysToAdd
FROM DUAL
CONNECT BY LEVEL <= 60
)
SELECT StartDate + DaysToAdd TheDate
FROM TestDR
CROSS JOIN NUMS
WHERE TestDR.EndDate - TestDR.StartDate + 1 > DaysToAdd
ORDER BY 1
THEDATE
---------
08-MAR-13
19-MAR-13
20-MAR-13
21-MAR-13
With the query adapted from Tom's posting, you have to know the maximum range going in to "seed" the NUMS query. He used 60 in his example so that's what I used above. If you don't think any row from your subquery will ever have a range of more than 60 days then this will do the trick. If you think the maximum could be as much as 1000 days (about three years) then change the 60 to 1000. I tried this and queried a 2 1/2 year range and the results were instantaneous.
If you want to specify the exact "seed" count you can calculate it if you're willing to make the query a bit more complicated. Here's how I can do it with my TestDR table:
WITH NUMS AS (
SELECT LEVEL-1 DaysToAdd
FROM DUAL
CONNECT BY LEVEL <= (
SELECT MAX(EndDate - StartDate + 1)
FROM TestDR)
)
SELECT ... and the rest of the query as above
For your problem, you don't need to enumerate the dates. A simple JOIN suffices.
SELECT o.*
FROM outerTable o
INNER JOIN innerTable i
ON i.ID = 100
AND o.my_date BETWEEN i.REQ_DT and i.DUE_DT
From your code, I can tell that you must be a OO programmer and not familiar with SQL. It does a lot for you, so don't try to control it. It will hinder it's optimization features.
Don't take this in the wrong way, I had the same mindset (believing that I am smarter than the machine).
In your outer query use an OR statement, which allows your date to be equal to either the return Start_Date or End_Date
AND (date = subQuery.Start_Date
OR date = subQuery.End_Date)
Using your dates:
SELECT smth... FROM some_tab
WHERE your_date IN
( -- remove unnecessary columns, leave only what you select in outer query
-- or select *
SELECT start_date
, TRUNC(start_date, 'iw') wk_starts
, TRUNC(start_date, 'iw') + 7 - 1/86400 wk_ends
, TO_NUMBER (TO_CHAR (start_date, 'IW')) ISO_wk#
FROM
(
SELECT (start_date-1) + LEVEL AS start_date
FROM
( -- replace this part with selecting your start and end dates from your table --
SELECT to_date('03/21/2013', 'MM/DD/YYYY') end_date
, to_date('03/19/2013', 'MM/DD/YYYY') start_date
FROM dual
)
CONNECT BY LEVEL <= (end_date - start_date)
)
) -- your outer query ends --
/
START_DATE WK_STARTS WK_ENDS ISO_WK#
----------------------------------------------------------
3/19/2013 3/18/2013 3/24/2013 11:59:59 PM 12
3/20/2013 3/18/2013 3/24/2013 11:59:59 PM 12
Annual table of dates and ISO weeks etc... Use any dates for start and end dates. The connect by and number of days between is used to generate table on the fly. You may use between operator if using hard structures...:
SELECT start_date
, TRUNC(start_date, 'iw') wk_starts
, TRUNC(start_date, 'iw') + 7 - 1/86400 wk_ends
, TO_NUMBER (TO_CHAR (start_date, 'IW')) ISO_wk#
FROM
(-- This part simplifies above formatting and optional --
SELECT (start_date-1) + LEVEL AS start_date
FROM
(-- Replace start/end dated with any dates --
SELECT TRUNC(ADD_MONTHS (SYSDATE, 12), 'Y')-1 end_date
, TRUNC(SYSDATE, 'YEAR') start_date
FROM dual
)
CONNECT BY LEVEL <= (end_date - start_date) -- number of days between dates
)
/
START_DATE WK_STARTS WK_ENDS ISO_WK#
-----------------------------------------------------------
1/1/2013 12/31/2012 1/6/2013 11:59:59 PM 1
1/2/2013 12/31/2012 1/6/2013 11:59:59 PM 1
1/3/2013 12/31/2012 1/6/2013 11:59:59 PM 1
...
12/28/2013 12/23/2013 12/29/2013 11:59:59 PM 52
12/29/2013 12/23/2013 12/29/2013 11:59:59 PM 52
12/30/2013 12/30/2013 1/5/2014 11:59:59 PM 1
See the results of below queries:
>> SELECT ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR'),-4) FROM DUAL;
30-NOV-10
>> SELECT ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4) FROM DUAL;
31-MAR-11
How can I get '30-MAR-11' when adding 4 months to some date?
Please help.
There is another question here about Oracle and Java
It states that
From the Oracle reference on add_months http://download-west.oracle.com/docs/cd/B19306_01/server.102/b14200/functions004.htm
If date is the last day of the month or if the resulting month has fewer days than the day component of date, then the result is the last day of the resulting month. Otherwise, the result has the same day component as date.
So I guess you have to manually check stating day and ending day to change the behaviour of the function. Or maybe by adding days instead of months. (But I didn't find a add_day function in the ref)
As a workaround, I might possibly use this algorithm:
Calculate the target date TargetDate1 using ADD_MONTHS.
Alternatively calculate the target date TargetDate2 like this:
1) apply ADD_MONTHS to the first of the source date's month;
2) add the difference of days between the source date and the beginning of the same month.
Select the LEAST between the TargetDate1 and TargetDate2.
So in the end, the target date will contain a different day component if the source date's day component is greater than the number of day in the target month. In this case the target date will be the last day of the corresponding month.
I'm not really sure about my knowledge of Oracle's SQL syntax, but basically the implementation might look like this:
SELECT
LEAST(
ADD_MONTHS(SourceDate, Months),
ADD_MONTHS(TRUNC(SourceDate, 'MONTH'), Months)
+ (SourceDate - TRUNC(SourceDate, 'MONTH'))
) AS TargetDate
FROM (
SELECT
TO_DATE('30-NOV-10', 'DD-MON-RR') AS SourceDate,
4 AS Months
FROM DUAL
)
Here is a detailed illustration of how the method works:
SourceDate = '30-NOV-10'
Months = 4
TargetDate1 = ADD_MONTHS('30-NOV-10', 4) = '31-MAR-11' /* unacceptable */
TargetDate2 = ADD_MONTHS('01-NOV-10', 4) + (30 - 1)
= '01-MAR-11' + 29 = '30-MAR-11' /* acceptable */
TargetDate = LEAST('31-MAR-11', '30-MAR-11') = '30-MAR-11'
And here are some more examples to show different cases:
SourceDate | Months | TargetDate1 | TargetDate2 | TargetDate
-----------+--------+-------------+-------------+-----------
29-NOV-10 | 4 | 29-MAR-11 | 29-MAR-11 | 29-MAR-11
30-MAR-11 | -4 | 30-NOV-10 | 30-NOV-10 | 30-NOV-10
31-MAR-11 | -4 | 30-NOV-10 | 01-DEC-10 | 30-NOV-10
30-NOV-10 | 3 | 28-FEB-11 | 02-MAR-11 | 28-FEB-11
You can use interval arithmetic to get the result you want
SQL> select date '2011-03-30' - interval '4' month
2 from dual;
DATE'2011
---------
30-NOV-10
SQL> ed
Wrote file afiedt.buf
1 select date '2010-11-30' + interval '4' month
2* from dual
SQL> /
DATE'2010
---------
30-MAR-11
Be aware, however, that there are pitfalls to interval arithmetic if you're working with days that don't exist in every month
SQL> ed
Wrote file afiedt.buf
1 select date '2011-03-31' + interval '1' month
2* from dual
SQL> /
select date '2011-03-31' + interval '1' month
*
ERROR at line 1:
ORA-01839: date not valid for month specified
How about something like this:
SELECT
LEAST(
ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR'),-4),
ADD_MONTHS(TO_DATE('30-MAR-11','DD-MON-RR')-1,-4)+1
)
FROM
DUAL
;
Result: 30-NOV-10
SELECT
LEAST(
ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4),
ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR')-1,4)+1
)
FROM
DUAL
;
Result: 30-MAR-11
the add_months function returns a date plus n months.
Since 30th November is the last date of the month, adding 4 months will result in a date that's the end of 4 months. This is expected behavior. If the dates are not bound to change, a workaround is to subtract a day after the new date has been returned
SQL> SELECT ADD_MONTHS(TO_DATE('30-NOV-10','DD-MON-RR'),4) -1 from dual;
ADD_MONTH
---------
30-MAR-11
SELECT TO_DATE('30-NOV-10','DD-MON-RR') +
(
ADD_MONTHS(TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM'),4) -
TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM')
) RESULT
FROM DUAL;
This section in paranthesis:
ADD_MONTHS(TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM'),4) - TRUNC(TO_DATE('30-NOV-10','DD-MON-RR'),'MM')
gives you number of days between the date you entered and 4 months later. So, adding this number of days to the date you given gives the exact date after 4 months.
Ref: http://www.dba-oracle.com/t_test_data_date_generation_sql.htm
Simple solution:
ADD_MONTHS(date - 1, x) + 1
Here is the trick:
select add_months(to_date('20160228', 'YYYYMMDD')-1, 1)+1 from dual;
Enjoy!
We have come to simpler (in our understanding) solution to this problem - take the least day number from original and add_month result dates, as this:
TRUNC(ADD_MONTHS(input_date,1),'MM') + LEAST(TO_CHAR(input_date, 'DD'), TO_CHAR(ADD_MONTHS(input_date,1), 'DD')) - 1
Some other examples here do not work on every date, below our test results:
WITH DATES as (
SELECT TO_DATE('2020-01-31', 'YYYY-MM-DD HH24:MI:SS') as input_date,
'2020-02-29' as expected_date
FROM dual
UNION ALL
SELECT TO_DATE('2020-02-28', 'YYYY-MM-DD HH24:MI:SS'),
'2020-03-28'
FROM dual
UNION ALL
SELECT TO_DATE('2020-09-30', 'YYYY-MM-DD HH24:MI:SS'),
'2020-10-30'
FROM dual
UNION ALL
SELECT TO_DATE('2020-09-01', 'YYYY-MM-DD HH24:MI:SS'),
'2020-10-01'
FROM dual
UNION ALL
SELECT TO_DATE('2019-01-30', 'YYYY-MM-DD HH24:MI:SS'),
'2019-02-28'
FROM dual
UNION ALL
SELECT TO_DATE('2020-02-29', 'YYYY-MM-DD HH24:MI:SS'),
'2020-03-29'
FROM dual
UNION ALL
SELECT TO_DATE('2020-09-29', 'YYYY-MM-DD HH24:MI:SS'),
'2020-10-29'
FROM dual
UNION ALL
SELECT TO_DATE('2020-03-01', 'YYYY-MM-DD HH24:MI:SS'),
'2020-04-01'
FROM dual
),
methods as (
SELECT
input_date,
expected_date,
ADD_MONTHS(input_date,1) as standard_way,
add_months(input_date-1, 1)+1 as wrong_way,
TO_DATE(LEAST(TO_CHAR(input_date, 'DD'), TO_CHAR(ADD_MONTHS(input_date,1), 'DD')) || '-' || TO_CHAR(ADD_MONTHS(input_date,1), 'MM-YYYY'), 'DD-MM-YYYY') as good_way,
TRUNC(ADD_MONTHS(input_date,1),'MM') + LEAST(TO_CHAR(input_date, 'DD'), TO_CHAR(ADD_MONTHS(input_date,1), 'DD')) - 1 as better_way
FROM
DATES
)
SELECT
input_date,
expected_date,
standard_way,
CASE WHEN TO_CHAR(standard_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as standard_way_ok,
wrong_way,
CASE WHEN TO_CHAR(wrong_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as wrong_way_ok,
good_way,
CASE WHEN TO_CHAR(good_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as good_way_ok,
better_way,
CASE WHEN TO_CHAR(better_way,'YYYY-MM-DD') = expected_date THEN 'OK' ELSE 'NOK' END as better_way_ok
FROM
methods
;
CREATE OR REPLACE FUNCTION My_Add_Month(
STARTDATE DATE,
MONTHS_TO_ADD NUMBER
)
RETURN DATE
IS
MY_ADD_MONTH_RESULT DATE;
BEGIN
SELECT ORACLES_ADD_MONTH_RESULT + NET_DAYS_TO_ADJUST INTO MY_ADD_MONTH_RESULT FROM
(
SELECT T.*,CASE WHEN SUBSTRACT_DAYS > ADD_DAYS THEN ADD_DAYS - SUBSTRACT_DAYS ELSE 0 END AS NET_DAYS_TO_ADJUST FROM
(
SELECT T.*,EXTRACT(DAY FROM ORACLES_ADD_MONTH_RESULT) AS SUBSTRACT_DAYS FROM
(
SELECT ADD_MONTHS(STARTDATE,MONTHS_TO_ADD) AS ORACLES_ADD_MONTH_RESULT,EXTRACT(DAY FROM STARTDATE) AS ADD_DAYS FROM DUAL
)T
)T
)T;
RETURN TRUNC(MY_ADD_MONTH_RESULT);
END My_Add_Month;
/
--test & verification of logic & function both
SELECT T.*,ORACLES_ADD_MONTH_RESULT + NET_DAYS_TO_ADJUST AS MY_ADD_MONTH_RESULT,
My_Add_Month(STARTDATE,MONTHS_TO_ADD) MY_ADD_MONTH_FUNCTION_RESULT
FROM
(
SELECT T.*,CASE WHEN SUBSTRACT_DAYS > ADD_DAYS THEN ADD_DAYS - SUBSTRACT_DAYS ELSE 0 END AS NET_DAYS_TO_ADJUST FROM
(
SELECT T.*,EXTRACT(DAY FROM ORACLES_ADD_MONTH_RESULT) AS SUBSTRACT_DAYS FROM
(
SELECT T.*,ADD_MONTHS(STARTDATE,MONTHS_TO_ADD) AS ORACLES_ADD_MONTH_RESULT,EXTRACT(DAY FROM STARTDATE) AS ADD_DAYS FROM
(
SELECT TO_DATE('28/02/2014','DD/MM/YYYY') AS STARTDATE, 1 AS MONTHS_TO_ADD FROM DUAL
)T
)T
)T
)T;
Query-result
STARTDATE 2/28/2014
MONTHS_TO_ADD 1
ORACLES_ADD_MONTH_RESULT 3/31/2014
ADD_DAYS 28
SUBSTRACT_DAYS 31
NET_DAYS_TO_ADJUST -3
MY_ADD_MONTH_RESULT 3/28/2014
MY_ADD_MONTH_FUNCTION_RESULT 3/28/2014