When is systimestamp generated by Oracle - oracle

When is the return value of systimestamp function generated in an Oracle sql query? Returned value concerns the moment in which the query is submitted or is ended? And if the function is used in a subquery?
thanks!

NOTE: The discussion below is for SQL only. The behavior of sysdate and systimestamp is different in PL/SQL, the procedural programming language associated with Oracle SQL. Please see Jon Heller's comment below this Answer, and the link he provides in the comment. END OF NOTE
systimestamp is calculated once, at the BEGINNING of the execution of a query. It will have the same value within the query and all its subqueries, no matter how deep and no matter how many times systimestamp is referenced in the query.
For example, the following (intentionally convoluted) query will always return zero:
select sum (abs( date '2000-01-01'
+ ( systimestamp - (select systimestamp from dual) )
- date '2000-01-01'
)
)
from dual
connect by level <= 300000;
NOTE: In a comment below this Answer, the OP points out that - while systimestamp - systimestamp always returns zero, as it should, systimestamp - (systimestamp - 1) returns something like +01 00:00:00.762611. Which is unexpected. So let's explain that.
Date arithmetic with numbers (like 1 to represent one day) is for expressions of date data type - not for timestamps. So for the operation systimestamp - 1, the timestamp is truncated first - only whole seconds are kept, and the fraction of second after the decimal point is discarded.
Then, for an operation of type [timestamp] - [date], Oracle will first cast the date as a timestamp, and then take the difference and return an interval. Which is what the OP saw. The fraction of a second is due to truncation for the systimestamp - 1 calculation, while the first systimestamp is not truncated.
To get the same calculation "done right" (and to see that indeed the result will be exactly +01 00:00:00.000000), we must use the proper data type when we subtract one day from systimestamp. Namely, just like numbers are "what we should use" for date arithmetic, for timestamp arithmetic we must use the interval data type.
select systimestamp - (systimestamp - interval '1' day) from dual
will always return exactly +01 00:00:00.000000.

Related

Strange behavior of systimestamp and sysdate

Today I encountered a situation which I can not explain and I hope you can.
It boils down to this:
Let say function sleep() return number; loops for 10 seconds and then returns 1.
A sql query like
SELECT systimestamp s1, sleep(), systimestamp s2 from dual;
Will result in two identical values for s1 and s2. So this is somewhat optimized.
A PLSQL-Block with
a_timestamp := systimestamp + 5sec;
IF systimestamp < a_timestamp and sleep() = 1 and systimestamp > a_timestamp THEN
[...]
END IF;
will evaluate to true, because the expression get evaluated from left to right and because of the sleep() the second systimestamp is 10sec greater than the first and a_timestamp lies between both. The +5sec syntax is pseudo code, but bear with me. So here the systimestamp is not optimized.
But now it gets funky:
IF systimestamp between systimestamp and systimestamp THEN [...]
Is always false, but
IF systimestamp + 0 between systimestamp + 0 and systimestamp + 0 THEN [...]
Is always true.
Why? I am confused...
This happens with sysdate as well
When you do:
if systimestamp between systimestamp and systimestamp then
the non-deterministic systimestamp call is just being evaluated three times, and getting very slightly different results each time.
You can see the same effect with
if systimestamp >= systimestamp then
which also always returns false.
Except, it's not quite always. If the server is fast enough and/or the platform it's on has low-enough precision for timestamp fractional seconds (i.e. on Windows, which I believe still limits the precision to milliseconds) then all of those calls could still get the same value some or most of the time.
Things are a bit different in SQL; as an equivalent:
select *
from dual
where systimestamp between systimestamp and systimestamp;
will always return a row, so the condition is always true. That is explicitly mentioned in the documentation:
All of the datetime functions that return current system datetime information, such as SYSDATE, SYSTIMESTAMP, CURRENT_TIMESTAMP, and so forth, are evaluated once for each SQL statement, regardless how many times they are referenced in that statement.
There is no such restriction/optimisation (depending on how you look at it) in PL/SQL. When you use between it does say:
The value of the expression x BETWEEN a AND b is defined to be the same as the value of the expression (x>=a) AND (x<=b) . The expression x will only be evaluated once.
and the SQL reference also mentions that:
In SQL, it is possible that expr1 will be evaluated more than once. If the BETWEEN expression appears in PL/SQL, expr1 is guaranteed to be evaluated only once.
but it says nothing about skipping evaluation of a and b (or expr2 or expr3) even for system datetime functions in PL/SQL.
So all three expressions in your between will be evaluated, it will make three separate calls to systimestamp, and they will all (usually) get slightly different results. You effectively end up with:
if initial_time between initial_time + 1 microsecond and initial_time + 2 microseconds then
or to put it another way
if (initial_time >= initial_time + 1 microsecond) and (initial_time <= initial_time + 2 microseconds) then
While (initial_time <= initial_time + 2 microseconds) is always going to be true, (initial_time >= initial_time + 1 microsecond) has to be false - unless the interval between the first and third evaluations is actually zero for that platform/server/invocation. When it is zero the condition evaluates to true; the rest of the time, when there is any measurable delay, it will evaluate to false.
Your other examples all manipulate the timestamp in way that removes the fractional seconds, by turning some or all of the results into dates, as #Connor showed (and I alluded to in comments). Those aren't really relevant to your core question of why if systimestamp between systimestamp and systimestamp then is (usually) false.
If you add a number to a timestamp, you do not get a timestamp...you get a date. Thus you have lopped off the fractional seconds part, and thus comparisons that look like they should be equal might not be
SQL> select systimestamp, systimestamp+0 from dual;
SYSTIMESTAMP SYSTIMESTAMP+0
--------------------------------------------------------------------------- -------------------
18-JUN-20 11.57.28.350000 AM +08:00 18/06/2020 11:57:28
SQL> select * from dual where systimestamp > sysdate;
D
-
X
If you want to add days etc to a timestamp, use an INTERVAL data type not a NUMBER.

