H2 vs PostgreSQL generated column with function - sql

I'm trying to setup a generated column which will also take null checks into consideration when subtracting values. In PostgreSQL I did:
ALTER TABLE session ADD COLUMN
duration INTERVAL GENERATED ALWAYS AS age(time_ended, time_started) STORED;
H2 doesn't support age function so I another patch to create alias to function:
CREATE ALIAS age FOR "net.agileb.config.H2Functions.age";
and corresponding java code:
package net.agileb.config;
import java.time.Duration;
import java.time.LocalDateTime;
public class H2Functions {
public static Duration age(LocalDateTime endDate, LocalDateTime startDate) {
return Duration.between(endDate, startDate);
}
}
I run H2 in PostgreSQL compatibility mode:
public:
type: com.zaxxer.hikari.HikariDataSource
url: jdbc:h2:mem:agileb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;MODE=PostgreSQL;
driverClassName: org.h2.Driver
but h2 still doesn't like the syntax of the generated column:
SQL State : 42001
Error Code : 42001
Message : Syntax error in SQL statement "ALTER TABLE SESSION ADD COLUMN
DURATION INTERVAL GENERATED[*] ALWAYS AS AGE(TIME_ENDED, TIME_STARTED) STORED"; expected "YEAR, MONTH, DAY, HOUR, MINUTE, SECOND"; SQL statement:
ALTER TABLE session ADD COLUMN
duration INTERVAL GENERATED ALWAYS AS age(time_ended, time_started) STORED [42001-200]
Location : db/migration/V1606395529__topic_calculated_duration_column.sql (/home/agilob/Projects/springowy/build/resources/main/db/migration/V1606395529__topic_calculated_duration_column.sql)
Line : 3
Statement : ALTER TABLE session ADD COLUMN
duration INTERVAL GENERATED ALWAYS AS age(time_ended, time_started) STORED
I understand H2 wants me to use specific interval like INTERVAL SECOND, generated as identity and STORED keyword doesn't seem to be supported.
Is there a way to make this query work in PostgreSQL and H2?

There is no way to use the same syntax for generated columns in PostgreSQL and H2.
INTERVAL data type without interval qualifier is a feature of PostgreSQL. Other DBMS, including the H2, support only standard-compliant intervals such as INTERVAL YEAR, INTERVAL YEAR(3) TO MONTH, INTERVAL DAY TO SECOND, etc. Hopefully, you can use standard-compliant interval data types in PostgreSQL too, they are also supported. But all these types a either year-month intervals or daytime intervals. Interval with YEAR and/or MONTH fields can't have DAY, HOUR, MINUTE, or SECOND fields and vice versa. If you really need a mixed interval with all these fields, you can use only the PostgreSQL and its forks.
H2 1.4.200 supports only non-standard syntax for generated columns with AS keyword (upcoming H2 2.0 also supports the standard syntax GENERATED ALWAYS AS). PostgreSQL doesn't support non-standard syntax from H2. You can build H2 from its current sources to have the possibility to use the same standard syntax here.
The biggest problem is that PostgreSQL requires non-standard STORED clause at the end of definition of generated column for a some weird reason and doesn't accept standard-compliant definitions of columns. H2 and others don't have and don't accept this clause.
So the only solution here is to use different SQL for PostgreSQL and for H2.

Related

Why does time_bucket_gapfill consistently throw a PSQLException, at 10th request?

I have a Spring Boot application, with a REST service where I use JdbcTemplate. Then I have a PostgreSQL with TimescaleDB (verson 2.3.0) where the data is stored.
In one of my endpoinds, I call the following code to get some timestamps from the database in the clients local time zone:
SELECT time_bucket_gapfill(CAST(:numOfHours * INTERVAL '1 hour' AS INTERVAL),
timestamp AT TIME ZONE 'UTC' AT TIME ZONE :timezone,
:start AT TIME ZONE :timezone, :end AT TIME ZONE :timezone) AS time
FROM info
WHERE timestamp >= :start AND timestamp < :end
GROUP BY time
When I call that specific endpoint it returns the data perfectly the first 9 times, and then on the 10th time, it throws the following SQL error:
ERROR: invalid time_bucket_gapfill argument: start must be a simple expression
The TimescaleDB manual states:
Note that explicitly provided start and stop or derived from WHERE clause values need to be simple expressions. Such expressions should be evaluated to constants at the query planning. For example, simple expressions can contain constants or call to now(), but cannot reference to columns of a table.
What they are trying to say is that these arguments must be constants. So you cannot use a parameter here.
Why this works for the first 10 executions is because of the way the JDBC driver and PostgreSQL handle these parameters:
for the first 5 executions of the JDBC java.sql.PreparedStatement, the PostgreSQL driver interpolates the parameters into the query and sends a simple query string
from the sixth execution on, the JDBC driver deems it worth creating a named prepared statement in PostgreSQL
during the first five executions of that prepared statement, PostgreSQL generates a custom plan that uses the actual parameter values
only from the sixth execution on, PostgreSQL will consider a generic plan, where the parameters are placeholders
Executing this generic plan causes the TimescaleDB error.
So there are several remedies:
Don't use parameters there. That is the best and most reliable solution. However, then you cannot use a prepared statement, but you have to construct the query string each time (dynamic SQL).
See the JDBC driver documentation:
The driver uses server side prepared statements by default when PreparedStatement API is used. In order to get to server-side prepare, you need to execute the query 5 times (that can be configured via prepareThreshold connection property). An internal counter keeps track of how many times the statement has been executed and when it reaches the threshold it will start to use server side prepared statements.
That allows you to work around the problem by setting prepareThreshold to 0, at the price of worse performance.
Set the PostgreSQL parameter plan_cache_mode to force_custom_plan to avoid the use of generic plans. This could affect your overall performance negatively.
All three solutions reduce the effectiveness of prepared statements, but that's the price you have to pay to work around this limitation of TimescaleDB.

