I have a screen where users can search a table based on 1 or more of its columns. I am passing the columns the users would like to filter by as a DTO to the backend.
There I am building a SQL to query by checking for the presence of these fields in the DTO...
Object transactionSearch (TransactionSearchDTO dto ){
StringBuilder transactionQuery = new StringBuilder()
transactionQuery.append("SELECT id, delivery_date, order_date, customer_name FROM transaction where 1=1 ")
if (dto.order_id) {
transactionQuery.append("and order_id = ${dto.order_id}")
}
if (delivery_date) {
transactionQuery.append("and order_id = ${delivery_date}")
}
if (customer_name) {
transactionQuery.append("and order_id = ${customer_name}")
}
}
What I am ending up with is a ton of if statements for each column.
Is there a way to achieve this without having an if block per filter?
I found some ideas here but I need a solution in Groovy or Java.
i suggest to use GString instead of StringBuilder to pass parameters correctly to groovy.sql.Sql
class DTO{
int id
String name
Date created
}
def a = new DTO(id:123, created:new Date())
GString f(DTO a){
GString q = GString.EMPTY + 'select ' + a.getProperties().collect{k,v-> k}.join(',')+' from T'
a.getProperties().each{k,v->
if(k!='class' && v!=null){
q = q + ( q.getValueCount()==0 ? ' WHERE ' : ' AND ' )
q = q + k + " = ${v}"
}
}
return q
}
def s = f(a)
println s
println s.getValues()
println s.getStrings()
In the app I am building, I need to access an SQLite database run a query in which I have a name in the "where" clause. This name is randomly generated (from another query) and is assigned to a global variable. This variable then get's passed to another query (in another method) where it's used. Something a little like this:
public class DatabaseAccess {
public static String player_name;
Cursor c = null;
public String getPlayerName(String string) {
c = db.rawQuery("select player_name FROM name_table where level = 1 ORDER BY RANDOM() LIMIT 1", new String[]{});
StringBuffer buffer = new StringBuffer();
while (c.moveToNext()) {
String diff = c.getString(0);
buffer.append("" + diff);
}
player_name = buffer.toString();
return buffer.toString();
}
public String getClue(String string) {
c = db.rawQuery("SELECT clue FROM clue_table where player_name = '" + player_name +"'", new String[]{});
StringBuffer buffer = new StringBuffer();
while (c.moveToNext()) {
String diff = c.getString(0);
buffer.append("" + diff);
}
return buffer.toString();
}
The issue is that some of the names in the database have a ' symbol in them (for example Dara O'Briain).
When a name like this comes up, my code crashes with the error message:
Caused by: android.database.sqlite.SQLiteException: near "Briain": syntax error (code 1): , while compiling: select alt_name FROM player_names where player_name = 'Dara O'Briain'
It looks like it see's the ' as a means to end the string and this causes a faulty query.
How can I account for these instances in my code?
c = db.rawQuery("SELECT clue FROM clue_table where player_name = '" +
player_name +"'", new String[]{});
// see here ^^^^^^^^^^^^^^
Ok, so that empty String[] array you're passing to the call is exactly the place parameters are supposed to go. So, the query should look like that:
c = db.rawQuery("SELECT clue FROM clue_table where player_name = ?", new String[]{player_name});
Just put a question mark where you want the parameter inserted. They will be inserted in order, the array can have more of these.
The driver will take care of escaping any apostrophes for you. If you don't escape user input in any way, you risk a lot. Consider player name coming from user input in the app, and they write into the search box on a whim:
' delete from users; --
If you just paste it into your query it now looks like this:
SELECT clue FROM clue_table where player_name = ''; delete from users; --'
And you get your data dropped as a free bonus to search function.
I'm working on a simple VertX Application. I have a hsqlDB and I'm trying to execute a query where I want to get all IDs from the Table where the Name contains a search parameter
String sql = "SELECT ID FROM MYTABLE WHERE NAME LIKE ?";
So this works when the Name is the same as the ?
When I try to use wildcards:
String sql = "SELECT ID FROM MYTABLE WHERE NAME LIKE %?%";
or
String sql = "SELECT ID FROM MYTABLE WHERE NAME LIKE '%?%'";
it doesn't work.
My Code:
private void getIDsBySearchString(String search, SQLConnection conn, Handler<AsyncResult<Vector<Integer>>> resultHandler) {
String sql = "SELECT ID FROM MYTABLE WHERE NAME LIKE ?";
conn.queryWithParams(sql, new JsonArray().add(search), asyncResult -> {
if(asyncResult.failed()) {
resultHandler.handle(Future.failedFuture("No Names Found"));
} else {
int numRows = asyncResult.result().getNumRows();
if(numRows >= 1) {
Vector<Integer> IDVector = new Vector<>();
for(int i = 0; i < numRows; i++) {
int id = asyncResult.result().getRows().get(i).getInteger("ID");
IDVector.add(id);
}
resultHandler.handle(Future.succeededFuture(IDVector));
} else {
resultHandler.handle(Future.failedFuture("No Names found"));
}
}
});
}
How do I need to edit my query String so the ? will be replaced by the search String and I will be able to use wildcards?
A parameter cannot be inside a quoted string. It can be part of a concat expression involving other strings.
String sql = "SELECT ID FROM MYTABLE WHERE NAME LIKE '%' || ? || '%'";
The part that should be changed is your search parameter, not the sql part:
String sql = "SELECT ID FROM MYTABLE WHERE NAME LIKE ?";
conn.queryWithParams(sql, new JsonArray().add("%"+search+"%"), asyncResult -> { ... }
I am using jsqlparser to parse a SQL string and replace table names in the string.
My input is
SELECT id, test
FROM test1 JOIN test2
ON test1.aa = test2.bb
WHERE test1.conf = \"test\"
LIMIT 10"
and my goal output is
SELECT id, test
FROM test1_suffix
JOIN test2_suffix
ON test1_suffix.aa = test2_suffix.bb
WHERE test1_suffix.conf = \"test\"
LIMIT 10"
And I managed to replace table name by extend the TablesNamesFinder, but it gave me this:
SELECT id, test
FROM test1_suffix
JOIN test2_suffix
ON test1.aa = test2.bb
WHERE test1.conf = \"test\"
LIMIT 10
I say that's half of the job done, but how can I do the rest of my job?
So here is a complete (hopefully) example to replace all occurances of table names. The problem is, that JSqlParser does not differ between aliases and table names. There has to be some logic to skip aliases of your sqls, if you do not want to correct those.
The usage of TableNamesFinder does not do the full job, because it traverses the AST only as far as it is needed to find table names and stops then. That is why my example uses the deparsers.
This code transforms
select id, test from test where name = "test"
to
SELECT id, test FROM test_mytest WHERE name = "test"
and
select * from t2 join t1 on t1.aa = t2.bb where t1.a = "someCondition" limit 10
to
SELECT * FROM t2_mytest JOIN t1_mytest ON t1_mytest.aa = t2_mytest.bb WHERE t1_mytest.a = "someCondition" LIMIT 10
I think that solves your problem.
public class SimpleSqlParser24 {
public static void main(String args[]) throws JSQLParserException {
replaceTableName("select id, test from test where name = \"test\"");
replaceTableName("select * from t2 join t1 on t1.aa = t2.bb where t1.a = \"someCondition\" limit 10");
}
private static void replaceTableName(String sql) throws JSQLParserException {
Select select = (Select) CCJSqlParserUtil.parse(sql);
StringBuilder buffer = new StringBuilder();
ExpressionDeParser expressionDeParser = new ExpressionDeParser() {
#Override
public void visit(Column tableColumn) {
if (tableColumn.getTable() != null) {
tableColumn.getTable().setName(tableColumn.getTable().getName() + "_mytest");
}
super.visit(tableColumn);
}
};
SelectDeParser deparser = new SelectDeParser(expressionDeParser, buffer) {
#Override
public void visit(Table tableName) {
tableName.setName(tableName.getName() + "_mytest");
super.visit(tableName);
}
};
expressionDeParser.setSelectVisitor(deparser);
expressionDeParser.setBuffer(buffer);
select.getSelectBody().accept(deparser);
System.out.println(buffer.toString());
}
}
Add an alias when parsing that gets used instead of the table name.
SELECT *
FROM test a
WHERE a.conf = 'something'
Then this should be changed to, that is the where clause can be the same
SELECT *
FROM test_suffix a
WHERE a.conf = 'something'
I have several tables. I have a query also. My problem is to generate the SQL query dynamically using Java.
I have the following fields in a separate table:
Collumn name status
po_number, Y
unit_cost, Y
placed_date , Y
date_closed, Y
scheduled_arrival_date Y
date_closed Y
order_quantity Y
roll_number N
product_sku N
product_category_name N
rec_vendor_quantity Y
vendor_name Y
et_conversion_unit_quantity Y
from which i have to generate a query when the status is Y, the problem here is some time the above columns
The following query is the out put of the above :
here i have inculded all the columns but i have to exculde the column which has the status of N, please help me to construt the query using java.
select
pi.po_number,poi.unit_cost,pi.placed_date CreateDate,
case when isnull(pi.date_closed) then pi.scheduled_arrival_date
else pi.date_closed end as ReceviedDate,
poi.order_quantity,poi.roll_number,p.product_sku product_name,
pc.product_category_name,poi.rec_vendor_quantity,pv.vendor_name,p.et_conversion_unit_quantity,pi.note
from
purchase_order as pi,
purchase_order_inventory as poi,
product_vendors as pv,
products AS p,
product_categories AS pc
where
pi.purchase_order_id=poi.purchase_order_id and
pc.product_category_id=p.product_category_id and
poi.product_id = p.product_id and
poi.product_category_id=pc.product_category_id and
pi.vendor_id=pv.product_vendor_id and
( ( pi.date_closed >= '2012-01-01' and pi.date_closed <='2012-09-05 23:59:59' )
or ( pi.scheduled_arrival_date >= '2012-01-01' and pi.scheduled_arrival_date <='2012-09-05 23:59:59') ) and
pi.po_type=0
and pi.status_id = 0 and poi.transaction_type = 0
order by pi.po_number
UPDATE :
QUERY : STEP 1:
SELECT rcm.id,rcm.tablename,rcm.columnname,rcm.size,rcm.displayorder,rcm.isactive FROM report_customise_master rcm where rcm.tablename !='employee' and rcm.isactive='Y' order by rcm.displayorder;
STEP 2 :
Java method to construct the query :
public Map getComplexReportQuery() {
String query = "SELECT rcm.id,rcm.tablename,rcm.columnname,rcm.size,rcm.displayorder,rcm.isactive FROM report_customise_master rcm where rcm.tablename !='employee' and rcm.isactive='Y' order by rcm.displayorder;";
String tableName = "", from = "", select = "";
StringBuffer sb = new StringBuffer();
Map<String, List<String>> resultsMap = new LinkedHashMap<String, List<String>>();
Map<String, String> displayOrderMap = new LinkedHashMap<String, String>();
Map queryMap = new LinkedHashMap();
if (!query.isEmpty() || query.length() > 0) {
sb.append(query);
}
Connection connection = getConnection();
if (connection != null) {
try {
PreparedStatement reportQueryPS = connection.prepareStatement(sb.toString());
ResultSet reportQuery_rst = reportQueryPS.executeQuery();
List<String> tables = new ArrayList<String>();;
if (reportQuery_rst != null) {
StringBuilder selectQuery = new StringBuilder(" SELECT ");
StringBuilder fromQuery = new StringBuilder(" FROM ");
while (reportQuery_rst.next()) {
tableName = reportQuery_rst.getString("tablename");
List<String> columns = resultsMap.get(tableName);
if (columns == null) {
columns = new ArrayList<String>();
resultsMap.put(tableName, columns);
}
columns = resultsMap.get(tableName);
String columnName = reportQuery_rst.getString("columnname");
columns.add(columnName);
}
tableName = "";
for (Entry<String, List<String>> resultEntry : resultsMap.entrySet()) {
tableName = resultEntry.getKey();
List<String> columns = resultEntry.getValue();
int i = 0;
for (String column : columns) {
selectQuery.append(tableName + "." + column);
if (i != columns.size()) {
selectQuery.append(",");
} else {
selectQuery.append("");
}
i++;
}
if (!tables.contains(tableName)) {
tables.add(tableName);
}
}
//to remove comma at the end of line
select = selectQuery.toString().replaceAll(",$", "");
tableName = "";
int i = 0;
for (String table : tables) {
fromQuery.append(table);
fromQuery.append(" ");
fromQuery.append(table);
if (i != tables.size()) {
fromQuery.append(",");
} else {
fromQuery.append("");
}
i++;
}
from = fromQuery.toString().replaceAll(",$", "");
queryMap.put("query", select + from);
}
//from = from+"ORDER BY "+orderbyColumn+" "+sort+" ";
} catch (Exception ex) {
ex.printStackTrace();
} finally {
try {
closeConnection(connection, null, null);
} catch (Exception ex) {
ex.printStackTrace();
}
}
} else {
System.out.println("Connection not Established. Please Contact Vendor");
}
return queryMap;// return the map/ list which contains query and sory and display order
}
STEP 3 : Result query
{query= SELECT purchase_order.po_number,purchase_order.placed_date,purchase_order.date_closed,purchase_order.scheduled_arrival_date,purchase_order_inventory.unit_cost,purchase_order_inventory.order_quantity,purchase_order_inventory.roll_number,purchase_order_inventory.rec_vendor_quantity,products.product_sku,products.et_conversion_unit_quantity,product_categories.product_category_name ,product_vendors.vendor_name FROM purchase_order purchase_order,purchase_order_inventory purchase_order_inventory,products products,product_categories product_categories,product_vendors product_vendors}
but this not what i wanted, Please help me to construct the query i have given.
Two queries
You need to make two queries:
Query which fields are enabled
Build the second query string (the one you want to build dinamically)
It's this way because a SQL query has to tell which columns will be included before querying any data. In fact it will be used to build the internal DB query plan, it is, the way the DB motor will use to retrieve and organize the data you ask.
Query all columns
Is it necesary to query only that fields? Can't you query everything and use the relevant data?
Joins
Looking at the updated question I guess you need to dynamically add where conditions to join tables correctly. What I should do is have a reference telling me what coindition to add when a table is present.
There are at least two options:
Based on table pairs present (by example: "if A and B are present then add A.col1 = B.col2")
Based on tables present ("if B is present, then add A.col1 = B.col2; A should be present"
Based on your example I think the second option is more suitable (and easy to implement).
So I should have some static Map<String, JoinInfo> where JoinInfo has at least:
JoinInfo
+ conditionToAdd // by example "A.col1 = B.col2"
+ dependsOnTable // by example "A" to indicate that A must be present when B is present
So you can use:
that info to add tables that should be (by example: even if A has no selected cols, must be present to join with B)
include the conditionToAdd to the where clause
Anyway... I think you are getting into much trouble. Why so dynamic?
You have to approach the thing step by step.
Firstly you have to create a query that will return all rows that have status='Y'
Then you will put the COLUMN_NAME in a list of Strings.
List<String> list = new List<String>();
while(rs.next()){
list.add(rs.getString(columnNumber));
}
And then you have to loop the List and generate dynamically your second sql statement
String sqlSelect = "SELECT ";
String sqlFrom = " FROM SOME_OTHER_TABLE "
String sqlWhere = " WHERE SOME_CONDITION = 'SOME_VALUE' "
for(String x : list){
sqlFrom += x +" , "+;
}
//here make sure that you remove the last comma from sqlFrom because you will get an SQLException
String finalSql = sqlSelect + sqlFrom + sqlWhere ;