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();
Related
The problem I'm trying to solve here is, filtering the table using dynamic queries supplied by the user.
Entities needed to describe the problem:
Table: run_events
Columns: user_id, distance, time, speed, date, temperature, latitude, longitude
The problem statement is to get the run_events for a user, based on a filterQuery.
Query is of the format,
((date = '2018-06-01') AND ((distance < 20) OR (distance > 10))
And this query can combine multiple fields and multiple AND/OR operations.
One approach to solving this is using hibernate and concatenating the filterQuery with your query.
"select * from run_events where user_id=:userId and "+filterQuery;
This needs you to write the entire implementation and use sessions, i.e.
String q = select * from run_events where user_id=:userId and "+filterQuery;
Query query = getSession().createQuery(q);
query.setParameter("userId", userId);
List<Object[]> result = query.list();
List<RunEvent> runEvents = new ArrayList<>();
for(Object[] obj: result){
RunEvent datum = new RunEvent();
int index = -1;
datum.setId((long) obj[++index]);
datum.setDate((Timestamp) obj[++index]);
datum.setDistance((Long) obj[++index]);
datum.setTime((Long) obj[++index]);
datum.setSpeed((Double) obj[++index]);
datum.setLatitude((Double) obj[++index]);
datum.setLongitude((Double) obj[++index]);
datum.setTemperature((Double) obj[++index]);
runEvents.add(datum);
}
This just doesn't seem very elegant and I want to use the #Query annotation to do this i.e.
#Query(value = "select run_event from RunEvent where user_id = :userId and :query order by date asc")
List<RunEvent> getRunningData(#Param("userId") Long userId,
#Param("query") String query,
);
But this doesn't work because query as a parameter cannot be supplied that way in the query.
Is there a better, elegant approach to getting this done using JPA?
Using Specifications and Predicates seems very complicated for this sort of a query.
To answer the plain question: This is not possible with #Query.
It is also in at least 99% of the cases a bad design decision because constructing SQL queries by string concatenation using strings provided by a user (or any source not under tight control) opens you up for SQL injection attacks.
Instead you should encode the query in some kind of API (Criteria, Querydsl, Query By Example) and use that to create your query. There are plenty of questions and answers about this on SO so I won't repeat them here. See for example Dynamic spring data jpa repository query with arbitrary AND clauses
If you insist on using a SQL or JPQL snippet as input a custom implementation using String concatenation is the way to go.
This opens up attack for SQL injection. Maybe that’s why this feature is not possible.
It is generally a bad idea to construct query by appending random filters at the end and running them.
What if the queryString does something awkward like
Select * from Foo where ID=1234 or true;
thereby returning all the rows and bringing a heavy load on DB possibly ceasing your whole application?
Solution: You could use multiple Criteria for filtering it dynamically in JPA, but you’ll need to parse the queryString yourself and add the necessary criteria.
You can use kolobok and ignore fields with null values.
For example create one method like bellow
findByUserIdAndDistanceaLessThanAndDistancebGreaterThan....(String userid,...)
and call that method only with the filter parameters while other parameters are null
I need to retrieve from my PostgreSQL database, which has been mapped to Java using Hibernate, the average difference of dates (start and end, as you may say) of the records.
I wrote a native PostgreSQL query which works fine:
SELECT avg(date_part('days', age(datasaida, dataentrada))) as avg_days
FROM processo.processo
WHERE processo.codsituacao = '14'
AND processo.dataEntrada >= now() - interval '30 days';
The problem is that I can't figure out how to translate this query to HQL (Hibernate SQL) because of the avg(date_part('days', age(datasaida, dataentrada))) part.
I need the information to be shown in the front-end, which I am building with JSF Primefaces.
P.S.: dataEntrada - means startDate (kind of). dataSaida means endDate (kind of)
You could try
avg(DAY(function('age', datasaida, dataentrada)))
or
avg(DAY(datasaid - dataentrada))
DAY is standard JPA function: http://www.objectdb.com/java/jpa/query/jpql/date
And with function('age', datasaida, dataentrada) you call a database specific function from JPA
Well you can use function('function_name', param1, param2) in the HQL to call the native Postgresql functions:
SELECT avg(function('date_part', 'days', function('age', datasaida, dataentrada))) as avg_days
FROM processo.processo
WHERE processo.codsituacao = '14'
AND processo.dataEntrada >= now() - interval '30 days';
For further details you can check the Call PostgreSQL-specific SQL Functions section of the Hibernate with PostgreSQL – 6 things you need to know tutorial.
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.
I have a PreparedStatement intended to be run both on ORACLE and on MYSQL.
But I cannot figure out how to handle the CAST(NULL AS ...)
On Oracle the following works (but not on Mysql):
SELECT TIMB_INS,
CAST(NULL AS TIMESTAMP) AS TIMB_CLO
FROM TOPS
On Mysql the following works (but not on Oracle):
SELECT TIMB_INS,
CAST(NULL AS DATETIME) AS TIMB_CLO
FROM TOPS
(Please note that the first column selected, "TIMB_INS", returns the correct data type for target database type in both cases, i.e. TIMESTAMP for Oracle and DATETIME for MySql.)
There is a way to put it so that it works for both?
I.E. Can i make it db-indipendent in some way?
Thanks
Marco
Based on the tags I can see you're calling this statement from some java code. There are several ways doing so:
Use the DAO pattern. I.e. for each SQL flavor provide a java file that contains the SQL-s.
Use an ORM like Hibernate or JPA. That will take care of this kind of differences.
As a quick hack, you can edit the SQL manually, like in the snippet below. But then you have to determine somehow if the underlying database is Oracle or MySQL
String SQL_PATTERN = "... CAST(NULL AS %s) AS TIMB_CLO ...";
String SQL = String.format(SQL_PATTERN, isOracle ? "TIMESTAMP" : "DATETIME");
I am wondering how to query the database using the model in play 2.0 with a query like the one I listed below. I didn't see an option to pass in direct sql into the play framework 2.0.
I am trying to get a list of the expenses from a particular month.
SELECT * FROM Expensesdb.expense
WHERE month(expense.purchase_date) = 01
The option I see is to query for all the expenses and then parse each one for the month they are listed using the Date object.
I think there should be an efficient way, I can't seem to find a way to do this using ebean with Java play framework 2.0 to perform this query.
Update
Thanks Nico, I tried the exact code you have, with DateTime and I tried to use the code below, it doesn't return any Expenses. Am I doing something wrong?
Calendar calendar = Calendar.getInstance();
calendar.set(2012, 0, 01);
Date startDate = calendar.getTime();
calendar.set(2012, 0, calendar.getActualMaximum(Calendar.DAY_OF_MONTH));
Date endDate = calendar.getTime();
List<Expense> expenses = find.where().between("purchaseDate", startDate, endDate).findList();
return expenses;
I see two options:
1 - Using Ebean mapping
The idea is to search the expenses between the beginning and the end of the month, something like:
Datetime firstDayOfMonth= new Datetime().withDayOfMonth(1);
Datetime lastDayOfMonth = new Datetime().dayOfMonth().withMaximumValue();
return finder.where()
.between("purchaseDate", firstDayOfMonth, lastDayOfMonth).findList();
2 - Using RawSQL
For this, please take a look at the Ebean documentation.
The main drawback of raw sql is that this code will not be portable for different SQL servers (if you don't plan to use several db engine, it will not matter).
+1 for #nico_ekito
On the other hand, while you are suggesting getting all rows from DB and then parsing them in the loop, I'd rather suggest to parse them... while creating and store in format easier to search and index. Just create additional column(s) in your DB, and override save() and/or update(Object o) methods in your model, to make sure, that every change will set the field, ie use String purchasePeriod for storing string like 2012-11;
you can find then:
# in November of ANY year
SELECT * FROM table WHERE purchase_period LIKE '%-11';
# in whole 2012
SELECT * FROM table WHERE purchase_period LIKE '2012-%';
# in December 2012
SELECT * FROM table WHERE purchase_period LIKE '2012-12';
alternatively you can divide it into two Integer fields: purchaseYear, purchaseMonth.
For the first scenario the overriden save() method in the Expense model can look ie like this:
public void save() {
this.purchaseDate = new Date();
this.purchasePeriod = new SimpleDateFormat("yyyy-MM").format(this.purchaseDate);
super.save();
}