Unfortunately, this is my first approach with SQL!
I am creating with the following code a query between an oracle DB and Excel (Power Query).
select "$Table"."Order" as "Order",
"$Table"."NR" as "Nr",
"$Table"."JDDATE" as "JDDATE"
from "POOLDB2"."3112" "$Table"
WHERE "Key" >118001
AND "CodeAA" = 1
This code works!
Now I want to format the Julian Date (CYYDDD) - for example 118001 for the 01.01.2019 - to a normal date format.
Does anyone know, how to implement this into the code above?
Maybe something like :
select "$Table"."Order" as "Order",
"$Table"."NR" as "Nr",
DATEADD(DAY, JDDATE % 1000 - 1, DATEADD(year, JDDATE/1000, 0))
"$Table"."JDDATE" as "JDDATE"
from "POOLDB2"."3112" "$Table"
WHERE "Key" >118001
AND "CodeAA" = 1
Best regards
There are many different formats for Julian Date... In your use case, this should do it :
with t as (select 118001 jd from dual)
select to_char( to_date(to_char(1901 + floor(jd / 1000)),'YYYY') + mod(jd,1000) - 1, 'dd.mm.yyyy' ) from t
Yields : 01.01.2019
For Oracle,
select to_char(sysdate,'J') from dual; --To Julian Date
select to_date(2456143,'J') from dual; --To Normal Date
must work.
Edit: Sorry I didn't see oracle tag.
Edit: For the requested behavior by OP
select to_date(to_char(1901 + floor(118001 / 1000)),'YYYY') from dual;
You can use the 118001 value you have, split into separate year and day sections, by adding to the nominal starting date 1900-01-01 (based on your comment that 118001 is actually 2018-01-01, not 2019-01-01):
select date '1900-01-01'
+ floor(118001 / 1000) * interval '1' year
+ (mod(118001, 1000) - 1) * interval '1' day
from dual;
DATE'1900-
----------
2018-01-01
or by startng the fixed date a day earlier you can remove the explicit -1:
select date '1899-12-31'
+ floor(118019 / 1000) * interval '1' year
+ mod(118019, 1000) * interval '1' day
from dual;
DATE'1899-
----------
2018-01-19
This avoids having to build up a longer string to convert to a date, though you could do that (modifying #GMB's approach) as:
select to_date(to_char(1900 + floor(118001 / 1000)) || '-01-01', 'YYYY-MM-DD')
+ (mod(118001, 1000) - 1)
from dual;
You need to specify the month, at least, in the to_date() call as Oracle defaults to the current month if that is not supplied. That behaviour is tucked away in the documentation:
If you specify a date value without a time component, then the default time is midnight. If you specify a date value without a date, then the default date is the first day of the current month.
The first part of that is fairly well known and makes sense ; the second part is a bit less obvious, and doesn't make it clear that it applies to partial dates too - so ifyou don't supply a year then the current year is used; if you don't supply a month then the current month is used; but if you don't supply a day then the 1st is used.
You can see what it's doing with some test conversions:
select to_date('2018-12-25', 'YYYY-MM-DD') as demo_a,
to_date('12:34:56', 'HH24:MI:SS') as demo_b,
to_date('2019', 'YYYY') as demo_c,
to_date('07-04', 'MM-DD') as demo_d,
to_date('2019-01', 'YYYY-MM') as demo_e
from dual;
DEMO_A DEMO_B DEMO_C DEMO_D DEMO_E
------------------- ------------------- ------------------- ------------------- -------------------
2018-12-25 00:00:00 2018-12-01 12:34:56 2019-12-01 00:00:00 2018-07-04 00:00:00 2019-01-01 00:00:00
Related
I am working out of Oracle SQL. I have some dates that may have been poorly formatted when loading.
I'm doing a basic Max(date)-Min(Date) to get the difference in days. My results are:
+000000156 00:00:00.000000
+000000149 00:00:00.000000
+00 00:00:00.000000
I want to do some basic math with these date differences (average, etc) but I get an error message.
How do I convert these strings into numbers?
My guess is that the columns are timestamps, not dates, as the results are intervals not numbers. As you've found, Oracle have not got around to overloading the standard aggregate functions for intervals (vote for this feature on the Oracle Database Ideas forum) and currently you still have to either write your own or cast the timestamps to dates.
with demo (start_date, end_date) as
( select timestamp '2019-12-31 00:00:00', timestamp '2020-06-04 00:00:00' from dual union all
select timestamp '2020-01-31 00:00:00', timestamp '2020-06-28 00:00:00' from dual
)
select end_date - start_date as elapsed_interval
, cast(end_date as date) - cast(start_date as date) as elapsed_days
from demo;
ELAPSED_INTERVAL ELAPSED_DAYS
----------------------------- ------------
+000000156 00:00:00.000000000 156
+000000149 00:00:00.000000000 149
Basic math with dates:
date + number = date + number of days (also fractions)
SELECT SYSDATE + 1 FROM DUAL; -- tomorrow
date - number = date - number of days
SELECT SYSDATE - 1/24 FROM DUAL; -- one hour ago
date - date = numbers of days between dates (also fraction of days)
date + date = impossible
months_between(date1, date2) = returns months between two dates
add_months(date, number) = adds number (months) to date
if you have a string or number and it can be the n-th day of the year (for instance 156),
you can transform in date with TO_DATE('156', 'DDD')
if you have a string with a particular format, you can transform it in date with
TO_DATE(string, format of the date you imagine)
https://www.techonthenet.com/oracle/functions/to_date.php
if you need the opposite transform, that is transforming date to char (or number), use TO_CHAR(date, format of the date)
I have a start_time which is already formatted as date type and have duration as number like 449. It means 449 seconds. So i need end_time. Of course i can obviously convert duration to date format and add duration on start_time using below simply queries
select to_char(to_date(USE_SEC,'sssss'),'hh24miss')
from ABA_RM_INB_USAGE;
USE_SEC column is containing integer(number in oracle) like 1167
and above query is returning date formatted result like 001927 that is okay.
This is query that add duration on start_time
select to_char(USE_STRT_DTTM, 'hh24miss') + to_char(to_date(USE_SEC, 'sssss'), 'hh24miss') as duration
from ABA_RM_INB_USAGE;
This is returning that result which is problem that convert to date format
95980.
It means 09:59:80 oops 80 seconds is absolutely wrong. Can i add dates with keep their formats. How can i ?
You can use +. This is the traditional method:
select start_time + duration / 24*60*60
You can write this now as:
select start_time + duration * interval '1' second
Your first query is converting your number-of-second value to a string. In your second query you are converting the start time to another string. Both represent HHMISS. Then you add them together, effectively:
'094053' + '001927'
For the addition operator to work they are implicitly converted to numbers, so it becomes:
94053 + 1927
which gives you your (numeric) result of 95980.
As soon as you convert to strings you are losing the ability to treat them as dates and honour the mod-60 behaviour for minutes and seconds, which is my you appear to end up with 80 seconds - but they aren't really seconds at all, it's just a number. You also lose the mod-24 behaviour for hours, so if your start time is just before midnight and the duration pushes you over midnight, your result wouldn't reflect that either.
As #GordonLinoff suggested, keep your date as a date, and add the number of seconds as a number, or a number converted to an interval:
USE_STRT_DTTM + USE_SEC / (24*60*60)
or:
USE_STRT_DTTM + USE_SEC * interval '1' second
Demo:
-- CTE for sample data
with ABA_RM_INB_USAGE (USE_STRT_DTTM, USE_SEC) as (
select to_date('09:40:53', 'HH24:MI:SS'), 1167 from dual
union all
select to_date('23:54:55', 'HH24:MI:SS'), 449 from dual
)
-- query showing working
select USE_STRT_DTTM,
USE_SEC,
to_char(to_date(USE_SEC, 'sssss'), 'hh24:mi:ss') as use_sec_hhmiss,
USE_SEC * interval '1' second as use_sec_interval,
USE_STRT_DTTM + USE_SEC / (24*60*60) as result1,
USE_STRT_DTTM + USE_SEC * interval '1' second as result2
from ABA_RM_INB_USAGE;
USE_STRT_DTTM USE_SEC USE_SEC_HHMISS USE_SEC_INTERVAL RESULT1 RESULT2
------------------- ------- -------------- ------------------- ------------------- -------------------
2019-08-01 09:40:53 1167 00:19:27 +00 00:19:27.000000 2019-08-01 10:00:20 2019-08-01 10:00:20
2019-08-01 23:54:55 449 00:07:29 +00 00:07:29.000000 2019-08-02 00:02:24 2019-08-02 00:02:24
Read more about Datetime/Interval Arithmetic.
I have a start_time which is already formatted as date type
Your column is (I hope, and seems to be the case from your query) a date. Dates do not have intrinsic human-readable formats. When you query your table your client will format the date to something readable, using either its own preferences or your session's NLS_DATE_FORMAT.
Of course i can obviously convert duration to date format and add duration on start_time
You originally converted your duration to a date data type (via to_date()), at 00:19:27 on the first day of the current month (which is what if defaults to if not day, month or year components are supplied; my CTE above is doing the same). You cannot add a date to another date. That even has its own error, "ORA-00975: date + date not allowed". So you then converted both your date values (start time and converted duration) to strings. You can't add strings together either, as that makes no sense; but if you try Oracle will implicitly try to convert both strings to numbers. In this case that implicit conversion works for both strings, but it usually won't; the superficially-similar '09:40:53' + '00:19:27' would get "ORA-01722: invalid number".
In Oracle DATE values do not have a format - you use the TO_CHAR function to format them when you need to output them.
In this case it looks like you need to use an interval. You have a field which contains a number of seconds that you want to convert to an interval - for this you can use the TO_DSINTERVAL function, although amusingly enough you have to convert the number to a string in order to use the function to convert it to an interval:
-- Version using TO_DSINTERVAL
WITH cteData AS (SELECT USE_STRT_DTTM + TO_DSINTERVAL('PT' || TO_CHAR(USE_SEC) || 'S') AS DT_TIME
FROM ABA_RM_INB_USAGE)
SELECT TO_CHAR(DT_TIME, 'YYYY-MM-DD HH24:MI:SS') FORMATTED_DATE_TIME
FROM cteData;
Docs for TO_DSINTERVAL here
dbfiddle demonstrating this in use here
EDIT
As #AlexPoole points out, the better function to use here is NUMTODSINTERVAL:
-- Version using NUMTODSINTERVAL
WITH cteData AS (SELECT USE_STRT_DTTM + NUMTODSINTERVAL(USE_SEC, 'SECOND') AS DT_TIME
FROM ABA_RM_INB_USAGE)
SELECT TO_CHAR(DT_TIME, 'YYYY-MM-DD HH24:MI:SS') FORMATTED_DATE_TIME
FROM cteData;
Docs for NUMTODSINTERVAL here
updated dbfiddle here
Given - number of minutes (number oracle type) from the beginning of a day, e.g. 480. Need to get standard oracle time, e.g. - 08:00:00 AM. Is there any good functions to do such operation?
Better use INTERVAL 'minutes' MINUTE to add the number of minutes. Easy to understand.
your_date_time + INTERVAL '480' MINUTE
For example,
SQL> SELECT TRUNC(SYSDATE), TRUNC(SYSDATE) + INTERVAL '480' MINUTE tmstamp FROM dual;
TRUNC(SYSDATE) TMSTAMP
------------------- -------------------
11/19/2015 00:00:00 11/19/2015 08:00:00
In fact, another way which is independent of NLS settings when you have to pass the date as literal. Thus, instead of using TO_DATE, use the ANSI Date literal which uses a fixed format 'YYYY-MM-DD' and is NLS independent.
SQL> SELECT DATE '2015-11-19' curr_date, DATE '2015-11-19' + INTERVAL '480' MINUTE tmstamp
2 FROM dual;
CURR_DATE TMSTAMP
------------------- -------------------
11/19/2015 00:00:00 11/19/2015 08:00:00
UDPATE
Given - number of minutes (number oracle type) from the beginning of a day, e.g. 480
If the minutes value is not static in SQL to be hard-coded, but a PL/SQL variable, then as #AlexPoole mentioned you need to use NUMTODSINTERVAL.
For example,
NUMTODSINTERVAL(480, 'MINUTE')
Having said that,
The Oracle PL/SQL NUMTODSINTERVAL function converts an input number to its specified Interval Day to Second Unit equivalent. The allowed interval units can be DAY, HOUR, MINUTE, or SECOND.
The return type of the function is INTERVAL.
For example,
SQL> SELECT NUMTODSINTERVAL(480, 'MINUTE') intrvl FROM DUAL;
INTRVL
---------------------------------------------------------------------
+000000000 08:00:00.000000000
Just add minutes/1440 to the date, e.g.
select to_date('1.1.2015','dd.mm.yyyy')+480/1440 from dual;
There is open date 2015-05-19 10:40:14 and close date 2015-05-20 09:21:11
when I subtract them I am getting (close_date.date_value - open_date.date_value)
some 9.45104166666666666666666666666666666667E-01 value
I want to ignore the time 10:40:14 and 09:21:11 from 2 dates
similarly I am subtracting (SYSDATE - open_date.date_value) and get the number of days in number when I subtract 2 dates
Could anyone help me resolving this problem
case
when s then
(close_date.date_value - open_date.date_value)
else
(SYSDATE - open_date.date_value)
end as "dd",
You can use DATEDIFF function. Here is the code
SELECT DATEDIFF(DAY, CONVERT(DATETIME, '2015-05-19 10:40:14'), CONVERT(DATETIME, ' 2015-05-20 09:21:11'))
Try this
case
when status_name.list_value_id=9137981352013344123 then
(TRUNC(close_date.date_value) - TRUNC(open_date.date_value))
else
(TRUNC(sysdate) - TRUNC(open_date.date_value))
end as "e2e execution time",
From performance point of view, I would not use TRUNC as it would suppress any regular index on the date column. I would let the date arithmetic as it is, and ROUND the value.
For example,
SQL> SELECT SYSDATE - to_date('2015-05-20 09:21:11','YYYY-MM-DD HH24:MI:SS') diff,
2 ROUND(
3 SYSDATE - to_date('2015-05-20 09:21:11','YYYY-MM-DD HH24:MI:SS')
4 ) diff_round
5 FROM dual;
DIFF DIFF_ROUND
---------- ----------
29.1248264 29
SQL>
I have a table called "subscription" as below.
desc subscription;
Name Null Type
--------------------- -------- ----------
SUBSCRIPTION_ID NOT NULL NUMBER(38)
EXPIRATIONDATE DATE`
And output of the query as below.
SELECT
subscription_id,
expirationdate
FROM subscription
WHERE subscription_id = 41919;
SUBSCRIPTION_ID EXPIRATIONDATE
---------------------- -------------------------
41919 18-JAN-14 13:45:56
And I'm trying to execute following query in different ways.
1st Query returns one row:
SELECT s.subscription_id
FROM subscription$active s
WHERE s.expirationdate - (116 / 24)
BETWEEN TO_DATE('13-JAN-14 11:38:22', 'dd/mm/yyyy hh24:mi:ss')
AND TO_DATE('13-JAN-14 18:30:00', 'dd/mm/yyyy hh24:mi:ss')
AND s.subscription_id = 41919;
SUBSCRIPTION_ID
----------------------
41919
2nd Query returns no rows:
SELECT s.subscription_id
FROM subscription$active s
WHERE s.expirationdate - (116 / 24)
BETWEEN (trunc(1389613102220 / (1000), 0) / (24 * 60 * 60))
+ to_date('01/01/1970', 'mm/dd/yyyy')
AND (trunc(1389637800000 / (1000), 0) / (24 * 60 * 60))
+ to_date('01/01/1970', 'mm/dd/yyyy')
AND s.subscription_id = 41919;
SUBSCRIPTION_ID
----------------
Here both the above where clause are same. 1st one is trying to use "to_date" and 2nd one converts "long to date". But when I see the out put, the first one returns a row and 2nd doesnot return any result.
I couldn't find out what is difference the 'long to date' conversion makes here.
The conversion between long to date is also correct.
select (trunc(1389613102220 / (1000), 0) / (24 * 60 * 60)) + to_date('01/01/1970','mm/dd/yyyy') from dual
Output:
13-JAN-14 11:38:22
And
select (trunc(1389637800000 / (1000), 0) / (24 * 60 * 60)) + to_date('01/01/1970','mm/dd/yyyy') from dual
Output:
13-JAN-14 18:30:00
Can someone help me to understand the difference between the 1st and 2nd query ?
The problem isn't really your epoch-based query, it's the data in your table (and to some extent the way you're constructing your dates for the non-epoch version). You are using 2-digit years and a 4-digit format mask. When you use to_date in your filter you're actually using the year 0014, not 2014; you can see that just by converting the string value, but showing the result with the full four-digit year:
select to_char(to_date('13-JAN-14 11:38:22', 'dd/mm/yyyy hh24:mi:ss'),
'YYYY-MM-DD HH24:MI:SS') as test_date
from dual;
TEST_DATE
-------------------
0014-01-13 11:38:22
The critical part is that you're converting 14 using format model YYYY. As the documentation mentions:
Numeric elements are padded with leading zeros to the width of the
maximum value allowed for the element. For example, the YYYY element
is padded to four digits (the length of '9999')
Although that's mostly talking about to_char in that section, the same applies to to_date. When you do to_date('14', 'YYYY') it's interpreted as to_date('0014', 'YYYY'). You could use RRRR instead, or RR since you're only providing two digits of the year anyway, either of which would give you 2014; but it's better to be explicit.
It looks like you did that during insertion as well, because your first query will only find record 41919 if it's expiration date is also in 0014.
When you use the epoch timestamp conversion you are actually getting 2014, so your record is really not in that range.
To confirm that, specify a format model with the full year in your initial query:
select subscription_id,
to_char(expirationdate, 'YYYY-MM-DD HH24:MI:SS') as expirationdate
from subscription
where subscription_id = 41919;
SUBSCRIPTION_ID EXPIRATIONDATE
--------------- -------------------
41919 0014-01-18 13:45:56
You'll also see no data if you change the date string, or the format model (which only works now because Oracle is sometimes too helpful parsing these), in your first query:
select s.subscription_id from subscription s
where s.expirationdate - (116/24) between TO_DATE('13-JAN-14 11:38:22',
'dd-mon-rr hh24:mi:ss')
and TO_DATE('13-JAN-14 18:30:00', 'dd-mon-rr hh24:mi:ss')
and s.subscription_id=41919;
... which will return no rows if the table date is 0014.
Examine the full output of the explain plan, generated by querying DBMS_Xplan(), and it will show you the values being used internally as a result of evaluating those constants. That might show the presence of an unexpected data type conversion.