How to use format string with SQL query by resources - java

I have some string const inside code. Can I put all this into resources strings.xml? I assume that the possibility of separate use of column names will remain. It is necessary to search for the index of posts in future requests to the database. Not to use the indexes the numbers is confusing, uncomfortable.
private static final String NAME_DB = "cars.db";
private static final String TABLE_NAME = "cars";
private static final String COL_BRAND = "brand";
private static final String COL_MODEL = "model";
private static final String COL_COLOR = "color";
private static final String COL_MAX_SPEED = "max_speed";
private static final String COL_ENGINE_POWER = "engine_power";
private static final String CREATE_TABLE =
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + " (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
COL_BRAND + " TEXT NOT NULL, " +
COL_MODEL + " TEXT NOT NULL, " +
COL_COLOR + " TEXT, " +
COL_MAX_SPEED + " INTEGER," +
COL_ENGINE_POWER + " INTEGER)";
private static final String INSERT_DEFAULT_VALUES =
"INSERT INTO " + TABLE_NAME + "(brand, model, color, max_speed, engine_power) VALUES" +
"('Audi', 'Q7 II', 'White', 233, 252)," +
"('Acura', 'ZDX', 'Black', 241, 300)," +
"('BMW', 'X1 I', 'Blue', 205, 204)," +
"('Hyundai', 'Solaris', 'Red', 100, 100)," +
"('Hyundai', 'Tucson', 'Gray', 150, 177)," +
"('BMW', '8 G14', 'Black', 250, 320)";
private static final String SELECT_ALL = "SELECT * FROM " + TABLE_NAME;
private static final String SELECT_TOP_1 = "SELECT * FROM " + TABLE_NAME + " LIMIT 1";
Example code
DatabaseAdapter(Context context) {
db = context.openOrCreateDatabase(NAME_DB, MODE_PRIVATE, null);
db.execSQL(CREATE_TABLE);
Cursor cursor = db.rawQuery(SELECT_TOP_1, null);
if (!cursor.moveToFirst()) {
db.execSQL(INSERT_DEFAULT_VALUES);
}
}
String[] selectAll() {
Cursor cursor = db.rawQuery(SELECT_ALL, null);
if (!cursor.moveToFirst()) {
return null;
}
ArrayList<String> list = new ArrayList<>();
while (cursor.moveToNext()) {
list.add(
cursor.getString(cursor.getColumnIndex(COL_BRAND)) + " " +
cursor.getString(cursor.getColumnIndex(COL_MODEL)) + " " +
cursor.getString(cursor.getColumnIndex(COL_COLOR)) + " " +
cursor.getString(cursor.getColumnIndex(COL_MAX_SPEED)) + " " +
cursor.getString(cursor.getColumnIndex(COL_ENGINE_POWER))
);
}
return list.toArray(new String[0]);
}

There are some solutions with SQL in XML. Look if you can use one of those.
However IMHO it is better to centralize the code and keep text there, without constants even.
Separate concerns: database query and resulting object. This can be done by providing a Stream of a low-level result set object, that using classes can map to their own classes.
Decouples classes; keeps the layers separate. (Maybe less interesting on Android.)
The using code becomes nicely compact, the queries being elsewhere.
My arguments:
Less back and forth jumping to different sources
Better readability
Column names do not change
Capitals of constants are less readable
Keeping the code together prevents similar queries to be spread around unattended
However it still is better than SQL in the higher level business code, to isolate SQL in XML to have smaller source, queries elsewhere. So at least do that.
Java - Storing SQL statements in an external file

Related

SQLite database "No such column" error on verified existing column [duplicate]

