How to compare date to format date on oracle - sql

How can we compare a date to a format in Oracle?
Something like this: if MyDate is on format DD MONTH YYYY THEN /....
elsif MyDate is on format YYYY-MONTH-DD Then...
EDIT: My dates are in varchar2 and i want to keep them that way. I want just to know how to write a regex that would reprensent for example 10 October 2010.
Is it possible ? If it is a regex how would its format be please

Echoing what was mentioned in the comments to your question, best practice would be to have an actual DATE type field instead of VARCHAR2, and if you needed specific display formats, store those in another field as a format pattern. That said, you can use REGEXP_LIKE to check the format using the patterns in the below example.
with dateinfo as (
select 1 as id, '2018-MARCH-10' as dtString from dual
union all
select 2 as id, '10 MARCH 2018' as dtString from dual )
select id, dtString,
case
when regexp_like(dtString, '^[0-9]{4}-.[a-zA-Z]{3,}-.[0-9]{1,2}$') then 'format1'
when regexp_like(dtString, '^[0-9]{1,2} [a-zA-Z]{3,} [0-9]{4}$') then 'format2'
else 'no format'
end as dtFormat
from dateinfo;

Related

How to convert a date to a string

I want yo get only the 'date hours:minutes:seconds' from the Date column
Date
10/11/22 12:14:01,807000000
11/12/22 13:15:46,650000000
29/12/22 14:30:46,501000000
and I want to get a string column with date hours:minutes:seconds
Date_string
10/11/22 12:14:01
11/12/22 13:15:46
29/12/22 14:30:46
I tried this code but it doesn't work:
select*, TO_CHAR(extract(hour from (Date)))||':'||TO_CHAR(extract(minute from (Date)))||':'||TO_CHAR(extract(second from (Date))) as Date_string
from table;
If this is a date column, you could use to_char directly:
SELECT m.*, TO_CHAR(my_date_column, 'dd/mm/yy hh24:mi:ss')
FROM mytable m
You can use REGEX SUBSTRING function to get the date string on the left.
SELECT REGEXP_SUBSTR (Date_string, '[^,]+', 1, 1)
AS left_part
FROM Table1;
where ^, means look for chars that are NOT comma on 1st position
and get the first occurrence (on the left)
Result:
LEFT_PART
10/11/22 12:14:01
11/12/22 13:15:46
29/12/22 14:30:46
reference:
https://docs.oracle.com/cd/B12037_01/server.101/b10759/functions116.htm
Just do it with the TO_DATE() and TO_CHAR() function pair, both operating on the Oracle date format strings:
Building the scenario:
-- your input ..
WITH indata(dt) AS (
SELECT '10/11/22 12:14:01,807000000' FROM dual UNION ALL
SELECT '11/12/22 13:15:46,650000000' FROM dual UNION ALL
SELECT '29/12/22 14:30:46,501000000' FROM dual
)
-- end of your input. Real query starts here.
-- Change following comma to "WITH" ..
,
-- Now convert to TIMESTAMP(9) ...
as_ts AS (
SELECT
TO_TIMESTAMP(dt ,'DD/MM/YY HH24:MI:SS,FF9') AS ts
FROM indata
)
SELECT
ts
, CAST(ts AS TIMESTAMP(0)) AS recast -- note: this is rounded
, TO_CHAR(ts,'DD/MM/YY HH24:MI:SS') AS reformatted -- this is truncated
FROM as_ts
Result:
TS
RECAST
REFORMATTED
10-NOV-22 12.14.01.807000000
10-NOV-22 12.14.02
10/11/22 12:14:01
11-DEC-22 13.15.46.650000000
11-DEC-22 13.15.47
11/12/22 13:15:46
29-DEC-22 14.30.46.501000000
29-DEC-22 14.30.47
29/12/22 14:30:46
Going by what you have in your question, it appears that the data in the field Date is a timestamp. This isn't a problem, but the names of the table (TABLE) and field (Date) present some challenges.
In Oracle, TABLE is a reserved word - so to use it as the name of a table it must be quoted by putting it inside double-quotes, as "TABLE". Similarly, Date is a mixed-case identifier and must likewise be quoted (e.g. "Date") every time it's used.
Given the above your query becomes:
SELECT TO_CHAR("Date", 'DD/MM/YY HH24:MI:SS') AS FORMATTED_DATE
FROM "TABLE"
and produces the desired results. db<>fiddle here
Generally, it's best in Oracle to avoid using reserved words as identifiers, and to allow the database to convert all names to upper case - if you do that you don't have to quote the names, and you can refer to them by upper or lower case as the database automatically converts all unquoted names to upper case internally.

