java.sqlite setObject not setting on PreparedStatement - java

Am I missing something obvious here?
I have the following method to execute queries against an SQLite local database using org.xerial:sqlite-jdbc:3.21.0.
public List<HashMap<String, Object>> executeQuery(String sql, List<Object> vals) throws Exception {
List<HashMap<String, Object>> rows = new ArrayList<>();
Connection conn = getConnection();
try (PreparedStatement stmnt = conn.prepareStatement(sql)) {
if (!vals.isEmpty()) {
for (int i = 0; i < vals.size(); i++) {
stmnt.setObject(i + 1, vals.get(i));
}
}
ResultSet rs = stmnt.executeQuery();
ResultSetMetaData meta = rs.getMetaData();
HashMap<String, Object> row;
while (rs.next()) {
row = new HashMap<>();
for (int i = 0; i < meta.getColumnCount(); i++) {
row.put(meta.getColumnName(i + 1), rs.getObject(i + 1));
}
rows.add(row);
}
} finally {
putConnection(conn);
}
return rows;
}
However, when I pass the following SQL into the method along with the following values, they don't get set (but it also doesn't throw an exception). It's like it internally assigns it but forgets to tell the database.
SELECT * FROM 'airlines' WHERE 'name' LIKE ? LIMIT 1
vals: size = 1 {"MyAirline"}
I can see from debugging that it gets inside the loop to setObject.

In ANSI standard SQL, single quotes (') are used to delimit literal strings and double quotes (") are used to delimit table/column names. So
SELECT * FROM 'airlines' WHERE 'name' LIKE ? LIMIT 1
really means "select all columns from the literal string 'airlines' where the literal string 'name' matches the pattern supplied by the parameter".
Interestingly, SQLite seems to be clever enough to interpret the literal string 'airlines' as the table name "airlines" but it is still interpreting 'name' as a literal string. Therefore, for every row in "airlines" it is comparing the literal string 'name' to the string value 'MyAirline' and it never matches, so the ResultSet contains no rows.
Your SQL command text should be
SELECT * FROM "airlines" WHERE "name" LIKE ? LIMIT 1
so SQLite will compare the contents of the "name" column with the value 'MyAirline'.

Related

Statement.executeQuery() throws java.sql.SQLSyntaxErrorException when executing SELECT command in MySQL

I have a method that fetches records from MySQL db table using JDBC API.
The command that I have been using is:
"SELECT column_1, column_2, ... FROM table;"
The column names are provided to the method in form of ArrayList a query is being constructed based on the column & table names using StringBuilder.
When executing the constructed query using createStatement(), it is throwing: "java.sql.SQLSyntaxErrorException: 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 from $tableName' at line 1"
This is the full code of the method:
public ObservableList<ObservableList<String>> getTableData(String tableName, List<String> selectedParams, int rowCount) {
try {
StringBuilder query = new StringBuilder("select ");
for (int i = 0; i < selectedParams.size(); i++) {
query.append(selectedParams.get(i)).append(", ");
}
query.append("\b\b from ").append(tableName).append(" limit ").append(rowCount);
System.out.println("query:" + query);
ObservableList<ObservableList<String>> rows;
try (ResultSet rset = st.executeQuery(query.toString())) {
ResultSetMetaData rsmd = rset.getMetaData();
rows = FXCollections.observableArrayList();
while (rset.next()) {
ObservableList<String> row = FXCollections.observableArrayList();
int count = 1;
while (count <= rsmd.getColumnCount()) {
row.add(rset.getString(count));
count++;
}
rows.add(row);
}
}
rows.forEach((row) -> {
System.out.println(row);
});
return rows;
} catch (SQLException ex) {
Logger.getLogger(DBHelper.class.getName()).log(Level.SEVERE, null, ex);
}
return null;
}
When running the code, the generated query form print statement looks something like this:
"query:select territory from offices"
I have tested this query against db directly and another simple JDBC program and they both run fine. Except for in this method. Please help.
---------------Edit: forgot to paste the exception message:---------------
java.sql.SQLSyntaxErrorException: 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 from offices limit 10' at line 1
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:120)
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97)
at com.mysql.cj.jdbc.exceptions.SQLExceptionsMapping.translateException(SQLExceptionsMapping.java:122)
at com.mysql.cj.jdbc.StatementImpl.executeQuery(StatementImpl.java:1218)
issue id in
for (int i = 0; i < selectedParams.size(); i++) {
query.append(selectedParams.get(i)).append(", ");
}
the above code will append an extra ',' after param names
instead you can use
query.append(String.join(", ", selectedParams))
The problem is that
query.append("\b\b from ")
does not remove the trailing comma and space from the StringBuilder. It may look like it does when you print it to the console, but the string actually does contain
select territory, ␈␈ from offices
and MySQL apparently doesn't like that.
Instead, you want to actually delete the comma from the StringBuilder (and leave the space):
query.deleteCharAt(query.length() - 2).append("from ")

