How to convert timestamp to a 5-6digit number? - oracle

I have a tiemstamp date like this 12/31/1999 12:00:00.000000 AM
I want it to turn into this 121999. Basically omit the day.
If the month is between January-September, the number should be 5 digits(IE.21998), October to December should be 6 digits(IE.102014)
I have something that works but it looks like a caveman. Is there a better way?
SELECT TO_NUMBER(SUBSTR(TO_CHAR(EX_DATE,'MMDDYYYY'),1,2))
||TO_NUMBER(SUBSTR(TO_CHAR(EX_DATE,'MMDDYYYY'),5)) AS EX_DATE
FROM TOL
Thanks in advance

I think you just want
SELECT to_char( ex_date, 'fmMMYYYY' ) as ex_date
FROM tol
if you want a 5 or 6 character string or
SELECT to_number( to_char( ex_date, 'fmMMYYYY' )) as ex_date
FROM tol
if you want a number.
Of course, I would hesitate to call either a string or a number ex_date. And I would hesitate to call a column of type timestamp something like ex_date that implies that the data type is really a date. I would either use something that identifies the data type properly, i.e. ex_ts for a timestamp, or I would change the data type to match the name of the column.

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

Single Month Digit Date Format issue in Oracle

Am getting the below issue when am using 'mon-d-yyyy' to convert date to char, as i need a single day digit for values from 1 to 9 days in a month.
When i use the 'mon-d-yyyy' format, am losing out on 5 days and getting a wrong date. Any help on this would be great.
select to_char(sysdate-22,'mon-d-yyyy') from dual;--aug-2-2017
select to_char(sysdate-22,'mon-dd-yyyy') from dual;--aug-07-2017
select sysdate-22 from dual;--07-AUG-17 11.06.43
In Oracle date formats, d gets the day of week. The 2 in your output means monday, not august the 2nd.
Try using Fill Mode as Format Model Modifier
select to_char(sysdate-22,'mon-fmdd-yyyy') from dual;
One option might be to piece together the date output you want:
SELECT
TO_CHAR(sysdate-22, 'mon-') ||
TRIM(LEADING '0' FROM TO_CHAR(sysdate-22, 'dd-')) ||
TO_CHAR(sysdate-22, 'yyyy')
FROM dual;
The middle term involving TRIM strips off the leading zeroes, if present, from the date.
Output:
Demo here:
Rextester
SQL>SELECT TO_CHAR(TO_DATE('29-AUG-2017','DD-MON-YYYY') - 22,'"WEEKDAY :"D, MON-FMDD-YYYY') "Before22Days" FROM DUAL;
D- Gives you a numeric weekday(2nd weekday in a week) on AUG-07-2017.
DD-Gives a Numeric Month Day i.e,07th
FMDD-Gives 7th
Before22Days
----------------------
WEEKDAY :2, AUG-7-2017

Difference in two dates not coming as expected in Oracle 11g