Oracle SQL Extract Year from VARCHAR2

Please don't roast me for not being able to figure this out. I'm a beginner with SQL and I've been trying to find a solution and make it work for at least a couple hours in total. I know the date fields should be stored as dates, not varchar2, but I have no control over that.
EXTRACT ( YEAR FROM ( TO_DATE ( 'ENTDAT', 'MM/DD/YYYY' ))) = 2018
"ORA-01858: a non-numeric character was found where a numeric was expected"
I feel like I'm close with this, just missing some magic and hoping for an assist here.
'ENTDAT' is a string literal.
ENTDAT (without the quotes) could be a column name.
So if your column name is ENTDAT you probably wanted:
SELECT *
FROM your_table
WHERE EXTRACT ( YEAR FROM ( TO_DATE ( ENTDAT, 'MM/DD/YYYY' ))) = 2018
If your string is in that format, just use like:
where enddate like '%2018'
Of course, you should be storing a date as a date. Then you can use:
where enddate >= date '2018-01-01' and
enddate < date '2019-01-01'

REGEX Date Match Format

I currently have a dataset with varying date entries (and a mixture of string entries) for which I need to parse. There are a few: 'M/DD/YY', 'M/D/YY', 'MM/DD/YY', 'MM/D/YY', 'MM/DD/YYYY'...). I could use some support with improving my regex to handle the varying formats and possible text entered in the date field.
My current Postgres query breaks out other entries into another column and reformats the date. Although, I've increased the year to 4 digits rather than 2, I believe the issue may live somewhere in the 'YYYY-MM-DD' formatting or that my query does not properly accommodate additional formatting within.
CASE WHEN date ~ '^\\\\d{1,2}/\\\\d{1,2}/\\\\d{4}$' THEN TO_DATE(date::date, 'YYYY-MM-DD')
ELSE NULL END AS x_date,
CASE WHEN NOT date ~ '^\\\\d{1,2}/\\\\d{1,2}/\\\\d{4}$' AND date <> '' THEN date
ELSE NULL END AS x_date_text
For the various date formats, they should be reformatted accordingly and for other non-date values, they should be moved over to the other column.
Based on your list of formats, I believe that just two regexes should be enough to check the values:
'^[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}/$' would map to date format 'MM/DD/YYYY'
'^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2}/$' would map to 'MM/DD/YY'
You can use a CASE construct to check the value against the regex and apply the proper mask when using TO_DATE().
However, since you need to split the data over two columns, you would need to tediously repeat the CASE expression twice, one for each column.
One way to simplify the solution (and to make it easier to maintain afterwards) would be to use a CTE to list the regexes and the associated date format. You can LEFT JOIN the CTE with the table.
Consider the following query:
WITH vars AS (
SELECT '^[0-9]{1,2}/[0-9]{1,2}/[0-9]{4}/$' reg, 'MM/DD/YYYY' format
UNION ALL '^[0-9]{1,2}/[0-9]{1,2}/[0-9]{2}/$', 'MM/DD/YY'
)
SELECT
CASE WHEN vars.reg IS NOT NULL THEN TO_DATE(t.date, vars.format) END x_date,
CASE WHEN vars.reg IS NULL THEN t.date END x_date_text
FROM
mytable t
LEFT JOIN vars ON t.date ~ vars.reg
If more regex/format pairs are needed, you just have to expand the CTE. Just pay attention to the fact that regexes should be exclusives (ie two different regexes should not possibly match on a single value), else you will get duplicated records in the result.
While the regex by #GMB insures format validity it passes many invalid dates, and with liberal to_date conversion by Postgres could introduce errors and or confusion. Run the following to see the liberal conversion:
set datestyle = 'ISO';
select dd,'01/' || dd || '/2019' mmddyyyy, to_date ( '01/' || dd || '/2019', 'mm/dd/yyyy')
from ( select generate_series( 0,40)::text dd) d;
select mm , mm ||'/01/2019' mmddyyyy, to_date ( mm ||'01/2019', 'mm/dd/yyyy')
from ( select generate_series( 0,40)::text mm) d;
If that liberal date conversion is acceptable - Great. But if not we can tighten it down considerable (although still not 100% valid results). Lets break the format down:
for date formats mm/dd/yyyy or mm/dd/yy
breakdown MM valid 1 - 12
valid character 0 followed by 1-9
1 followed by 0-2
regex (0?[1-9]|1[0-2)
DD valid 0 - 31 (sort of)
day 31 valid for April, June, Sep, Nov also evaluate valid but become
day 1 of May, July, Oct, Dec respectivally
days 29-31 of Feb also eveluate valid but become day
1-3 of march and 1-2 in lead yearsin non-leap years
valid character optional 0 followed by 1-9
1-2 followed by 0-9
3 followed by 0-1
regex (0?[1-9]|[1-2][0-9]|3[0-2])
YEAR valid 1900 - 2999 (no ancient history)
valid character 1-2 followed by 0-9,0-9,0-9
0-9,0-9
Now putting that together we get.
-- setup
drop table if exists my_dates;
create table my_dates(test_date text, status text);
insert into my_dates (test_date, status)
values ('01/15/2019', 'valid')
, ('12/25/0001', 'invalid year < 1900')
, ('12/01/2020', 'valid')
, ('oops', 'yea a date NOT')
, ('6/3/19', 'valid')
, ('2/29/2019', 'valid sort of, Postgres liberal evaluation of to_date')
, ('2/30/2019', 'valid sort of, Postgres liberal evaluation of to_date')
, ('2/31/2019', 'valid sort of, Postgres liberal evaluation of to_date')
, ('2/29/2020', 'valid')
, ('14/29/2020', 'invalid month 14')
, ('01/32/2019', 'invalid day 32')
, ('04/31/2019', 'valid sort of, Postgres liberal evaluation of to_date')
;
-- as query
set datestyle = 'ISO';
with patterns (pat, fmt) as (values ('^(0?[1-9]|1[0-2])/(0?[1-9]|[1-2][0-9]|3[0-1])/[12][0-9]{3}$'::text, 'mm/dd/yyyy')
, ('^(0?[1-9]|1[0-2])/(0?[1-9]|[1-2][0-9]|3[0-1])/[0-9]{2}$'::text, 'mm/dd/yy')
)
select to_date(test_date, fmt),status, test_date, pat, fmt
from my_dates
left join patterns on test_date ~ pat;
------------------------------------------------------------------
-- function accessable from SQL
create or replace function parse_date(check_date_in text)
returns date
language sql
as $$
with patterns (pat, fmt) as (values ('^(0?[1-9]|1[0-2])/(0?[1-9]|[1-2][0-9]|3[0-1])/[12][0-9]{3}$'::text, 'mm/dd/yyyy')
, ('^(0?[1-9]|1[0-2])/(0?[1-9]|[1-2][0-9]|3[0-1])/[0-9]{2}$'::text, 'mm/dd/yy')
)
select to_date(check_date_in, fmt)
from patterns
where check_date_in ~ pat;
$$;
--- test function
select test_date, parse_date(test_date), status from my_dates;
-- use demo
select * from my_dates
where parse_date(test_date) >= date '2020-01-02';

Oracle sql - convert string to date

i am having problems with converting a varchar(yyyymmdd) to date(yyyymmdd).
i have a procedure with a parameter (pdate varchar2, yyyymmdd format) which needed to be converted to date type in yyyymmdd format as well.
so far i tried.
vdate date;
vdate := (to_date(SUBSTR(pdate,1,4)+SUBSTR(pdate,5,2)+SUBSTR(pdate,7,2), 'yyyymmdd'));
this threw a error of ORA-01840: input value not long enough for date format.
any help would be appreciated.
Just use a to_date
select to_date(MyDate, 'yyyymmdd')
from mytable
Test with:
select to_date('20170831','yyyymmdd')
from dual
Also, to concatenate in Oracle, use a double pipe ||
select 'Chicken'||'Food'
from dual
If pdate has other characters after yyyymmdd, and yyyymmdd is in the beginning of the whole text, you can just use
SELECT TO_DATE(SUBSTR(pdate,1,8), 'yyyymmdd')
FROM yourtable;
Example
SELECT TO_DATE(SUBSTR('20170831 10:30am',1,8), 'yyyymmdd')
FROM dual;
Otherwise, you can directly use TO_DATE() as suggested by most that replied

Selecting YYYYMM of the previous month in HIVE

I am using Hive, so the SQL syntax might be slightly different. How do I get the data from the previous month? For example, if today is 2015-04-30, I need the data from March in this format 201503? Thanks!
select
employee_id, hours,
previous_month_date--YYYYMM,
from
employees
where
previous_month_date = cast(FROM_UNIXTIME(UNIX_TIMESTAMP(),'yyyy-MM-dd') as int)
From experience, it's safer to use DATE_ADD(Today, -1-Day(Today)) to compute last-day-of-previous-month without having to worry about edge cases. From there you can do what you want e.g.
select
from_unixtime(unix_timestamp(), 'yyyy-MM-dd') as TODAY,
date_add(from_unixtime(unix_timestamp(), 'yyyy-MM-dd'), -1-cast(from_unixtime(unix_timestamp(), 'd') as int)) as LAST_DAY_PREV_MONTH,
substr(date_add(from_unixtime(unix_timestamp(), 'yyyy-MM-dd'), -1-cast(from_unixtime(unix_timestamp(), 'd') as int)), 1,7) as PREV_MONTH,
cast(substr(regexp_replace(date_add(from_unixtime(unix_timestamp(), 'yyyy-MM-dd'), -1-cast(from_unixtime(unix_timestamp(), 'd') as int)), '-',''), 1,6) as int) as PREV_MONTH_NUM
from WHATEVER limit 1
-- today last_day_prev_month prev_month prev_month_num
-- 2015-08-13 2015-07-30 2015-07 201507
See Hive documentation about date functions, string functions etc.
below works across year boundaries w/o complex calcs:
date_format(add_months(current_date, -1), 'yyyyMM') --previous month's yyyyMM
in general,
date_format(add_months(current_date, -n), 'yyyyMM') --previous n-th month's yyyyMM
use proper sign for needed direction (back/ahead)
You could do (year('2015-04-30')*100+month('2015-04-30'))-1 for the above mentioned date, it will return 201503 or something like (year(from_unixtime(unix_timestamp()))*100+month(from_unixtime(unix_timestamp())))-1 for today's previous month. Assuming your date column is in 'yyyy-mm-dd' format you can use the first example and substitute the date string with your table column name; for any other format the second example will do, add the column name in the unix_timestamp() operator.
Angelo's reply is a good start but it returns 201500 if the original date was 2015-01-XX. Building on his answer, I suggest using the following:
IF(month(${DATE}) = 1,
(year(${DATE})-1)*100 + 12,
year(${DATE})*100 + month(${DATE})-1
) as month_key
provided you get rid of those hyphens in your input string , previous date's month id in YYYYMM format you can get by:-
select if( ((${hiveconf:MonthId}-1)%100)=0 ,${hiveconf:MonthId}-89,${hiveconf:MonthId}-1 ) as PreviousMonthId;