I am getting an ORA-01873 "leading precision of the interval is too small" error from this statement and can't figure out why:
The v_not_auto_bl_num is declared as VARCHAR2(1000).
What is causing the error?
In the code you originally posted you are doing:
ABS( EXTRACT(DAY FROM (TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000) / 1000)
The relevant part is this:
(TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000
If you subtract two timestamps you get an interval data type, not a number; e.g. if your table columns were, say, '20170419065416' and '20170419000000' then subtracting them would generate:
(TO_TIMESTAMP(DHS.A
-------------------
+00 06:54:16.000000
If you multiply that by 86400*1000 you exceed the precision of the interval data type. I chose that value because one second less is OK:
with dhs (assignment_iodt, completed_iodt) as (
select '20170419065415', '20170419000000' from dual
)
select (TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) as original,
(TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF')
- TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000 as multiplied
from dhs;
ORIGINAL MULTIPLIED
------------------- -------------------------
+00 06:54:15.000000 +24855000 00:00:00.000000
Once second more (or, in fact, anything beyond 20170419065415.134814814, or any pair of values with the actual interval above 06:54:15.134814814) will error as the multiplied interval is out of range for the data type.
What's actually happening under the hood is unclear; using a smaller multiplier also causes the issues once you cross that raw interval size limit.
Anyway, you seem to be trying to get the number of while seconds, which you can do by extracting each time element and multiplying them individually:
select abs(
(extract(day from diff) * 86400)
+ (extract (hour from diff) * 3600)
+ (extract (minute from diff) * 60)
+ trunc(extract (second from diff))
) as c_f_previous_time
from (
select to_timestamp(dhs.assignment_iodt,'YYYYMMDDHH24MISS.FF')
- to_timestamp(dhs.completed_iodt,'YYYYMMDDHH24MISS.FF') as diff
from dhs
);
I've put the timestamp subtraction in an inline view just so it doesn't have to be repeated within each extract call. You can put the rest of your original query inside that inline view (or a CTE) too.
Incidentally, the abs() implies you can have rows in your table where the completed date is earlier than the assignment; or just that you didn't notice you're doing the subtraction the wrong way round. If you data cannot have completed before assigned then you can swap the terms over and lose the abs(); I'd probably swap the terms anyway just to make it look more logical.
first try this:
create table test_table as
SELECT ACT_BL.BL_NUM,
ABS( EXTRACT(DAY FROM (TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF') - TO_TIMESTAMP(DHS.COMPLETED_IODT,'YYYYMMDDHH24MISS.FF')) *86400*1000) / 1000) AS C_F_PREVIOUS_TIME
FROM DOCI_ACTIVITY ACT ,
DOCI_ACTIVITY_RELATED_BL ACT_BL ,
DSH_ACTIVITY DHS
WHERE ACTIVITY_TYPE IN ('BlCodingAndFormatting','BlCreationFromESI')
AND ACT.ACTIVITY_ID =ACT_BL.ACTIVITY_ID
AND ACT_BL.ACTIVITY_ID = DHS.ACTIVITY_ID
AND ACT_BL.BL_NUM = v_not_auto_bl_num;
then check the test_table columns type(BL_NUM and C_F_PREVIOUS_TIME)
after that you apply that column types to your table
In your case, the exception is raised when you multiply an interval by 86400.
As I've posted here you could use the following shorter method to convert interval to milliseconds.
SELECT ROUND((EXTRACT(DAY FROM (
TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF') - TO_TIMESTAMP(DHS.COMPLETED_IODT ,'YYYYMMDDHH24MISS.FF')
) * 24 * 60) * 60 + EXTRACT(SECOND FROM (
TO_TIMESTAMP(DHS.ASSIGNMENT_IODT,'YYYYMMDDHH24MISS.FF') - TO_TIMESTAMP(DHS.COMPLETED_IODT ,'YYYYMMDDHH24MISS.FF')
))) * 1000) AS MILLIS FROM DUAL;
Your numeric number appears to be too large for the ABS function to handle. The biggest value you can pass to ABS() as the number is 2^31-1:
Related
Morning Team,
I have an Oracle SQL script that is calculating from creation of an event and how many minutes old compared to the systimestamp.
I need to convert the minutes, which are coming out as 120 for 2 hour for example, into Hours:Minutes version, i.e. 2:00
I'm struggling with that part and would like to ask if someone could help? My current code for the calculation is:
(ROUND(((cast(systimestamp as date) - cast(n.createdttm as date)))*1440,0)) "Minutes old",
I'm sure it's something simple but with all my fiddling I am not able to get it.
Thank you
You can create an INTERVAL form the minutes.
select numtodsinterval(120, 'minute') from dual
Or create a datetime from the minutes and convert its time part to a string of hours and minutes.
select to_char(trunc(sysdate) + interval '1' minute * 120, 'hh24:mi') from dual
It looks like createdttm is a timestamp, so you can just subtract:
systimestamp - createdtm
... to get an interval value like +000000000 02:00:00.00000. You can't format that directly, but you can either extract the various elements and concatenate those back together, or treat it as a string and cut out the bits you want.
If you only want the time part and it will always be less than a day you can just do:
substr(systimestamp - createdtm, 12, 5)
02:00
But if it can go over 24 hours then you probably want the day part too, which you could still get just with substr (and maybe replace to change the space to another colon) if you know it can never be more than 2 days:
substr(systimestamp - createdtm, 10, 7)
0 02:00
That's unlikely to be a safe assumption though, so instead you could extract the number of days and concatenate that:
extract(day from (systimestamp - createdtm)) || ':' || substr(systimestamp - createdtm, 12, 5)
0:02:00
You could only show the number of days if it's non-zero, but that would probably be quite confusing to who/whatever is looking at the results; but if you really wanted to:
case when extract(day from (systimestamp - createdtm)) > 0
then extract(day from (systimestamp - createdtm)) || ':'
end || substr(systimestamp - createdtm, 12, 5)
02:00
db<>fiddle with a few sample values.
One thing to note is this effectively truncates the seconds off the time; your original attempt included round(), but that might not have been what you meant.
If u want to convert minutes to hours and minutes.
If x is the number of minutes (such as 350):
TO_CHAR ( FLOOR (x / 60)) || ':'
|| TO_CHAR ( MOD (x, 60)
, 'FM00'
)
If u want to convert hours and minutes to minutes.
SELECT (TRUNC (x) * 60) +
( (MOD (x, 1)
* 100
)
FROM dual;
where x is a NUMBER. If you have a sting, s, instead, use TO_NUMBER (s) in place of x.
I would like to get difference between two columns (both TT_TIMESTAMP(26,6)) select timestamp1 - timestamp2 as diff from table; but getting this error: An interval data type must be specified for a datetime arithmetic result
Any ideas?
In T-SQL you would need to provide an internal type (year,month,day ect) that would identify the specific element that you are wanting to se the differance in.
A formula like the following should give you the differance in days for example in T-SQL:
DATEDIFF(day, timestamp1, timestamp2) AS DateDiff
W3 Schools has a good indicator of the various options you can use
TimestTen 18.1 Documentation Reference
For TimesTen Database,
The difference between the two TT_TIMESTAMP datatype columns results into an INTERVAL datatype. (Not TT_TIMESTAMP)
And to get the desired component of the INTERVAL datatype, we must use EXTRACT function.
Below is one example.
-- Data preparation
CREATE TABLE DEMO (A TT_TIMESTAMP, B TT_TIMESTAMP;
INSERT INTO DEMO VALUES (TT_TIMESTAMP '2022-01-01 01:01:01.000000', TT_TIMESTAMP '2022-01-05 01:01:01.000000');
-- Below will return an error (as expected)
SELECT B-A FROM DEMO;
2789: An interval data type must be specified for a datetime arithmetic result
The command failed.
So, for the actual difference, we need to calculate like below.
-- Extract data like below
SELECT EXTRACT(DAY FROM B-A) FROM DEMO;
< 4 >
SELECT EXTRACT(HOUR FROM B-A) FROM DEMO;
< 0 >
SELECT EXTRACT(MINUTE FROM B-A) FROM DEMO;
< 0 >
SELECT EXTRACT(SECOND FROM B-A) FROM DEMO;
< 0 >
-- Get SECONDS between two TT_TIMESTAMP columns
SELECT
(EXTRACT(DAY FROM B-A) * 24 * 3600
+ EXTRACT(HOUR FROM B-A) * 3600
+ EXTRACT(MINUTE FROM B-A) * 60
+ EXTRACT(SECOND FROM B-A))
FROM
demo;
< 345600 >
I have two timestamp columns: arrTime and depTime.
I need to find the number of munites the bus is late.
I tried the following:
SELECT RouteDate, round((arrTime-depTime)*1440,2) time_difference
FROM ...
I get the following error: inconsistent datatype . expected number but got interval day to second
How can i parse the nuber of minutes?
If i simply subtract: SELECT RouteDate, arrTime-depTime)*1440 time_difference
The result is correct but not well formatted:
time_difference
+00000000 00:01:00 0000000
The result of timestamp arithmetic is an INTERVAL datatype. You have an INTERVAL DAY TO SECOND there...
If you want the number of minutes one way would be to use EXTRACT(), for instance:
select extract( minute from interval_difference )
+ extract( hour from interval_difference ) * 60
+ extract( day from interval_difference ) * 60 * 24
from ( select systimestamp - (systimestamp - 1) as interval_difference
from dual )
Alternatively you can use a trick with dates:
select sysdate + (interval_difference * 1440) - sysdate
from (select systimestamp - (systimestamp - 1) as interval_difference
from dual )
The "trick" version works because of the operator order of precedence and the differences between date and timestamp arithmetic.
Initially the operation looks like this:
date + ( interval * number ) - date
As mentioned in the documentation:
Oracle evaluates expressions inside parentheses before evaluating those outside.
So, the first operation performed it to multiply the interval by 1,440. An interval, i.e. a discrete period of time, multiplied by a number is another discrete period of time, see the documentation on datetime and interval arithmetic. So, the result of this operation is an interval, leaving us with:
date + interval - date
The plus operator takes precedence over the minus here. The reason for this could be that an interval minus a date is an invalid operation, but the documentation also implies that this is the case (doesn't come out and say it). So, the first operation performed is date + interval. A date plus an interval is a date. Leaving just
date - date
As per the documentation, this results in an integer representing the number of days. However, you multiplied the original interval by 1,440, so this now represented 1,440 times the amount of days it otherwise would have. You're then left with the number of seconds.
It's worth noting that:
When interval calculations return a datetime value, the result must be an actual datetime value or the database returns an error. For example, the next two statements return errors:
The "trick" method will fail, rarely but it will still fail. As ever it's best to do it properly.
SELECT (arrTime - depTime) * 1440 time_difference
FROM Schedule
WHERE ...
That will get you the time difference in minutes. Of course, you can do any rounding that you might need to to get whole minutes....
Casting to DATE first returns the difference as a number, at least with the version of Oracle I tried.
round((cast(arrTime as date) - cast(depTime as date))*1440)
You could use TO_CHAR then convert back to a number. I have never tested the performance compared to EXTRACT, but the statement works with two dates instead of an interval which fit my needs.
Seconds:
(to_char(arrTime,'J')-to_char(depTime,'J'))*86400+(to_char(arrTime,'SSSSS')-to_char(depTime,'SSSSS'))
Minutes:
round((to_char(arrTime,'J')-to_char(depTime,'J'))*1440+(to_char(arrTime,'SSSSS')-to_char(depTime,'SSSSS'))/60)
J is julian day and SSSSS is seconds in day. Together they give an absolute time in seconds.
Thanks to previous question...
I have a more simplified OR statement.
Question is instead of a IN how could I change this to a between?
TO_DATE(TO_CHAR(TO_DATE(''19700101'',''yyyymmdd'') + + (FLOOR(ph.change_date/24/60/60)))) IN (''23-DEC-2020'', ''29-DEC-2020'')
So I want to say between the 23-DEC-2020 and 29-DEC-2020 including both?
Thanks
If you want to use an index on the change_date column then perform the conversion on the literal values and convert them to epoch times (rather than converting the column's epoch time to a date, which would not allow you to use a normal index on the column):
ph.change_date BETWEEN ( DATE '2020-12-23' - DATE '1970-01-01' ) * 86400
AND ( DATE '2020-12-29' - DATE '1970-01-01' ) * 86400 + 86399
change_date seems to be in Unix timestamp format -- the number of seconds since 1970-01-01. I would recommend doing the comparison by converting constant values to the same format:
where ph.change_date >= (date '2020-12-23' - date '1970-01-01') * 24 * 60 * 60 and
ph.change_date < (date '2020-12-30' - date '1970-01-01') * 24 * 60 * 60
Note that this is index (and partition) friendly. And, the second comparison is < on the next day to get the entire day.
If you need to deal with the column as "real" dates, you can add a computed column
alter table t add column change_date_date date generated always as
(cast(date '1970-01-01' as timestamp) + change_date * interval '1' second);
You can then reference change_date_date and even define a an index on it.
You can do it with the between keyword.
For example:
to_date('2021.01.06', 'yyyy.mm.dd') between to_date('2021.01.01', 'yyyy.mm.dd') and to_date('2021.01.31', 'yyyy.mm.dd')
I think we can try like this
where <expression>
between TO_DATE('23-DEC-2020','DD-MON-YYYY') and
TO_DATE('29-DEC-2020','DD-MON-YYYY')
You can convert the epoch time(ph.change_date) to date and then compare as follows:
Date'1970-01-01' + FLOOR(ph.change_date/24/60/60) -- considering that change_date is epoch time
between date'2020-12-23' and date'2020-12-23'
I have multiple rows in a table with a column named "created" that has a timestamp in the format "1309494407". I need to do a SQL query to get how many rows there are that has a certain value of column called "category" with the value of "book". Basically I need to know how many books are created every day in the database.
How is this accomplished with PostgreSQL?
My schema:
Items(id, category, created)
This is what I've tried so far:
SELECT
*
FROM
my_schema.items
WHERE
(category = 'books' AND TO_TIMESTAMP(created) < NOW() - INTERVAL '1 days')
And it doesn't work I get problem:
function to_timestamp(character varying) does not exist
Basically I need to know how many books are created every day in the database.
Your timestamp looks like a Unix epoch. If so, you can get what you want using simple arithmetic:
SELECT floor(i.created / (24 * 60 * 60)) as dy, COUNT(*)
FROM my_schema.items i
WHERE i.category = 'books'
GROUP BY dy;
If you like, you can convert it to a calendar date using:
SELECT '1970-01-01' + floor(i.created / (24 * 60 * 60)) * interval '1 day' as dy, COUNT(*)
FROM my_schema.items i
WHERE i.category = 'books'
GROUP BY dy;
What a wonder example of why Unix epoch is such a poor choice for storing dates, and a text representation of the number at that. Always store dates/timestamps with the appropriate type. If needed the epoch is easily retrieved from a date, but deriving the date from epoch is - well ugly. And actual timestamp even uglier.
If you happen to to be on version 12 you can at least capture the date in a table column itself, without having to change the code already using the existing column. Create a generated column that derives the actual date. (Guess that would make an interesting round trip, given a date derive the epoch then given the epoch derive the date).
alter table items
add created_date date generated always as
('1970-01-01'::timestamp + (created::integer/ 86400) * interval '1 day')
stored;
Now this query reduces itself to the simple and straight forward:
select i.created_date,count(*)
from my_schema.items i
where i.category = 'books'
group by i.created_date
order by i.created_date;
It also has the additional benefit of making all the Postgres date handling functions available.