Cassandra: copy from Row to BoundStatement

I'm trying to write a generic tool for copying Cassandra table contents from one keyspace to another one (probably in a different cluster). All the tables are not too large.
Here is what I do:
Session source = ...
Session destination = ...
TableMetadata table = ...
final ResultSet rs = source.execute("select * from " + table.getName());
String insertCql = ...
PreparedStatement preparedStatement = destination.prepare(insertCql);
for (Row row : rs) {
final BoundStatement boundStatement = preparedStatement.bind();
for (int i = 0; i < rs.getColumnDefinitions().size(); i++) {
// bind column value from row to bountStatement
}
session.execute(boundStatement);
}
The problem is how to copy the column value from row to boundStatement. I can read it with row.getObject(i), but there is no corresponding setObject() in BoundStatement.
More precisely, this method exists in version 2.2 of the driver (it's cassandra-driver-dse), but that version does not work with Cassandra 3, and in version 3 of the driver (cassandra-driver-core) setObject() method does not exist. Instead, there are bunch of set() methods, all of them require Class, TypeToken or TypeCodec.
Where can I get those? ColumnDefinition only gives me DataType. It seems a doubtful idea to use row.getObject(i).getClass() to get Class.
Maybe there is a better approach to this task (schema-agnostic copying)?
I can look at DataType if the column and make a case per type to use setString() and so on, but this seems a bit overcomplicated and fragile.
You need to use bind variables in your insert statement and then bind the prepared statement with the column values from the result. Something along the lines of:
String insertCql = "INSERT INTO ks.tb (...) values (?,?,...)";
for (Row row : rs) {
List bindVariables = new ArrayList();
for (int i = 0; i < rs.getColumnDefinitions().size(); i++) {
bindVariables.add(rs.getObject(i));
}
final BoundStatement boundStatement = preparedStatement.bind(bindVariables.toArray(new Object[0]));
session.execute(boundStatement);
}

How sql works and why this result is returned?

Hi I'm using preparedStatement in Java to execute query in DB.
The table:
When it comes to update, delete and insert it's all fine, however when it comes to select( ex. I've done "SELECT ?,?,?,?,? from person" and set strings afterwards) and the following result is returned:
I'm assuming that because it's the strings that are replacing ? so it did not come out as expected:(please correct me if it's wrong)
Expected sql: "SELECT no,name,tel,birthday,address FROM person"
Actual sql: "SELECT \"no\",\"name\",\"birthday\",\"address\" FROM person"
I've tested the second one in in Navicat:
I'd like to understand that why executing this query statement would return a result like this?
If it would help here's Java code:
// Data Assist Object
public class DAO {
static String jdbcurl;
static String username;
static String password;
static{
try {
Class.forName("com.mysql.jdbc.Driver");
ResourceBundle rb = ResourceBundle.getBundle("db");
jdbcurl = rb.getString("jdbcurl");
username = rb.getString("username");
password = rb.getString("password");
} catch (ClassNotFoundException ex) {
ex.printStackTrace();
}
}
// for insert, delete and update
public int modify(String sql, String[] args){
int x=0;
try(Connection con = DriverManager.getConnection( jdbcurl,username ,password);
PreparedStatement ps = con.prepareStatement(sql);){
for (int i = 0; i < args.length; i++) {
ps.setString(i+1, args[i]);
}
x =ps.executeUpdate();
System.out.println(x);
}catch(SQLException e){
e.printStackTrace();
}
return x;
}
// for select
public List<Map<String,String>> query(String sql, String[] params){
List<Map<String,String>> resList = new ArrayList<>();
try(Connection con = DriverManager.getConnection( jdbcurl,username ,password);
PreparedStatement ps = con.prepareStatement(sql);){
for (int i = 0; i < params.length; i++) {
ps.setString(i+1, params[i]);
}
try(ResultSet res =ps.executeQuery();){
ResultSetMetaData mdata = res.getMetaData();
int num = mdata.getColumnCount();
while(res.next()){
HashMap<String,String> data = new HashMap<>();
for (int i = 1; i <= num; i++) {
String result = res.getString(i);
String columnName = mdata.getColumnName(i);
data.put(columnName,result);
}
resList.add(data);
}
}
}catch(Exception e){
e.printStackTrace();
}
return resList;
}
public static void main(String[] args) throws SQLException {
DAO dao = new DAO();
String sql = "insert into person(name,tel,birthday,address) values(?,?,?,?)";
sql = "select ?,?,?,?,? from person";
List<Map<String,String>> res = dao.query(sql, new String[]{"no","name","tel","birthday","address"});
for(Map m:res){
System.out.print("no: "+m.get("no")+",");
System.out.print("name: "+m.get("name")+",");
System.out.print("tel: "+m.get("tel")+",");
System.out.print("birthday: "+m.get("birthday")+",");
System.out.println("address: "+m.get("address"));
}
}
}
Thanks for any help.
SQL basically works on a show me these columns where this criteria is true basis.
In the statement:
"SELECT \"no\",\"name\",\"birthday\",\"address\" FROM person"
You're getting
SELECT "no", "name", "birthday", "address" FROM person
when it actually hits the database. The "" operator creates a string in SQL. In plain English, that means that you're telling the database to return that specified set of strings for each row in person where the criteria you listed is met.
Since you didn't list a where clause, all rows are true by default so you get one row of strings for every single row in the person table. The first query is the same thing, but instead of directly passing the strings, you're adding them in as bind variables.
If you actually want to see the values in the table, write the query without the "'s
SELECT no, name, birthday, address FROM person
Unless otherwise specified, bind functions generally pass the value as a string. Which is why the query behaved the way it did. I don't recommend using bind variables in the select clause. That's a strange practice.
Edit:
As Adrian pointed out in the comments, " denotes columns in SQL. My apologies for not catching that. I assume that you meant to use the ' operator which actually denotes strings.
If not, something else is going on here entirely.
For the select you use the question marks in the WHERE clause, not where you list the fields you need as output.
Replace
sql = "select ?,?,?,?,? from person";
with
sql = "select no,name,tel,birthday,address from person";
For this particular query there is no binding to do. It will retrieve all the records from the table.

How to insert date or NULL to array

I have following question.
I'm using JDBC in my project and I made simple method to insert data into my database.
My problem is: What to do when I want to insert something like sysdate or just NULL to auto increment? To my method I send only strings and writing NULL to string doesn't work.
Can you give me any advice how to improve it?
This is the code with constant null in query, but it isn't what I want to.
public static void insertInto(String Table, ArrayList<String> values) throws SQLException
{
Connection conn = JavaConnectDB.ConnectDb();
OraclePreparedStatement pst = null;
StringBuilder Query = new StringBuilder("INSERT INTO " + Table + " VALUES (NULL, ");
for (int i = 0; i < values.size(); i++)
{
Query.append("? ");
if (i + 1 != values.size())
Query.append(", ");
}
Query.append(")");
pst = (OraclePreparedStatement) conn.prepareStatement(Query.toString());
for (int i = 0; i < values.size(); i++)
{
pst.setString(i + 1, values.get(i));
}
pst.executeUpdate();
}
This method creates query like "INSERT INTO TABLE VALUES (NULL, ?, ? ,?)" and then fills gaps with values from array.
There is java.sql.PreparedStatement.setNull(int, int)
Try, e.g.
pst.setNull(1, Types.BIGINT);
for (int i = 1; i < values.size(); i++)
{
pst.setString(i + 1, values.get(i));
}
Change Types.BIGINT for apporpriate type for your column.
Note, that values.get(0) is just ignored, but should present in the array.
Problem is the setString function. If you have a string "NULL" or "SYSDATE", it will result in the query being quoted ('NULL', 'SYSDATE'), so this will be inserted as string.
According to this answer, pst.setString(n, null) should do the trick already, so inserting SQL NULL values is yet relatively easy, just insert a Java null value into the array where you want the database value to be SQL NULL.
SYSDATE gets more delicate. But I think, here comes something more fundamental into play: how are you going to handle data types other than VARCHAR (see setString documentation). If you really wanted such a generic method, I would rather to pass ArrayList<Object> as parameter (or with ellipsis ...) and call the appropriate setXXX method for the specific Object type - or setObject with appropriate Type parameter set. You could then create your own class SysDate which could easily be detected.
Is the database layout known to your application? Then I'd rather recommend to have a separate insert method for each table accepting exactly the number of required parameters of correct type, such as
bool insertIntoTest(int someValue, Integer anotherValue, String andAnotherOne)
{
Connection conn = JavaConnectDB.ConnectDb();
OraclePreparedStatement pst
= (OraclePreparedStatement) conn.prepareStatement(
"INSERT INTO TEST (someValue, anotherValue) VALUES(?, ?)"
);
pst.setInt(1, someValue);
if(anotherValue == 0)
pst.setNull(2, Types.INTEGER);
else
pst.setInt(2, anotherValue);
// can handle null already...
pst.setString(3, andAnotherOne);
/* ... (execute, try/catch, return) */
}
Well you should define exactly what do ou want to do.
It's not possible to put null in an autoinkrement field on database by definition
Auto-increment allows a unique number to be generated when a new record is inserted into a table.
So if you want just to insert some filed to your table and delegate the genration of autoincrement to your database, you should create your query like that :
INSERT INTO TABLE VALUES (?, ? ,?).
Example :
Table employee(id,time,name)
Query :
INSERT INTO employee (time, name) VALUES (?, ?)

Efficient way to select data with a single condition

Is there an efficient way to obtain a list (preferably an array, a ResultSet will do) to SELECT a lot of rows.
For example:
Connection con = DriverManager.getConnection(host,username,password);
String sql = "SELECT * FROM table_name WHERE food = ? AND expiration > ?";
PreparedStatement stmt = con.prepareStatement(sql);
Using the above code, I want to get all the food from a given array that isn't expired.
String[] foodList = {"banana","apple","orange",...}
where the expiration date is a constant date (lets say 3 days ago). However, the way I have it is that the String and PreparedStatement are in a for loop that loop the number of foods in the array to individually check the expiration date. This creates a lot of ResultSets after I execute each individually.
Most SQL Databases support a IN (list) expression. This is roughly equivalent to providing a or expression:
SELECT id FROM table WHERE food IN ('Apple', 'Banana') AND exp < ?
is similar to
SELECT id FROM table WHERE (food = 'Apple' or food = 'Banana') AND exp < ?
In both cases some RDBMS can optimize it.
However first of all there is a limitation in the number of list items you can specify in the IN or number of characters you can use in the statement. So if your list can be variable long you need to be prepared to run multiple statements.
Secondly you cannot* set a array as an argument to a PreparedStatement and expect it to work with IN.
Unfortunately in plain JDBC all you can do is to concatenate a String. This is frowned upon, but there is no good alternative (unless you want to do something like giving the list of foods as a single list and use a "instring" expression).
Make sure to add as many ? (but not too many) as you expect parameters and then set them in the IN:
String[] foods = ...;
int remain = foods.length;
int start = 0;
while(remain > 0)
{ if (remain >= 100)
executeBatch(foods, start, 100); start+=100; remain-=100;
else if (remain >= 30)
executeBatch(foods, start, 30); start+=30; remain-=30;
else {
executeBatch(foods, start, 1); start+=1; remain-=1;
}
}
void executeBatch(String[] f, int off, int len)
{
StringBuilder sqlBuf = StringBuilder("... IN(");
for(int i=0;i<len;i++) {
sqlBuf.append((i!=0)?",?":"?");
}
String sql = sqlBuf.append(") AND exp < ?").toString();
PreparedStatement ps = c.prepareStatement(sql);
for(int i=0;i<foods.length;i++)
ps.setString(i+1, foods[i+off]);
ps.setTimestamp(foods.length+1, now);
....
}
This avoids to generate a lot of different SQL statement to compile. (Only 100,30 or 1 ?)). You can use the same logic for the OR case.
* not to be confused with ARRAY database type.
Probably not the most elegant solution, and you won't get any performance benefit from the prepared statement (but you will get parameter binding):
StringBuilder sql = new StringBuilder("SELECT * FROM table_name WHERE expiration > ? AND food IN (");
for (int i = 0; i < foodList.length; i++) {
if (i > 0) {
sql.append(',');
}
sql.append('?');
}
sql.append(")");
Connection con = DriverManager.getConnection(host, username, password);
PreparedStatement stmt = con.prepareStatement(sql.toString());
stmt.setDate(1, expirationDate);
for (int i = 0; i < foodList.length; i++) {
stmt.setString(i + 2, foodList[i]);
}
ResultSet rs = stmt.executeQuery();
/* ... Do Stuff ... */

Categories

Resources