DATE queries using BETWEEN on oracle view

i have a problem with a date parameter query using 'between' operator in oracle view, when i do this query :
SELECT *
FROM MY_VIEW
WHERE STATUS = 'Active'
AND CHECKER_DATE BETWEEN to_date(sysdate - 1, 'DD-MON-YY') AND to_date(sysdate, 'DD-MON-YY');
it does not give me the records (actualy i have any record on that date).
I try using 'in' operator, but still not give me the records.
Please throw some information for this.
*checker_date defined as date
Your first error is to call to_date() on a value that is already a DATE. to_date() expects a VARCHAR value, so sysdate will be first converted to VARCHAR and will then immediately be converted back to a DATE value which it was to begin with.
You probably want
AND CHECKER_DATE BETWEEN trunc(sysdate) - 1 AND trunc(sysdate)
Most probably this will still not give you want you want as that would not include rows from "today". trunc(sysdate) means "today at midnight" and any row that was created today after midnight will not be included. With date/time values (and Oracle's DATE type does contain a time, despite the name) it's better to not use BETWEEN, but explicit range operators instead:
AND CHECKER_DATE >= trunc(sysdate) - 1
AND CHECKER_DATE < trunc(sysdate) + 1
trunc(sysdate) + 1 is tomorrow at midnight, so any value that is (strictly) smaller than that is "today".
All the above assumes that CHECKER_DATE is defined as DATE or TIMESTAMP
You can try:
SELECT *
FROM MY_VIEW
WHERE STATUS = 'Active'
AND CHECKER_DATE BETWEEN trunc(sysdate - 1) AND trunc(sysdate);
Oracle advises against using to_date for date. Also trunc is here because Trunc removes the time component.
may be records are not 'Active' or 'Active' a not-existing value in one of the reference tables if any and trunc(sysdate -1) and trunc(sysdate) would help

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)

compare 13digit (millisecond) unix timestamp with date in oracle

