In MonetDB, how can I get the date as an integer? - sql

I want to be able to do something like
SELECT cast(my_date_col AS int) FROM my_table;
I would like to get the integer which MonetDB uses internally, i.e. the value you'd find if you looked into the BAT structure and got the appropriate element in code in MonetDB's GDK. Now, AFAICT, this internal value is the number of days since the Epoch, being Jan 1st on "Year 0" (so January 3rdt year 2 would be 366+365+2 = 732).
The best I could actually manage is
SELECT my_date_col AS int - cast('1-1-1' AS date) - 366 FROM my_table;
As MonetDB won't accept "Year zero" dates. This is rather an ugly hack, I'd like to do better. Help me?

If you're trying to get the number of days between "my_date_col" and 1970-01-01, in standard SQL you'd just subtract the one from the other. Your platform, monetdb, seems to support this syntax, but I don't have it installed. I wrote these examples in PostgreSQL.
select current_date - date '1970-01-01' as num_days;
num_days
--
16213
Check that result by adding 16213 days to the current date (2014-05-23).
select cast ((date '1970-01-01' + interval '16213' day) as date) as target_date
target_date
--
2014-05-23
The cast is necessary, because the result of this addition is a timestamp, not a date.
In your case, you want a column name instead of "current_date". So you're looking for something along these lines.
select my_date_col - date '1970-01-01' as num_days
from your-table-name;

Related

Query with CASE WHEN / LAST_DAY is not giving any output

