For example, I have a statement
"SELECT * FROM Reports WHERE StartDate >= ? WHERE EndDate <= ? AND Performer = ?"
But sometimes some input fields on the web page are not filled, so I have to not take into account this conditions. i.e. I have no startdate filled, so statement must be
"SELECT * FROM Reports WHERE EndDate <= ? AND Performer = ?"
There are 3 different conditions. So, Do I have to write 8 different statements and DAO methods to accomplish the task? Really? Maybe there are other solutions?
Edit: I use MySQL/
Change your SQL to cater for nulls. Because you have not told us which database you are using, I will use "vanilla" SQL:
SELECT *
FROM Reports
WHERE (EndDate <= ? OR ? is null)
AND (Performer = ? OR ? is null)
Pass the parameters in twice each.
The other choice is to alter the SQL based on parameters being null (for example omitting Performer = ? from the where clause), but this can require a lot of code and testing. Iwould use the adaptable SQL and if it performs badly, then attempt something more advanced.
You dont need 8 different statements. You can build the query using if statements. For e.g.,
String query = "SELECT * FROM Reports where true";
if(startDate != null)
query = query + " and startDate <= ?";
if(endDate != null)
query = query + " and endDate <= ?";
if(performer != null)
query = query + " and performer = ?";
Hope it works for you.
No Prepared statement cannot exclude contions on its own. The query as to be contructed to avoid unnecessary conditions.
You can generate SQL using the code :
StringBuilder whereClause = new StringBuilder();
String and = "";
if(EndDate == null || EndDate.length == 0)
{
whereClause.append(your condition);
and = " and";
}
if(StartDate == null || StartDate.length == 0)
{
whereClause.append(and).append(your condition);
and = " and";
}
if(Performer == null || Performer.length == 0)
{
whereClause.append(and).append(your condition);
}
and based on you generated query you need to set the parameters to the prepared statement.
Related
I need to implement a fuzzy search for two fields, businessName and businessAddress. Both of them can be null. If one field is null, search should be based on the other field.
To be specific,
if businessName="name" and businessAddress="address" then execute select * from business where businessName like '%name%' and businessAddress like '%address%'
if businessName=null and businessAddress="address" then execute select * from business where businessAddress like '%address%'
if businessName=null and businessAddress=null then execute select * from business
My code:
StringBuilder sb = new StringBuilder("select * from business where 1=1 ");
if (businessName != null) {
sb.append("and businessName like '%" + businessName + "%' ");
}
if (businessAddress != null) {
sb.append("and businessAddress like '%" + businessAddress + "%' ");
}
try {
con = DBUtil.getConnection();
pst = con.prepareStatement(sb.toString());
rs = pst.executeQuery();
} ...
Apparently it's in danger of SQL-injection attack. I know method prepareStatement.setString() can avoid attack, but number of fields is uncertain before verification.
How can I modify it? Separate method for each case or code like below seem ugly.
if(businessName!=null){
if(businessAddress!=null){
sql = ...;
}else {
sql = ...;
}
else{
...
Never, ever, concatenate values into a query string like that. Always use prepared statements with parameters when executing queries, especially with user-sourced values.
A simple solution for your case is to use a list of values for each parameter you add, and then set the values collected for those parameters before execute:
StringBuilder sb = new StringBuilder("select * from business where 1=1 ");
List<String> parameters = new ArrayList<>();
if (businessName != null) {
sb.append("and businessName like '%' || ? || '%' ");
parameters.add(businessName);
}
if (businessAddress != null) {
sb.append("and businessAddress like '%' || ? || '%' ");
parameters.add(businessAddress)
}
try (Connection con = DBUtil.getConnection();
PreparedStatement pst = con.prepareStatement(sb.toString())) {
int index = 1;
for (String parameter : parameters) {
pst.setString(index++, parameter);
}
try (ResultSet rs = pst.executeQuery()) {
// ...
}
}
If you have parameters of varying types, use a List<Object> and setObject instead.
The solution in the answer by MT0 also works, but not all database systems optimize that type of query well (especially if you have a lot of such conditions), which might affect performance. For only a few conditions, the solution by MT0 is more readable, while having same/similar performance.
You do not need dynamic SQL and can use bind variables in the query:
String query = "select * from business where businessName LIKE '%' || ? || '%' AND businessAddress LIKE '%' || ? || '%'";
(Assuming || is the string concatenation operator for your SQL dialect.)
And then use a prepared statement and bind businessName and businessAddress to the variables.
PreparedStatement st = con.prepareStatement(query);
st.setString(1,businessName);
st.setString(2,businessAddress);
ResultSet rs = st.executeQuery();
(Add exception handling.)
Or, if your SQL dialect requires you to handle NULL separately from the LIKE then :
String query = "select * from business
where (:name IS NULL OR businessName LIKE '%' || :name || '%')
AND (:addr IS NULL OR businessAddress LIKE '%' || :addr || '%')";
and use named bind variables :name and :addr (or use ? pass the values twice).
Except Mark Rotteveel's answer, I think there are additional methods to solve your question.
1.Escape user's input string to avoid sql-injection.But this way is not 100% secure.You can use ESAPI to do this, please refer to SQL_Injection_Prevention_Cheat_Sheet.
2.You can use ORM framework,like MyBatis. For example, MyBatis have dynamic-sql,it allows you generate different sql according to different conditions. In addition, use #{xxx} not ${xxx} to keep sql-injection away.
I have 2 datebox to make a filter. Their value will determine the 'from' and 'to' in my query (I'm using Oracle now), here is the code.
#Listen("onClick=#btnSaveFilter")
public void saveFilter() throws Exception {
SimpleDateFormat formatter = new SimpleDateFormat("yyyy-MM-dd");
c_main_filter.detach();
cc.refreshFilter(""
+"[Date of Birth] between '" + formatter.format(dbDateBirthFrom.getValue()).toString() + "' "
+ "and '" + formatter.format(dbDateBirthto.getValue()).toString() + "'");
}
when both datebox has value, the query work. but when they have no value the query is giving no data.
when datebox has value, it's giving data
"[Registration Date] between '2010-09-23' and '2010-09-23' "
when datebox has no value, it's giving no data
"[Registration Date] between '' and '' "
like another filter I expect if the value is '' then all data will appear, but not :D hahaha. the condition is more than this actually, the filter has a lot of parameter one of them is this condition, and some of them use date format so there will be more condition like this.
do you know how to elegantly fix this problem, I've been thinking to use 'if' to determine the datebox has value or no then I will append the text to query text if both of them has value, but then I found another problem how I can add 'and' in query to give another condition,
let say I have 5 conditions so then
"select * from xxxx where 1stcondition and 2ndcondition and
3rdcondition and 4thcondition and 5th condition"
so when the dateboxes of the 5thcondition has no value the query will be wrong like this
"select * from xxxx where 1stcondition and 2ndcondition and
3rdcondition and 4thcondition and"
if I want to use 'if' how can I play with the 'and'? but if you have alternative it will be great cause I don't have to deal with 'if' :D
You can use String.isEmpty() to determine whether you need to put an and:
String where = "";
if (from != null && to != null) {
where += <yourDateCondition>;
}
if (<needToAddSecondCondtion>) {
if (!where.isEmpty()) {
where += " and ";
}
where += <secondCondition>;
}
// continue with other conditions
String query = "select * from xxxx where " + where;
I don't know if you use plain JDBC or ORM framework like hibernate to querying to the database, but you can try something like this :
public void saveFileter(){
StringBuilder sb = new StringBuilder();
sb.append("select * from table ");
if(dbDateBirthFrom.getValue() != null{
sb.append(determineFilterWord(sb.toString()));
sb.append("your condition ");
}
if(dbDateBirthTo.getValue() != null{
sb.append(determineFilterWord(sb.toString()));
sb.append("your condition ")
}
session.createQuery(sb.toString()); //if you using hibernate
}
private string determineFilterWord(String query){
if(query.toLowerCase().indexOf("where") != -1){
return "and ";
}else{
return "where ";
}
}
I have the following DAO method:
public String getSomeTable(final String param1) {
String sqlString = "select * from table where name ilike ?";
Query query = this.getEntityManager().createNativeQuery(sqlString);
query.setParameter(1, "%param1%");
}
If param1 is null or empty then I want to select all entries from the table. What is the correct way to do this? I am currently using the following:
public String getSomeTable(final String param1) {
String sqlString = "select * from table where name = ?";
Query query = this.getEntityManager().createNativeQuery(sqlString);
if(param1 == null)
query.setParameter(1, "%%");
else
query.setParameter(1, "%param1%");
}
But this is not scalable. I have datatypes like integer, date, etc. I want to know if there is a way to skip checking for that parameter if it is null.
I was planning to use COALESCE(?, CASE WHEN ? = '' THEN '%%' ELSE '%?%') but I think ? can be used only once for a particular parameter. The next one > I write is linked to second param.
On SQL Server, I use something like this, perhaps you can translate it to postgres:
DECLARE #variable INT = NULL;
SELECT *
FROM sysobjects
WHERE
(1 = CASE WHEN #variable IS NULL THEN 1 ELSE 2 END)
OR
(id LIKE #variable);
I have a jtable. I run data from mysql database. I use a query with prepared statement:
"select * from customer where city=? and region=? and price>?"
pst.setString(1,"rome")
pst.setstring(2,"italy")
pst.setdouble(3,"1500")
my problem is that one time i need only one parameter (for example city).
i ask you i change every time a query o r i can set dynimicaly number of parameter??
Thanks
(I think seperating them into seperate queries is a better solution, but if you want to do it this way, maybe something like this could help you)
I once wrote something like this
public ArrayList<User> searchUsers(HashMap userProperties)
{
ArrayList<User> foundUsers = new ArrayList<User>();
String searchString = "select * from chatusers where ";
int addedProps = 0;
if (userProperties.get("username") != null && !userProperties.get("username").equals(""))
{
searchString += "nickname like '%" + userProperties.get("username") + "%'";
addedProps++;
}
if (userProperties.get("firstname") != null && !userProperties.get("firstname").equals(""))
{
if (addedProps > 0)
searchString += " AND ";
searchString += "voornaam like '%" + userProperties.get("firstname") + "%'";
addedProps++;
}
if (userProperties.get("lastname") != null && !userProperties.get("lastname").equals(""))
{
if (addedProps > 0)
searchString += " AND ";
searchString += "naam like '%" + userProperties.get("lastname") + "%'";
}
....
Of course you would have to change the "userProperties.get("prop"). to a "?" (I know I need to use preparedStatements, this was just for something silly at university before we had seen preparedStatements.
Furthermore I would go on in a similar fashion to actually bind the variables in the end. (keep track of where you added which variable, at which index, which you know because of the if-statement).
Are this JDOQL same as the following SQL?
Query query = pm.newQuery(Vote.class, ":p.contains(personId)");
if (startDate != null) {
query.setFilter("personId == listOfIds && createdDate > startDateParam");
query.declareParameters("java.util.List listOfIds, java.util.Date startDateParam");
}
else {
query.setFilter("personId == :listOfIds");
}
query.setOrdering("createdDate desc");
List<Vote> list = (List<Vote>) query.execute(listOfIds, startDate);
SELECT * FROM VOTE
WHERE persionID IN (id1, id2..)
AND createdDate > '2011-07-11'
ORDER BY createdDate desc;
Yes. Bear in mind that 'in' queries translate to multiple underlying datastore queries, and so aren't particularly efficient - if you can avoid them, do.