How to use toChar function in JOOQ? - java

I have to use toChar() function in JOOQ? Right now i have used below code
TO_CHAR(PaymentDate, 'YYYY-MM-DD') <= TO_CHAR(SYSDATE,'YYYY-MM-DD')");
Which i have to convert into JOOQ. How to use this in JOOQ?

Oracle's TO_CHAR() function is not explicitly supported by jOOQ 3.2. I have added a feature request for this: #2832.
In the mean time, you will have to resort to plain SQL as documented in the manual. For instance, you could write:
// Create reusable fields:
Field<String> f = DSL.field(
"TO_CHAR({0}, 'YYYY-MM-DD')", String.class, T.PaymentDate);
// Create reusable conditions:
Condition c = DSL.condition(
"TO_CHAR({0}, 'YYYY-MM-DD') <= TO_CHAR(SYSDATE, 'YYYY-MM-DD')",
T.PaymentDate);
Note that {0} is a reference to the first QueryPart argument of DSL.condition(String, QueryPart...), for instance.

Related

How do I use Postgres's to_char in a jOOQ query?

I am trying to convert the following PostgreSQL query to jOOQ:
SELECT count(*), to_char(created_date, 'YYYY-MM-DD') as year_month_date
FROM log
GROUP BY year_month_date
ORDER BY year_month_date
What I have is:
jooq.select(
DSL.count(),
DSL.field("to_char(created_date, 'YYYY-MM-DD') as year_month_date")
)
.from(LOG)
.groupBy(DSL.field("year_month_date"))
.orderBy(DSL.field("year_month_date"))
.fetch();
Is there a way to do using jOOQ's fluent API so I don't have to use strings?
Using TO_CHAR()
There is a pending feature request to add support for vendor specific to_char() functions: https://github.com/jOOQ/jOOQ/issues/8381
In order to standardise on such a function, more research needs to be done to be sure we can cover everything each vendor implements here, as the formatting logic is unfortunately quite vendor specific, and stringly typed.
So, if you want to use to_char(), currently, you will have to resort to using plain SQL templating, which you already did. You could obviously factor out this utility in a reusable form, such as:
public static Field<String> toChar(Field<?> date, String format) {
return DSL.field("to_char({0}, {1})", SQLDataType.VARCHAR, date, DSL.inline(format));
}
Truncating dates
Of course, in your particular query, you could also resort to using standard SQL features, such as CAST(). I think that what you're trying to do is truncate time information from your timestamp or timestamptz column, so you could do this instead:
SELECT count(*), CAST (created_date AS DATE) d
FROM log
GROUP BY d
ORDER BY d
Or with jOOQ:
Field<Date> d = LOG.CREATED_DATE.cast(SQLDataType.DATE);
jooq.select(count(), d)
.from(LOG)
.groupBy(d)
.orderBy(d)
.fetch();

How to bind variables to conditional statement when using jOOQ to build SQL?

I'm using jOOQ-3.11.9 to build SQL. The following is my code:
String sql = DSL.using(SQLDialect.MYSQL)
.select(DSL.asterisk())
.from(table("service"))
.where("name = ?", "service1")
.getSQL();
What I expect is
select * from service where (name = "service1")
But the result is
select * from service where (name = ?)
Is there anything wrong with my code?
This works as expected. The default Settings.statementType value is StatementType.PREPARED_STATEMENT, so jOOQ by default generates bind value placeholders in your SQL string, which can be used for execution in other tools, such as JDBC, Spring, etc.
You can pass the ParamType.INLINE value to the getSQL() method, or specify Settings.withStatementType(StatementType.STATIC_STATEMENT)
Please consider the explanation in the Javadoc for more details:
https://www.jooq.org/javadoc/latest/org/jooq/Query.html#getSQL--
You should use the field name from jOOQ-generated classes:
String sql = DSL.using(SQLDialect.MYSQL)
.select(DSL.asterisk())
.from(YOURENTITY)
.where(YOURENTITY.NAME.eq(nameParam))
.getSQL();
YOURENTITY should be the jOOQ-generated class in your project. nameParam would then be an argument passed to the method wrapping the above query.
jOOQ has pretty good docs with lots of examples:
https://www.jooq.org/doc/3.11/manual/sql-building/sql-statements/select-statement/where-clause/