A database column (VARCHAR2 datatype) stores the date/time as 13 digit (milliseconds
) unixtimestamp format. Now when I want to compare the column with a oracle date (in question), The error thrown as 'invalid number'
I tried both ways,
converting the 13digit number to Date and compare with the date in question like below. The expressions seems valid as they are printed in select query, but if i include in the where part, it throws 'invalid number'
Here 'value' is 13th digit unixtimestamp column of VARCHAR2 datatype.
select
TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000,
TO_DATE('2014-04-21', 'YYYY-MM-DD')
from dummytable
-- where and TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000 > TO_DATE('2014-04-21', 'YYYY-MM-DD')
converting the date in question to 13digit unixtimestamp and comparing with the database column.The expressions seems valid as they are printed in select query, but if i include in the where part, it throws 'invalid number'
.
select
value,
(to_date('2013-04-21', 'YYYY-MM-DD') - to_date('1970-01-01', 'YYYY-MM-DD')) * (1000*24*60*60)
from dummytable
-- where value > ((to_date('2013-04-21', 'YYYY-MM-DD') - to_date('1970-01-01', 'YYYY-MM-DD')) * (1000*24*60*60))
any pointers? Thanks in advance.
[EDIT- 1 day later] I see the problem now. There are some data (other rows) for the 'value' column that are non-numeric. But I have another column say field, where always field='date' return value as 13 digit timestamp. Now I think when 'where' condition executes, although the field='date' is in the condition, it is still validating the other values for 'value' which are non-numeric. Is there a way to avoid this ?
Your code works just fine. The problem is in your data. Some of your values is not a number.
create table test
(value varchar2(13));
insert into test(value) values('2154534689000');
--insert into test(value) values('2 54534689000');
select TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000
from test
where TO_DATE('1970-01-01', 'YYYY-MM-DD') + value/86400000 > TO_DATE('2014-04-21', 'YYYY-MM-DD');
This code works fine. But if you uncommented the second insert, you would get exactly the same invalid number error as you get.
UPD. Allan gave you a nice hint, but i feel that it can be good to explain you a bit about views. The fact that you select from a view CAN make a difference. A view is not stored somewhere physically, when you select from it, it is just "added to your query". And then Oracle Query Optimizer starts working. Among other things, it can change the order in which your where predicates are evaluated.
For example, your the view query can have a line where value is not null and it would normally show only 'good' values. But if your query has a predicate where to_date(value,'ddmmyyyy') > sysdate, Oracle can decide to evaluate your predicate earlier, because Oracle predicts that it would "cut off" more rows, thus making the whole query faster and less momery consuming. Of course, execution will crash because of an attempt to convert a null string to date.
I believe, that Allan in his answer that he gave a link to, gave a great way to solve this problem: "wrapping" your query in a subquery that Oracle can't "unwrap":
select value
from
(select value
from my_view
where rownum > 0)
where to_date(value,'ddmmyyyy') > sysdate
Hope that helps.

Oracle sqlSubtract between two date

I need help to write query do the following:
subtract between two columns (start date and end date), and please note that the type for the columns are char not date, this is the exact format: 10-MAR-12 11.11.40.288389000 AM), then get the average for the result.
I assume this is homework, so some hints...
First don't ever store dates as varchar, it will cause you and the optimiser all sorts of problems.
Second, Oracle's date datatype can only store to second precision, and your string has fractions of a second, so you are looking at timestamp rather than date. You can convert your string to a timestamp with the to_timestamp() function, passing a suitable format mask. Oh OK, I'm feeling generous:
select to_timestamp(start_date, 'DD-Mon-RR HH.MI.SS.FF9 AM') from your_table;
Third, subtracting two timestamps will give you an interval data type, from which you will need to extract the information you want in a readable format. Search this site or elsewhere for timestamp subtraction, but I'll point you at this recent one as a sample.
The average is a bit trickier, so you may want to convert your intervals to numbers for that; again search for previous questions, such as this one. The size of the intervals, the precision you actually care about, and the way you want the output formatted, etc. will have some bearing on the approach you want to take.
If you need an approximate result then #Joachim Isaksson's answer will give you that - 'approximate' because of rounding; a duration of less than a second will show up as zero, for example. The same effect can be seen with timestamps cast to dates, which also loses the fractional seconds:
select 24*60*60*avg(
cast(to_timestamp(step_ending_time, 'DD-Mon-RR HH.MI.SS.FF9 AM') as date)
- cast(to_timestamp(step_starting_time, 'DD-Mon-RR HH.MI.SS.FF9 AM') as date)
) as avg_duration
from process_audit;
A more accurate answer can be found by extracting the various components of the timestamps, as in a question I linked to earlier. You may not need them all if you know that your durations are always less then an hour, say, but if you need more than one (i.e. if a duration could be more than a minute) then using an intermediate common table expression simplifies things a bit:
with cte as (
select to_timestamp(step_ending_time, 'DD-Mon-RR HH.MI.SS.FF9 AM')
- to_timestamp(step_starting_time, 'DD-Mon-RR HH.MI.SS.FF9 AM') as duration
from process_audit
)
select avg(extract(second from duration)
+ extract(minute from duration) * 60
+ extract(hour from duration) * 60 * 60
+ extract(day from duration) * 60 * 60 * 24) as avg_duration
from cte;
With two sample rows, one with a gap of exactly a second and one with exactly 1.5 seconds, this gives the result 1.25.
Comments about storing times in VARCHAR aside; Oracle's to_date to the rescue; this should work for you to show the average number of seconds between the times. Since you're a bit low on details on precision, I didn't bother about the "sub seconds";
SELECT 24*3600*AVG(
to_date(enddate, 'DD-Mon-YY HH.Mi.SS.????????? AM') -
to_date(startdate, 'DD-Mon-YY HH.Mi.SS.????????? AM')) avg_seconds
FROM TableA;
Demo here.

Resources