Same Function returning different results in Oracle - oracle

I run two queries and they are returning different results. Please help me find what am I missing.
select UPPER(TO_CHAR(Hire_date,'MONTH'))
from employees
returns a list of months(containing "2" entries for 'MARCH')
SELECT COUNT(EMPLOYEEID) "March Joinees"
FROM Employees
WHERE UPPER(TO_CHAR(Hire_date,'MONTH')) = 'MARCH';
returns 0 as count

to_char(<date>, 'Month') produces a string of length equal to the greatest length of any month name (in the current session's language) - it does so by padding with spaces. This is why the second query produces no results; if you wrap the upper.... within trim(...) it will work, but it would be better not to use to_char(..., 'Month') for this kind of query to begin with. I am sure you came to the same conclusion but you just want to know what is going on...
Here's an illustration. It may seem like the first column in the result is the string 'March'; however, the second column doesn't lie: the result in the first column is actually 'March ' (with four spaces at the end).
select to_char(date '2016-03-01', 'Month') as month_as_string,
length( to_char(date '2016-03-01', 'Month') ) as len
from dual
;
MONTH_AS_STRING LEN
--------------- ---
March 9
Then you may ask why Oracle made such a weird choice. That's a much tougher question. Anyway, this behavior is documented. http://docs.oracle.com/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34924 and scroll down to MONTH in table 2.15.

As stated by mathguy already, 'MONTH' makes padding with spaces. Try
SELECT '"'||TO_CHAR(Hire_date, 'Month')||'"' FROM employees
to see the effect. Use either function TRIM or Format Model Modifiers FM
Then result of TO_CHAR(Hire_date, 'Month') depends your current session NLS_DATE_LANGUAGE value. Other session may get "März" or "Μάρτιος". Specify either date language or use Month numbers.
Actually UPPER(TO_CHAR(Hire_date,'MONTH')) is redundant. Format MONTH returns month name in upper case, you don't have to make UPPER() again.
Taking all this into account you should use one of the expressions below
WHERE TO_CHAR(Hire_date, 'fmMONTH', 'NLS_DATE_LANGUAGE = english') = 'MARCH'
WHERE UPPER(TO_CHAR(Hire_date, 'fmMonth', 'NLS_DATE_LANGUAGE = english')) = 'MARCH'
WHERE TRIM(TO_CHAR(Hire_date, 'MONTH', 'NLS_DATE_LANGUAGE = english')) = 'MARCH'
WHERE TO_CHAR(Hire_date, 'MM') = '03'
WHERE EXTRACT(MONTH FROM Hire_date) = 3

Related

Is it possible to use multiple conditions in a check constraint in Oracle?

I want to use (between, and, or) conditions in a check constraint at the same time. For example:
Alter table called_no add constraint called_no_chck
check ("call_time" between '01:00:00 AM' and '12:59:59 AM' or
between '01:00:00 PM' and '12:59:59 PM')
You certainly may use multiple conditions in a single check constraint.
Your attempt fails because you wrote an invalid condition. The same condition would fail if it was in the where clause of a query, or in any other place where you need a condition.
The correct way to check that a date-time is between A and B or between C and D looks like this:
...check (call_time between A and B OR call_time between C and D)
^^^^^^^^^
You understand the shorthand, where you don't repeat the column name (call_time) before the second between. I understand it too. A computer does not - they are not human. OR must appear between two conditions. A condition such as between must be applied to something, and you can't leave that "something" out and expect the computer to understand it's the same "something" you already used in another condition.
There are other mistakes in your condition though. 12:59:59 AM is about one hour after midnight; it's not about one hour after noon, as you seem to believe. Nothing is ever between 1 AM and 12:59:59 AM (of the same day), because 1 AM is after 12:59:59 AM.
It's also not clear what the data type of your column is. If it's date (as it almost surely should be), you can't just compare it to strings (as you have in your condition). And, you can't simply compare a date-time to something that's just time-of-day.
All these mistakes, though, are out of the scope of your question as you asked it (which was: Can you use "compound" conditions in a constraint; the answer, again, is YES).
Do not use a string column.
If you just want a time without a date, use an INTERVAL DAY(0) TO SECOND(0) column.
If you want a date-time column with accuracy down to whole seconds (and no time-zone) then use a DATE column (which always has the components year, month, day, hour, minute and seconds).
With either of those two options, you do not need a CHECK constraint as it will not accept invalid times.
If you want to format the value using the 12-hour clock then you can use a virtual column.
CREATE TABLE table_name (
time INTERVAL DAY(0) TO SECOND(0),
datetime DATE,
formatted_time VARCHAR2(11)
GENERATED ALWAYS AS (TO_CHAR(DATE '1970-01-01' + time, 'HH12:MI:SS AM')),
formatted_datetime VARCHAR2(11)
GENERATED ALWAYS AS (TO_CHAR(datetime, 'HH12:MI:SS AM'))
);
INSERT INTO table_name (time, datetime)
SELECT INTERVAL '00:12:34' HOUR TO SECOND, SYSDATE FROM DUAL UNION ALL
SELECT INTERVAL '12:34:56' HOUR TO SECOND, SYSDATE + 0.5 FROM DUAL;
Then:
SELECT * FROM table_name;
Outputs:
TIME
DATETIME
FORMATTED_TIME
FORMATTED_DATETIME
+0 00:12:34
2021-12-18 15:37:34
12:12:34 AM
03:35:47 PM
+0 12:34:56
2021-12-19 03:37:34
12:34:56 PM
03:35:47 AM
If you are storing it in a string (don't) then, yes, you can use multiple conditions in a single constraint:
CREATE TABLE table_name (
time VARCHAR2(11)
CHECK (
SUBSTR(time, 1, 2) BETWEEN '01' AND '12'
AND SUBSTR(time, 4, 2) BETWEEN '00' AND '59'
AND SUBSTR(time, 7, 2) BETWEEN '00' AND '59'
AND SUBSTR(time, 10, 2) IN ('AM', 'PM')
AND time LIKE '__:__:__ __'
)
);
or multiple constraints:
CREATE TABLE table_name (
time VARCHAR2(11)
CONSTRAINT invalid_hours CHECK (SUBSTR(time, 1, 2) BETWEEN '01' AND '12')
CONSTRAINT invalid_minutes CHECK (SUBSTR(time, 4, 2) BETWEEN '00' AND '59')
CONSTRAINT invalid_seconds CHECK (SUBSTR(time, 7, 2) BETWEEN '00' AND '59')
CONSTRAINT invalid_meridian CHECK (SUBSTR(time, 10, 2) IN ('AM', 'PM'))
CONSTRAINT invalid_format CHECK (time LIKE '__:__:__ __')
);
But its much easier to not implement all those constraints and just use either an INTERVAL or a DATE when you won't be allowed to enter invalid data.
db<>fiddle here

Run a select based on an if condition

Prior to posting this question, I have looked at case when, but it doesn't solve my problem.
The scenario is, based on whether it is a Monday in the week, I need to fetch some data from a table. I can write it in PL/SQL block, but I want to achieve this in plain SQL.
Any suggestions please?
Assuming the tables have the same structure this would work:
select * from table1
where to_char(sysdate, 'DY') = 'MON'
union all
select * from table2
where to_char(sysdate, 'DY') != 'MON'
Using day literals like this is dependent on your NLS settings. So adjust the solution if you need to.
to_char(sysdate-3, 'DAY') will return MONDAY you can trim it and using decode return different value than null:
select decode(TRIM(to_char(sysdate-3, 'DAY')),'MONDAY', 1, null) from dual
EDIT:
In where you can
where decode(to_char(sysdate-3, 'fmDAY'), 'MONDAY', 1, null) =1
*sysdate-3 was choose because it's Thursday today

Comparison between day of week in oracle

I have problems with comparing the day of week
Here is my query in oracle database.
SELECT DAY, WEEKDAY
FROM (SELECT v.TNI, v.FRMP, v.LR, v.DAY, v.HH, v.VOLUME,
CASE WHEN hd.HOLIDAY_DATE is not null then 'HOLIDAY'
ELSE to_char(v.DAY, 'Day') END AS WEEKDAY
FROM v_nem_rm16 v
LEFT JOIN DBP_ADMIN.DBP_HOLIDAY hd
ON v.DAY = hd.HOLIDAY_DATE
WHERE v.STATEMENT_TYPE != 'FORECAST')
WHERE WEEKDAY = 'Monday';
Basically, the "WEEKDAY" column has the day of week based on the "DAY". Day is just date like 13/Mar/17
If you look at the "CASE" statement, you will notice that the "WEEKDAY" column is filled by "to_char(v.DAY, 'Day')".
So the column has values like "Sunday, Saturday and so on, the day of week".
The problem is the outer query's where clause, "WHERE WEEKDAY = 'Monday'"
When I execute this query, It does not give me any rows even if I have rows having Monday as the value in "WEEKDAY" column
But when I change the WHERE clause to "WHERE WEEKDAY = to_char(sysdate, 'Day')", It works fine. The sql statement gives me the rows having "Saturday" in "WEEKDAY" column since the "to_char(sysdate, 'Day')" gives me "Saturday".
So what is problems with my first query??
I just want to filter rows by the name of the day of week like if i pass "Monday", i want to have all the rows having " Monday" in "WEEKDAY" column.
How can I do??
THANKS GUYS
It appears TO_CHAR(, 'Day') returns a fixed length string with right side padding of blanks. So, you are not getting 'Monday', you are really getting 'Monday '. The longest day is 'Wednesday', so it is a 9 character string. So, either trim the value or compare against 'Monday ' exactly.
select '|'||to_char(sysdate+level,'Day') ||'|' days_of_the_week
from dual
connect by level <= 7;
Results...
|Saturday |
|Sunday |
|Monday |
|Tuesday |
|Wednesday|
|Thursday |
|Friday |
if you would like to use the day names (either full or abbreviated), I suggest to specify the language in the to_char function as follows:
select '|'||to_char(sysdate+level, 'fmDay', 'NLS_DATE_LANGUAGE = American') ||'|' days_of_the_week_enu,
'|'||to_char(sysdate+level, 'fmDay', 'NLS_DATE_LANGUAGE = German') ||'|' days_of_the_week_ger,
'|'||to_char(sysdate+level, 'DY', 'NLS_DATE_LANGUAGE = American') ||'|' days_of_the_week_abbr_enu,
'|'||to_char(sysdate+level, 'DY', 'NLS_DATE_LANGUAGE = German') ||'|' days_of_the_week_abbr_ger
from dual
connect by level <= 7;
This way the returned day names will be independent of session locale settings.
Result:

Oracle Recursive Query - Dates

I've got two sets of dates being passed into a query and I would like to find all the months/years between both sets of dates.
When I try this:
WITH CTE_Dates (cte_date) AS (
SELECT cast(date '2014-01-27' as date) from dual
UNION ALL
SELECT cast(ADD_MONTHS(TRUNC(cte_date, 'MONTH'),1) as date)
FROM CTE_Dates
WHERE ( TO_DATE(ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)) BETWEEN TO_DATE ('27-01-2014','DD-MM-YYYY') AND TO_DATE ('27-04-2014','DD-MM-YYYY'))
OR
( TO_DATE(ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)) BETWEEN TRUNC(TO_DATE('27-11-2014','DD-MM-YYYY'), 'MONTH') AND TO_DATE ('27-01-2015','DD-MM-YYYY'))
)
SELECT * from CTE_Dates
I get:
27-JAN-14
01-FEB-14
01-MAR-14
01-APR-14
I would also want to get:
01-NOV-14
01-DEC-14
01-JAN-15
It looks like the OR portion of the WHERE clause gets ignored.
Suggestions on how to create this query?
Thanks
Cory
The problem with what you have now (aside from extra cast() and to_date() calls) is that on the fourth iteration both the conditions are false so the recursion stops; there's nothing to make it skip a bit and pick up again, otherwise it would continue forever. I don't think you can achieve both ranges within the recursion.
You can put the latest date you want inside the recursive part, and then filter the two ranges you want afterwards:
WITH CTE_Dates (cte_date) AS (
SELECT date '2014-01-27' from dual
UNION ALL
SELECT ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)
FROM CTE_Dates
WHERE ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1) <= date '2015-01-27'
)
SELECT * from CTE_Dates
WHERE cte_date BETWEEN date '2014-01-27' AND date '2014-04-27'
OR cte_date BETWEEN date '2014-11-27' AND date '2015-01-27';
CTE_DATE
---------
27-JAN-14
01-FEB-14
01-MAR-14
01-APR-14
01-DEC-14
01-JAN-15
6 rows selected
You can replace the hard-coded values with your pairs of start and end dates. If the ranges might overlap or the second range could be (or end) before the first one, you could pick the higher date:
WHERE ADD_MONTHS(TRUNC(cte_date, 'MONTH'), 1)
<= greatest(date '2015-01-27', date '2014-04-27')
... though that only makes sense with variables, not fixed values.