Get table/column metadata from JOOQ parser result

Using the JOOQ parser API, I'm able to parse the following query and get the parameters map from the resulting Query object. From this, I can tell that there is one parameter, and it's name is "something".
However, I haven't been able to figure out how to determine that the parameter "something" is assigned to a column named "BAZ" and that column is part of the table "BAR".
Does the parser API have a way to get the table/column metadata associated to each parameter?
String sql = "SELECT A.FOO FROM BAR A WHERE A.BAZ = :something";
DSLContext context = DSL.using...
Parser parser = context.parser();
Query query = parser.parseQuery(sql);
Map<String, Param<?>> params = query.getParams();
Starting from jOOQ 3.16
jOOQ 3.16 introduced a new, experimental (as of 3.16) query object model API, which can be traversed, see:
The manual
A blog post about traversing jOOQ expression trees
Specifically, you can write:
List<QueryPart> parts = query.$traverse(
Traversers.findingAll(q -> q instanceof Param)
);
Or, to conveniently produce exactly the type you wanted:
Map<String, Param<?>> params = query.$traverse(Traversers.collecting(
Collectors.filtering(q -> q instanceof Param,
Collectors.toMap(
q -> ((Param<?>) q).getParamName(),
q -> (Param<?>) q
)
)
));
The Collectors.toMap() call could include a mergeFunction, in case you have the same param name twice.
Pre jOOQ 3.16
As of jOOQ 3.11, the SPI that can be used to access the internal expression tree is the VisitListener SPI, which you have to attach to your context.configuration() prior to parsing. It will then be invoked whenever you traverse that expression tree, e.g. on your query.getParams() call.
However, there's quite a bit of manual plumbing that needs to be done. For example, the VisitListener will only see A.BAZ as a column reference without knowing directly that A is the renamed table BAR. You will have to keep track of such renaming yourself when you visit the BAR A expression.

How to select points within polygon in PostGIS using jOOQ?