This question already has an answer here:
android.database.sqlite.SQLiteException: no such column or name
(1 answer)
Closed 3 months ago.
I am stuck on an "SQLiteException: no such column:" error. I am trying to make some code to check if an item exists in the database before storing it, I don't know if it's the best way or not but it does the job. Or not really, when I use all numbers for the data in the particular column that I'm searching it works fine. But if there is any letter in the column data it crashes.
The MainActivity
private ConversationsDatabaseHelper db;
//onCreate stuff here
db = new ConversationsDatabaseHelper(this);
String threadId = "886";
Log.d(TAG, "dbTest: EXISTS; " + db.conversationExists(threadId));
and in the databaseHelper class is the converstionExists function
public boolean conversationExists(String threadId) {
// Select All Query
String selectQuery = "SELECT * FROM " + Conversations.TABLE_NAME + " WHERE " +
Conversations.COLUMN_THREAD_ID + " LIKE " + threadId;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
boolean returnValue = false;
// Check if this message exists
if (cursor.moveToFirst()) {
returnValue = true;
}
// close db connection
db.close();
return returnValue;
}
So if I use for example "886" as the threadId value then all is fine. If I create a row with matching threadId then it returns true. In this case I did not so hence false.
stack trace...
dbTest: EXISTS; false
but using a886 results in
Caused by: android.database.sqlite.SQLiteException: no such column: a886 (code 1 SQLITE_ERROR): , while compiling: SELECT * FROM conversations WHERE thread_id LIKE a886
and 88a6 results in this
Caused by: android.database.sqlite.SQLiteException: unrecognized token: "88a6" (code 1 SQLITE_ERROR): , while compiling: SELECT * FROM conversations WHERE thread_id LIKE 88a6
It almost looks like mixing letters and numbers might be part of the reason here but should not be as the column was created to hold TEXT datatype. here is the database create table query string.
public static final String TABLE_NAME = "conversations";
public static final String COLUMN_ID = "id";
public static final String COLUMN__ID = "_id";
public static final String COLUMN_GROUP_ID = "group_id";
public static final String COLUMN_LAST_MESSAGE_ID = "last_message_id";
public static final String COLUMN_THREAD_ID = "thread_id";
public static final String COLUMN_ADDRESS = "address";
public static final String COLUMN_CONTACT = "contact";
public static final String COLUMN_BODY = "body";
public static final String COLUMN_DATE = "date";
public static final String COLUMN_TYPE = "type";
public static final String COLUMN_STATE = "state";
public static final String COLUMN_READ = "read";
public static final String COLUMN_STATUS = "status";
public static final String COLUMN_CT = "ct";
// Create table SQL query
public static final String CREATE_TABLE =
"CREATE TABLE " + TABLE_NAME + "("
+ COLUMN_ID + " INTEGER PRIMARY KEY ,"
+ COLUMN_LAST_MESSAGE_ID + " TEXT,"
+ COLUMN__ID + " TEXT,"
+ COLUMN_GROUP_ID + " TEXT,"
+ COLUMN_THREAD_ID + " TEXT,"
+ COLUMN_ADDRESS + " TEXT,"
+ COLUMN_CONTACT + " TEXT,"
+ COLUMN_BODY + " TEXT,"
+ COLUMN_DATE + " TEXT,"
+ COLUMN_TYPE + " TEXT,"
+ COLUMN_STATE + " TEXT,"
+ COLUMN_READ + " TEXT,"
+ COLUMN_STATUS + " TEXT,"
+ COLUMN_CT + " TEXT"
+ ")";
I am "up a creek" with this and any help would be greatly appreciated.
The value a886 should be a text/string literal not a numeric literal. Therefore it should be enclosed in single quotes. The errors are because:-
a886 fails with no column found as it's taken to be a column name as it's not a literal.
whilst 88a6 is first not a valid literal (due to the a) and therefore a column name but then an invalid column (cannot start with a numeric unless enclosed) name and thus not a known token.
See Literal Values (Constants)
You could fix this using (enclosing the threadId in single quotes ) :-
String selectQuery = "SELECT * FROM " + Conversations.TABLE_NAME + " WHERE " +
Conversations.COLUMN_THREAD_ID + " LIKE '" + threadId + "'";
However, it is recommended to use bound parameters to protect against SQL Injection.
Thus it would be recommended to use :-
String selectQuery = "SELECT * FROM " + Conversations.TABLE_NAME + " WHERE " +
Conversations.COLUMN_THREAD_ID + " LIKE ?";
along with :-
Cursor cursor = db.rawQuery(selectQuery, new String[]{threadId});
i.e. the ? is replaced by the threadId value properly enclosed.
You may wish to consider using the convenience query method rather than rawQuery, this would be :-
Cursor cursor = db.query(Conversations.TABLE_NAME,null,Conversations.COLUMN_THREAD_ID + " LIKE ?", new String[]{theadId},null,null, null);
The SQL is built for you.
It almost looks like mixing letters and numbers might be part of the reason here but should not be as the column was created to hold TEXT datatype.
SQlite has no issue storing any value in any type. The type, which itself can be virtually anything (rules are used to determine the resultant type), is only an indication of the value that will be stored. The only exception is that a rowid or an alias of the rowid MUST be an integer value (your id column is an alias of the rowid column).

