Is it possible to use between operator within a CASE statement within a WHERE Clause ? For example in the code below, the condition should be pydate between (sysdate-12) and (sysdate-2) if its a monday and pydate between (sysdate-11) and (sysdate-1) if its a tuesday and so on. But the following doesn't work. May be there is another way of writing this. Can someone please help ?
select * from table_name
where pricekey = 'JUF' and
case when to_char(to_date(sysdate,'DD-MON-YY'), 'DY')='MON' then pydate between to_date(sysdate-12,'DD-MON-YY') and to_date(sysdate-2,'DD-MON-YY')
when to_char(to_date(sysdate,'DD-MON-YY'), 'DY')='TUE' then pydate between to_date(sysdate-11,'DD-MON-YY') and to_date(sysdate-1,'DD-MON-YY')
else pydate='sysdate'
end
You can apply the logic you are attempting, but it is done without the CASE. Instead, you need to create logical groupings of OR/AND to combine the BETWEEN with the other matching condition from your case.
This is because CASE is designed to return a value, rather than to dynamically construct the SQL inside it.
SELECT *
FROM table_name
WHERE
pricekey = 'JUF'
AND (
-- Condition 1
(to_char(to_date(sysdate,'DD-MON-YY'), 'DY') = 'MON' AND pydate BETWEEN to_date(sysdate-12,'DD-MON-YY') AND to_date(sysdate-2,'DD-MON-YY'))
-- Condition 2
OR (to_char(to_date(sysdate,'DD-MON-YY'), 'DY')='TUE' AND pydate BETWEEN to_date(sysdate-11,'DD-MON-YY') AND to_date(sysdate-1,'DD-MON-YY'))
-- ELSE case, matching neither of the previous 2
OR (to_char(to_date(sysdate,'DD-MON-YY'), 'DY') NOT IN ('MON', 'TUE') AND pydate = 'sysdate')
)
This is hard to write using a case. Just do:
where pricekey = 'JUF' and
((to_char(to_date(sysdate,'DD-MON-YY'), 'DY') = 'MON' and
pydate between to_date(sysdate-12,'DD-MON-YY') and to_date(sysdate-2,'DD-MON-YY')
) or
(to_char(to_date(sysdate,'DD-MON-YY'), 'DY') = 'TUE' and
pydate between to_date(sysdate-11,'DD-MON-YY') and to_date(sysdate-1,'DD-MON-YY')
) or
(o_char(to_date(sysdate,'DD-MON-YY'), 'DY') not in ('MON', 'TUE') and
pydate = trunc(sysdate)
)
)
Note, I also removed the single quotes around "sysdate", so it won't be treated as a string. And, I trunc'ed it to just get the date portion with no time.
First of all, sysdate is a date so don't use to_date on it.
Secondly, don't rely on day names. Once another language setting is chosen, your statement may not work anymore.
Then pydate is a date. sysdate is a date. Dont compare pydate to the string 'sysdate', but to sysdate.
It also seems that you are trying to remove the time part from sysdate with to_date(sysdate-11,'DD-MON-YY'). The function used for removing a time part of a datetime is trunc.
Then the case statement is supposed to return a value. There is no boolean data type in SQL, so you cannot evaluate an expression inside and return the result. Rather than doing so, you will return a valid data type such as a number or a string which you can compare outside:
select *
from table_name
where pricekey = 'JUF' and
case
when to_char(sysdate, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN')='MON' then
case when pydate between trunc(sysdate)-12 and trunc(sysdate)-2 then 'GOOD' else 'BAD' end
when to_char(sysdate, 'DY', 'NLS_DATE_LANGUAGE=AMERICAN')='TUE' then
case when pydate between trunc(sysdate)-11 and trunc(sysdate)-1 then 'GOOD' else 'BAD' end
else
case when pydate = trunc(sysdate) then 'GOOD' else 'BAD' end
end = 'GOOD';
If pydate can have a time part, you may want to replace pydate with trunc(pydate), too.
Of course you can do all this with some ANDs and ORs and parentheses instead of CASE statements. It's up to you what is more readable for you.
Related
I tried to do so, but my data types are char and date
select * from table_name
where
smthng
order by
case when to_date(to_char( :edate, 'dd.mm.yyyy'),'dd.mm.yyyy')-
to_date(to_char( :sdate, 'dd.mm.yyyy'),'dd.mm.yyyy')>0
then ddate
else department end
You don't need any conversion for substraction considering :edate and :sdate are of type date
select *
from table_name
where smthng
order by case
when :edate - :sdate > 0 then
to_char(ddate ,'dd.mm.yyyy')
else
department
end
All the outputs from a CASE statement must have the same data-type and since you can't convert department to a DATE data-type then you will need to convert ddate to a String or else have two CASE statements with mutually exclusive conditions.
Some additional points:
Firstly, you don't need to convert :sdate and :edate from a DATE data type to a string and then back to a DATE; just perform the comparison on the DATEs.
Secondly, as you are doing this in the ORDER BY clause then you need to make sure what you are ordering by makes sense. Ordering by a date formatted as a 'dd.mm.yyyy' string does not make sense as it will perform an alpha-numeric string comparison and not a comparison in ascending date order. If you do want to use a single case statement and convert both to strings then use an ISO 8601 format YYYY-MM-DD which will let you order the formatted date string using an alpha-numberic sort.
Like this:
SELECT *
FROM table_name
WHERE something
ORDER BY
CASE
WHEN :edate > :sdate
THEN TO_CHAR( ddate ,'YYYY-MM-DD' )
ELSE department
END
or you can just use 2 CASE statements, one for each mutually exclusive case and defaulting each to NULL (or some other constant) when it does not match:
SELECT *
FROM table_name
WHERE something
ORDER BY
CASE WHEN :edate > :sdate THEN ddate ELSE NULL END,
CASE WHEN :edate > :sdate THEN NULL ELSE department END;
The value in order must match the data type so if you need can't return date or string but a single data type only A8ssuming department is a strint data type you should convert data as char)
select *
from table_name
where ....... smthng ....
order by
case when to_date(to_char( :edate, 'dd.mm.yyyy'),'dd.mm.yyyy')-
to_date(to_char( :sdate, 'dd.mm.yyyy'),'dd.mm.yyyy')>0
then to_char(ddate , 'dd.mm.yyyy')
else department
end
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.
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
I have a question regarding Oracle SQL case statement.
in the where condition I would like to apply the following condition.
if salary_date is null then effective_date should be greater than 01-Jan-2016
I have the tried as
case when salary_date is null then
trunc(effective_date) >= '01-JAN-2016' else
null end
However the above resulted in
ORA-00933: SQL command not properly ended
How can I resolve this?
The problem with your code is that SQL interpreter expects to see the value after 'then' keyword, whereas you have a condition clause.
Try something like this maybe:
case when salary_date is null and trunc(effective_date) >= '01-JAN-2016'
then <value you need>
else null
You can try this without CASE statement
SELECT
..
..
WHERE ( trunc(effective_date) >= '01-JAN-2016' AND salary_date is null )
OR ( <some other condition> )
...where salary_date is not null or effective_date >= date '2016-01-01'
Since Oracle SQL does not have "IF... THEN", use basic logic to transform your boolean expression. IF a THEN b is the same thing as (NON a) OR b. This is what I did above.
DO...NOT... compare dates to strings. '01-JAN-2016' is a string, not a date. You MUST convert it to a date, for example with to_date('01-JAN-2016', 'dd-MON-yyyy').
Or, as an alternative, note how I input a "date literal" (a fixed date). If I don't need to input a time of day, I can use the expression date '2016-01-01', which is a SQL Standard (ANSI) "date literal". Then you don't need to give a date format model; it MUST ALWAYS be in the exact format yyyy-mm-dd.
Running this query (below) returns a 'Too Many Values' error:
select
case
when to_char(sysdate, 'yyyymmdd') = to_char(sysdate, 'yyyymm') || '01'
then (select FirstReportGroups.*, FirstReportDetails.*
from FirstReportGroups, FirstReportDetails)
when to_char(sysdate, 'yyyymmdd') = to_char(sysdate, 'yyyymm') || '16'
then (select SecondReportGroups.*, SecondReportDetails.*
from SecondReportGroups, SecondReportDetails)
end as LetsSee
from cmtmpentered t1 join cmtmpconf t2
on t1.casenumber = t2.casenumber
and t1.enc = t2.enc
;
I'm using CTE's (they are not included because it would make this very long) and it makes logical sense to me, but googling this 'Too Many Values' error gives me no substantial help. Running the CTE's individually works, so that is not the problem.
I think all would be solved if I could only get rid of the outside 'Select' statement and just keep the selects inside the Case. If I'm explaining this poorly, an example of what I'm looking for is this:
case
when to_char(sysdate, 'yyyymmdd') = to_char(sysdate, 'yyyymm') || '01'
then (select FirstReportGroups.*, FirstReportDetails.*
from FirstReportGroups, FirstReportDetails)
when to_char(sysdate, 'yyyymmdd') = to_char(sysdate, 'yyyymm') || '16'
then (select SecondReportGroups.*, SecondReportDetails.*
from SecondReportGroups, SecondReportDetails)
end as LetsSee
;
Is this doable in any capacity? This syntax obviously doesn't work, otherwise I wouldn't be asking the question - but is there a related way I can do this?
select FirstReportGroups.*, FirstReportDetails.*
from (select 1 a from dual) FirstReportGroups
cross join (select 2 b from dual) FirstReportDetails
where extract(day from sysdate) = 1
---------
union all
---------
select SecondReportGroups.*, SecondReportDetails.*
from (select 3 a from dual) SecondReportGroups
cross join (select 4 b from dual) SecondReportDetails
where extract(day from sysdate) = 16;
Replaced common table expressions with inline views. CTEs should only be used if they are referenced more than once. They may look a little nicer with small examples and for programmers only used to procedural code. Serious SQL requires multiple nested levels of inline views. And debugging is much easier without CTEs - the CTEs make it difficult to highlight and run sub-blocks of code.
Replaced case expression with predicate to filter by date. A CASE expression can only return a single value. You could probably do something fancy with types, but that would be horribly complicated. My code still makes the assumption that the two sets return the same types of values. If that's not true you'll need to do something different at the application level.
Replaced to_char with extract. Date handling can be confusing in Oracle. The trick is to keep everything in their native type. You should almost never need to use to_char or to_date for anything other than formatting for display. For any other operation there's almost certainly a function to handle it without converting types.
Replaced , with ANSI-syntax cross join. The ANSI-style syntax has several advantages. One major advantage is it makes it clear when cross joins are intentional.
You aren't specifying a join condition for LetsSee. You are effectively cross joining LetsSee with the result of your from clause:
from cmtmpentered t1
join cmtmpconf t2 on t1.casenumber = t2.casenumber and t1.enc = t2.enc
I suggest you join LetsSee with t1 or t2.
Also, you are not specifying a join condition for the pairs:
FirstReportGroups and FirstReportDetails
SecondReportGroups and SecondReportDetails