I am using Java 1.7 and JDBC 4 and Postgres. I am trying to use a PreparedStatement with an array to fill a SQL in clause. But, the SQL generated seems to have "{" and "}" in it. Here is the code:
PreparedStatement ptmt =
connection.prepareStatement("select * from foo where id in (?)");
String[] values = new String[3];
values[0] = "a";
values[1] = "b";
values[2] = "c";
ptmt.setArray(1, connection.createArrayOf("text", values));
The resulting SQL looks like this:
select * from foo where id in ('{"a","b","c"}')
Which, can't work. This is how it should look:
select * from foo where id in ("a","b","c")
or
select * from foo where id in ('a','b','c')
What am I missing here?
Use = ANY subquery expression.
PreparedStatement ptmt = connection.prepareStatement("select * from foo where id = ANY(?)");
String[] values = new String[]{"a","b","c"};
ptmt.setArray(1, connection.createArrayOf("text", values));
And if you need to enforce types in the query you can do something like this.
select * from foo where id = ANY(?::text[])
The PostgreSQL documentation has more details. This snippet is worth noting:
SOME is a synonym for ANY. IN is equivalent to = ANY.
When your database field is of type array, then you can use the PreparedStatement.setArray() to send an array to the query. But, in your case, it's not really an array, rather is a variable no of arguments, and you can't do that. i.e.
PreparedStatement ptmt = connection.prepareStatement("select * from foo where id in (?)");
can take only one parameter. If you want 3 parameters to be passed, you have to do
PreparedStatement ptmt = connection.prepareStatement("select * from foo where id in (?, ?, ?)");
And do ptmt.setString(n, "String") thrice.
If your no of arguments aren't constant, then construct the query dynamically, although, you loose the efficiency.
PostgreSQL has a couple of array capabilities that can handle this situation.
First is the unnest function, available since 8.4. A little cludgy, but effective.
select * from foo where id in (SELECT * FROM unnest(?));
Next is the array intersection operator.
select * from foo where ARRAY[id] && ?;
Converts your column value into an array with a single element and then checks for intersection with the array that you set up as a parameter.
As best as I can tell, these are functionally equivalent, but I haven't checked which might be more performant.
I'd consider this a PgJDBC issue. It should really be writing an array constructor or an array literal with an explicit cast, eg:
select * from foo where id in (ARRAY['a','b','c'])
or
select * from foo where id in ('{"a","b","c"}'::text[])
As a workaround you should be able to write:
"select * from foo where id in ( (?)::text[] )"
.. though I haven't tested to verify that.
Please consider writing this up as a unit test for PgJDBC and submit it to the PgJDBC project via github as a bug report. Look at how the existing unit tests work and just add another. It's all pretty simple JUnit.
Related
I have a tricky issue with the Oracle JDBC driver's handling of CHAR data types. Let's take this simple table:
create table x (c char(4));
insert into x (c) values ('a'); -- inserts 'a '
So when I insert something into CHAR(4), the string is always filled with whitespace. This is also done when I execute queries like this:
select * from x where c = 'a'; -- selects 1 record
select * from x where c = 'a '; -- selects 1 record
select * from x where c = 'a '; -- selects 1 record
Here, the constant 'a' is filled with whitespace as well. That's why the record is always returned. This holds true when these queries are executed using a JDBC PreparedStatement as well. Now the tricky thing is when I want to use a bind variable:
PreparedStatement stmt =
conn.prepareStatement("select * from x where c = ?");
stmt.setString(1, "a"); // This won't return any records
stmt.setString(1, "a "); // This will return a record
stmt.executeQuery();
This is a workaround:
PreparedStatement stmt =
conn.prepareStatement("select * from x where trim(c) = trim(?)");
stmt.setString(1, "a"); // This will return a record
stmt.setString(1, "a "); // This will return a record
stmt.executeQuery();
EDIT: Now these are the constraints:
The above workaround is not desireable as it modifies both the contents of c and ?, AND it makes using indexes on c quite hard.
Moving the column from CHAR to VARCHAR (which it should be, of course) is not possible
EDIT: The reasons for these constraints is because I ask this question from the point of view of the developer of jOOQ, a database abstraction library. So my requirements are to provide a very generic solution that doesn't break anything in jOOQ's client code. That is why I'm not really a big fan of the workaround. And that's why I don't have access to that CHAR column's declaration. But still, I want to be able to handle this case.
What would you do instead? What's a good practice for handling CHAR data types when I want to ignore trailing whitespace?
If you want
stmt.setString(1, "a"); // This won't return any records
to return a record, try
conn.prepareStatement("select * from x where c = cast(? as char(4))")
I don't see any reason to use CHAR datatype even if it is char(1) in Oracle. Can you change the datatype instead?
Gary's solution works well. Here's an alternative.
If you are using an Oracle JDBC driver, the call to prepareStatement() will actually return an OraclePreparedStatement, which has a setFixedCHAR() method that automatically pads your inputs with whitespace.
String sql = "select * from x where c = ?";
OraclePreparedStatement stmt = (OraclePreparedStatement) conn.prepareStatement(sql);
stmt.setFixedCHAR(1, "a");
...
Obviously, the cast is only safe if you are using the Oracle driver.
The only reason I would suggest that you use this over Gary's answer is that you can change your column sizes without having to modify your JDBC code. The driver pads the correct number of spaces without the developer needing to know/manage the column size.
I have nice fix for this. You have to add one property while getting connection from database.
NLS_LANG=american_america.AL32UTF8
or in Java connection you can use below code:
java.util.Properties info = new java.util.Properties();
info.put ("user", user);
info.put ("password",password);
info.put("fixedString","TRUE");
info.put("NLS_LANG","american_america.AL32UTF8");
info.put("SetBigStringTryClob","TRUE");
String url="jdbc:oracle:thin:#"+serverName;
log.debug("url="+url);
log.debug("info="+info);
Class.forName("oracle.jdbc.OracleDriver");
conn = DriverManager.getConnection(url,info);
the other way is modify your sql as
select * from x where NVL(TRIM(c),' ') = NVL(TRIM('a'),' ')
Simply add RTRIM() to the column name(which is defimed) in the update query.
Is there any easy way to get a completed SQL statement after parameter substitution?
I am using elasticsearch-sql to query elastic search with sql statements, however I have to submit the query with all the parameters substituted.
I tried Hibernate Query getQueryString, but the parameter substitution is not happening for those sql strings.
The following sql string is produced:
"SELECT * FROM USER WHERE NAME=? AND SURNAME=?"
rather than:
"SELECT * FROM USER WHERE NAME='Selva' AND SURNAME='Esra'
Appreciate any better idea/thoughts?
1. Named parameters
This is the most common and user friendly way. It use colon followed by a parameter name (:example) to define a named parameter. See examples…
String hql = "SELECT * FROM USER WHERE NAME= :userName AND SURNAME= :surName";
Query query = session.createQuery(hql);
query.setParameter("userName ", "userName");
query.setParameter("surName", "SurName");
List results = query.list();
An object-oriented representation of a Hibernate query. A Query instance is obtained by calling Session.createQuery(). This interface exposes some extra functionality beyond that provided by Session.iterate() and Session.find():
a particular page of the result set may be selected by calling setMaxResults(), setFirstResult()
named query parameters may be used
the results may be returned as an instance of ScrollableResults
Named query parameters are tokens of the form :name in the query string. A value is bound to the integer parameter :foo by calling
setParameter("foo", foo, Hibernate.INTEGER);
for example. A name may appear multiple times in the query string.
JDBC-style ? parameters are also supported. To bind a value to a JDBC-style parameter use a set method that accepts an int positional argument (numbered from zero, contrary to JDBC).
You may not mix and match JDBC-style parameters and named parameters in the same query.
2. Positional parameters
It’s use question mark (?) to define a named parameter, and you have to set your parameter according to the position sequence. See example…
Java
String hql = "from Stock s where s.stockCode = ? and s.stockName = ?";
List result = session.createQuery(hql)
.setString(0, "7277")
.setParameter(1, "DIALOG")
.list();
This approach is not support the setProperties function. In addition, it’s vulnerable to easy breakage because every change of the position of the bind parameters requires a change to the parameter binding code.
Java
String hql = "from Stock s where s.stockName = ? and s.stockCode = ?";
List result = session.createQuery(hql)
.setParameter(0, "DIALOG")
.setString(1, "7277")
.list();
Conclusion
In Hibernate parameter binding, i would recommend always go for “Named parameters“, as it’s more easy to maintain, and the compiled SQL statement can be reuse (if only bind parameters change) to increase the performance.
I'm trying to pass an array of strings to a select statement and I keep getting the error:
org.postgresql.util.PSQLException: Can't infer the SQL type to use for an instance of clojure.lang.PersistentVector. Use setObject() with an explicit Types value to specify the type to use.
I know that the column type is correct, it looks as if passing a vector is the culprit. What is the correct way to do this?
The sql statement is formatted like so:
"SELECT * FROM said_table WHERE item_id IN (?)"
This answer assumes you are using jdbc and not korma or something like it and need to generate the sql directly instead of going through some tool:
the in directive requires you to crate one ? for each item in the list. I end up using this pattern when something else requires me to build the SQL manually:
(let [placeholders (s/join ", " (repeat (count things-go-here) "?"))
query "SELECT * FROM said_table WHERE item_id IN (%s)"]
(exec-raw [(format query placeholders) things-go-here] :results)
....)
I currently have this:
MyObject myObject = getJdbcTemplate().queryForObject("select * from my_objects where id = ?",
new Object[]{new Integer(id)},
new MyObjectRowMapper());
Now in my method I want to pass an enumeration:
SortOrder.ASC
SortOrder.DESC
So it will either be:
ORDER BY ID ASC
or
ORDER BY ID DESC
So inside the sql string, do I just add another '?' or do I have to build up the string like:
"select * from abc ORDER BY ID " + sortOrder;
Is there a preferred way?
You have to use the second way. A prepared statement isn't just a "query-replace placeholders by the String I pass". All parameters must be typed values to insert into the syntax tree generated from the query. You can't pass a portion of a query as a parameter.
You can not change the query in PreparedStatements so you can not use ? for asc or desc. I think concatenation is the simplest way you can use.
Following on from one of my previous questions to do with method design I was advised to implemented my SQL queries as a parameterized query as opposed to a simple string.
I've never used parameterized queries before so I decided to start with something simple, take the following Select statement:
String select = "SELECT * FROM ? ";
PreparedStatement ps = connection.prepareStatement(select);
ps.setString(1, "person");
This gives me the following error: "[SQLITE_ERROR] SQL error or missing database (near "?": syntax error)"
I then tried a modified version which has additional criteria;
String select = "SELECT id FROM person WHERE name = ? ";
PreparedStatement ps = connection.prepareStatement(select);
ps.setString(1, "Yui");
This version works fine, in the my first example am I missing the point of parameterized queries or am I constructing them incorrectly?
Thanks!
Simply put, SQL binds can't bind tables, only where clause values. There are some under-the-hood technical reasons for this related to "compiling" prepared SQL statements. In general, parameterized queries was designed to make SQL more secure by preventing SQL injection and it had a side benefit of making queries more "modular" as well but not to the extent of being able to dynamically set a table name (since it's assumed you already know what the table is going to be).
If you want all rows from PERSON table, here is what you should do:
String select = "SELECT * FROM person";
PreparedStatement ps = connection.prepareStatement(select);
Variable binding does not dynamically bind table names as others mentioned above.
If you have the table name coming in to your method as a variable, you may construct the whole query as below:
String select = "SELECT * FROM " + varTableName;
PreparedStatement ps = connection.prepareStatement(select);
Parameterized queries are for querying field names - not the table name!
Prepared statements are still SQL and need to be constructed with the appropriate where clause; i.e. where x = y. One of their advantages is they are parsed by the RDMS when first seen, rather than every time they are sent, which speeds up subsequent executions of the same query with different bind values.