H2 Database error Unknown data type INTERVAL

I'm working on integration tests for a JPA project. The tests run on an embedded h2 database. However, I'm getting an error from h2 during hibernate schema generation when I use
#Column(columnDefinition = "INTERVAL HOUR TO MINUTE")
The error is org.h2.jdbc.JdbcSQLException: Unknown data type: "INTERVAL";
The h2 documentation indicates that INTERVAL is supported:
http://www.h2database.com/html/datatypes.html#interval_type
I am using h2 version 1.4.197
Stepping away from JPA and working directly in the h2 console, I have tried the following script which also generates the Unknown data type error:
CREATE TABLE test_interval (id INTEGER, test_hours INTERVAL HOUR TO MINUTE);
I have tried other variations of the INTERVAL type, all of which generate the same error
I can not find any discussion of this issue anywhere.
You need to use a more recent version of H2. H2 supports the standard INTERVAL data type since 1.4.198, but 1.4.198 is a beta-quality version, use a more recent one, such as 1.4.199 or 1.4.200.
The online documentation is actual only for the latest release, currently it is 1.4.200. If you use some older version, you have to use the documentation from its distribution.

What is standard date time string format for SQL query for insering data in datetime columns?

Today I faced a problem while porting an SQL server database to H2DB. The SQL queries I had written to insert date time, wasn't working on H2DB. i.e '26-Jun-2019 01:00:00' gave exception on H2DB. My question is what is standard format for inserting date time which is database independent?
The standard format as defined by the SQL standard is the ISO format, prefixed with the keyword DATE, e.g.
DATE '2019-06-26'
or for a timestamp:
TIMESTAMP '2019-06-26 17:42:00'
However not all database products support the SQL standard in that regard, so you will most probably not find one single format that will work across all database products.

Set timezone as IST (+5:30) in DB Browser for SQLite

I have been searching for a setting in DB BRowser for SQLite on how to change the timezone to IST (Indian Standard Time +5:30) Is there a way to set it directly without running any queries? I also found some SQL queries that can convert the db time to IST but almost all are SELECT statements. I am looking for a setting to change the timezone permanently and if that is not possible then may be an update query which can read all records in the database and change/convert/replace all times to IST. Can someone shed some light on it?
My field name is "expire_time" set as DATETIME NOT NULL in CREATE TABLE
What I searched for was
INSERT INTO MyTable(MyColumn) VALUES(datetime(CURRENT_TIMESTAMP, 'localtime'))
but I am not looking for insert statement
SELECT datetime(1092941466, 'unixepoch', 'localtime');
but I am not looking for select statement
Please help me either with a setting (if available in DB Browser for SQLite) or an update query that can change all times from GMT TO IST.
Thanks.
EDIT
SQLite has no DATETIME type. And it treats datatypes very different from other DBMS. For example
CREATE TABLE T (
Field MYTYPE
);
will run OK. Sqlite is applying so called datatype affinity https://www.sqlite.org/datatype3.html#affinity to figure out one of the implemented datatypes it will use instead of stuff specified it CREATE TABLE. DATETIME (as well as MYTYPE) affinity is NUMERIC - a special affinity which means column can store any type you want, TEXT for example.
This boils down the only way to work with DATETIME in Sqlite is datetime functions. And those functions use default timezone UTC. Any other timezone must be provided explicitly as a part of the datetime string. No PRAGMA or something to change this default.
EDIT
If expire_time is currently a string expression of UTC time you can get specific timezone text value, for example
select datetime(expire_time, '+05 hours','+30 minutes') || ' IST' as t
Note datetime(d,'utc') will most probably return NULL if string d contains explicit timezone. So i advice you standardize on storing datetime as UTC in DB and convert it to different timezone needed only when generating an output. This way you have all Sqlite toolbelt at your disposal.

Are there SQL datatypes that don't work with R?

I am trying run an sqlQuery in Rstudio which seems to crash the program. I want to use the RODBC package to import a name called package name and elapsed time from a Oracle database. When I try to do an sqlQuery such as the following
dataframe <- sqlQuery(channel,
"select package_name, elapsed_time from fooSchema.barTable")
When I run this with just the package_name or other fields in the table, it works fine. If I try to run this with the elapsed_time, RStudio crashes. The datatype of elapsed_time is INTERVAL DAY (3) TO SECOND (6) so one record for example looks like this, "+000 00:00:00.22723"
Are there certain data types, such as Interval Day to Second, from Oracle that don't work in RStudio or R in general?
The problem isn't R, Rstudio, or even RODBC. The problem is that Oracle doesn't support interval data types for ODBC connections.
It is under section E.1
https://docs.oracle.com/cd/B28359_01/server.111/b32009/app_odbc.htm#CIHBFHCG
To get back to your question in a more general sense. Base R supports Date, POSIXct, and POSIXlt objects.
Dates and POSIXct objects are stored as the number of days/seconds respectively since 1/1/1970 whereas POSIXlt is a list of elements.
Whatever SQL connector you're using will need to coerce the SQL version of a date and time into one of the above. Sometimes it'll just convert to a character string. For instance with RPostgreSQL it'll take columns stored as Postgre's Date type as a character but Postgres timestamp columns will be coerced into POSIXct directly.