Oracle - how to create date from an already existing date?

Say I issue:
select date_field from table1;
date_field is like '25.11.2009'
I will try to change the positions of date fields with the month and vice versa. (of course for days > 12 some manipulations)
TO_DATE( MOD(SUBSTR(TO_CHAR(a.A_DATE, 'DD.MM.YYYY'), 4, 2), 12) || '.' ||
SUBSTR(TO_CHAR(a.A_DATE, 'DD.MM.YYYY'), 1, 2) ||
SUBSTR(TO_CHAR(a.A_DATE, 'DD.MM.YYYY'), 6, 4),
'DD.MM.YYYY')
THE THING IS THAT THE VALUE RETURNED FROM MOD() function is a number, i.e. for 01.07.2009 --> I get 1 for date, not '01' as expected. Later on I cannot get the date.
Is there a shortcut solution to my problem?
I suspect you need to seriously reconsider what you are trying to do.
I think what you started with is that you want to simply change the formatting of the date, e.g. change '25.11.2009' to '11.25.2009'.
If date_field is actually a DATE type, there is no inherent formatting stored in the field. It is a numeric value representing a specific date and time. It is formatted into text when you SELECT it in SQLPlus or some other tool, but that formatting is not stored in the table.
If you want to view the date in a particular format, you use the TO_CHAR function to force it. You can also set a default format model for a single session, a single client, or the whole database using NLS_DATE_FORMAT.
I used:
CASE MOD(SUBSTR(TO_CHAR(a.birthday, 'DD.MM.YYYY'), 1, 2), 12)
WHEN 1 THEN '01'
WHEN 2 THEN '02'
WHEN 3 THEN '03'
WHEN 4 THEN '04'
WHEN 5 THEN '05'
WHEN 6 THEN '06'
WHEN 7 THEN '07'
WHEN 8 THEN '08'
WHEN 9 THEN '09'
WHEN 10 THEN '10'
WHEN 11 THEN '11'
WHEN 12 THEN '12'
END
not very elegant but works :)
yli> "I am somehow anonymising the date, actually Hire Date of employees, to be used in a test environment."
So here was your actual requirement. If you want to change some dates, you don't need to use the date/string conversion functions (TO_CHAR/TO_DATE) at all. Oracle supports arithmetic operations on date values directly.
If you want to randomize your dates why not just use something like:
select date_field + dbms_random.value * 100 from table1;
Does this work?
TO_DATE(TO_CHAR(a.A_DATE, 'DD/MM/YYYY'), 'MM/DD/YYYY')
Have you tried TO_CHAR(<date>, '<format>')?

Resources