Case just give me the same date :( - sql

This CASE only shows me the same date '01 / 01/2999 ', I need to get this date '01 / 01/2999' only when the date is less '01 / 01/1950 '.
If someone can help me, I feel so grateful
to_char((
CASE
WHEN to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY') <= to_date('01/01/1950','DD/MM/YYYY')
THEN
to_date('01/01/2999','DD/MM/YYYY')
ELSE
to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY')
END) ,'DD/MM/YYYY')
as FechaEntregaRemesa

This is because you're doing implicit date conversions, and your NLS_DATE_FORMAT has a two-digit year, e.g. 'DD/MM/YY' or this one:
alter session set nls_date_format = 'DD-Mon-RR';
-- CTE to generate two dummy values
with RM (REME_FECHA_ENTREGA) as (
select date '1950-01-01' from dual
union all select date '1950-01-02' from dual
)
-- your query plus the original date value
select to_char(RM.REME_FECHA_ENTREGA, 'SYYYY-MM-DD') as REME_FECHA_ENTREG,
to_char((
CASE
WHEN to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY') <= to_date('01/01/1950','DD/MM/YYYY')
THEN
to_date('01/01/2999','DD/MM/YYYY')
ELSE
to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY')
END) ,'DD/MM/YYYY')
as FechaEntregaRemesa
from rm;
REME_FECHA_ FECHAENTRE
----------- ----------
1950-01-01 01/01/2999
1950-01-02 01/01/2999
With a 4-digit format it works as you expect:
alter session set nls_date_format = 'DD/MM/YYYY';
...
REME_FECHA_ FECHAENTRE
----------- ----------
1950-01-01 01/01/2099
1950-01-02 02/01/1950
When you do
to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY')
you're actually really doing:
to_date(to_char(RM.REME_FECHA_ENTREGA, '<NLS_DATE_FORMAT>'),'DD/MM/YYYY')
which is why you see the issue with a 2-digit year model. The implicit to_char(RM.REME_FECHA_ENTREGA, 'DD-MON-RR') will give a string like '01-JAN-50'. When you pass that string back into to_date() with a YYYY format model it's interpreted as year 0050, so it's always going to be before 1950.
"Oracle Database converts strings to dates with some flexibility", which is not always helpful... here it isn't complaining that you've supplied two digits for a 4-digit model, or that (in my example) you've passed a month abbreviation instead of a month number. (You can change the behaviour with the FM/FX modifiers, but it's flexible by default.)
You may be tempted to use alter session as I did for this demo, but shouldn't rely on NLS settings as you usually have no control over what other users' settings will be. (I suspect you didn't realise you were in this query, but it's still something to watch out for.) Always use explicit conversion and full format models when you have to convert dates to strings and vice versa.
But you don't need to convert your existing date value at all. It's a date, so leave it alone. You can also use date literals for the fixed values to avoid any ambiguity and for a bit less typing:
to_char(
CASE
WHEN RM.REME_FECHA_ENTREGA <= date '1950-01-01'
THEN
date '2099-01-01'
ELSE
RM.REME_FECHA_ENTREGA
END,'DD/MM/YYYY')
as FechaEntregaRemesa
...
REME_FECHA_ FECHAENTRE
----------- ----------
1950-01-01 01/01/2099
1950-01-02 02/01/1950
or even (modifying #expenguin's suggestion) only using dates when you have to:
CASE
WHEN RM.REME_FECHA_ENTREGA <= date '1950-01-01'
THEN
'01/01/2099'
ELSE
TO_CHAR(RM.REME_FECHA_ENTREGA, 'DD/MM/YYYY')
END as FechaEntregaRemesa

EDIT: so it looks like you don't even need the to_char if your data is already stored as text, and you shouldn't need it for comparing. Converting to a date is the only thing you should need for comparing the string literal.
so we take OP's Code:
to_char((
CASE
WHEN to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY') <= to_date('01/01/1950','DD/MM/YYYY')
THEN
to_date('01/01/2999','DD/MM/YYYY')
ELSE
to_date(RM.REME_FECHA_ENTREGA,'DD/MM/YYYY')
END) ,'DD/MM/YYYY')
as FechaEntregaRemesa
And change below
IF REME_FECHA_ENTREGA IS A DATE:
CASE
WHEN RM.REME_FECHA_ENTREGA <= date '1950-01-01'
THEN
'01/01/2099'
ELSE
to_char(RM.REME_FECHA_ENTREGA, 'MM/DD/YYYY')
END as FechaEntregaRemesa
IF REME_FECHA_ENTREGA IS A STRING:
CASE
WHEN to_date(RM.REME_FECHA_ENTREGA, 'DD/MM/YYYY') <= date '01/01/1950'
THEN
'01/01/2999'
ELSE
RM.REME_FECHA_ENTREGA
END as FechaEntregaRemesa

Related

Oracle SQL TO_DATE - two date formats

I am trying to update several dates to the end of the month. However, my table has two separate date formats ('DD-MON-YY' and 'YYYYMMDD'). How can I update both dates in an update statement? Also, I want the new dates to be in 'YYYYMMDD' format.
Update MY_TABLE
set MY_DATE = TO_CHAR(LAST_DAY(TO_DATE(MY_DATE,'DD-MON-YY')),'YYYYMMDD');
As David noted in the comments, the "real" solution here would be to add a date column and use that. If that's not an option, you could differentiate old data from new data based on the existence of the - character:
UPDATE my_table
SET my_date =
TO_CHAR(LAST_DAY(TO_DATE(my_date, CASE WHEN my_date LIKE '%-%'
THEN 'DD-MON-YY'
ELSE 'YYYYMMDD'
END)),
'YYYYMMDD')

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';

How to compare date to format date on oracle

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;

Case statement with date and string

I have the following error, ORA-01841
(full) year must be between -4713 and +9999 and not be 0
The error is coming from the below case statement.
Any help on what's going on and how to fix?
SQL
SELECT
CASE
WHEN NVL(uap.us_pend_dt, act_d_dt) >= TO_CHAR(TO_DATE('".PENDING_DATE_CUTOFF."','YYYY-MM-DD'),'mm/dd/yyyy')
THEN NVL(uap.us_pend_dt, act_d_dt)
ELSE CASE WHEN NVL(act_d_dt, SYSDATE) <TO_CHAR(TO_DATE('".HIRE_DATE_CUTOFF."','YYYY-MM-DD'),'mm/dd/yyyy')
THEN act_d_dt
ELSE ua_dt
END
END AS h_DT
It looks like you are trying to convert a string ".PENDING_DATE_CUTOFF." to a date. I just did the following and got an identical error:
SELECT TO_DATE('".PENDING_DATE_CUTOFF."', 'YYYY-MM-DD') FROM dual;
Is .PENDING_DATE_CUTOFF. (with periods) the name of a column in your table? If so then omit the single quote characters, e.g.:
SELECT TO_DATE(".PENDING_DATE_CUTOFF.", 'YYYY-MM-DD') FROM dual;
Of course this will give an altogether different error [ORA-00904: ".PENDING_DATE_CUTOFF.": invalid identifier] if you run as is! So I think you might want something like the following (I'm assuming the other date columns are actual dates, and the cutoff columns are VARCHAR2 columns that are storing dates in the format YYYY-MM-DD:
SELECT CASE WHEN COALESCE(uap.us_pend_dt, act_d_dt) >= TO_DATE(".PENDING_DATE_CUTOFF.", 'YYYY-MM-DD') THEN COALESCE(uap.us_pend_dt, act_d_dt)
ELSE WHEN COALESCE(act_d_dt, SYSDATE) < (TO_DATE(".HIRE_DATE_CUTOFF.", 'YYYY-MM-DD') THEN act_d_dt
ELSE act_d_dt
END AS h_dT
FROM mytable;
Note that I also got rid of the extraneous CASE statement and converted the Oracle-specific NVL() function to the ANSI-standard COALESCE() function.
EDIT: In the event that your *_dt columns are strings and not dates, best to convert them to dates using TO_DATE() before comparing - that way you're comparing dates to dates.
Hope this helps.
Presumably, your date columns are stored as dates. So, do the comparison as dates not strings:
SELECT (CASE WHEN NVL(uap.us_pend_dt, act_d_dt) >= TO_DATE('".PENDING_DATE_CUTOFF."', 'YYYY-MM-DD')
THEN NVL(uap.us_pend_dt, act_d_dt)
WHEN NVL(act_d_dt, SYSDATE) < TO_DATE('".HIRE_DATE_CUTOFF."', 'YYYY-MM-DD')
THEN act_d_dt
ELSE ua_dt
END) AS h_DT
You also don't need the extra case expressions.

Oracle - Format return of sysdate inside select

I did a select in oracle database to return the time that ticket is in a support group.
Sometimes, I have the scenario where we have the date of ticket joined the group, but I'm not out of time.
To workaround this problem, I put in my select a condition and worked with diff between the date of entry in the group and the sysdate.
The problem is the output that is being formatted as follows: +00 00:28:32.00000, and I need only the time in minutes.
Below, I added the whole query, I suppose that the problem is in this part:
CASE
WHEN PBTI_TEMPONOGRUPO IS NULL
THEN (SYSDATE-(SECS_TO_DATE(PBTI_DATAENTRADAGRUPO)))
END AS TEMPO_NO_GRUPO
How to format this output?
The query is:
SELECT PBTI_WORKORDER_ID AS ID_WO,
PBTI_IDREQUISICAO AS ID_REQ,
PBTI_GRUPOSUPORTEATUAL AS GRUPO_SUP_ATUAL,
PBTI_GRUPOSUPORTEANTERIOR AS GRUPO_SUP_ANTERIOR,
CASE
WHEN PBTI_DATASAIDAGRUPO IS NULL
THEN SYSDATE
WHEN PBTI_DATASAIDAGRUPO IS NOT NULL
THEN SECS_TO_DATE(PBTI_DATASAIDAGRUPO)
END AS DATA_SAIDA_GRUPO,
SECS_TO_DATE(PBTI_DATAENTRADAGRUPO) AS DATA_ENTRADA,
CASE
WHEN PBTI_TEMPONOGRUPO IS NULL
THEN (SYSDATE-(SECS_TO_DATE(PBTI_DATAENTRADAGRUPO)))
END AS TEMPO_NO_GRUPO
FROM PBTI_TABELA_INDICADORES
WHERE PBTI_WORKORDER_ID = 'WO0000000142585';
CASE
WHEN PBTI_DATASAIDAGRUPO IS NULL
THEN SYSDATE
WHEN PBTI_DATASAIDAGRUPO IS NOT NULL
THEN SECS_TO_DATE(PBTI_DATASAIDAGRUPO)
END
So the output of the CASE expressions will be DATE data type. To get the output in your desired format, use TO_CHAR along with desired FORMAT MODEL to convert it into a string.
For example,
SQL> SELECT TO_CHAR(SYSDATE, 'YYYY-MM-DD') only_date FROM DUAL;
ONLY_DATE
----------
2015-10-20
SQL> SELECT TO_CHAR(SYSDATE, 'HH24:MI:SS') only_time FROM DUAL;
ONLY_TIME
---------
20:29:54