Hi I am using JOOQ to build a SQL statement in my java application. I need to include an or clause in my statement under a certain condition and omit under the other.
e.g.
org.jooq.Query ps = select(field("q.*"))
.from(table("QUEUES q"))
.where(field("q.APPLICATION").eq("APP1"));
I then need to unclude an or part to the query depending on some other variable..
org.jooq.Query ps = select(field("q.*"))
.from(table("QUEUES q"))
.where(field("q.APPLICATION").eq("APP1"))
.or(field("q.APPLICATION").eq("APP2"));
I can't figure out how to do this without having two separate statements, one including the or statement and one without. Thanks in advance.
What you want to do is use the extensions of SelectConditionStep which will allow you to break up the query into individual steps and use a flag as a means to attach only one specific extension of SelectConditionStep to the query.
Given your query and a flag (boolean - to separate the extension of SelectConditionStepto be used) called for e.g. checkBothApps.
boolean checkBothApps = false;
org.jooq.SelectJoinStep<R> joinStep = select(field("q.*"))
.from(table("QUEUES q"));
org.jooq.SelectConditionStep<R> conditionStep = joinStep.where(field("q.APPLICATION").eq("APP1"));
if (checkBothApps) {
// This condition will be added to the join step.
joinStep.or(field("q.APPLICATION").eq("APP2"));
}
Edit: Tested code.
Thanks to Koshux
I did it like this...
Condition whereClause = field("q.APPLICATION").eq("APP1");
if(bothApps){
whereClause = whereClause.or(field("q.APPLICATION").eq("APP2"));
}
org.jooq.Query ps = select(field("q.*"))
.from(table("QUEUES q"))
.where(whereClause);
Related
How to make long queries more readable?
For example I have this one:
String query = "SELECT CASE WHEN EXISTS (SELECT * FROM users WHERE username = 'username' AND user_password = crypt('password', user_password)) THEN 'match' ELSE 'differ' END";
And it's completely unreadable, are there any ways to beautify it?
Since Java 15, you can use text blocks:
String query = """
SELECT CASE
WHEN
EXISTS (
SELECT *
FROM users
WHERE
username = 'username'
AND user_password = crypt('password', user_password)
)
THEN 'match'
ELSE 'differ'
END
""";
In cases when you don't wont to blend SQL and JAVA you can put SQL queries in an .sql file. And get this text when needed.
public class QueryUtil {
static public String getQuery(String fileName) throws IOException {
Path path = Paths.get("src/test/resources//" + fileName + ".sql");
return Files.readAllLines(path).get(0);
}
}
If you can mix SQL and JAVA then starting from JDK15 you can use text blocks for this.
Also you can generates Java code from your database by using JOOQ, it gives many benefits.
Assuming that you can't move to a newer-than-8 version of Java (or even if you can), by far the best solution is to use an ORM. For Java it pretty much comes down to Hibernate, or jOOQ. jOOQ (and possibly Hibernate, I haven't used it so can't say, sorry) allows you to use a fluent programming interface, which is very much in keeping with existing Java code style and patterns.
Another specific advantage of using an ORM is that you can very easily change which DB engine you use without having to change the Java code that you've written beyond changing the SQL dialect in your setup functions. See https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/SQLDialect.html.
You can use JOOQ and get multiple other benefits like type safety, auto-complete, easy mapping and great support.
Have used it for several projects so far and also competition like Kotlin Exposed but always came back to JOOQ.
Move to Java 13+. There are Text Blocks for this.
Or use some ORM library.
Does anyone know how to parse SQL statements, and again build in back using Java? This is required because I would need to add extra columns to WHERE clause based on the some conditions. FOr example, based on the Logon user, I would need to decide whether the user is restricted to see the records like it is restricted outside USA.
use jsqlparser
examples:
CCJSqlParserManager ccjSqlParserManager = new CCJSqlParserManager();
Select statement = (Select) ccjSqlParserManager.parse(new FileReader(path));
PlainSelect plainSelect = (PlainSelect) statement.getSelectBody();
Expression expression = plainSelect.getWhere();
See below example
String sqlstr= "select * from [table name] where [ column 1]='value' or ? "
If( your condition){
sqlstr= sqlstr+" [ column 2]=' value 2'";
}
// Now write your execution statement
ps = (PreparedStatement) connection.prepareStatement(
"SELECT nm.id, nid.key, nm.name, nm.languageCode FROM odds.name as nm JOIN (odds.name_id as nid)\r\n"
+ "ON (nm.id = nid.id) where nm.name like '%' and nid.key not like \"vhc%\" and nid.key not like \"vdr%\" and nid.key not like \"vto%\" and nid.key not like \"vbl%\"\r\n"
+ "and nid.key not like \"vf%\" and nid.key not like \"vfl%\" and nid.key not like \"vsm%\" and nid.key not like \"rgs%\"\r\n"
+ "and nid.key not like \"srrgs%\" and nm.typeId=8 and nm.sourceId=-1 and nm.languageCode = 'en'");
for(Entry <String, Tag> e : allTags.entrySet()) {
ResultSet rs = ps.executeQuery();
while(rs.next()) {
if(rs.getString("name").equals(e.getValue().getTranslation(Language.EN))) {
e.getValue().setAlternativeKey(rs.getString("name"));
break;
}
}
}
);
Do you have any Ideas how I can do this a way faster. I'll try to find a string in the database and add an extra information to my object. But I have to do this for 1265 objects, so the program runs about 80 seconds.
Thanks in advance!
First of all, when tackling performance problems, get yourself a profiling tool that tells you where you're spending the time, how often a given method is called and so on.
But I think the case is clear enough to give some more specific hints.
You're executing your PreparedStatement over and over again, once for every entry in allTags.entrySet(), always giving you the same results, and inside in software you filter out the lines you're interested in. So you're doing the same query 1265 times, correct?
And it's puzzling me what you're doing inside the while(rs.next()) loop. Effectively, your code does (after introducing some local variables, moving constant values out of loops, ...):
for(Entry <String, Tag> e : allTags.entrySet()) {
Tag tag = e.getValue();
String translation = tag.getTranslation(Language.EN);
ResultSet rs = ps.executeQuery();
while(rs.next()) {
if(rs.getString("name").equals(translation)) {
tag.setAlternativeKey(translation);
break;
}
}
}
So, the only role of the query result seems to be to decide whether the alternative key should be set (if the translation of your Tag shows up as name in the ResultSet) - the value is already fixed by the result of the method call getTranslation(Language.EN), independent of any database result.
I'd suggest to do one execution of your query, collecting the name values in a HashSet names, and after that do the allTags loop setting the translation if the translation is contained in your names set. That should give the same result as your code, and probably much faster.
Open the DB-client of your choice (e.g. HeidiSQL) and do an
explain [the select statement that is originally executed]
That way MySQL explains to you what it's doing when trying to create the result and where time gets lost.
From there you can go on e.g. creating indizes or changing your query to make use of existing ones.
BTW:
nm.name like '%'
looks strange. Is that a variant of
is not null
The latter might be faster. If the texts in the other like-statements are always the same, a better performance might be achieved by checking these conditions when inserting the data, add columns of type int or boolean and save the result of this check as integer/boolean in addition to the text itself. Checking against a fixed numeric value is way faster than text searches.
I'm having problems creating a proper query using an eclipselink expression when it comes to needing an OR clause. This is my code:
Expression filter = /* filter is not null */;
if (secondStatus != null) {
Integer second = ticketService.getStatusIdByName(secondStatus);
filter = filter.and(
builder.get(Complaint.propertyName.status)
.equal(second)
.or(builder.get(Complaint.propertyName.status)
.equal(status)));
// Also tried the below
// filter = filter.or(builder.get(Complaint.propertyName.status).equal(second));
}
The first way only returned a query including one of the statuses, meanwhile the second (commented out) way returned it's own subquery. Both ways I was getting incorrect results. What is the correct way to incorporate an OR clause into a query?
I figured out what I was doing wrong. I was missing the initial filter in the second statement assuming it would produce the proper SQL automatically.
filter = filter.and(builder.get(Complaint.propertyName.status)
.equal(second)
.or(filter.and(builder.get(Complaint.propertyName.status)
.equal(status))));
This works as intended.
I've recently been migrating my database and have been moving our remaining SQL queries over to jooq. Having some fun with this one:
private SelectQuery<?> getIdeasQuery() {
Select<?> numComments = dslContext.select(DSL.count().as("comment_count"), COMMENT.IDEA_ID).from(COMMENT).groupBy(COMMENT.IDEA_ID);
Select<?> numLikes = dslContext.select(DSL.count().as("like_count"), USER_LIKES_IDEA.IDEA_ID).from(USER_LIKES_IDEA).groupBy(USER_LIKES_IDEA.IDEA_ID);
return dslContext
.select(DSL.field("comment_count").as("num_comments"))
.select(DSL.field("like_count").as("num_likes"))
.select(USER.DISPLAY_NAME)
.select(USER.AUTHORITY)
.select(IDEA.fields())
.from(IDEA.leftOuterJoin(numComments).on(COMMENT.IDEA_ID.eq(IDEA.ID))
.leftOuterJoin(numLikes).on(USER_LIKES_IDEA.IDEA_ID.eq(IDEA.ID))
.leftOuterJoin(USER).on(IDEA.USER_ID.eq(USER.ID)))
.getQuery();
}
The returned query is then used to append additional modifiers (using addConditions(), addOrderBy(), addLimit() etc.) depending on the context, and then executed.
The issue I'm having is that the two sub-select queries are not named as their original names for the joins. This is correct behaviour! However, within jOOQ I'm having a hard time finding how I can specify the sub-query's fields to join on. I've tried going down the route of renaming the sub-queries as described in this StackOverflow answer, but the types don't line up for me.
Any ideas?
So I've found what I think is a solution for now. However, it seems like a workaround. I've given the sub-queries names when I do the join, and using that name to point to the field. If my schema changes in future, I don't think this will flag up a compile time error.
Here it is for anyone interested:
private SelectQuery<?> getIdeasQuery() {
Select<?> numComments = dslContext.select(DSL.count().as("comment_count"), COMMENT.IDEA_ID).from(COMMENT).groupBy(COMMENT.IDEA_ID);
Select<?> numLikes = dslContext.select(DSL.count().as("like_count"), USER_LIKES_IDEA.IDEA_ID).from(USER_LIKES_IDEA).groupBy(USER_LIKES_IDEA.IDEA_ID);
return dslContext
.select(DSL.field("comment_count").as("num_comments"))
.select(DSL.field("like_count").as("num_likes"))
.select(USER.DISPLAY_NAME)
.select(USER.AUTHORITY)
.select(IDEA.fields())
.from(IDEA.leftOuterJoin(numComments.asTable("com")).on(DSL.field("com.idea_id").eq(IDEA.ID))
.leftOuterJoin(numLikes.asTable("like")).on(DSL.field("like.idea_id").eq(IDEA.ID))
.leftOuterJoin(USER).on(IDEA.USER_ID.eq(USER.ID)))
.getQuery();
}