Runtime SQL Query Builder - java

My question is similar to
Is there any good dynamic SQL builder library in Java?
However one important point taken from above thread:
Querydsl and jOOQ seem to be the most popular and mature choices however there's one thing to be aware of: Both rely on the concept of code generation, where meta classes are generated for database tables and fields. This facilitates a nice, clean DSL but it faces a problem when trying to create queries for databases that are only known at runtime.
Is there any way to create the queries at runtime besides just using plain JDBC + String concatenation?
What I'm looking for is a web application that can be used to build forms to query existing databases. Now if something like that already exists links to such a product would be welcome too.

While source code generation for database meta data certainly adds much value to using jOOQ, it is not a prerequisite. Many jOOQ users use jOOQ for the same use-case that you envision. This is also reflected in the jOOQ tutorials, which list using jOOQ without code generation as a perfectly valid use-case. For example:
String sql = create.select(
fieldByName("BOOK","TITLE"),
fieldByName("AUTHOR","FIRST_NAME"),
fieldByName("AUTHOR","LAST_NAME"))
.from(tableByName("BOOK"))
.join(tableByName("AUTHOR"))
.on(fieldByName("BOOK", "AUTHOR_ID").eq(
fieldByName("AUTHOR", "ID")))
.where(fieldByName("BOOK", "PUBLISHED_IN").eq(1948))
.getSQL();
In a similar fashion, bind values can be extracted from any Query using Query.getBindValues().
This approach will still beat plain JDBC + String concatenation for dynamic SQL statements, as you do not need to worry about:
Syntax correctness
Cross-database compatibility
SQL Injection
Bind variable indexing
(Disclaimer: I work for the vendor of jOOQ)

SQLBuilder http://openhms.sourceforge.net/sqlbuilder/ is very useful for me.
Some simple examples:
String query1 = new InsertQuery("table1")
.addCustomColumn("s01", "12")
.addCustomColumn("stolbez", 19)
.addCustomColumn("FIRSTNAME", "Alexander")
.addCustomColumn("LASTNAME", "Ivanov")
.toString();
String query2 = new UpdateQuery("table2")
.addCustomSetClause("id", 1)
.addCustomSetClause("FIRSTNAME", "Alexander")
.addCustomSetClause("LASTNAME", "Ivanov")
.toString();
Results:
INSERT INTO table1 (s01,stolbez,FIRSTNAME,LASTNAME) VALUES ('12',19,'Alexander','Ivanov')
UPDATE table2 SET id = 1,FIRSTNAME = 'Alexander',LASTNAME = 'Ivanov'

I have a custom solution for dynamically generating such SQL queries with just 2-3 classes for similar requirement. It is a simple approch.
This can be referred at Creating Dynamic SQL queries in Java
For simpler use cases like a dynamic filter condition based on the inputs selected from UI, one can use the below simpler approach by directly modifying the query in below style:
select t1.id, t1.col1, t1.col2,
from table1 t1
where (:col1Value is null or t1.col1 = :col1Value)
and (:col2Value is null or t1.col2 = :col2Value);
Here values for col1 or col2 can be null but the query will work fine.

Related

Add limit and offset to query that was created from a String

I have query as String like
select name from employee
and want to limit the number of rows with limit and offset.
Is this possible with jOOQ and how do I do that?
Something like:
dsl.fetch("select name from employee").limit(10).offset(10);
Yes you're close, but you cannot use fetch(sql), because that eagerly executes the query and it will be too late to append LIMIT and OFFSET. I generally don't recommend the approach offered by Sergei Petunin, because that way, you will tell the RDBMS less information about what you're going to do. The execution plan and resource allocations are likely going to be better if you actually use LIMIT and OFFSET.
There are two ways to do what you want to achieve:
Use the parser
You can use DSLContext.parser() to parse your SQL query and then modify the resulting SelectQuery, or create a derived table from that. Creating a derived table is probably a bit cleaner:
dsl.selectFrom(dsl.parser().parse("select name from employee"))
.limit(10)
.offset(10)
.fetch();
The drawback is that the parser will have to understand your SQL string. Some vendor specific features will no longer be available.
The advantage (starting from jOOQ 3.13) is that you will be able to provide your generated code with attached converters and data type bindings this way, as jOOQ will "know" what the columns are.
Use plain SQL
You were already using plain SQL, but the wrong way. Instead of fetching the data eagerly, just wrap your query in DSL.table() and then use the same approach as above.
When using plain SQL, you will have to make sure manually, that the resulting SQL is syntactically correct. This includes wrapping your query in parentheses, and possibly aliasing it, depending on the dialect you're using:
dsl.selectFrom(table("(select name from employee)").as("t"))
.limit(10)
.offset(10)
.fetch();
The best thing you can do with a string query is to create a ResultQuery from it. It allows you to limit the maximum amount of rows fetched by the underlying java.sql.Statement:
create.resultQuery("select name from employee").maxRows(10).fetch();
or to fetch lazily and then scroll through the cursor:
create.resultQuery("select name from employee").fetchLazy().fetch(10);
Adding an offset or a limit to a query is only possible using a SelectQuery, but I don't think there's any way to transform a string query to a SelectQuery in JOOQ.
Actually, if you store SQL queries as strings in the database, then you are already in a non-typesafe area, and might as well append OFFSET x LIMIT y directly to a string-based query. Depending on the complexity of your queries, it might work.