concat string without "exit" from the string

i use java from a lot of time but i never found a better "graphical" way for concat strings, when i write SQL query i have a hard to read query for all the concats i made (i use a java class for the name values of all the columns of the db) something like
public static class DBMetaData {
static class LISTINO_TABLE {
static final String TABLE_NAME = "listino";
static final String ID = "_ID";
static final String PRODUCT_NAME_KEY = "nome_prodotto";
static final String CODE_KEY = "codice";
static final String PRICE_KEY = "prezzo";
static final String ENABLE_KEY = "enable";
static final String PREZZO_VENDITA_KEY = "prezzo_vendita";
static final String RICAVO_KEY = "ricavo";
static final String NUMERO_COLLI_KEY = "num_colli";
static final String TIPO_COLLO_KEY = "tipo_collo";
static final String DATA_INSERIMENTO_KEY = "data_inserimento";
}
and when i write the query i have query like that
String sql = "select *,lo." + DBMetaData.LISTINO_ORDINE_TABLE.QUANTITY_KEY + " as quant from " + DBMetaData.LISTINO_ORDINE_TABLE.TABLE_NAME +
" lo left join "+DBMetaData.OFFERTE_TABLE.TABLE_NAME + " o on lo."+ DBMetaData.LISTINO_ORDINE_TABLE.CODICE_ID + " = o." + DBMetaData.OFFERTE_TABLE.CODICE_ARTICOLO_KEY +
" left join "+DBMetaData.LISTINO_TABLE.TABLE_NAME + " l on lo."+DBMetaData.LISTINO_ORDINE_TABLE.CODICE_ID + "= l."+DBMetaData.LISTINO_TABLE.CODE_KEY +
" left join " + DBMetaData.INVENTARIO_TABLE.TABLE_NAME + " i on lo."+DBMetaData.LISTINO_ORDINE_TABLE.CODICE_ID + "= i."+DBMetaData.INVENTARIO_TABLE.CODE_KEY +
" where "+DBMetaData.LISTINO_ORDINE_TABLE.NUMBER_KEY + " = 0 order by "+DBMetaData.LISTINO_ORDINE_TABLE.QUANTITY_KEY + " DESC";
there is a better way for write the query? i know i can just write the query without use my DBMetaData, but if i wanna edit in the future something i have to re-edit all my sqls and the use of the class is a practice i learnt when i started with android and i'm using it but i don't like it :-)
any tips? tnx
You could use String.format to remove the need for all the string concatenation.
For example:
String sql = String.format("select * from %s", TABLE_NAME);
You could also use Apache Commons Text's StringSubstitutor.
final Map<String, String> valuesMap = new HashMap<>();
valuesMap.put("tableName", "Table");
//etc...
final String string = "select * from ${tableName}";
final String sql = new StringSubstitutor(valuesMap).replace(string);
//"select * from Table"

Java Slow SQL retrieval on BeanPropertyRowMapper to ArrayList<accountQueryResult>