I get some data through a OSB Proxy Service and have to transform that using Xquery. Earlier the transformation was done on the database but now it is to be done on the proxy itself. So I have been given the SQL queries which were used and have to generate Xquery expressions corresponding to those.
Here is the SQL query which is supposed to find the difference between 2 dates.
SELECT ROUND((CAST(DATEATTRIBUTE2 AS DATE) -
CAST(DATEATTRIBUTE1 AS DATE) ) * 86400 ) AS result
FROM SONY_TEST_TABLE;
DATEATTRIBUTE1 and DATEATTRIBUTE2 are both of TIMESTAMP type.
As per my understanding this query first casts the TIMESTAMP to DATE so that the time part is stripped then subtracts the dates. That difference in days in multiplied with 86400 to get the duration in seconds.
However, when I take DATEATTRIBUTE2 as 23-02-17 01:17:19.399000000 AM and DATEATTRIBUTE1 as 23-02-17 01:17:18.755000000 AM the result should ideally be 0 as the dates are same and i'm ignoring the time difference but surprisingly the result comes as 1. After checking I found that the ( CAST(DATEATTRIBUTE2 AS DATE) - CAST(DATEATTRIBUTE1 AS DATE) ) part aparently does not give an integer value but a fractional one. How does this work?? o_O
Any help is appreciated. Cheers!
EDIT : So got the problem thanks to all the answers! Even after casting to DATE it still has time so the time difference is also calculated. Now how do I implement this in XQuery? See this other question.
Oracle DATE datatype is actually a datetime. So casting something as a date doesn't remove the time element. To do that we need to truncate the value:
( trunc(DATEATTRIBUTE2) - trunc(DATEATTRIBUTE1) )
you should try this to find difference by day
SELECT (trunc(DATEATTRIBUTE2) -
trunc(DATEATTRIBUTE1) ) AS result
FROM SONY_TEST_TABLE;
alternative 2
you can use extract like below:
SELECT ROUND (
EXTRACT (MINUTE FROM INTERVAL_DIFFERENCE) / (24 * 60)
+ EXTRACT (HOUR FROM INTERVAL_DIFFERENCE) / 24
+ EXTRACT (DAY FROM INTERVAL_DIFFERENCE))
FROM (SELECT ( TO_TIMESTAMP ('23-02-17 01:17:19', 'dd-mm-yy hh24:mi:ss')
- TO_TIMESTAMP ('23-02-17 01:17:17', 'dd-mm-yy hh24:mi:ss'))
INTERVAL_DIFFERENCE
FROM DUAL)

Converting a timestamp value to number

I've a timestamp value in the format 2005-01-31T00:00:00.000-05:00. I want covert it to a number in the format20050131, to equate it to a column which has the datatype of number.
I tried to do it by to_number(to_date(timestamp, 'yyyymmdd')). But it is resulting in error not a valid month.
Could you please help me in resolving it.
try using like below
to_number(to_char(timestamp, 'yyyymmdd'))
first you have to convert to char and then to number.
If your timestamp value is a VARCHAR2 value, maybe this will help:
WITH tstmp AS (
SELECT '2005-01-31T00:00:00.000-05:00' AS val FROM dual
)
SELECT TO_NUMBER(TO_CHAR(FROM_TZ(TO_TIMESTAMP(SUBSTR(val, 1, 23), 'YYYY-MM-DD"T"HH24:MI:SS.FF3'), SUBSTR(val, 24)) , 'YYYYMMDD')) AS newval
FROM tstmp;

Dynamic order by date data type in Oracle using CASE

My code in the stored procedure:
SELECT * FROM
my_table ir
WHERE
--where clause goes here
ORDER BY
CASE WHEN p_order_by_field='Id' AND p_sort_order='ASC' THEN IR.ID end,
CASE WHEN p_order_by_field='Id' AND p_sort_order='DESC' THEN IR.ID end DESC,
CASE WHEN p_order_by_field='Date' AND p_sort_order='ASC' THEN TO_CHAR(IR.IDATE, 'MM/dd/yyyy') end,
CASE WHEN p_order_by_field='Date' AND p_sort_order='DESC' THEN TO_CHAR(IR.IDATE, 'MM/dd/yyyy') end DESC;
Problem is that sorting is done based on the char, which comes out wrong for the date case. CASE statement, however, won't allow any other datatype other than char. So what is the solution in this case? I need to be able to pass the p_order_by_field into the stored procedure.
Thanks
Should be simple - just use ISO date format in your case:
TO_CHAR(IR.IDATE, 'yyyy-mm-dd')
and you should be fine.
Another problem could occure when you want to sort on the date difference (let say number of days between two days).
For example such a sort would return number 13 (days) before 9 (days).
The solution is that you concatenate length of date difference and the difference itself:
length(trunc(date2) - trunc(date1)) || to_char(date2 - date1)

Resources