Running below query and not getting the output. Can someone please tell whats wrong in it?
Select distinct (table.datex)
from table
where table.datex =
(
CASE when extract( day from sysdate) >=19
then last_day(add_months(sysdate, -1))
else last_day(add_months(sysdate, -2))
END
)
Sample data
Datex
ID
30-JUN-21
A
31-MAY-21
B
29-JUN-21
C
Expected result
Datex
30-JUN-21
When I am passing the value hard-coded(calculated by the case) to where clause it's working fine, but when I apply the case it's not working. No error. No output is coming.
Date or datetime?
Oracle's LAST_DAY doesn't do what the name suggests, and the docs (https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/LAST_DAY.html#GUID-296C7C02-7FB9-4AAC-8927-6A79320CE0C6) fail to explain that, too.
Unlike several other DBMS Oracle doesn't have a date type. It only has a datetime type and they even call that inappropriately DATE. This means that a "date" in Oracle always has a time part. A date with its time part set to 00:00:00 can be considered a day's midnight (i.e. the very beginning of the day) or the whole day.
The function SYSDATE gives us a date in the sense of the DATE datatype, not in the sense of a real day, i.e. it gives us the datetime of "now", e.g. 2021-07-20 14:38:00. ADD_MONTHS changes the month in that datetime (and sometimes the year and sometimes even the day), i.e. leaves the time part untouched. LAST_DAY, too, changes the date part to get to the last day of the month, but leaves the time part untouched.
Your CASE expression hence results in something like TIMESTAMP '2021-07-20 14:38:00' and not in DATE '2021-07-20' as one might expect.
You say that you tried your query with the date you computed wth your case expression, and it worked. Did you compute the resulting day in your head or with a query? If the latter: The tool you are using may be set to only display a datetime's date part and omit the time part. This would explain why you only saw 30-JUN-21 when checking the CASE expression.
Solution
Truncate the datetime down to a whole day
Select distinct datex
from mytable
where (extract(day from sysdate) >=19 and datex = trunc(last_day(add_months(sysdate, -1))))
or (extract(day from sysdate) < 19 and datex = trunc(last_day(add_months(sysdate, -2))))
It doesn't matter whether you apply TRUNC late as in my example or right away on SYSDATE (with TRUNC(SYSDATE)) by the way. The only aim is to get rid of the time part at some point in the expression.
Don't use case in where clauses. Boolean logic can handle that.
And take a look if it is really the condition you want
Select distinct datex
from your_table
where
(
extract(day from sysdate) >=19
and datex = last_day(add_months(sysdate,-1))
)
or
(
extract(day from sysdate) < 19
and datex = last_day(add_months(sysdate,-2))
)

Difference between 2 timestamps in Oracle SQL

Please help in finding the difference between 2 timestamps in SQL
I tried using
Select (cast(sysdate as timestamp) - cast(sysdate-2 as timestamp)) diff from dual;
I got 'ORA-00911:invalid character' error
If you want something that looks a bit simpler, try this for finding events in a table which occurred in the past 1 minute:
With this entry you can fiddle with the decimal values till you get the minute value that you want. The value .0007 happens to be 1 minute as far as the sysdate significant digits are concerned. You can use multiples of that to get any other value that you want:
select (sysdate - (sysdate - .0007)) * 1440 from dual;
Result is 1 (minute)
Then it is a simple matter to check for
select * from my_table where (sysdate - transdate) < .00071;
You are not finding the difference between two timestamps; you are finding the difference between two dates and casting them to timestamps in the process. This is unnecessary and you can just leave them as date values and explicitly cast the difference to an INTERVAL DAY TO SECOND data type (which would be the output if you used timestamps):
SELECT (SYSDATE - (SYSDATE - 2)) DAY TO SECOND AS diff
FROM DUAL;
Which outputs the interval:
DIFF
+02 00:00:00.000000
However, your code works db<>fiddle.
You will probably find that the error:
ORA-00911:invalid character
Is nothing to do with your query and is related to the ; statement terminator at the end. If you are running your query through a 3rd-party application (i.e. C#, Java, Python, etc.) then they will pass single statements to the database to execute and, in this case, having the statement terminator is invalid syntax (as there will only be one SQL statement) and you just need to remove it.

Date not valid for month specified

I have a problem when running this Oracle SQL statement:
SELECT *
FROM tbl_content
WHERE last_updated >= (systimestamp - INTERVAL '1' month(1))
ORDER BY last_updated desc
And this error:
java.sql.SQLException: ORA-01839: date not valid for month specified
at oracle.jdbc.driver.DatabaseError.throwSqlException(DatabaseError.java:111)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:330)
at oracle.jdbc.driver.T4CTTIoer.processError(T4CTTIoer.java:287)
at oracle.jdbc.driver.T4C8Oall.receive(T4C8Oall.java:742)
at oracle.jdbc.driver.T4CPreparedStatement.doOall8(T4CPreparedStatement.java:212)
at oracle.jdbc.driver.T4CPreparedStatement.executeForRows(T4CPreparedStatement.java:951)
at oracle.jdbc.driver.OracleStatement.executeMaybeDescribe(OracleStatement.java:1053)
at oracle.jdbc.driver.T4CPreparedStatement.executeMaybeDescribe(T4CPreparedStatement.java:835)
at oracle.jdbc.driver.OracleStatement.doExecuteWithTimeout(OracleStatement.java:1123)
at oracle.jdbc.driver.OraclePreparedStatement.executeInternal(OraclePreparedStatement.java:3284)
at oracle.jdbc.driver.OraclePreparedStatement.executeQuery(OraclePreparedStatement.java:3328)
at com.vtdd.sms.content.xskt.XsktService.getKQXSFollowArea(XsktService.java:4044)
at com.vtdd.sms.content.xskt.XsktService.getMessages(XsktService.java:421)
at com.vht.sms.content.ContentAbstract.getSubmitMessages(ContentAbstract.java:47)
at com.vht.sms.content.ContentFactory.getSubmitMessages(ContentFactory.java:335)
at com.vht.sms.content.ContentFactory.run(ContentFactory.java:62)
at java.lang.Thread.run(Thread.java:662)
Could you tell me what is wrong?
Firstly, why are you using systimestamp? If you want this to the month then surely sysdate is exact enough? Secondly, I like - i.e. it's personal preference - to make it extremely clear what's happening. Oracle has an add_months function, which will do what you want. So your query could easily be:
SELECT *
FROM tbl_content
WHERE last_updated >= add_months(sysdate, -1)
ORDER BY last_updated desc
What is actually wrong is that interval arithmetic doesn't adjust days - see the 6th bullet in the link:
When interval calculations return a datetime value, the result must be an actual datetime value or the database returns an error.
ADD_MONTHS does; as that link says:
If date is the last day of the month or if the resulting month has fewer days than the day component of date, then the result is the last day of the resulting month.
So, ADD_MONTHS(DATE '2011-12-31', -1) gives you 2011-11-30, while DATE '2011-12-31' - INTERVAL '1' MONTH tries to give you 2011-11-31, which as the message says, isn't a valid date.
(It's debatable if this behaviour is actually wrong; it's unexpected, but I believe it's conforming to ANSI. There may be times you want it to work this way, though I can't think of any...)

How do you find results that occurred in the past week?

I have a books table with a returned_date column. I'd like to see the results for all of the books with a returned date that occurred in the past week.
Any thoughts? I tried doing some date math, but Postgres wasn't happy with my attempt.
You want to use interval and current_date:
select * from books where returned_date > current_date - interval '7 days'
This would return data from the past week including today.
Here's more on working with dates in Postgres.
Assuming returned_date is data type date, this is simplest and fastest:
SELECT * FROM books WHERE returned_date > CURRENT_DATE - 7;
now()::date is the Postgres implementation of standard SQL CURRENT_DATE. Both do exactly the same in PostgreSQL.
CURRENT_DATE - 7 works because one can subtract / add integer values (= days) from / to a date. An unquoted number like 7 is treated as numeric constant and initially cast to integer by default (only digits, plus optional leading sign). No explicit cast needed.
With data type timestamp or timestamptz you have to add / subtract an interval, like #Eric demonstrates. You can do the same with date, but the result is timestamp and you have to cast back to date or keep working with timestamp. Sticking to date is simplest and fastest for your purpose. Performance difference is tiny, but there is no reason not to take it. Less error prone, too.
The computation is independent from the actual data type of returned_date, the resulting type to the right of the operator will be coerced to match either way (or raise an error if no cast is registered).
For the "past week" ...
To include today make it > current_date - 7 or >= current_date - 6. But that's typically a bad idea, as "today" is only a fraction of a day and can produce odd results.
>= current_date - 7 returns rows for the last 8 days (incl. today) instead of 7 and is wrong, strictly speaking.
To exclude today make it:
WHERE returned_date >= current_date - 7
AND returned_date < current_date
Or:
WHERE returned_date BETWEEN current_date - 7
AND current_date - 1
To get the last full calendar week ending with Sunday, excluding today:
WHERE returned_date BETWEEN date_trunc('week', now())::date - 7
AND date_trunc('week', now())::date - 1
BETWEEN ... AND ... is ok for data type date (being a discrete type), but typically the wrong tool for timestamp / timestamptz. See:
How to add a day/night indicator to a timestamp column?
The exact definition of "day" and "week" always depends on your current timezone setting.
What math did you try?
This should work
select * from books where current_date - integer '7'
Taken from PostgreSQL Date/Time Functions and Operators

Oracle to_date function with quarter-format

I need to find some records created in a range of quarters. For example, I'm looking for all records created between the 4th quarter of 2008 and the 1st quarter of 2010. I have this in my WHERE-clause:
...and r.record_create_date between to_date('2008 4','YYYY Q')
and to_date('2010 1','YYYY Q')
but Oracle says: ORA-01820: format code cannot appear in date input format. The Q is a valid date format symbol, so I'm not sure what's happened. Is this even a valid way to find values in between calender quarters, or is there a better way?
Also interesting, and possibly related, if I execute this:
select to_date('2009','YYYY') from dual;
The value displayed in my IDE is 2009-08-01. I would have expected 2009-08-04, since today is 2010-08-04.
This:
select to_date('2009 1','YYYY Q') from dual;
of course, fails.
(Oracle 10g)
Oracle says: ORA-01820: format code cannot appear in date input format. The Q is a valid date format symbol, so I'm not sure what's happened.
See the second column of table 2.15 at http://download.oracle.com/docs/cd/B19306_01/server.102/b14200/sql_elements004.htm#i34948. Not all format elements are allowed when converting to dates, timestamps, etc.
I recommend against using between for date range checks. People often will miss values within the ending day that the expect to be included. So I would translate:
and r.record_create_date between to_date('2008 4','YYYY Q')
and to_date('2010 1','YYYY Q')
To
and to_date('2008-10-01', 'YYYY-MM-DD') <= r.record_create_date
and record_create_date < to_date('2010-04-01', 'YYYY-MM-DD') -- < beginning of 2Q2010.
Someone asked the same question on OTN: http://forums.oracle.com/forums/thread.jspa?threadID=1081398&tstart=255
The crux of the issue is that you can not specify "Q" in the TO_DATE function.
Given that you're already specifying a portion of the date, why not provide the entire date? Mind too that to_date('2010 1','YYYY Q') would give you Jan 1st, 2010 when you really want March 31st, 2010... at a second to midnight.
Since the relationship between quarters to months is one-to-many, it doesn't make sense to do TO_DATE('2008 1', 'yyyy q'); what date should be returned? The first of the quarter, the end of the quarter, ...? (On the other hand, converting a date to a quarter - like TO_CHAR(SYSDATE, 'yyyy q') makes sense because a specific date only exists in one quarter.)
So, if you do want a query that looks for a date that falls between two quarters, you will have to "rolll your own" (explicitly stating the dates of the start/end of a quarter.)
As a side note, in case anyone is considering not using TO_DATE please do not use things like: WHERE date_value BETWEEN 'date string1' and 'date string2' without the TO_DATE function. It assumes a default date format and under certain situations can avoid potentially useful indexes altogether.
Below is one example where the same query can have a different result.
select sysdate from dual where sysdate between '1-Jan-10' and '31-Dec-10';
SYSDATE
---------
04-AUG-10
SQL> alter session set nls_date_format = 'YYYY-MM-DD';
Session altered.
SQL> select * from dual where sysdate between '1-Jan-10' and '31-Dec-10';
no rows selected
(Notice that in the second instance no error is returned. It just assumes Jan 10, 0001 and Dec. 10th, 0031.)
I think the best way is to just input the quarter start date and quarter end dates without even bothering with to_date. I think if you use
between '1-Jan-10' and '31-Dec-10'
for example, then you don't (in Oracle I believe) need to_date and it isn't much more difficult than typing in the quarter number
To calculate in Oracle the first day of a quarter and the last day of a quarter from the year and quarter:
I Use the fact
start_month= -2 + 3 * quarter
last_month = 3 * quarter
variable v_year number
variable v_quarter number
exec :v_year :=2017
exec :v_quarter:=4
select :v_year as year,
:v_quarter as quarter,
to_date(:v_year||to_char(-2+3*:v_quarter,'fm00'),'yyyymm') as quarter_start,
last_day(to_date(:v_year||to_char(3*:v_quarter,'fm00')||'01 23:59:59','yyyymmdd hh24:mi:ss')) as quarter_end
from dual a;
YEAR|QUARTER|QUARTER_START |QUARTER_END
2017| 4|2017-10-01 00:00:00|2017-12-31 23:59:59