PreparedStatement in Java on a Fulltext MSSQL Table only takes 3 Arguments - java

Strange behavior.
We have the MSSQL server 2008 R2.
Our Java Servlet is calling a PreparedStatement with the following join.
... inner join Containstable (fulltextTable, mycolumn, ?) as KeyTable on id = KeyTable.[KEY] ...
Where ? = needle
This works fine.
But when i add two more arguments to Containstable like this:
... inner join Containstable (fulltextTable, mycolumn, ?, ?, ?) as KeyTable on id = KeyTable.[KEY] ...
Where ? = needle (Search for), ? = LANGUAGE N'German' (Language), ? = 250 (Limit result to 250 rows)
I get this exception:
java.sql.SQLException: Argument data type nvarchar is invalid for argument 4 of CONTAINSTABLE function.
This works fine in SQL Server Management Studio.
Is this a Problem with the SQLServerDriver in Java?
Cheers
Per
Here is my Code - this works:
if (contains!=null && contains.length()!=0) {
sql.append(" inner join Containstable( myFulltext, myColumn, ?) as KeyTable on id = KeyTable.[KEY]");
elementTypes.add(Types.NVARCHAR);
paramObject.add(contains);
//elementTypes.add(Types.NVARCHAR);
//paramObject.add("LANGUAGE N'German'");
//elementTypes.add(Types.BIGINT);
//paramObject.add(maxResults);
}

I suspect this is because the third 'parameter' to CONTAINSTABLE is not a normal, scalar parameter, it's a keyword (LANGUAGE) followed by a value.
Most likely JDBC is transforming LANGUAGE N'German' into 'LANGUAGE N''German''' and the resulting SQL is this, which is also invalid in SSMS:
...containstable(myTable, myColumn, 'needle', 'language N''German''', 250)
But of course it should be this:
...containstable(myTable, myColumn, 'needle', language N'German', 250)
You can confirm this guess by using SQL Profiler to trace the SQL actually being sent to the server. As for a solution, I don't know if JDBC will accept this:
...containstable(myTable, myColumn, ?, language ?, ?)
If it doesn't, you may have to use dynamic SQL to build a complete SQL string to execute, something like this (my quotes may not be completely correct, but you should get the idea):
set #sql = N'...containstable(myTable, myColumn, ''' + #search term + ''', language ''' + #language + ''', + cast(#rows as nvarchar(10)) + ')'
Unfortunately TSQL syntax is very inconsistent about where parameters are allowed and not, and this appears to be one such case.

Related

Method to get the placement of a player with MySQL Query

