Find date in a specific week - sql

I would like to select an item where the date is in a specific week in the year.
For example :
SELECT item
FROM table
WHERE my_date in week 44
Where week 44 is from monday 31/10 to sunday 06/11.
I know to get the week number with
to_number(to_char(to_date(my_date,'MM/DD/YYYY'),'IW'))
How can I do that ?
Thanks.

Try:
SELECT item
FROM table
WHERE to_char( my_date, 'IW ) = '44'
See this link for details:
https://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34924
IW ---> Week of year (1-52 or 1-53) based on the ISO standard.

Working with ISO Weeks is not trivial because week 1 can start in previous year and first days in January may be counted as week 52 or 53.
So, providing just week number without a year can be ambiguous (for week number 52, 53, 1).
The best function I found in order to get the first day of an ISO-Week is
NEXT_DAY(TO_DATE( yearNo || '0104', 'YYYYMMDD' ) - INTERVAL '7' DAY, 'MONDAY') + ( weekNo - 1 ) * 7
So, for your need it would be
SELECT item
FROM table
WHERE my_date
between NEXT_DAY(TO_DATE( yearNo || '0104', 'YYYYMMDD' ) - INTERVAL '7' DAY, 'MONDAY') + ( weekNo - 1 ) * 7
AND 6 + (NEXT_DAY(TO_DATE( yearNo || '0104', 'YYYYMMDD' ) - INTERVAL '7' DAY, 'MONDAY') + ( weekNo - 1 ) * 7)
Actually the "full error proven" way would be this one:
FUNCTION ISOWeekDate(weekNo INTEGER, yearNo INTEGER) RETURN DATE DETERMINISTIC IS
res DATE;
BEGIN
IF weekNo > 53 OR weekNo < 1 THEN
RAISE VALUE_ERROR;
END IF;
res := NEXT_DAY(TO_DATE( yearNo || '0104', 'YYYYMMDD' ) - INTERVAL '7' DAY, 'MONDAY') + ( weekNo - 1 ) * 7;
IF TO_CHAR(res, 'fmIYYY') = yearNo THEN
RETURN res;
ELSE
RAISE VALUE_ERROR;
END IF;
END ISOWeekDate;

If the data type of the my_date column is, in fact, VARCHAR2 (which it shouldn't be, but it often is), then you do need to convert it to date first, and then back to char (string) in a different format. Then, if 44 is not necessarily a number but it may be the result of a calculation, or a subquery, or a value in another table (and if it is of NUMBER data type), then you need to do exactly what you were doing. What was the problem with it?
select item
from your_table
where to_number(to_char(to_date(my_date,'MM/DD/YYYY'),'IW')) = 44;
Perhaps you want to know if this can be done more efficiently? So, for example, can you instead write the where condition as
my_date between (something) and (something else)
which would allow the use of an index on my_date? The answer is NO, if my_date is in VARCHAR2 format - you would still have to wrap it within to_date() so you would lose the index.
But, you may build an index function on to_date(my_date) which perhaps would help you in other queries as well. So then the question of making your present query more efficient is meaningful. Now you run into the complication of ISO week not being well defined (you are not also saying which YEAR), so the assumption is that you want the date to be in ISO week 44 of whatever year it is in. If the date is a pure date (time component equal to 00:00:00), you could do this:
where to_date(my_date, 'MM/DD/YYYY') between
trunc(sysdate, 'iw') + 7 * (44 - to_number(to_char(sysdate, 'iw'))) and
trunc(sysdate, 'iw') + 7 * (44 - to_number(to_char(sysdate, 'iw'))) + 6
There are a lot of computation on the right-hand side, but they are done just once for all the rows in your table; they will take essentially no time at all.

Related

SQL - Julien Date (CYYDDD) to date

Unfortunately, this is my first approach with SQL!
I am creating with the following code a query between an oracle DB and Excel (Power Query).
select "$Table"."Order" as "Order",
"$Table"."NR" as "Nr",
"$Table"."JDDATE" as "JDDATE"
from "POOLDB2"."3112" "$Table"
WHERE "Key" >118001
AND "CodeAA" = 1
This code works!
Now I want to format the Julian Date (CYYDDD) - for example 118001 for the 01.01.2019 - to a normal date format.
Does anyone know, how to implement this into the code above?
Maybe something like :
select "$Table"."Order" as "Order",
"$Table"."NR" as "Nr",
DATEADD(DAY, JDDATE % 1000 - 1, DATEADD(year, JDDATE/1000, 0))
"$Table"."JDDATE" as "JDDATE"
from "POOLDB2"."3112" "$Table"
WHERE "Key" >118001
AND "CodeAA" = 1
Best regards
There are many different formats for Julian Date... In your use case, this should do it :
with t as (select 118001 jd from dual)
select to_char( to_date(to_char(1901 + floor(jd / 1000)),'YYYY') + mod(jd,1000) - 1, 'dd.mm.yyyy' ) from t
Yields : 01.01.2019
For Oracle,
select to_char(sysdate,'J') from dual; --To Julian Date
select to_date(2456143,'J') from dual; --To Normal Date
must work.
Edit: Sorry I didn't see oracle tag.
Edit: For the requested behavior by OP
select to_date(to_char(1901 + floor(118001 / 1000)),'YYYY') from dual;
You can use the 118001 value you have, split into separate year and day sections, by adding to the nominal starting date 1900-01-01 (based on your comment that 118001 is actually 2018-01-01, not 2019-01-01):
select date '1900-01-01'
+ floor(118001 / 1000) * interval '1' year
+ (mod(118001, 1000) - 1) * interval '1' day
from dual;
DATE'1900-
----------
2018-01-01
or by startng the fixed date a day earlier you can remove the explicit -1:
select date '1899-12-31'
+ floor(118019 / 1000) * interval '1' year
+ mod(118019, 1000) * interval '1' day
from dual;
DATE'1899-
----------
2018-01-19
This avoids having to build up a longer string to convert to a date, though you could do that (modifying #GMB's approach) as:
select to_date(to_char(1900 + floor(118001 / 1000)) || '-01-01', 'YYYY-MM-DD')
+ (mod(118001, 1000) - 1)
from dual;
You need to specify the month, at least, in the to_date() call as Oracle defaults to the current month if that is not supplied. That behaviour is tucked away in the documentation:
If you specify a date value without a time component, then the default time is midnight. If you specify a date value without a date, then the default date is the first day of the current month.
The first part of that is fairly well known and makes sense ; the second part is a bit less obvious, and doesn't make it clear that it applies to partial dates too - so ifyou don't supply a year then the current year is used; if you don't supply a month then the current month is used; but if you don't supply a day then the 1st is used.
You can see what it's doing with some test conversions:
select to_date('2018-12-25', 'YYYY-MM-DD') as demo_a,
to_date('12:34:56', 'HH24:MI:SS') as demo_b,
to_date('2019', 'YYYY') as demo_c,
to_date('07-04', 'MM-DD') as demo_d,
to_date('2019-01', 'YYYY-MM') as demo_e
from dual;
DEMO_A DEMO_B DEMO_C DEMO_D DEMO_E
------------------- ------------------- ------------------- ------------------- -------------------
2018-12-25 00:00:00 2018-12-01 12:34:56 2019-12-01 00:00:00 2018-07-04 00:00:00 2019-01-01 00:00:00

Oracle SQL: Syntax for WHERE clause with one date between two others

Apologies in advance, feel like I'm missing something fundamental here.
I'm limiting a query WHERE one field (already in date format) is between two others:
SELECT Stuff
FROM Table
WHERE datefield BETWEEN (currentdate - 28) AND (current_date - 1)
This returns nothing. Now if I format both as dates explicitly and look for a single date:
SELECT Stuff FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') = TO_DATE((current_date - 1), 'YYYY-
MM-DD')
That returns the single day's results as intended. However, if I then try something along the lines of:
SELECT Stuff FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') >= TO_DATE((current_date - 28),'YYYY-
MM-DD')
This returns the entire table, including dates from long before (current_date - 28)
And finally if I try the BETWEEN after explicitly formatting:
SELECT Stuff FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') BETWEEN TO_DATE((current_date - 28)
,'YYYY-MM-DD') AND TO_DATE((current_date - 1) ,'YYYY-MM-DD')
This returns nothing again.
Any help would be much appreciated.
SELECT Stuff
FROM Table
WHERE datefield BETWEEN (currentdate - 28) AND (current_date - 1)
Will return values between the 28 days before the current date at the same time of day as now and 1 day before the current date at the same time of day as now. So, if you run this at 10:23 today then it will get results before 10:23 yesterday and if your results for yesterday were all entered in the afternoon then they will not be included in the results.
SELECT Stuff
FROM Table
WHERE TO_DATE(datefield, 'YYYY-MM-DD') = TO_DATE((current_date - 1), 'YYYY-MM-DD')
TO_DATE( date_string, format_model ) takes a string as the first argument so Oracle will implicitly convert your date to a string using the NLS_DATE_FORMAT session parameter. So your query is effectively:
SELECT Stuff
FROM Table
WHERE TO_DATE(
TO_CHAR(
datefield,
( SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_DATE_FORMAT' )
),
'YYYY-MM-DD'
)
=
TO_DATE(
TO_CHAR(
(current_date - 1),
( SELECT VALUE FROM NLS_SESSION_PARAMETERS WHERE PARAMETER = 'NLS_DATE_FORMAT' )
),
'YYYY-MM-DD'
)
Firstly, don't ever rely on an implicit conversion. Secondly, depending on what your NLS_DATE_FORMAT session parameter is your query will either: raise an exception if the format model does not match YYYY-MM-DD; or give you gibberish dates (i.e. if your default format model is RR-MM-DD then you'll lose the centuries from your dates; if it is DD-MM-YY then you'll swap days and years - which might "work" [i.e. not raise an exception] for the next decade or so and then blow up horribly in February 2029); or it will work. However, this is a session parameter so users can change it and your query will randomly stop working without changing your SQL.
Assuming that your NLS_DATE_FORMAT is YYYY-MM-DD then this will effectively truncate your dates and you could do it much simpler using:
SELECT Stuff
FROM Table
WHERE TRUNC(datefield) = TRUNC(current_date - 1)
This will get all the results (regardless of time of day) for yesterday.
What you probably want is:
SELECT Stuff
FROM Table
WHERE datefield >= TRUNC( currentdate - 28 )
AND datefield < TRUNC( current_date )
As it will get all the results from Midnight 28 days ago to before midnight today.
If this doesn't work then you need to check your date values:
SELECT TO_CHAR( datevalue, 'YYYY-MM-DD HH24:MI:SS' )
FROM table;
You will possibly find that the dates are stored with the year in the 1st century AD (i.e. 0018-10-24 09:50:00 as the value) and this will (probably) be because dates were uploaded as a string with a 2-digit year (YY-MM-DD) when a 4-digit year (YYYY-MM-DD) was expected so Oracle will assume the century is 0.

Oracle SQL Create Date from Parts

I have two date fields in a database called "EFFECTIVE_DATE" and "POINT_DATE"
How do I create a new date field, where the date is made up from the year of "EFFECTIVE_DATE", the month of "POINT_DATE" and the day of "POINT_DATE" ?
I would normally use Datefromparts, but this is an Oracle Database not Microsoft
Kind Regards
Here's an approach using ADD_MONTHS and EXTRACT(YEAR FROM ....). You simply add or subtract the needed number of months (always a multiple of 12, since you are only changing the year). Unlike the TO_CHAR / TO_DATE solutions, this approach handles leap days (Feb. 29). On the other hand, be advised that changing the date from 28 Feb. 2003 to the year 2012 will change it to Feb. 29 (ADD_MONTHS changes the last day of a month to the last day of the resulting month).
with
inputs as (
select date '2013-03-22' as effective_date,
date '2017-08-14' as point_date
from dual
)
-- end of TEST data (do not include in the solution!)
select effective_date, point_date,
add_months(point_date, 12 * ( extract (year from effective_date) -
extract (year from point_date) )
) as mixed_date
from inputs;
EFFECTIVE_DATE POINT_DATE MIXED_DATE
-------------- ---------- ----------
03/22/2013 08/14/2017 08/14/2013
Hmmm . . . this produces a nice string in the YYYY-MM-DD format:
select to_char(effective_date, 'YYYY') || '-' || to_char(point_date, 'MM-DD')
And this parses it back to a date:
select to_date(to_char(effective_date, 'YYYY') || '-' || to_char(point_date, 'MM-DD'), 'YYYY-MM-DD')
Note: You might want to be careful about Feb 29th.
Assuming all coulmns are DATE data types, you can use this one
TO_DATE(TO_CHAR(EFFECTIVE_DATE, 'YYYY') || TO_CHAR(POINT_DATE, 'MMDD'), 'YYYYMMDD')
Be aware of leap years!

In Oracle SQL, how to get the time for only this current week?

I have a query , where I want to obtain some data for different time durations (this month, this week, etc).
For the "this week" column, I want it to get all the data from the most recent Monday until now. How would I do this?
I have the following SQL so far :
WHERE prla.CREATION_DATE >= SYSDATE - ?
trunc(sysdate, 'iw') is what you're after. IW is the format mask used for Monday of the week the specified date is in (as Monday is the ISO standard for the start of the week). E.g.:
with dates as (select trunc(sysdate, 'mm') - 10 + level dt
from dual
connect by level <= 40)
select dt
from dates
where dt >= trunc(sysdate, 'iw')
and dt <= sysdate; -- only needed if the dates in the column could be beyond now.
Yeah that will do: But it is better to use sysdate-8. Because if the current day is same as your searching day, it will return the current date. For Eg.
select next_day(sysdate-7,'WED') from dual;
OUTPUT
19-AUG-15
Whereas the below one will give you the last week
select next_day(sysdate-8,'WED') from dual;
OUTPUT
12-AUG-15
You should truncate the current date.
TRUNC(SYSDATE, 'DAY')
This should give you the first day of the week, which is Monday in lot of countries.
If it's giving you the previous Sunday instead you should do this.
TRUNC(SYSDATE, 'DAY')+1
I found out to do this now:
select next_day (sysdate-7, 'MONDAY') Last_Monday from dual;
So in my case, we can remove the SYSDATE subtraction and it is simply :
prla.CREATION_DATE >= next_day (sysdate-7, 'MONDAY')
source

How to convert a sysdate month value to number in oracle?

Im trying to return the CARDS of my CARD table that will expire in the next month. But the problem is that the table has two columns to represent the card date. The columns are EXPIREDAY and EXPIREMONTH ,both are numbers. So when i do that query i get an error:
select * from CARD WHERE EXPIREDAY <= sysdate - interval '2' DAY;
//Oracle error: ORA-00932: inconsistent datatypes: expected NUMBER got DATE
Is there a way to convert the sysdate - interval '2' DAY as Number data type?
Thanks!
If you want to compare the values as strings you can use this to convert the SYSDATE
SELECT TO_CHAR(sysdate, 'MM') || TO_CHAR(sysdate, 'DD') MONTH_NUM FROM DUAL
-- gives you "0922"
and this for your numeric columns which will pad with leading zeros if you only have a single digit
SELECT TO_CHAR(9, 'FM00') || TO_CHAR(22, 'FM00') MONTH_NUM FROM DUAL
-- also gives you "0922"
If you have control over the table schema it would be best practise to store both the DAY and MONTH values in a single numeric field, so that 9-SEP would be stored in this column as the numeric value 0922 where the month is first so that the natural ordering is used.
A simple and not necessarily very efficient approach is to convert the day and month values into an actual date, using to_date(), and then compare that with your target date range:
select * from card
where to_date(lpad(expireday, 2, '0')
||'/'|| lpad(expiremonth, 2, '0'), 'DD/MM')
between sysdate and add_months(sysdate, 1);
Which appears to work. But this will have problems if the dates span the end of the year. Because your table doesn't specify the year, you either have to work one out, or allow to_date to default it to the current year. And if you let it default then it won't work. For example, if you have values for December and January in your table, and run this query in December, then the January dates will be seen as January 2014, and won't be counted as being in the next month. So you'll need to do more to pick the right year.
This treats any month numbers before the current one as being next year, which may be good enough for you as you only have a one-month window:
select * from card
where to_date(lpad(expireday, 2, '0')
||'/'|| lpad(expiremonth, 2, '0')
||'/'|| (extract(year from sysdate) +
case when expiremonth < extract(month from sysdate) then 1 else 0 end),
'DD/MM/YYYY')
between sysdate and add_months(sysdate, 1);
SQL Fiddle using a date range from December to January.
And you can see the ways the two columns are being combined to form a date in this Fiddle.
As so often, the moral is... store things as the right data type. Store dates as dates, not as string or numbers.
Im trying to return the CARDS of my CARD table that will expire in the next month. But the problem is that the table has two columns to represent the card date.
Assuming:
you are using floating months (say: from 23 dec. to 23 jan.) and
your table somehow only contains one (floating ?) year of data
Why can't you use simple arithmetics? Like that:
-- some constant definitions for testing purpose
with cst as (
select EXTRACT(DAY from TO_DATE('23/12','DD/MM')) as theDay,
EXTRACT(MONTH from TO_DATE('23/12','DD/MM')) as theMonth
from dual)
-- the actual query
select card.* from card,cst
where (expiremonth = theMonth AND expireday > theDay)
or (expiremonth = 1+MOD(theMonth,12) AND expireday <= theDay);
-- ^^^^^^^^^^^^^^^^^^
-- map [01 .. 12] to [02 .. 12, 01] (i.e.: next month)
This will simply select all "pseudo-dates" from tomorrow to the end of the month, as well as any one before (and including) the current day# next month.
See this example.
For something a little bit more generic, but probably more efficient than converting all your values TO_DATE, you might want to try that:
-- the calendar is the key part of the query (see below)
with calendar as (
select extract(month from sysdate + level) as theMonth,
extract(day from sysdate + level) as theDay
from DUAL connect by ROWNUM <= 8)
-- ^
-- adjust to the right number of days you are looking for
select card.* from card join calendar
on expiremonth = theMonth and expireDay = theDay
The idea here is to simply build a calendar with all the upcoming days and then join your data table on that calendar. See an example here.
Try using to_char(sysdate - interval '2' DAY,'ddmmyyyy') to convert to character type. The date format('ddmmyyyy') will depend of the value of expiredate