we have a very slow Java API function in SpringBoot framework, and follows is a profiling output of it:
We believe this is coming from this line of code:
The query before that line, retrieves 41,573 rows (given specified accountId and one more parameter) - this is done in acceptable time of about 2 secs.
return (ArrayList) this.pbaJdbc.query(sql, new Object[] { accountId, taxRegId }, bpm);
We tried to set the FetchSize to 1000 (it was set to -1 by default which I was unable to determine the meaning of in documentation) - this change did not improve the performance of the operation noticeably.
One other suggestion was to switch to a HashMap structure instead of ArrayList, but I am not sure how to do that... tried to write a hash map, something like:
HashMap<Integer, accountQueryResult> accmap = new HashMap(Integer, accountQueryResult);
But this errors with Expression expected, I am not sure how to do it right.
This is the relevant piece of code for your review:
#Repository
public class UserRepository {
#Autowired
#Qualifier("pbaJdbc")
private JdbcTemplate pbaJdbc;
public ArrayList<accountQueryResult> getAccountsSubscriptionsResources(int accountId, String taxRegId) {
BeanPropertyRowMapper bpm = new BeanPropertyRowMapper(accountQueryResult.class);
bpm.setPrimitivesDefaultedForNullValue(true);
String sql =
"SELECT " +
"\"Account\".\"AccountID\",\n" +
"\"Account\".\"VendorAccountID\",\n" +
"\"Account\".\"AdminPhAreaCode\",\n" +
"\"Account\".\"AdminPhNumber\",\n" +
"\"Account\".\"AdminFaxAreaCode\",\n" +
"\"Account\".\"AdminFaxNumber\",\n" +
"\"Account\".\"AdminEmail\",\n" +
"\"Account\".\"PersPhAreaCode\",\n" +
"\"Account\".\"PersPhNumber\",\n" +
"\"Account\".\"PersFaxAreaCode\",\n" +
"\"Account\".\"PersFaxNumber\",\n" +
"\"Account\".\"PersEmail\",\n" +
"\"Account\".\"TaxStatus\",\n" +
"\"Account\".\"CompanyName\",\n" +
"\"Account\".\"Address1\",\n" +
"\"Account\".\"Address2\",\n" +
"\"Account\".\"City\",\n" +
"\"Account\".\"Zip\",\n" +
"\"ActivePaytool\".\"CutNumber\",\n" +
"\"ActivePaytool\".\"PaySystem\",\n" +
"\"Subscription\".\"subscriptionID\",\n" +
"\"Subscription\".\"SubscriptionName\",\n" +
"\"Subscription\".\"Status\",\n" +
"\"Subscription\".\"PlanID\",\n" +
"\"PlanPeriod\".\"Period\",\n" +
"\"PlanPeriod\".\"PlanPeriodID\",\n" +
"\"PlanPeriod\".\"PeriodType\",\n" +
"\"PlanPeriod\".\"RenewalFee\",\n" +
"\"PlanPeriod\".\"SetupFee\",\n" +
"\"SubscrParam\".\"resourceID\",\n" +
"\"BMResource\".\"name\" AS \"ResourceName\",\n" +
"\"SubscrParam\".\"IncludedValue\",\n" +
"\"SubscrParam\".\"Amount\",\n" +
"\"SubscrParamValue\".\"IdParameter\",\n" +
"\"SubscrParamValue\".\"Value\",\n" +
"\"IntUsers\".\"UsersID\",\n" +
"\"IntUsers\".\"Login\" AS \"LoginID\"\n" +
"FROM\n" +
"\"Account\"\n" +
"LEFT JOIN \"IntUsers\" ON \"IntUsers\".\"AccountID\" = \"Account\".\"AccountID\"\n" +
"LEFT JOIN \"Subscription\" ON \"Subscription\".\"AccountID\" = \"Account\".\"AccountID\"\n" +
"AND \"Subscription\".\"Status\" IN ('30', '40', '15')\n" +
"LEFT JOIN \"SubscrParam\" ON \"SubscrParam\".\"subscriptionID\" = \"Subscription\".\"subscriptionID\"\n" +
"LEFT JOIN \"BMResource\" ON \"BMResource\".\"resourceID\" = \"SubscrParam\".\"resourceID\"\n" +
"LEFT JOIN \"SubscrParamValue\" ON \"SubscrParamValue\".\"subscriptionID\" = \"Subscription\".\"subscriptionID\"\n" +
"LEFT JOIN \"PlanPeriod\" ON \"PlanPeriod\".\"PlanID\" = \"Subscription\".\"PlanID\"\n" +
"AND NOT (\n" +
"\"Subscription\".\"Period\" = \"PlanPeriod\".\"Period\"\n" +
"AND \"Subscription\".\"PeriodType\" = \"PlanPeriod\".\"PeriodType\"\n" +
")\n" +
"AND \"PlanPeriod\".\"Enabled\" = 1\n" +
"AND \"PlanPeriod\".\"Trial\" = 0\n" +
"LEFT JOIN (\n" +
"SELECT\n" +
" \"DefPayTool\".\"PayToolID\",\n" +
" \"PayTool\".\"CutNumber\",\n" +
" \"PayTool\".\"PaySystem\",\n" +
" \"PayTool\".\"OwnerAccountID\"\n" +
"FROM\n" +
" \"PayTool\"\n" +
"INNER JOIN \"DefPayTool\" ON \"DefPayTool\".\"AccountID\" = \"PayTool\".\"OwnerAccountID\"\n" +
"WHERE\n" +
" \"PayTool\".\"IsSuspended\" = 0\n" +
"GROUP BY\n" +
" \"PayTool\".\"PayToolID\",\n" +
" \"PayTool\".\"CutNumber\",\n" +
" \"PayTool\".\"PaySystem\",\n" +
" \"PayTool\".\"OwnerAccountID\",\n" +
" \"DefPayTool\".\"PayToolID\"\n" +
"HAVING\n" +
" COUNT (*) > 0\n" +
") AS \"ActivePaytool\" ON \"ActivePaytool\".\"OwnerAccountID\" = \"Account\".\"AccountID\"\n" +
"WHERE\n" +
"\"Account\".\"AccountID\" = ? " +
"AND \"Account\".\"TaxRegID\" = ? " +
"ORDER BY\n" +
"\"Account\".\"AccountID\",\n" +
"\"Subscription\".\"subscriptionID\";";
return (ArrayList) this.pbaJdbc.query(sql, new Object[] { accountId, taxRegId }, bpm);
}
Follows is the accountQueryResult class which is currently used as mapper.
package com.store.models.query;
public class accountQueryResult {
private int AccountID;
private int VendorAccountID;
private String CompanyName;
private String AdminPhAreaCode;
private String AdminPhNumber;
private String AdminFaxAreaCode;
private String AdminFaxNumber;
private String AdminEmail;
private String PersPhAreaCode;
private String PersPhNumber;
private String PersFaxAreaCode;
private String PersFaxNumber;
private String PersEmail;
private int TaxStatus;
private String CutNumber;
private String PaySystem;
private int subscriptionID;
private String SubscriptionName;
private int Status;
private int PlanID;
private int resourceID;
private String ResourceName;
private int IncludedValue;
private int Amount;
private String IdParameter;
private String Value;
private int UsersID;
private String LoginID;
private String Address1;
private String Address2;
private String City;
private String Zip;
private int period;
private int planPeriodID;
private int periodType;
private double renewalFee;
private double setupFee;
// Ommited getter and setters
}
Please assist, how to change this one of the suggestions was using JPA, preferably with minimal changes as to avoid having to change all the business logic that processes the resulting array later..
Thanks!
The first thing when tackling a performance question like this is to find out where the time is spent.
Given your profilers information, it strikes me as odd that getting the ints seems to take so much longer than getting the other fields. I see following possible reasons:
the getInt does some expensive conversion, e.g. the JDDBC driver might return BigDecimal which then have to get converted to int.
it is just the first column to get accessed and actually triggers fetching and spends lot's of time waiting.
For the overall performance another problem might be the use of reflection by BeanPropertyRowMapper but this doesn't even show up in the profiler so far.
What I consider more likely is that the statement might return after 2 seconds but actually takes much longer to fetch all the results.
To clarify the situation I'd recommend testing the following:
Inspect the ResultSet returned by the JDBC driver for the datatypes it actually contains and check if you can get something more suitable out of it by changing column types or by casting in the SQL statement.
Obtain all the columns from all the rows without doing anything expensive with them. Just make sure the optimizer doesn't get rid of the access. For example you might want to create a hash from each value and add all hashes and print that at the end. Check the performance of that.

Android unable to write datetime to sqllite database

I'm using the Google Tasks API in my app. One of the fields in my app "due date" requires a DateTime object. Im using the Android datepicker dailog and an EditText view to capture the due date and then converting the user input into the datetime format. When I try to write to my SQLite database I get the following exception.
Error inserting due=2015-06-14T15:58:38.572-04:00 title=Test app _id=TaskId0.07429873487580996 status=needsAction notes=Test write
android.database.sqlite.SQLiteException: table tasks has no column named due (code 1): , while compiling: INSERT INTO tasks(due,title,_id,status,notes) VALUES (?,?,?,?,?)
which is confusing because I do define a column named "due". Further up in the logs there is another exception.
06-14 15:58:38.607 15395-15395/com.github.idclark.forgetmenot E/EDITFRAGMENT﹕ null
java.text.ParseException: Unparseable date: "06/14/15" (at offset 8)
at java.text.DateFormat.parse(DateFormat.java:579)
at com.github.idclark.forgetmenot.EditFragment.getTaskDueDate(EditFragment.java:64)
The Schema is defined as
public static final class TaskEntry implements BaseColumns {
public static final String TABLE_NAME = "tasks";
public static final String COLUMN_TASK_ID = "_id";
public static final String COLUMN_TASK_TITLE = "title";
public static final String COLUMN_TASK_UPDATED = "updated";
public static final String COLUMN_TASK_SELFLINK = "selfLink";
public static final String COLUMN_TASK_PARENT = "parent";
public static final String COLUMN_TASK_POSITION = "position";
public static final String COLUMN_TASK_NOTES = "notes";
public static final String COLUMN_TASK_STATUS = "status";
public static final String COLUMN_TASK_DUE = "due";
public static final String COLUMN_TASK_COMPLETED = "completed";
public static final String COLUMN_TASK_DELETED = "deleted";
public static final String COLUMN_TASK_HIDDEN = "hidden";
so the "due" column does exist. And the table is created by
#Override
public void onCreate(SQLiteDatabase db) {
//status must be either needsAction or completed
final String CREATE_TASK_TABLE =
"CREATE TABLE " + TaskEntry.TABLE_NAME + " (" +
TaskEntry._ID + " INTEGER PRIMARY KEY," +
TaskEntry.COLUMN_TASK_ID + "TEXT NOT NULL, " +
TaskEntry.COLUMN_TASK_TITLE + "TEXT NOT NULL, " +
TaskEntry.COLUMN_TASK_COMPLETED + "TEXT, " +
TaskEntry.COLUMN_TASK_NOTES + "TEXT, " +
TaskEntry.COLUMN_TASK_STATUS + "TEXT NOT NULL, " +
TaskEntry.COLUMN_TASK_DUE + "DATETIME, " +
TaskEntry.COLUMN_TASK_UPDATED + "DATETIME, " +
TaskEntry.COLUMN_TASK_PARENT + "TEXT, " +
TaskEntry.COLUMN_TASK_DELETED + "BOOLEAN, " +
TaskEntry.COLUMN_TASK_SELFLINK + "TEXT, " +
TaskEntry.COLUMN_TASK_POSITION + "TEXT, " +
TaskEntry.COLUMN_TASK_HIDDEN + "TEXT" + ")";
db.execSQL(CREATE_TASK_TABLE);
}
The due date conversion takes place in this method.
public DateTime getTaskDueDate() {
mDueDate = (EditText) getActivity().findViewById(R.id.task_due_date);
return new DateTime(mDueDate.getText().toString());
}
The data is then finally written to the database with
public boolean insertRow(Task task) {
ContentValues values = new ContentValues();
values.put(TaskContract.TaskEntry.COLUMN_TASK_STATUS, task.getStatus());
values.put(TaskContract.TaskEntry.COLUMN_TASK_ID,task.getId());
values.put(TaskContract.TaskEntry.COLUMN_TASK_TITLE,task.getTitle());
values.put(TaskContract.TaskEntry.COLUMN_TASK_DUE, task.getDue().toString());
values.put(TaskContract.TaskEntry.COLUMN_TASK_NOTES, task.getNotes());
SQLiteDatabase db = this.getWritableDatabase();
boolean createSuccessful = db.insert(TaskContract.TaskEntry.TABLE_NAME, null, values) > 0;
db.close();
return createSuccessful;
}
I'm confused as to why this write fails, and sql claims that there is no "due" column. Even though there is a parse exception, I see a timestamp in the logs as well. Is part of the problem that i'm calling .toString() on the datetime object before writing it to the db? I don't have a lot of sql experience and am genuinely confused as to what to make of these exceptions.
For the sqlite issue, you need to have whitespace between column names and types. For example, change
TaskEntry.COLUMN_TASK_DUE + "DATETIME, " +
to
TaskEntry.COLUMN_TASK_DUE + " DATETIME, " +
There's a similar problem with almost all of your columns.
After fixing the CREATE TABLE SQL, uninstall and reinstall your app to recreate the database.
For the date parsing problem, use a date format that matches your data. If you need detailed help with it, post a new question.

Sqlite : Unique makes my database go crazy

I'm having an issue with my app.
What my app does is this : gets some data from a couple of edittexts(3 per row,created dynamically) and puts them in a database .
What i want the database to do is this : take the product name,the quantity and the price and put them in the table.The name should be UNIQUE(it will be used to power an autocomplete,it needs to be unique not to have duplicates in the AC list).The price in the database must be the last price inserted for that product(for example,if Cheese at 3$ is inserted and after that Cheese at 2.5$ in the database we will find 2.5$).The quantity has to be summed up(if i enter Cheese in quantity 3 and then again Cheese in quantity 4 in the database we will find 7).
Now,my issue : Lets say i enter this in my shopping list :
1. Hhhh 4 2.5
2. Dddd 3 1
3. Eeee 2 2
4. Aaaa 5 3.5
In my database I will find this :
4. Aaaa 4 2.5
2. Dddd 3 1
3. Eeee 2 2
1. Hhhh 5 3.5
So,the issue is that it arranges the product name column alphabetically but the other columns remain in the same order,the one i entered in the edittexts.
I did some tests,if i remove the UNIQUE from the product name column,it will enter it as it should but of course,it will create duplicates,which i don't need.I don't get it,what's wrong ? why does UNIQUE trigger this ?
Here's my code :
My table creation :
public class SQLiteCountryAssistant extends SQLiteOpenHelper {
private static final String DB_NAME = "usingsqlite.db";
private static final int DB_VERSION_NUMBER = 1;
private static final String DB_TABLE_NAME = "countries";
private static final String DB_COLUMN_1_NAME = "country_name";
private static final String DB_COLUMN_2_NAME = "country_counter";
private static final String DB_COLUMN_3_NAME = "country_price";
private static final String DB_CREATE_SCRIPT = "create table "
+ DB_TABLE_NAME
+ " (_id INTEGER PRIMARY KEY,country_name text unique, country_quantity REAL DEFAULT '0',country_price REAL);) ";
private SQLiteDatabase sqliteDBInstance = null;
public SQLiteCountryAssistant(Context context) {
super(context, DB_NAME, null, DB_VERSION_NUMBER);
}
#Override
public void onCreate(SQLiteDatabase sqliteDBInstance) {
Log.i("onCreate", "Creating the database...");
sqliteDBInstance.execSQL(DB_CREATE_SCRIPT);
}
My insert method :
public void insertCountry(String countryName, String countryPrice,
String countryQuantity) {
sqliteDBInstance.execSQL("INSERT OR IGNORE INTO " + DB_TABLE_NAME
+ "(country_name, country_quantity, country_price) VALUES('"
+ countryName + "','0', '" + countryPrice + "')");
sqliteDBInstance.execSQL("UPDATE " + DB_TABLE_NAME
+ " SET country_name='" + countryName
+ "', country_quantity=country_quantity+'" + countryQuantity
+ "' WHERE country_name='" + countryName + "';");
sqliteDBInstance.execSQL("UPDATE " + DB_TABLE_NAME
+ " SET country_name='" + countryName + "', country_price='"
+ countryPrice + "' WHERE country_name='" + countryName + "';");
}
And this is how i call the insert method :
for (int g = 0; g < allcant.size() - 1; g++) {
if (prod[g] != "0.0") {
sqlliteCountryAssistant.insertCountry(prod[g],pret[g],cant[g]);
}
Also,please excuse my messy code,i've started learning android with no programming background like a month ago.I just got my bachelors degree in Sociology so yea,i'm an absolute beginner.If there is way to do it better then i did and i'm pretty sure there is,please,show me the way,heh.
Thanks and have a good day !
EDIT : Aaaand the whole db class :
public class SQLiteCountryAssistant extends SQLiteOpenHelper {
private static final String DB_NAME = "usingsqlite.db";
private static final int DB_VERSION_NUMBER = 1;
private static final String DB_TABLE_NAME = "countries";
private static final String DB_COLUMN_1_NAME = "country_name";
private static final String DB_COLUMN_2_NAME = "country_counter";
private static final String DB_COLUMN_3_NAME = "country_price";
private static final String DB_CREATE_SCRIPT = "create table "
+ DB_TABLE_NAME
+ " ( id INTEGER PRIMARY KEY AUTOINCREMENT,country_name text unique, country_quantity REAL DEFAULT '0',country_price REAL)";
private SQLiteDatabase sqliteDBInstance = null;
public SQLiteCountryAssistant(Context context) {
super(context, DB_NAME, null, DB_VERSION_NUMBER);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO: Implement onUpgrade
}
#Override
public void onCreate(SQLiteDatabase sqliteDBInstance) {
Log.i("onCreate", "Creating the database...");
sqliteDBInstance.execSQL(DB_CREATE_SCRIPT);
}
public void openDB() throws SQLException {
Log.i("openDB", "Checking sqliteDBInstance...");
if (this.sqliteDBInstance == null) {
Log.i("openDB", "Creating sqliteDBInstance...");
this.sqliteDBInstance = this.getWritableDatabase();
}
}
public void closeDB() {
if (this.sqliteDBInstance != null) {
if (this.sqliteDBInstance.isOpen())
this.sqliteDBInstance.close();
}
}
public void insertCountry(String countryName, String countryPrice,
String countryQuantity) {
ContentValues cv = new ContentValues();
cv.put("country_name", countryName);
cv.put("country_price", countryPrice);
sqliteDBInstance.insertWithOnConflict(DB_TABLE_NAME, null, cv, sqliteDBInstance.CONFLICT_IGNORE);
// Increment the quantity field (there isn't a good way to do this with sql.update() )
sqliteDBInstance.execSQL("UPDATE " + DB_TABLE_NAME + " SET country_quantity=country_quantity+? WHERE country_name=?",
new Object[] { new Long(countryQuantity), countryName });
/*sqliteDBInstance.execSQL("INSERT OR IGNORE INTO " + DB_TABLE_NAME
+ "(country_name) VALUES('" + countryName + "')");
sqliteDBInstance.execSQL("UPDATE " + DB_TABLE_NAME
+ " SET country_quantity=country_quantity+" + countryQuantity
+ " WHERE country_name='" + countryName + "';");
sqliteDBInstance.execSQL("UPDATE " + DB_TABLE_NAME
+ " SET country_price=" + countryPrice
+ " WHERE country_name='" + countryName + "';");*/
}
public boolean removeCountry(String countryName) {
int result = this.sqliteDBInstance.delete(DB_TABLE_NAME,
"country_name='" + countryName + "'", null);
if (result > 0)
return true;
else
return false;
}
public long updateCountry(String oldCountryName, String newCountryName) {
ContentValues contentValues = new ContentValues();
contentValues.put(DB_COLUMN_1_NAME, newCountryName);
return this.sqliteDBInstance.update(DB_TABLE_NAME, contentValues,
"country_name='" + oldCountryName + "'", null);
}
public String[] getAllCountries() {
Cursor cursor = this.sqliteDBInstance.query(DB_TABLE_NAME,
new String[] { DB_COLUMN_1_NAME }, null, null, null, null,
DB_COLUMN_1_NAME + " ASC");
if (cursor.getCount() > 0) {
String[] str = new String[cursor.getCount()];
// String[] strpri = new String[cursor.getCount()];
int i = 0;
while (cursor.moveToNext()) {
str[i] = cursor.getString(cursor
.getColumnIndex(DB_COLUMN_1_NAME));
// strpri[i] = cursor.getString(cursor
// .getColumnIndex(DB_COLUMN_2_NAME));
i++;
}
return str;
} else {
return new String[] {};
}
}
}
I haven't figured out the crazy order but I found two things that might even clear something up:
your create table sql has one closing bracket too much (remove the one after the semicolon)
your insert method is really messy :) I would split it into two methods.
The general approach would be to create an insertOrUpdate method that queries the database for the entry (in your case the countryName). If an entry exist, it will be updated, if not it will be inserted. As you are a beginner, this might be a good task to do that by yourself, you should get the basic code here on SO in different questions.
The final tip (you might have seen it already): Use the parameter version and/or the real update/insert methods from the database.
db.insert(TABLE_NAME, null, contentValues); // see class ContentValues for details
According to the execSQL() method, you shouldn't use that for any SELECT/INSERT/UPDATE/DELETE statement:
Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE.
Now my question which answer might help me to help you:
I would also like to know how you verified the order of your database content? Have you created a query in your android code where you query the content or have you opened the db file with a SQLite manager tool? If you query, can you include your query/display code in your question, too?
A couple of things to add to #WarrenFaith's excellent suggestions. I agree that the error is probably in code you haven't shown.
The quotes around the increment value in the UPDATE SQL are wrong. Should be e.g. quantity=quantity+42, not quantity=quantity+'42'
You need to use argument escapes (question marks ?) to avoid problems including SQL insertion attacks on your app.
The insert logic is insanely complicated. Perhaps this is where the problem lies.
You want something like:
// Insert or ignore.
ContentValues cv = new ContentValues();
cv.put("country_name", country_name);
cv.put("country_price", country_price);
sql.insertWithOnConflict(DB_TABLE_NAME, null, cv, CONFLICT_IGNORE);
// Increment the quantity field (there isn't a good way to do this with sql.update() )
sql.execSQL("UPDATE " + DB_TABLE_NAME + " SET country_quantity=country_quantity+? WHERE country_name=?",
new Object[] { new Long(country_quantity), country_name });
AND you didn't mention if the LogCat is clean. It must be showing DB errors at least regarding the quotes problem. Also suggest you make sure the table is dropped and rebuilt between debugging runs.

Categories