Actually I am not sure there is a problem but, I have some data and I want to insert it to database.
Code seems to works well, every statement works, there are no errors shown but I cant see the data in database. It doesn't insert the data to database but in code it is shown as data is inserted.
Is there a setting related with database? I am using Sqlite Firefox Add-on.
When selecting records there are no problems. I checked the .sqlite file's permissions but it is setted to read & write.
Sorry, I forgot to add code snippet;
NSString *stmt=[NSString stringWithFormat:#"INSERT INTO t_exam_applies(user_id,exam_id, apply_correct, apply_wrong, apply_empty, apply_start, apply_end) VALUES(%d, %d, %d, %d, %d, %d, %d)", 0,exam_id,0,0,0,0,0];
[stmt UTF8String];
const char *sql=(const char *) [stmt UTF8String];
//const char *sql= "INSERT INTO t_exam_applies(user_id,exam_id, apply_correct, apply_wrong, apply_empty, apply_start, apply_end) VALUES(?,?,?,?,?,?,?)";
sqlite3_stmt *statement;
sqlite3_prepare_v2(database, sql, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE)
NSLog(#"SQLITE statement executed");
else
NSLog(#"ERROR!!");
sqlite3_finalize(statement);
Given that this is tagged for iPhone dev I would recommend using an objective c wrapper for sqlite like FMDB. FMDB is very lightweight and widely used (check it out on github: https://github.com/ccgus/fmdb/) and will simplify this whole process for you.
Here is an example of a simple insert with FMDB:
self.db = [FMDatabase databaseWithPath:fullPath];
if (![db open]) {
NSLog(#"DB Open Failed");
return NO;
}
[db executeUpdate:#"INSERT INTO TestTable (id, value) VALUES (?,?)", [NSNumber numberWithInt:1], #"SomeString"];
[db close];
Maybe you are messaging nil when adding the data (assuming you use Obj-C)? That's not an error, but it also doesn't do anything. More info or code snippet needed for more specific answer.
Related
Using Java 8 and Oracle 11g. Regarding loading XML data from a flat file into an Oracle XMLType field. I can make it work with this code:
private String readAllBytesJava7(String filePath) {
Files files;
Paths paths;
String content;
content = "";
try {
content = new String ( Files.readAllBytes( Paths.get(filePath) ) );
}
catch (IOException e) {
log.error(e);
}
return content;
}
pstmt = oracleConnection.prepareStatement("update MYTABLE set XML_SOURCE = ? where TRANSACTION_NO = ?");
xmlFileAsString = this.readAllBytesJava7(fileTempLocation);
xmlType = XMLType.createXML(oracleConnection, xmlFileAsString);
pstmt.setObject(1,xmlType);
pstmt.setInt(2, ataSpecHeader.id);
pstmt.executeUpdate();
But as you might surmise, that only works for small XML files... Anything too large will cause a memory exception.
What I'd like to do is load the XML file in "chunks" as described here:
https://docs.oracle.com/cd/A97335_02/apps.102/a83724/oralob2.htm
and
https://community.oracle.com/thread/4721
Those posts show how to load a BLOB/CLOB column from a flat-file by "chunks". I can make it work if the column is blob/clob, but I couldn't adapt it for an XMLType column. Most of what I found online in regards to loading an XMLType column deals with using the oracle-directory object or using sql-loader, but I won't be able to use those as my solution. Is there any kind of post/example that someone knows of for how to load an XML file into an XMLType column as "chunks"?
Additional information:
I'm trying to take what I see in the posts for blob/clob and adapt it for XMLType. Here's the issues I'm facing:
sqlXml = oracleConnection.createSQLXML();
pstmt = oracleConnection.prepareStatement("update MYTABLE set
XML_SOURCE = XMLType.createXML('<e/>') where 1=1 and TRANSACTION_NO = ?");
pstmt.setInt(1, ataSpecHeader.id);
pstmt.executeUpdate();
With blob/clob, you start out by setting the blob/clob field to "empty" (so it isn't null)... I'm not sure how to do this with XMLType... the closest I can get it just to set it to some kind of xml as shown above.
The next step is to select the blob/clob field and get the output stream on it. Something like what is shown here:
cmd = "SELECT XML_SOURCE FROM MYTABLE WHERE TRANSACTION_NO = ${ataSpecHeader.id} FOR UPDATE ";
stmt = oracleConnection.createStatement();
rset = stmt.executeQuery(cmd);
rset.next();
xmlType = ((OracleResultSet)rset).getOPAQUE(1);
//clob = ((OracleResultSet)rset).getCLOB(1);
//blob = ((OracleResultSet)rset).getBLOB(1);
clob = xmlType.getClobVal();
//sqlXml = rset.getSQLXML(1);
//outstream = sqlXml.setBinaryStream();
//outstream = blob.getBinaryOutputStream();
outstream = clob.getAsciiOutputStream();
//At this point, read the XML file in "chunks" and write it to the outstream object by doing: outstream.write
The lines that are commented-out are to show the different things I've tried. To re-state... I can make it work fine if the field in the table is a BLOB or CLOB. But I'm not sure what to do if it's an XMLType. I'd like to get an outstream handle to the XMLType field so I can write to it, as I would if it were a BLOB or CLOB. Notice for BLOB/CLOB it selects the blob/clob field with "for update" and then gets an Outstream on it so I can write to it. For XMLType, i tried getting the field to an XMLType java class and SQLXML java class, but it won't work that way. I also tried getting the field first as xmltype/sqlxml and then casting to blob/clob to then get an outstream, but it won't work either. The truth is, I'm not sure what I'm supposed to do in order to be able to write to the XMLType field as a stream/chunks.
Our project recently updated to the newer Oracle.ManagedDataAccess DLL's (v 4.121.2.0) and this error has been cropping up intermittently. We've fixed it a few times without really knowing what we did to fix it.
I'm fairly certain it's caused by CLOB fields being mapped to strings in Entity Framework, and then being selected in LINQ statements that pull entire entities instead of just a limited set of properties.
Error:
Value cannot be null.
Parameter name: byteArray
Stack Trace:
at System.BitConverter.ToString(Byte[] value, Int32 startIndex, Int32 length)
at OracleInternal.TTC.TTCLob.GetLobIdString(Byte[] lobLocator)
at OracleInternal.ServiceObjects.OracleDataReaderImpl.CollectTempLOBsToBeFreed(Int32 rowNumber)
at Oracle.ManagedDataAccess.Client.OracleDataReader.ProcessAnyTempLOBs(Int32 rowNumber)
at Oracle.ManagedDataAccess.Client.OracleDataReader.Read()
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()
Suspect Entity Properties:
'Mapped to Oracle CLOB Column'
<Column("LARGEFIELD")>
Public Property LargeField As String
But I'm confident this is the proper way to map the fields per Oracle's matrix:
ODP.NET Types Overview
There is nothing obviously wrong with the generated SQL statement either:
SELECT
...
"Extent1"."LARGEFIELD" AS "LARGEFIELD",
...
FROM ... "Extent1"
WHERE ...
I have also tried this Fluent code per Ozkan's suggestion, but it does not seem to affect my case.
modelBuilder.Entity(Of [CLASS])().Property(
Function(x) x.LargeField
).IsOptional()
Troubleshooting Update:
After extensive testing, we are quite certain this is actually a bug, not a configuration problem. It appears to be the contents of the CLOB that cause the problem under a very specific set of circumstances. I've cross-posted this on the Oracle Forums, hoping for more information.
After installation of Oracle12 client we ran into the same problem.
In machine.config (C:\Windows\Microsoft.NET\Framework\v4.0.30319\Config) I removed all entries with Oracle.ManagedDataAccess.
In directory C:\Windows\Microsoft.NET\assembly\GAC_MSIL I removed both Oracle.ManagedDataAccess and Policy.4.121.Oracle.ManagedDataAccess.
Then my C# program started working as usually, using the Oracle.ManagedDataAccess dll in it's own directory.
We met this problem in our project an hour ago and found a solution. It is generating this error because of null values in CLOB caolumn. We have a CLOB column and it is Nullable in database. In EntityFramework model it is String but not Nullable. We changed column's Nullable property to True in EF model and it fixed problem.
We have this problem as well on some computers, and we are running the latest Oracle.ManagedDataAccess.dll (4.121.2.20150926 ODAC RELEASE 4).
We found a solution to our problem, and I just wanted to share.
This was our problem that occurred some computers.
Using connection As New OracleConnection(yourConnectionString)
Dim command As New OracleCommand(yourQuery, connection)
connection.Open()
Using reader As OracleDataReader = command.ExecuteReader()
Dim clobField As String = CStr(reader.Item("CLOB_FIELD"))
End Using
connection.Close()
End Using
And here's the solution that made it work on all computers.
Using connection As New OracleConnection(yourConnectionString)
Dim command As New OracleCommand(yourQuery, connection)
connection.Open()
Using reader As OracleDataReader = command.ExecuteReader()
Dim clobField As String = reader.GetOracleClob(0).Value
End Using
connection.Close()
End Using
I spent a lot of time trying to decipher this and found bits of this and that here and there on the internet, but no where had everything in one place, so I'd like to post what I've learned, and how I resolved it, which is much like Ragowit's answer, but I've got the C# code for it.
Background
The error: So I had this error on my while (dr.Read()) line:
Value cannot be null. \r\nParmeter name: byteArray
I ran into very little on the internet about this, except that it was an error with the CLOB field when it was null, and was supposedly fixed in the latest ODAC release, according to this: https://community.oracle.com/thread/3944924
My take on this -- NOT TRUE! It hasn't been updated since October 5, 2015 (http://www.oracle.com/technetwork/topics/dotnet/utilsoft-086879.html), and the 12c package I'm using was downloaded in April 2016.
Full stack trace by someone else with the error that pretty much mirrored mine: http://pastebin.com/24AfFDnq
Value cannot be null.
Parameter name: byteArray
at System.BitConverter.ToString(Byte[] value, Int32 startIndex, Int32 length)
at OracleInternal.TTC.TTCLob.GetLobIdString(Byte[] lobLocator)
at OracleInternal.ServiceObjects.OracleDataReaderImpl.CollectTempLOBsToBeFreed(Int32 rowNumber)
at Oracle.ManagedDataAccess.Client.OracleDataReader.ProcessAnyTempLOBs(Int32 rowNumber)
at Oracle.ManagedDataAccess.Client.OracleDataReader.Read()
at System.Data.Entity.Core.Common.Internal.Materialization.Shaper`1.StoreRead()
'Mapped to Oracle CLOB Column'
<Column("LARGEFIELD")>
Public Property LargeField As String
'Mapped to Oracle BLOB Column'
<Column("IMAGE")>
Public Property FileContents As Byte()
How I encountered it: It was while reading an 11-column table of about 3000 rows. One of the columns was actually an NCLOB (so apparently this is just as susceptible as CLOB), which allowed nulls in the database, and some of its values were empty - it was an optional "Notes" field, after all. It's funny that I didn't get this error on the first or even second row that had an empty Notes field. It didn't error until row 768 finished and it was about to start row 769, according to an int counter variable that started at 0 that I set up and saw after checking how many rows my DataTable had thus far. I found I got the error if I used:
DataSet ds = new DataSet();
OracleDataAdapter adapter = new OracleDataAdapter(cmd);
adapter.Fill(ds);
as well if I used:
DataTable dt = new DataTable();
OracleDataReader dr = cmd.ExecuteReader();
dt.Load(dr);
or if I used:
OracleDataReader dr = cmd.ExecuteReader();
if (dr.HasRows)
{
while (dr.Read())
{
....
}
}
where cmd is the OracleCommand, so it made no difference.
Resolution
The following is basically the code I used to parse through an OracleDataReader values in order to assign them to a DataTable. It's actually not as refined as it could be - I am using it to just return dr[i] to datarow in all cases except when the value is null, and when it is the eleventh column (index = 10, because it starts at 0) and a particular query has been executed so that I know where my NCLOB column is.
public static DataTable GetDataTableManually(string query)
{
OracleConnection conn = null;
try
{
string connString = ConfigurationManager.ConnectionStrings["MyConn"].ConnectionString;
conn = new OracleConnection(connString);
OracleCommand cmd = new OracleCommand(query, conn);
conn.Open();
OracleDataReader dr = cmd.ExecuteReader(CommandBehavior.CloseConnection);
DataTable dtSchema = dr.GetSchemaTable();
DataTable dt = new DataTable();
List<DataColumn> listCols = new List<DataColumn>();
List<DataColumn> listTypes = new List<DataColumn>();
if (dtSchema != null)
{
foreach (DataRow drow in dtSchema.Rows)
{
string columnName = System.Convert.ToString(drow["ColumnName"]);
DataColumn column = new DataColumn(columnName, (Type)(drow["DataType"]));
listCols.Add(column);
listTypes.Add(drow["DataType"].ToString()); // necessary in order to record nulls
dt.Columns.Add(column);
}
}
// Read rows from DataReader and populate the DataTable
if (dr.HasRows)
{
int rowCount = 0;
while (dr.Read())
{
string fieldType = String.Empty;
DataRow dataRow = dt.NewRow();
for (int i = 0; i < dr.FieldCount; i++)
{
if (!dr.IsDBNull[i])
{
fieldType = dr.GetFieldType(i).ToString(); // example only, this is the same as listTypes[i], and neither help us distinguish NCLOB from NVARCHAR2 - both will say System.String
// This is the magic
if (query == "SELECT * FROM Orders" && i == 10)
dataRow[((DataColumn)listCols[i])] = dr.GetOracleClob(i); // <-- our new check!!!!
// Found if you have null Decimal fields, this is
// also needed, and GetOracleDecimal and GetDecimal
// will not help you - only GetFloat does
else if (listTypes[i] == "System.Decimal")
dataRow[((DataColumn)listCols[i])] = dr.GetFloat(i);
else
dataRow[((DataColumn)listCols[i])] = dr[i];
}
else // value was null; we can't always assign dr[i] if DBNull, such as when it is a number or decimal field
{
byte[] nullArray = new byte[0];
switch (listTypes[i])
{
case "System.String": // includes NVARCHAR2, CLOB, NCLOB, etc.
dataRow[((DataColumn)listCols[i])] = String.Empty;
break;
case "System.Decimal":
case "System.Int16": // Boolean
case "System.Int32": // Number
dataRow[((DataColumn)listCols[i])] = 0;
break;
case "System.DateTime":
dataRow[((DataColumn)listCols[i])] = DBNull.Value;
break;
case "System.Byte[]": // Blob
dataRow[((DataColumn)listCols[i])] = nullArray;
break;
default:
dataRow[((DataColumn)listCols[i])] = String.Empty;
break;
}
}
}
dt.Rows.Add(dataRow);
}
ds.Tables.Add(dt);
}
}
catch (Exception ex)
{
// handle error
}
finally
{
conn.Close();
}
// After everything is closed
if (ds.Tables.Count > 0)
return ds.Tables[0]; // there should only be one table if we got results
else
return null;
}
In the same way that I have it assigning specific types of null based on the column type found in the schema table loop, you could add the conditions to the "not null" side of the if...then and do various GetOracle... statements there. I found it was only necessary for this NCLOB instance, though.
To give credit where credit is due, the original codebase is based on the answer given by sarathkumar at Populate data table from data reader .
For me it was simple! I had this error with odac v 4.121.1.0. I have just updated Oracle.ManagedDataAccess to 4.121.2.0 with Nuget and now it is working.
Have you have tried to uninstall and reinstall Oracle.ManagedDataAccess with Nugget?
upgrade Oracle.ManagedDataAccess.dll to version 4.122.1.0 solved.
If you are using vs 2017, we can update via NuGet.
This is a general question and not the usual StackOverflow "can someone please correct my syntax mistake..."
SQL is the most wonderfully condensed language, so in one line I can save a record. e.g.
INSERT INTO database VALUES (4,'Nilsen', 'Johan', 'Bakken 2', 'Stavanger')
But if I want to save/insert a record in Cocoa with SQLite I'll end up writing closer to 14, e.g.
NSString *insertSQL = [NSString stringWithFormat: #"INSERT INTO CONTACTS (name, address, phone) VALUES (\"%#\", \"%#\", \"%#\")", name.text, address.text, phone.text];
const char *insert_stmt = [insertSQL UTF8String];
sqlite3_prepare_v2(contactDB,insert_stmt, -1, &statement, NULL);
if (sqlite3_step(statement) == SQLITE_DONE) {
self.status.text = #"Contact added";
self.name.text = #"";
self.address.text = #"";
self.phone.text = #"";
} else {
self.status.text = #"Failed to add contact";
}
sqlite3_finalize(statement);
sqlite3_close(contactDB);
 }
}
I understand what each line is doing but as a Cocoa/SQLite newbie all I want to double check is if there is a simpler way or if I just have to regurgitate this level of verbosity every time I want to do the tritest SQL command...
Many thanks
Use FMDB (an excellent SQLite wrapper).
As an alternative, you could create a function that accepts both the SQL query and the database name. One great use of functions is to simplify the process of writing code.
This is a bit of subjective question about a specific situation. Main goal for this question for me is to remind my self to code up the solution. However if there is already a solution, or an alternate approach, I would like to know it.
I'm working on a project and I'm using Entity Framework 4 for database access. The database design is something that I don't have control over. The database was designed many years ago, and in my opinion the database design does not fit for the current database purposes. This results in very complicated queries.
This is the first time I'm using Entity Framework in a project, but I have extensive experience in development against MS SQL Server.
What I found myself doing again and again is this:
I write a complex L2E query. The query either slow or returns wrong results
I'm looking at my L2E query and I have absolutely no idea how to improve it
I fire up SQL Profiler and capture the SQL that EF generated from my query
I want to execute part of that sql to identify the part of the query that is giving problems
The query comes through as sp_executesql with a dozen of parameters, because if a parameter is used 3 times in a query, L2E creates 3 parameters and passes to all of them the same value. Same deal with every parameter.
Now I have to extract the SQL from sp_executesql, unescape all escaped apostrophes, and substitute every parameter in the query with its value
After this is done I finally can run parts of the query and pin-point the problem.
I go back to my L2E code, change it to fix the problem I found and the cycle repeats.
To be honest, I'm starting thinking that one should not use an ORM if you don't own database design.
This aside, the process of unescaping the sql and substituting the parameters is the one that I want to automate. The goal is to get 'naked', de-parametrized sql, that I can run in SSMS.
This a very simple example of what I see in the profile and what I want to get in result. My real cases are many times more complex.
The capture:
exec sp_executesql N'SELECT
[Extent1].[ProductName] AS [ProductName]
FROM [dbo].[Products] AS [Extent1]
INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
WHERE ([Extent1].[UnitPrice] > #p__linq__0) AND ([Extent2].[CategoryName] = #p__linq__1) AND (N''Chang'' <> [Extent1].[ProductName])',N'#p__linq__0 decimal(1,0),#p__linq__1 nvarchar(4000)',#p__linq__0=1,#p__linq__1=N'Beverages'
Desired result:
SELECT
[Extent1].[ProductName] AS [ProductName]
FROM [dbo].[Products] AS [Extent1]
INNER JOIN [dbo].[Categories] AS [Extent2] ON [Extent1].[CategoryID] = [Extent2].[CategoryID]
WHERE ([Extent1].[UnitPrice] > 1) AND ([Extent2].[CategoryName] = N'Beverages') AND (N'Chang' <> [Extent1].[ProductName])
I'm just going to write code to convert the likes of first to the likes of second if there is nothing better, I'll post the solution here. But maybe it's already done by someone? Or maybe there is a profiler or something, that can give me sql code I can execute partially in SSMS?
So here is what I ended up with. A couple of notes:
This won't work in 100% of cases, but this is good enough for me
There is a lot to improve in terms of usability. Currently I put a shortcut to the compiled binary on the desktop, cut the text to convert to clipboard, double-click the shortcut and paste the result.
using System;
using System.Text.RegularExpressions;
using System.Windows.Forms;
namespace EFC
{
static class Program
{
[STAThread]
static void Main()
{
try
{
string input = Clipboard.GetText();
const string header = "exec sp_executesql N'";
CheckValidInput(input.StartsWith(header), "Input does not start with {0}", header);
// Find part of the statement that constitutes whatever sp_executesql has to execute
int bodyStartIndex = header.Length;
int bodyEndIndex = FindClosingApostroph(input, bodyStartIndex);
CheckValidInput(bodyEndIndex > 0, "Unable to find closing \"'\" in the body");
string body = input.Substring(bodyStartIndex, bodyEndIndex - bodyStartIndex);
// Unescape 's
body = body.Replace("''", "'");
// Work out where the paramters are
int blobEndIndex = FindClosingApostroph(input, bodyEndIndex + 4);
CheckValidInput(bodyEndIndex > 0, "Unable to find closing \"'\" in the params");
string ps = input.Substring(blobEndIndex);
// Reverse, so that P__linq_2 does not get substituted in p__linq_20
Regex regexEf = new Regex(#"(?<name>#p__linq__(?:\d+))=(?<value>(?:.+?)((?=,#p)|($)))", RegexOptions.RightToLeft);
Regex regexLinqToSql = new Regex(#"(?<name>#p(?:\d+))=(?<value>(?:.+?)((?=,#p)|($)))", RegexOptions.RightToLeft);
MatchCollection mcEf = regexEf.Matches(ps);
MatchCollection mcLinqToSql = regexLinqToSql.Matches(ps);
MatchCollection mc = mcEf.Count > 0 ? mcEf : mcLinqToSql;
// substitutes parameters in the statement with their values
foreach (Match m in mc)
{
string name = m.Groups["name"].Value;
string value = m.Groups["value"].Value;
body = body.Replace(name, value);
}
Clipboard.SetText(body);
MessageBox.Show("Done!", "CEF");
}
catch (ApplicationException ex)
{
MessageBox.Show(ex.Message, "Error");
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "Error");
MessageBox.Show(ex.StackTrace, "Error");
}
}
static int FindClosingApostroph(string input, int bodyStartIndex)
{
for (int i = bodyStartIndex; i < input.Length; i++)
{
if (input[i] == '\'' && i + 1 < input.Length)
{
if (input[i + 1] != '\'')
{
return i;
}
i++;
}
}
return -1;
}
static void CheckValidInput(bool isValid, string message, params object[] args)
{
if (!isValid)
{
throw new ApplicationException(string.Format(message, args));
}
}
}
}
Well,may be this will be helpfull. MSVS 2010 has IntelliTrace. Every time when EF make a query there is an ADO.Net Event with a query
Execute Reader "SELECT TOP (1)
[Extent1].[id] AS [id],
[Extent1].[Sid] AS [Sid],
[Extent1].[Queue] AS [Queue],
[Extent1].[Extension] AS [Extension]
FROM [dbo].[Operators] AS [Extent1]
WHERE [Extent1].[Sid] = #p__linq__0" Command Text = "SELECT TOP (1) \r\n[Extent1].[id] AS [id], \r\n[Extent1].[Sid] AS [Sid], \r\n[Extent1].[Queue] AS [Queue], \r\n[Extent1].[Extension] AS [Extension]\r\nFROM [dbo].[Operators] AS [Extent1]\r\nWHERE [Extent1].[Sid] = #p__linq__0",
Connection String = "Data Source=paris;Initial Catalog=telephony;Integrated Security=True;MultipleActiveResultSets=True"
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.