Parse sql query using antlr parsetree to mongo bson document in Java

I have a SQL like query example:
Select id,name from employee where age > 30 and department = 'IT' limit 200
The SQL query grammer is defined in an ANTLR4 grammar file. Is there any implementation that converts the parse tree of this query to a bson document?
The bson document will then be used to query a mongo db.
In one of my previous jobs I did something similar: got a query (not an sql, but pretty similar) and translated it to mongo query with antlr.
I don't have a code to share, However I can share my thoughts:
Mongo is not SQL compliant, so you can't just take a sql grammar. What about JOINs and all the relational algebra? What about aggregations that are pretty tricky in mongo with their aggregation framework? In the opposite direction, how do you generate SQL that gets translated to "exists" clause in mongo. There are many things like this, some are small, some are huge, but bottom line you must be talking about some kind of subset of sql ,some DSL that is allowed to be used as a query language and looks "like" an sql because people are used to SQL.
With that in mind, you should create your own grammar and Antlr will generate a lexer/parser for you. You'll also get for granted a syntax check of the query in Runtime. Antlr won't be able to parse the query if its not in a correct format obviously, some grammar rule will fail. This is an another reason to not take SQL "as is".
So far so good, you've created your own listener / visitor. In my case I've opted for creating an object representation of the query with internal state and everything.
So the query
Select id,name
from employee
where age > 30
and department = 'IT'
limit 200
Was translated to objects of type:
class Query {
private SelectClause select;
private FromClause from;
private WhereClause where;
private Limit limit;
}
class SelectClause {
private List<String> fields;
}
...
class WhereClause {
Condition root;
}
interface Condition {
...
}
class AndCondition implements Condition { // the same for Not, Or
}
For this particular query its something like:
Query q = new Query(new SelectClause(["id", "name"]), new FromClause("employee"), new WhereClause(new AndCondition(new SimpleLeafCondition("age", Operators.GT, 30), new SimpleLeafCondition("department", Operators.EQ, "IT" )), new Limit(30));
Then Its possible to make some optimizations in the query (like embedding of where clauses if you need, or, for example, manipulating the "For" part if you're working multi tenant environment and have different collections for different tenants).
After all you can go with design pattern "interpreter" and recursively parse the query objects and "translate" them to valid mongo query.
I remember that this step took me something like 1 day to accomplish (it was 7 years ago with mongo 2 I guess, but still), given the correct structure of objects representing the query, so this should not be that complicated. I'm bringing this up, because It looks like its your primary concern in the question.

Use MySQL variables and assignments in hibernate

I have created the following query which is now in one of my java classes being used by Hibernate.
private static final String COUNT_INTERQUARTILE_SQL
= " SET #number_of_rows \\:= (SELECT COUNT(*) FROM carecube.visit)" +
" SET #quartile \\:= (ROUND(#number_of_rows*0.25))" +
" SET #medianquartile \\:= (ROUND(#number_of_rows*0.50))" +
" SET #sql_q1 \\:= (CONCAT('(SELECT 'Q1' AS quartile, visit.id FROM carecube.visit order by visit.id LIMIT 1 OFFSET ', #quartile, ')'))" +
" SET #sql \\:= (CONCAT_WS(' UNION ', #sql_q1, #sql_med))" +
" PREPARE stmt1 from #sql;" +
" EXECUTE stmt1;";`
The stack trace complains of a syntax errors for each line where I've set a mysql variable. Obviously it works in MySQL just fine.
I read that I can use double backslashes with assignments in Hibernate. This is the first time I've tried to use MySQL variables with Hibernate so am unsure if I'm missing anything out and whether 'PREPARE' and 'EXECUTE' are necessary?
Can someone with more knowledge point me where I am going wrong?
Also, where I am selecting Q1, I've placed that in single quotes, in MySQL workbench it is double quotes.
EDIT: I've added double quotes so hibernate doesn't throw a sissy fit with the assignments. I still can't for the life of me, figure out why I cannot just use '#sql' after i've prepared it.
EDIT: I receive the following error:
You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'PREPARE stmt1 from #sql_max; EXECUTE stmt1' at line 1
Thanks
I'm not sure if this is possible, but in my opinion this doesn't make much sense.
Some people have asked similar questions (they have some code samples in the answers if you want to check):
How to use Mysql variables with Hibernate?
How can I use MySQL assign operator(:=) in hibernate native query?
Hibernate is an ORM (Object Relational Mapping), so it's supposed to convert data between incompatible type systems (data from database) in objects. As far as I could understand your query, you're returning a COUNT, so it would be just one single result (row) and one single column, is that right?
Due the complexity of your query, I would say there are some options I could think of:
Use Criteria or HQL to run each query using Hibernate and then in Java work with the logic to have your desired result (may become much slower than the query in MySQL)
Create a VIEW with this SELECT (if possible), map it into an object as an #Entity and query directly to it
Create a FUNCTION/PROCEDURE (this is possible) and call it using CallableStatement
I like to think that the two elements here (Hibernate x Database) should have a well define responsibility in the project. First of all, I would try to use only Criteria/HQL for the queries (to use object properties), but if needed to use SQL I would keep all queries ANSI to allow interoperability. If it's not possible, I would create an object in the database to return what I want (view/procedure/function).
Mixing specific database provider code in the query, like your example, doesn't look a good practice.
If possible, I would definitely go for option 2. If it can't be done, surely for number 3.
Hope it's somehow helpful.

How to avoid quotes around table aliases in jOOQ

I have the following select-query creation:
final DSLContext create = DSL.using(..., SQLDialect.POSTGRES);
create
.select(DSL.field("identifier"), DSL.field("name"),
create.selectCount()
.from(DSL.table("person"))
.where(DSL.field("identifier").eq(DSL.field("personOuter.identifier")))
.asField("count"))
.from(DSL.table("person").as("personOuter"))
jOOQ generates the following query:
select
identifier,
name,
(select count(*)
from person
where identifier = personOuter.identifier) as "count"
from person as "personOuter"
The query should be:
select
identifier,
name,
(select count(*)
from person
where identifier = personOuter.identifier) as "count"
from person as personOuter
The latter query works perfectly in PostgreSQL. The table alias should not be surrounded by quotes.
Is this a bug?
(Note that the query is pretty dumb. I am playing around with jOOQ to evaluate.)
The following "hack" works:
create
.select(DSL.field("identifier"), DSL.field("name"),
create.selectCount()
.from(DSL.table("person"))
.where(DSL.field("identifier").eq(DSL.field("personOuter.identifier")))
.asField("count"))
.from("person as personOuter")
A note on using the code generator
I'm assuming you have a good reason to avoid using the code generator (e.g. you work on a dynamic schema), because working with generated code prevents having to worry about such details. Plus, you get access to many advanced features, like implicit joins, embeddable types, etc.
What's a string in the jOOQ API?
By default, jOOQ will wrap all your identifiers in quotes in order to be able to handle case-sensitivity correctly.
The confusing part is why this isn't done for DSL.field(String), but only for Field.as(String). The reason for this is that jOOQ re-uses the String type for both:
Plain SQL as in DSL.field(String), where the input String doesn't really represent an identifier, but an arbitrary SQL expression
Identifiers as in DSL.name(String), where the input String represents a name / identifier. There is also DSL.fieldByName(String) to create Field types composed of (schema) / table / column identifiers.
In order to remove the quotes from all generated identifiers, you can also change the Settings.renderNameStyle to RenderNameStyle.AS_IS.
More information about Settings can be found here. And also in this blog post about "What’s a “String” in the jOOQ API?"

Increase efficiency in comparing one field with multiple collection Mongodb Jdbc

Hi , I have one SQL query i'm trying to implement it in MongoDB using MongoJava driver Jdbc [2.10]. My sql query is,
SELECT DISTINCT table1.id FROM table1,table2 WHERE table1.x = table2.x and
table1.y IN ( somevalue ) AND table2.y IN (somevalue)
In MongoDB i have Table1 collection and Table2 collection. Using Jdbc i created two object to access two collection. Consider i have 1 lack record in each collection. If i try to compare each single document value with another collection, it takes 1 lack * 1 lack comparison. ?? after that i want to match it with 'y' value ??
Can any one suggest me how can i efficiently convert this query to MongoDB jdbc query ??
Thanks
mongdb doesn't support joins like that so you'd need to do multiple queries. something like this maybe:
db.collection1.distinct( 'id', { y: { $in: [...] } } )
then take those IDs and do another $in query against collection2.
Though, I have to ask why you'd have a table without unique IDs.
With classic RDBMS, you modeled your base and then you write your queries.
With MongoDB, it's tend to be the opposite : you list your use cases, i.e. access pattern and you model your data according to your needs.
The Mongo Java driver does not support SQL or the JDBC API. MongoDB does not support joins. If you want to use SQL, there is a JDBC driver available: JDBC Driver for MongoDB. You can also avoid joins by combining the two collections into one using nesting.

Categories