I need optimize a dynamic query. This an example, but it has more filters (BooleanExpression)
QTable qtable = QTable.table;
BooleanExpression pSubQueryFilter = qtable.field4.in(List<Long>).and((date != null) ? qtable.field5.goe(date) : null);
BooleanExpression pSubQuery =( ((qtable.field1.eq(string1)) .and(qtable .field2.eq(string2)).and(qtable .field3.eq(Boolean.FALSE)))
.or( (qtable.field1.eq(string2)).and(qtable .field2.eq(string1).and(qtable .field3.eq(Boolean.TRUE))))
).and(pSubQueryFilter);
List<T> list = createQuery(pSubQuery ).list(path);
This generates this select, but spend more than 30s in DB because OR clauses. This fields (field1 and field2) have already index in DB.
SELECT * FROM table WHERE
(field1= 'string1' AND field2='string2' AND field3=0)
OR (field1= 'string2' AND field2='string1' AND field3=1 )
AND field4 in (1,2,3,4,5,...) AND field5 > =SYSDATE-365
ORDER BY field5 DESC, id DESC
I optimized this select to 1s in SQL with this result:
SELECT * FROM table WHERE
id IN ( (SELECT id FROM table WHERE field1= 'string1' AND field2='string2' AND field3=0)
UNION (SELECT id FROM table WHERE field1= 'string2' AND field2='string1' AND field3=1 ) )
AND field4 in (1,2,3,4,5,...) AND field5 > =SYSDATE-365
ORDER BY field5 DESC, id DESC
But in QueryDsl didn't get improve. I did it this way, because Union didn't work.
BooleanExpression psubq1 = qtable .field1.eq(string1).and(qtable .field2.eq(string2)).and(qtable .field3.eq(Boolean.FALSE));
BooleanExpression psubq2 = qtable .field1.eq(string2).and(qtable .field2.eq(string1)).and(qtable .field3.eq(Boolean.TRUE));
Expression<?>[] args = { qtable .id};
List<Long> resultids = tableRepository.findAllIds(psubq1, args);
resultids.addAll(tableRepository.findAllIds(psubq2, args));
pSubQuery = qtable .id.in(resultids).and(pSubQueryFilter);
Exists any way to do the last sql with Querydsl with one call to DB?
Thank you
There exist a static method union in com.querydsl.sql.SQLExpressions to do this. As parameters use subqueries created with SQLExpressions::select.
import com.querydsl.core.types.dsl.*;
import com.querydsl.sql.DatePart;
import com.querydsl.sql.SQLExpressions;
import com.querydsl.sql.oracle.OracleGrammar;
import com.querydsl.sql.oracle.OracleQuery;
...
OracleQuery query = new OracleQuery<>(con);
query.select(table).from(table).where(
table.id.in(
SQLExpressions.union(
SQLExpressions.select(table.id).from(table)
.where(table.field1.eq("string1")
.and(table.field2.eq("string2"))
.and(table.field3.eq(Expressions.FALSE))),
SQLExpressions.select(table.id)
.from(table)
.where(table.field1.eq("string2")
.and(table.field2.eq("string1"))
.and(table.field3.eq(Expressions.TRUE))))
)
.and(table.field4.in(1,2,3,4,5))
.and(table.field5.goe(
SQLExpressions.dateadd(DatePart.day, OracleGrammar.sysdate, -365))))
.orderBy(table.field5.desc(), table.id.desc());;
Related
Currently I am doing it like this:
List<Table1Entity> findAllMatchingEntities(Table1Entity table1Entity) {
String queryString = "SELECT table1.* FROM table1 "
+ "JOIN table2 t2 ON table1.id=t2.table1_id";
if (table1Entity.getName() != null) {
queryString +=" where name like ?";
}
Query query = em.createNativeQuery(queryString, Table1Entity.class);
if (table1Entity.getName() != null) {
query.setParameter(1, table1Entity.getName())
}
return query.getResultedList();
}
If I want to check more parameters in this join this will quickly turn into a lot of if statements and it would be really complicated to set parameters correctly.
I know I can check parameters with criteria Builder API like this:
if(table1Entity.getName() != null) {
table1EntitySpecification = (root, query, criteriaBuilder)
-> criteriaBuilder.like(
criteriaBuilder.lower(root
.get("name")),
("%" + table1Entity.getName() + "%")
.toLowerCase());;
}
and after that get them all with:
findAll(table1EntitySpecification) with findAll from simpleJPARepository. Now I can chain them together with .or or .and etc. and avoid setting the parameter and checking for null second time.
But how do I do join with criteria APi?
I know I can have in my #Repository something like this:
#Query(value = "SELECT table1.* FROM table1 JOIN table2 t2 ON table1.id=t2.table1_id", nativeQuery = true)
List<Table1Entity> findAllMatchingEntities(Table1Entity table1Entity);
But since name is optional (can be null) I can't just leave it in #Query.
What is the best solution here to avoid using native query and in case of having to check many parameters to avoid using if statements?
I don't know if I fully get your question, but regarding the possibility of nulls, and using the CRUD repository, you can always do a null check before like:
#Query(value = "SELECT table1.* FROM table1 JOIN table2 t2 ON table1.id=t2.table1_id WHERE table1.id is not null", nativeQuery = true)
List<Table1Entity> findAllMatchingEntities(Table1Entity table1Entity);
Depending on what you are trying to achieve, you can always compose the query with similar checks like (not related to your code):
#Query("SELECT c FROM Certificate c WHERE (:id is null or upper(c.id) = :id) "
+ "and (:name is null or upper(c.name) = :name)")
List<Table1> findStuff(#Param("id") String id,
#Param("name") String name);
I have two tables: harvested_record and harvested_record_simple_keys there are in relation one-to-one.
harvested_record
id|harvested_record_simple_keys_id|a lot of columns
harvested_record_simple_keys
id| a lot of columns
and I want to make a query in which I need to join there two tables. As a result I will have a table:
joined_table
id(harvested_record)| (harvested_record_simple_keys)|a lot of columns.
Unfortunately I get an exception: nested exception is java.sql.SQLSyntaxErrorException: Column name 'ID' matches more than one result column.
I've understood this is because after join I will have two columns 'id'. Does anyone can help me with solution?
P.S.
SQL statement(works in IDEA console):
SELECT * FROM ( SELECT TMP_ORDERED.*, ROW_NUMBER() OVER () AS ROW_NUMBER FROM (SELECT * FROM harvested_record hr JOIN harvested_record_simple_keys hrsk ON hrsk.id = hr.harvested_record_simple_keys_id WHERE import_conf_id = ? ) AS TMP_ORDERED) AS TMP_SUB WHERE TMP_SUB.ROW_NUMBER <= 2 ORDER BY import_conf_id ASC, record_id ASC;
Java code(suppose error is here):
JdbcPagingItemReader<HarvestedRecord> reader = new JdbcPagingItemReader<HarvestedRecord>();
SqlPagingQueryProviderFactoryBean pqpf = new SqlPagingQueryProviderFactoryBean();
pqpf.setDataSource(dataSource);
pqpf.setSelectClause("SELECT *");
pqpf.setFromClause("FROM harvested_record hr JOIN harvested_record_simple_keys hrsk ON hrsk.id = hr.harvested_record_simple_keys_id");
String whereClause = "WHERE import_conf_id = :configId";
if (from!=null) {
fromStamp = new Timestamp(from.getTime());
whereClause += " AND updated >= :from";
}
if (to!=null) {
toStamp = new Timestamp(to.getTime());
whereClause += " AND updated <= :to";
}
if (configId != null) {
pqpf.setWhereClause(whereClause);
}
pqpf.setSortKeys(ImmutableMap.of("import_conf_id",
Order.ASCENDING, "record_id", Order.ASCENDING));
reader.setRowMapper(harvestedRecordRowMapper);
reader.setPageSize(PAGE_SIZE);
reader.setQueryProvider(pqpf.getObject());
reader.setDataSource(dataSource);
if (configId != null) {
Map<String, Object> parameterValues = new HashMap<String, Object>();
parameterValues.put("configId", configId);
parameterValues.put("from", fromStamp );
parameterValues.put("to", toStamp);
reader.setParameterValues(parameterValues);
}
reader.afterPropertiesSet();
Thank you in advance.
If you name your columns in the select statement instead of using 'SELECT *', you can omit the ID from one of the tables since it is always equal to the id from the other table.
I am trying to write a JPA NamedQuery. I have a working SQL query as follows:
SELECT MIN(t1.Id + 1) AS nextID
FROM
MyTable t1 LEFT JOIN MyTable t2
ON
t1.Id + 1 = t2.Id
WHERE
t2.Id IS NULL
I am not able to translate this query to JPQL syntax.
I think doing such a custom join is not possible with standard JPQL. I was looking for a possibility to do it some time ago and found that Hibernate offers a proprietary extension #JoinFormula to achieve this, cf. Hibernate #JoinFormula. But I couldn't find an equivalent for EclipseLink.
You might be able use a #NamedNativeQuery together with an #SqlResultSetMapping to map your SQL statement to a JPA entity, something like this:
#NamedNativeQuery( name = "customJoin",
query = "SELECT MIN(t1.Id + 1) AS nextID FROM MyTable t1 LEFT JOIN MyTable t2 ON t1.Id + 1 = t2.Id WHERE t2.Id IS NULL",
resultSetMapping = "customJoinMapping" )
#SqlResultSetMapping( name = "customJoinMapping",
entities = #EntityResult( entityClass = MyTable.class, fields = #FieldResult( name = "id", column = "nextID" ) ) )
#Entity
#Access( AccessType.Field )
public class MyTable {
private int id;
public int getId() {
return this.id;
}
public void setId( int id ) {
this.id = id;
}
}
UPDATE
Much later, I found a way to customize relationship joins in EclipseLink here. The feature to use is a DescriptorCustomizer, which can be placed on a JPA entity with #Customizer.
In the customize() method, one can express additional join criteria using the EclipseLink Expression API. For example, the following code fragment would produce the additional join criteria [...] AND TargetEntity.someAttribute IN ('V', 'W', 'U') (where someRelationship points to a TargetEntity).
OneToManyMapping mapping = (OneToManyMapping) descriptor.getMappingForAttributeName( "someRelationship" );
Expression origExp = mapping.buildSelectionCriteria();
ExpressionBuilder expBuilder = origExp.getBuilder();
Expression constantExp = expBuilder.get( "someAttribute" ).in( new String[] { "V", "W", "U" } );
Expression newExp = origExp.and( constantExp );
mapping.setSelectionCriteria( newExp );
Hi I need to do the following using Criteria
Select * from product pd, (Select productID pd1 from ... where ...) tpd1,
(Select productID pd2 from ... where ...) tpd2
where pd.productID = tpd1.pd1 and pd.productID = tpd1.pd2
May I know if it is possible?
The original SQL was using IN conditions
Select * from product pd where productID in (Select productID pd1 from ... where ...) and
productID in (Select productID pd2 from ... where ...)
but it takes too long to get the result, using the join SQL statement I was able to obtain my result faster.
any help?
Given you're using Hibernate, you may have to do something like this, which should work ok if the number of expected matches are relatively low:
select *
from product pd
where pd.productID in
(select productID
from product pd2
join tpd1 on (pd2.productID = tpd1.pd1)
join tpd2 on (pd2.productID = tpd2.pd2)
where tpd1....
and tpd2....
);
I assume there is a unique index on product.productID.
Alternatively, you could try the EXISTS formulation which may or may not work better than your original query:
select *
from product pd
where EXISTS
(select null from tpd1 where pd.productID = tpd1.pd1 and ...)
and EXISTS
(select null from tpd2 where pd.productID = tpd2.pd2 and ...)
;
I have 2 tables (table1, table2) table1 has a field id and table2 has a field id_eid that reference the id field of table1 as foreign key.
I have to delete from table1 all the rows that match a determinated criteria and then if these data are referenced in table2 delete the data from it too.
I do something like that, assuming con is the Connection object and autocommit is set to false on it.
String query1 = "delete from table2 where exists
(select * from table1 where someparameter = ? and table1.id = table2.id_eid)"
then i execute the first query1 using PreparedStatement.
then i have
String query2 = "delete from table1 where someparameter = ?
and exists (select * from table2 where table1.id = table2.id_eid)"
and i executed this with another PreparedStatment.
at the end i have the con.commit().
This doesn't work, i was thinking using autocommit to false the two queries was executed together but it is not, the second query deletes no rows, how can i do this ?
An important note, not all the rows in table1 have a referenced row in table2.
Thanks
You could always query the data to delete first, then delete it second:
1) Select ID from table1 where <criteria>
2) Select ID from table2 where <criteria>
3) Delete from table1 where ID in <results from (1)>
4) Delete from table2 where ID in <results from (2)>
If you
"have to delete from table1 all the rows that match a determinated criteria"
I think String query2 must be
String query2 = "delete from table1 where someparameter = ?"