I have a table sensor_location:
CREATE TABLE public.sensor_location (
sensor_id INTEGER NOT NULL,
location_time TIMESTAMP WITHOUT TIME ZONE NOT NULL,
location_point public.geometry NOT NULL,
CONSTRAINT sensor_location_sensor_id_fkey FOREIGN KEY (sensor_id)
REFERENCES public.sensor(id)
ON DELETE NO ACTION
ON UPDATE NO ACTION
NOT DEFERRABLE
)
I want a query which will return sensor_ids of sensors and location_times within selected polygon.
The query should look something like:
SELECT
sensor_id,
location_time,
FROM
public.sensor_location
WHERE
ST_Within(location_point, ST_Polygon(ST_GeomFromText('LINESTRING(-71.050316 48.422044,-71.070316 48.422044,-71.070316 48.462044,-71.050316 48.462044,-71.050316 48.422044)'), 0));
How can I do that using jOOQ? Is it even possible to use jOOQ with PostGIS? Do I have to write my own sql query and just execute it with jOOQ?
I found this but I have no idea how to use it. I'm still a novice Java programmer.
Using jOOQ 3.16 out-of-the-box GIS support
Starting with jOOQ 3.16 (see #982), jOOQ will offer out-of-the-box support for the most popular GIS implementations, including PostGIS
As always with jOOQ, just translate your query to the equivalent jOOQ query:
ctx.select(SENSOR_LOCATION.SENSOR_ID, SENSOR_LOCATION.LOCATION_TIME)
.from(SENSOR_LOCATION)
.where(stWithin(
SENSOR_LOCATION.LOCATION_POINT,
// The ST_Polygon(...) wrapper isn't really needed
stGeomFromText("LINESTRING(...)", 0
))
.fetch();
Historic answer, or when something is still missing
... then, using plain SQL will certainly do the trick. Here's one example, how to do that:
ctx.select(SENSOR_LOCATION.SENSOR_ID, SENSOR_LOCATION.LOCATION_TIME)
.from(SENSOR_LOCATION)
.where("ST_WITHIN({0}, ST_Polygon(ST_GeomFromText('...'), 0))",
SENSOR_LOCATION.LOCATION_POINT)
.fetch();
Note how you can still use some type safety by using the plain SQL templating mechanism as shown above
If you're running lots of GIS queries
In this case, you probably want to build your own API that encapsulates all the plain SQL usage. Here's an idea how to get started with that:
public static Condition stWithin(Field<?> left, Field<?> right) {
return DSL.condition("ST_WITHIN({0}, {1})", left, right);
}
public static Field<?> stPolygon(Field<?> geom, int value) {
return DSL.field("ST_Polygon({0}, {1})", Object.class, geom, DSL.val(value));
}
If you also want to support binding GIS data types to the JDBC driver, then indeed, custom data type bindings will be the way to go:
http://www.jooq.org/doc/latest/manual/sql-building/queryparts/custom-bindings
You will then use your custom data types rather than the above Object.class, and you can then use Field<YourType> rather than Field<?> for additional type safety.
I found jooq-postgis-spatial spatial support: https://github.com/dmitry-zhuravlev/jooq-postgis-spatial
It allows working with geometries either using jts or postgis types.

JOOQ : Unable to implement Seek in Dynamic Query

I am trying to write a Dynamic query which uses the seek method. My usage of addSeekAfter as a replacement for seek is flawed. How do I fix it?
// my current setup
create.select(TOPIC.ID, TOPIC.DESCRIPTION)
.from(TOPIC)
.orderBy(TOPIC.MODIFIED_ON.desc(), TOPIC.ID.desc())
.seek(recModifiedOn, ULong.valueOf(recentTopicId))
.limit(noOfRecords)
.fetch()
.map(new TopicRecordMapper());
// what I want to move to
SelectQuery query = create.selectQuery();
query.addSelect(TOPIC.ID, TOPIC.DESCRIPTION);
query.addFrom(TOPIC);
query.addOrderBy(TOPIC.MODIFIED_ON.desc(), TOPIC.ID.desc());
// below addSeekAfter is not yielding identical results as above
query.addSeekAfter(
DSL.field(TOPIC.MODIFIED_ON.le(TimeUtils.getTime(recentModifiedOn))),
DSL.field(TOPIC.ID.le(ULong.valueOf(recentTopicId))));
query.addLimit(noOfRecords);
query.fetch().map(new TopicRecordMapper());
The DSL API provides a convenience method seek(T1, T2) where this:
.seek(value1, value2)
Is just short for this:
.seek(DSL.val(value1), DSL.val(value2))
In fact, most of jOOQ's API is overloaded for convenience so that you don't have to explicitly create bind values using DSL.val() all the time.
However, the "model API" (see the manual's section about DSL vs model API for details) doesn't contain as many convenience methods. Which means that you have to create bind values explicitly. Write this:
query.addSeekAfter(
DSL.val(recentModifiedOn),
DSL.val(ULong.valueOf(recentTopicId)));
For more information, please consider also looking into the manual's section about bind variables.
This seems to solve it. Is it the right way?
Field[] topicIdArgs = new Field[]{DSL.val(ULong.valueOf(recentTopicId), ULong.class)};
Field[] args = new Field[]{DSL.val(TimeUtils.getTime(recentModifiedOn), Timestamp.class)};
query.addSeekAfter(DSL.function("", Timestamp.class, args), DSL.function("", ULong.class, topicIdArgs));
[Edited. Look at Lukas' answer below]

Categories