I`ve a Problem with a SQL Query. I want to develop a Minecraft Plugin. The Plugin contains a Statistic. I want to create a Ranking in the Statistic. The player who have the most kills is on the first place and so on.. I created a Method which calculate your placement. First the SQL Query sort the tabel and than it looks in which your you are.
PreparedStatement ps = MySQL.getConnection().prepareStatement("SELECT * FROM (SELECT #r := #r+1 as pos, kills. * FROM kills, (SELECT #r := 1) tmp ORDER BY Kills DESC) WHERE UUID = ?");
ps.setString(1, uuid.toString());
ResultSet rs = ps.executeQuery();
The Error is:
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MariaDB server version for the right syntax to use near 'WHERE UUID = '37bb2c2c-e170-469c-a08e-6a22e7d083cd'' at line 1
I dont know what the Error is.
You can't use user variables in a JDBC MySQL query. Here is one workaround for the query:
SELECT *,
1 + (SELECT COUNT(*) FROM kills k2 WHERE k2.Kills > k1.Kills) AS pos
FROM kills k1
WHERE UUID = ?
If you are using MySQL 8+, then analytic functions are a better way to go:
SELECT *, ROW_NUMBER() OVER (ORDER BY Kills DESC) pos
FROM kills
WHERE UUID = ?
You may want to instead use RANK or DENSE_RANK. Note that the first correlated subquery approach is technically returning a rank, not a row number, in the event that two or more records be tied for the number of kills.

Syntax error with upsert while using latest version

I am trying to execute the following query:
INSERT INTO `xguilds_relations` ( `id1`, `id2`, `dominance` ) VALUES ( ?, ?, ? )
ON CONFLICT DO UPDATE SET `dominance` = VALUES(`dominance`);
However, this results in giving me the following syntax error:
SQL error or missing database (near "UPDATE": syntax error)
I have been looking through Google and Stackoverflow for a while and all I found was that UPSERT is only supported since SQLite 3.24.0. However, I am using 3.30.1 and it's still not working.
What I would like to achieve is:
- Insert a new row into xguilds_relations with the provided id's (table contains a CHECK (id1 > id2) or something similar) and the provided dominance
- If a row with the provided id's already exists, update that row with the new dominance value
I don't think SQLite supports VALUES. Does this work?
INSERT INTO xguilds_relations ( id1, id2, dominance )
VALUES ( ?, ?, ? )
ON CONFLICT DO UPDATE SET dominance = excluded.dominance;
You may want to also include the columns that you conflict refers to.
UPSERT works when there is a violation of a UNIQUE constraint and not a CHECK constraint.
Is there a UNIQUE constraint for the combination of id1 and id2? I believe not.
So create it:
CREATE UNIQUE INDEX IF NOT EXISTS un_id1_id2 ON `xguilds_relations` (`id1`, `id2`);
Now write the statement like this:
INSERT INTO `xguilds_relations` ( `id1`, `id2`, `dominance` ) VALUES ( ?, ?, ? )
ON CONFLICT(id1, id2) DO UPDATE SET `dominance` = <value>;
See a simplified demo.

JDBC MSSQL Insert fails with error "near where"

I have a strange problem. I'm executing insert using prepared statement like this:
try (Connection connection = connectionPool.getConnection();
PreparedStatement ps = connection.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) { //TODO: caching of PS
int i = 1;
ParameterMetaData pmd = ps.getParameterMetaData();
...
} catch (SQLException e) {
throw new TGFIOException("Error executing SQL command " + sql, e);
}
Insert statement is like this:
insert into dbo.CurrencyRates(RateDate, CurrencyID, Rate) values ( ?, ?, ? )
Unfortunately it fails with following exception:
com.microsoft.sqlserver.jdbc.SQLServerException: com.microsoft.sqlserver.jdbc.SQLServerException: Incorrect syntax near the keyword 'WHERE'.
at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDriverError(SQLServerException.java:190)
at com.microsoft.sqlserver.jdbc.SQLServerParameterMetaData.<init>(SQLServerParameterMetaData.java:426)
at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.getParameterMetaData(SQLServerPreparedStatement.java:1532)
at com.jolbox.bonecp.PreparedStatementHandle.getParameterMetaData(PreparedStatementHandle.java:246)
There is no WHERE in the statement, so I am puzzled why it fails on metadata extraction...
EDIT:
SQL Server = 10.50.2500.0 Express Edition,
Driver = sqljdbc4.jar from 4.0 package
Also, I am using getParameterMetaData because I need to set some params to null and the preferred method is to use setNull() where you need SQLType.
EDIT2:
I've tested with Driver sqljdbc41 from newest 6.0 package - results are the same
EDIT3:
I've removed call to getParameterMetaData() and it worked, unfortunately it is a generic part that should max portable, yet it does not work with this single table (inserts to other tables on the same database works fine !!!)
EDIT4:
I've tried with different insert statements for this table and all of them works fine if I skip ps.getParameterMetaData() and fail when I call it. If I try with 2 or more params I get usual near WHERE error. If I try one column insert I get an error stating that the column name is incorrect, even if it is correct and without the meta data call it works perfectly fine. I will try to trace what driver tries to do underneath...
After some tracing on what actually the driver does (many thanks a_horse_with_no_name), I've come to some funny conclusion.
The solution for my question is to:
Replace following insert statement
INSERT INTO CurrencyRates(RateDate, CurrencyID, Rate) VALUES ( ?, ?, ? )
With this statement
INSERT INTO CurrencyRates (RateDate, CurrencyID, Rate) VALUES ( ?, ?, ? )
Logic behind that is that SQL driver does some metadata extraction in the background, and it creates a query with following fragment: ... FROM CurrencyRates(RateDate WHERE ... if you do not put space after table name, yet for the ordinary call this is perfectly possible!
EDIT:
This is obviously an inconsistency as (putting aside what actually is a valid insert) it should consistently accept or reject this query no matter if I call for meta data or not.

using java variable in sql statement

I'm trying to build a web page to better learn Java and SQL. My question is, is there a way in Java to make a generic SQL select statement? For example:
SELECT var1 FROM var2 WHERE var3=var4
or something of the sort.
My idea is to fill the vars with user selected items from the web page. I know this can be done in PHP using the Post method, but I'm not using PHP. Also, I've read about the Prepared Statement in Java, but seems only to work when the used after the comparison operator; ex:
SELECT * FROM table Where attr = ? &
Also, I do know i can do the hard coded version of "SELECT " + var1 + "FROM " + var2 + "WHERE attr = " + var3 + " " but that doesn't seem very generic and prone to a lot of errors.
Incase: I'm trying to build this test page using HTML & JSP.
What you are doing with the ? is parameterizing the query. The query can only be parameterized for values not names of tables or columns.
Every time you run a query. The database has to create a query plan. If you are running the same query again and again, you can reduce this overhead by creating a PreparedStatement.
The first execution of PreparedStatement will generate the query plan. The subsequent executions will reuse the same plan.
Same query here means, it is identical in all respects except values used in where clause, expressions etc.
If you change the Column or Table name or modify the structure of the query, then it is a different query and will require a different query plan. A PreparedStement is not useful in this case and you should stick to the hardcoded version you talked about. Because of this reason you will get an error if you try to parameterize Table or Column names in PreparedStement.
Having said that. It is not advisable to take such a generic approach for queries. If your queries are that simple, you can benefit from ORM tools. You would not have to maintain even a line of SQL. For complex queries you have an option of using ORM specific query language or JPQL or Native SQL. Look for JPA + Hibernate
Your specific usage is not permitted by JDBC. You need to hard code the table name when creating the prepared statement. If you really do want to do that I suggest you use String concatenation to create the SQL statements and then create a PreparedStatement with parameters to handle the where part. In case you are wondering why bother with PreparedStatements in the specific solution, it's to avoid SQL injection.
You can use PreparedStatement to achive your objective.
For example -
String query = "SELECT * FROM table Where attr = ?";
PreparedStatement pt = con.prepareStatement(query);
pt.setString(1, attribete);
pt.executeUpdate();
There is no such direct provision in any of SQL packaged classes or others to replace table, column names along with query parameter values, in a query string, using a single method.
You require to depend on both PreparedStatement and any of String methods replace(...) and replaceFirst(...) to achieve your requirement.
String sql = "Select $1, $2 from $3 where $4=? and $5=?";
sql = sql.replaceFirst( "$1", "col1_name" );
sql = sql.replaceFirst( "$2", "col2_name" );
sql = sql.replaceFirst( "$3", "table_name" );
sql = sql.replaceFirst( "$4", "col4_name" );
sql = sql.replaceFirst( "$5", "col5_name" );
// .. and so on
PreparedStatement pst = con.prepareStatement( sql );
// use relevant set methods to set the query parametrs.
pst.setXXX( 1, value_for_first_query_parameter ); // from a variable or literal
pst.setXXX( 2, value_for_second_query_parameter); // from a variable or literal
// ... and so on
If you are using JDBC, can try this
PreparedStatement statement = connection.prepareStatement("SELECT ? FROM ? WHERE ?=? ");
then
statement.setString(1, "column_name");
statement.setString(2, "table_name");
statement.setString(3, "column_name");
statement.setBigDecimal(4, 123);
If you are using other ORM like Hibernate or JPA, I believe there are also ways to do.

Error with simple Parameterized Query - Java/ SQL

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.

Categories

Resources