I recently encountered the following problem with buiding queries in jooq (version 3.1.0):
I want to build delete statement with order and limit constraints. So, my aim is to build something like this:
DELETE FROM table ORDER BY field DESC LIMIT 1 (this is MySql syntax)
But i haven't found nesessary methods in result delete query object:
DSLContext context = createContext();
DeleteWhereStep delete = context.delete(createTable(table));
DeleteConditionStep whereStep = delete.where(condition);
whereStep.orderBy(...)//and no such method here
There are all nesessary methods in select statements and none for delete.
Is it possible to set order and limit for delete request in jooq?
As of jOOQ 3.2, these sorts of extensions are currently not implemented yet. Chances are, that #203 could be implemented in jOOQ 3.3, though.
In the mean time, you have two options:
Resort to plain SQL
i.e. write something like:
context.execute("DELETE FROM {0} ORDER BY {1} DESC LIMIT 1",
createTable(table),
field);
Manually transform your SQL statement into something equivalent
I suspect that the ORDER BY .. LIMIT extension to the MySQL DELETE statement is just sugar for:
DELETE FROM table t
WHERE t.id IN (
SELECT id FROM table
ORDER BY field LIMIT 1
)
Or with jOOQ:
context.delete(TABLE)
.where(TABLE.ID.in(
select(TABLE.ID)
.from(TABLE)
.orderBy(TABLE.FIELD)
.limit(1)
))
Related
I have an update/insert SQL query that I created using a MERGE statement. Using either JdbcTemplate or NamedParameterJdbcTemplate, does Spring provide a method that I can use to update a single record, as opposed to a Batch Update?
Since this query will be used to persist data from a queue via a JMS listener, I'm only dequeuing one record at a time, and don't have need for the overhead of a batch update.
If a batch is the only way to do it through Spring JDBC, that's fine... I just want to make certain I'm not missing something simpler.
You can use a SQL MERGE statment using only a one row query containing your parameters.
For example if you have a table COMPANYcontaing IDas a key and NAMEas an attribute, the MERGE statement would be:
merge into company c
using (select ? id, ? name from dual) d
on (c.id = d.id)
when matched then update
set c.name = d.name
when not matched then insert (c.id, c.name)
values(d.id, d.name)
If your target table contains the parametrised key, the name will be updated, otherwise a new record will be inserted.
With JDBCTemplate you use the update method to call the MERGEstatement, as illustrated below (using Groovy script)
def id = 1
def name = 'NewName'
String mergeStmt = """merge into company c
using (select ? id, ? name from dual) d
on (c.id = d.id)
when matched then update
set c.name = d.name
when not matched then insert (c.id, c.name)
values(d.id, d.name)""";
def updCnt = jdbcTemplate.update(mergeStmt, id, name);
println "merging ${id}, name ${name}, merged rows ${updCnt}"
Just use one of update methods, for example this one: JdbcTemplate#update instead of BatchUpdate.
Update updates a single record, batchUpdate updates multiple records using JDBC batch
I have a mysql query which is in the following format
dslContext
.select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
ITEMNAMES.ITEMNAME
)
.from(ITEMDATA)
.join(ITEMNAMES)
.on(ITEMDATA.ITEMID=ITEMNAMES.ITEMID)
.where(conditions);
The above query joins ITEMDATA with ITEMNAMES table to select ITEMNAME in the result. I am caching ITEMNAMES table in-memory and want to avoid the join with ITEMNAMES table. This would speed up the query and would simplify the query since the actual query is much more complex.
I would like to use it something similar to the following. I want to call itemNamesCache.getItemName in the select params list which gives the ITEMNAME and returns a part of the select result. getItemName should take the ITEMID returned in the response as a parameter and give the ITEMNAME.
dslContext.
select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
itemNamesCache.getItemName(valueOfItemId)
)
.from(ITEMDATA)
.where(conditions);
P.S: I can iterate the results and call the itemNamesCache.getItemName. But I would like to use something embedded in the query if it's possible
You cannot have a callback from a SQL query back into some Java logic, even if the fact that you're constructing the SQL query with jOOQ (and thus Java) makes it look like that were feasible.
However, you could post-process the jOOQ result by patching records using a previously built cache:
A Java solution
In case you're working with a database that really can't handle this simple join (and you've checked that you have all proper indexes and constraints in place!) then you could try the following solution:
// Assuming this import:
import static org.jooq.impl.DSL.*;
write...
Map<Integer, String> itemNamesCache =
dslContext.selectDistinct(ITEMNAMES.ITEMID, ITEMNAMES.NAME)
.from(ITEMNAMES)
.fetchMap(ITEMNAMES.ITEMID, ITEMNAMES.NAME);
dslContext
.select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
// create an empty column here
inline(null, String.class).as(ITEMNAMES.NAME))
.from(ITEMDATA)
.where(conditions)
// fill the empty column with cached values
.fetch(r -> r.value3(itemNamesCache.get(r.value1())));
A SQL-based solution
The SQL way to do that would be to write a correlated subquery.
SELECT
itemdata.itemid,
itemdata.cost,
(SELECT itemnames.name FROM itemnames WHERE itemnames.itemid = itemdata.itemid)
FROM
itemdata
WHERE
...
With jOOQ
// Assuming this import:
import static org.jooq.impl.DSL.*;
... write:
dslContext
.select(
ITEMDATA.ITEMID,
ITEMDATA.COST,
field(select(ITEMNAMES.NAME)
.from(ITEMNAMES)
.where(ITEMDATA.ITEMID.eq(ITEMNAMES.ITEMID)))
.as(ITEMNAMES.NAME)
)
.from(ITEMDATA)
.where(conditions)
.fetch();
In theory, both queries should run at exactly the same speed, because they're equivalent (if you have a foreign key on ITEMDATA.ITEMID).
In practice, most databases will probably have better performance for the JOIN query, unless they implement scalar subquery caching (e.g. like Oracle), which can drastically speed up the second query, depending on the number of distinct ITEMIDs (the smaller, the better).
I have problems with understanding of way couchbase query plan works.
I use SpringData with Couchbase 4.1 and I provide custom implementation of Couchbase Repository. Inside my custom implememtnation of Couchbase Repository I have below method:
String queryAsString = "SELECT MyDatabase.*, META().id as _ID, META().cas as _CAS FROM MyDatabase WHERE segmentId = $id AND _class = $class ORDER BY executionTime DESC LIMIT 1";
JsonObject params = JsonObject.create()
.put(CLASS_VARIABLE, MyClass.class.getCanonicalName())
.put(ID_VARIABLE, segmentId);
N1qlQuery query = N1qlQuery.parameterized(queryAsString, params);
List<MyClass> resultList = couchbaseTemplate.findByN1QL(query, SegmentMembers.class);
return resultList.isEmpty() ? null : resultList.get(0);
In a result, Spring Data produces following json object represented query to Couchbase:
{
"$class":"path/MyClass",
"statement":"SELECT MyDatabase.*, META().id as _ID, META().cas as _CAS from MyDatabase where segmentId = $id AND _class = $class ORDER BY executionTime DESC LIMIT 1",
"id":"6592c16a-c8ae-4a74-bc17-7e18bf73b3f8"
}
And the problem is with performance when I execute it via Java and N1QL Rest Api or via cbq consol. For execute this query in cbq I simply replace parameters reference with exact values.
After adding EXPLAIN clause before select statement I mentioned different execution plans. Execution this query as parameterized query via Java Spring Data or N1QL Rest Api I've mentioned that query doesn't use index that I created exactly for this case. Index definiton can be found below:
CREATE INDEX `testMembers` ON MyDatabase `m`(`_class`,`segmentId`,`executionTime`) WHERE (`_class` = "path/MyClass") USING GSI;
So, when I execute query via cbq consol, Couchbase uses my idnex and query performance is very good. But, when I execute this query via N1QL rest api or Java i see that query doesn't use my index. Below you can find part of execution plan that proves this fact:
"~children": [
{
"#operator": "PrimaryScan",
"index": "#primary",
"keyspace": "CSM",
"namespace": "default",
"using": "gsi"
},
So, the question is that the right and legal behavior of couchbase query optimizer? And does it mean that query plan does not take into account real values of parameters? And have I manually put values into query string or exist eny other way to use N1Ql parameterized query with correct index selection?
EDIT
According to shashi raj answer I add N1qlParams.build().adhoc(false) parameter to parameterized N1QL query. This doesn't solve my problem, because I still have performance problem with this query. Moreover, when I print query I see that it is the same as I described earlier. So, my query still wrong analyzed and cause performance decline.
first of all you need to know how N1QL parameterized queries works query should be passed as:
String query= select * from bucketName where _class=$_class and segmentId=$segmentId LIMIT $limit ;
Now the query should be passed as:
N1QL.parameterized(query,jsonObject,N1qlParams.build().adhoc(false));
where jsonObject will have all the placeholder values.
JsonObject jsonObject=JsonObject.create().put("_class","com.entity,user").put("segmentId","12345").put("limit",100);
N1qlParams.build().adhoc(false) is optional since if you want your query to be optimized it will make use of it. It makes use of LRU where it keeps track of previously query entered and it keeps record of it so that next time it doesn't need to parse query and fetch it from previous what we call as prepared statement.
The only problem is couchbase only keeps record of last 5000 queried.
The problem in your case is caused by the fact that you have an index with 'where' clause WHERE ( _class = "path/MyClass"), and at the same time, you passing the _class as a parameter in your query.
Thus, the query optimizer analyzing the parametrized query has no idea that this query might use an index created for _class = "path/MyClass", cause it's _class = $class in a select's where. Pretty simple, right?
So, don't pass any fields mentioned in your index's 'where' as select parameters. Instead, hardcode _class = "path/MyClass" in your select in the same way you did for create index. And everything should be fine.
Here's the ticket in the couchbase issue tracking system about that.
https://issues.couchbase.com/browse/MB-22185?jql=text%20~%20%22parameters%20does%20not%20use%20index%22
In my Java Web application I use Postgresql and some data tables are filled automatically in server. In the database I have a STATUS table like below:
I want to select the data related to a vehicle between selected dates and where the vehicle stayed connected. Simply I want to select the data which are green in the above table which means I exactly want the data when firstly io1=true and the data when io1=false after the last io1=true. I have postgresql query statement which exactly gives me the desired data; however, I have to convert it to HQL because of my application logic.
working postgresql query:
WITH cte AS
( SELECT iostatusid, mtstrackid, io1,io2,io3, gpsdate,
(io1 <> LAG(io1) OVER (PARTITION BY mtstrackid
ORDER BY gpsdate)
) AS status_changed
FROM iostatus
WHERE mtstrackid = 'redcar' AND gpsdate between '2014-02-28 00:00:00' and '2014-02-28 23:59:59'
)
SELECT iostatusId, mtstrackid, io1, io2, io3,gpsdate
FROM cte
WHERE status_changed
OR io1 AND status_changed IS NULL
ORDER BY gpsdate ;
How should I convert the above query to HQL or how could I retrieve the desired data with HQL?
The goal of hibernate is mapping database entities to java objects. This kind of complex queries are not entities themselves. This is against the spirit of hibernate.
If this query generates an entity in your application logic, I recommend putting the results into a table and applying Hibernate queries to that table.
If this query generates some kind of aggregation or summary, there are two possible ways:
One way is you compute this aggregation/summary in your application after retrieving entities from iostatus table with hibernate.
If this query has nothing to do with your application logic then you can use Native SQL interface of Hibernate and execute the query directly. (You can even use JPA if you are willing to manipulate two database connections.)
If you absolutely need to convert it to HQL, you need to eliminate the partition function. If the order of iostatusId is identical to the order of gpsdate, you can do it similar to
SELECT i2.*
FROM iostatus i1
INNER JOIN iostatus i2 ON i1.iostatusId = i2.iostatusId - 1
AND i1.io1 <> i2.io1
AND i1.mstrackid = i2.mstrackid
WHERE i2.mtstrackid = 'redcar' AND
i2.gpsdate between '2014-02-28 00:00:00' and '2014-02-28 23:59:59'
If gpsdate is no way related to iostatusId then you need something like
SELECT i2.*
FROM iostatus i1
INNER JOIN iostatus i2 ON i1.gpsdate < i2.gpsdate
AND i1.io1 <> i2.io1
AND i1.mstrackid = i2.mstrackid
WHERE i2.mtstrackid = 'redcar' AND
i2.gpsdate between '2014-02-28 00:00:00' and '2014-02-28 23:59:59' AND
NOT EXISTS (SELECT * FROM iostatus i3
WHERE i3.gpsdate > i1.gpsdate AND
i2.gpsdate > i3.gpsdate AND
i3.io1 = i1.io1 AND
i1.mstrackid = i3.mstrackid)
I guess both of the queries can be converted to HQL, but I'm not positively sure.
By the way I must warn you that, these methods might not perform better then finding the changes in your application, because they involve joining the table onto itself, which is an expensive operation; and the second query involves a nested query after the join, which is also quite expensive.
I'm trying to do an update in hibernate HQL with a subselect in a set clause like:
update UserObject set code = (select n.code from SomeUserObject n where n.id = 1000)
It isnt working, it is not supported?
Thanks
Udo
I had the same problem, discovered that you need to put bulk updates in side a transaction:
tr = session.getTransaction();
tr.begin();
updateQuery.executeUpdate();
tr.commit;
From the Hibernate documentation:
13.4. DML-style operations
...
The pseudo-syntax for UPDATE and
DELETE statements is: ( UPDATE |
DELETE ) FROM? EntityName (WHERE
where_conditions)?.
Some points to note:
In the from-clause, the FROM keyword is optional
There can only be a single entity named in the from-clause. It can,
however, be aliased. If the entity
name is aliased, then any property
references must be qualified using
that alias. If the entity name is not
aliased, then it is illegal for any
property references to be qualified.
No joins, either implicit or explicit, can be specified in a bulk
HQL query. Sub-queries can be used
in the where-clause, where the
subqueries themselves may contain
joins.
The where-clause is also optional.
While the documentation doesn't explicitly mentions a restriction about the set part, one could interpret that sub-queries are only supported in the where-clause. But...
I found an 4 years old (sigh) issue about bulk update problems (HHH-1658) and according to the reporter, the following works:
UPDATE Cat c SET c.weight = (SELECT SUM(f.amount) FROM Food f WHERE f.owner = c)
I wonder if using an alias in the from-clause would help. Looks like there is some weirdness anyway.