I am trying to find the most efficient logic for the next situation.
I want to implement on my Android app storing in sql most used functions/actions by users.
dbase structure:
| _id | act_name (text unique) | counter (integer) |
code: (src https://stackoverflow.com/a/20568176/8006698)
int rowID = (int) db.insertWithOnConflict(TABLE_NAME, null, cv,SQLiteDatabase.CONFLICT_IGNORE);
if (rowID != -1){
myLog("Written to base with id = " + rowID);
} else {
db.execSQL("UPDATE stats SET counter=counter + 1 WHERE act_name = '" + mAction+"'");
myLog("stats updated");
}
I was surprised, that 'execSQL' doesn't return value as 'update' method.
I would use:
int rowID = db.update(...);
if (rowID == 0) {
//if not updated then insert
long insertedID = db.insert(...);
myLog("Inserted to base ");
} else
myLog("base updated");
but can't increase here count directly as 'counter=counter + 1' so I've to use 'insertWithOnConflict'.
I'm not sure, is it good practice to use it. Waiting for your ideas.
Replace this
db.execSQL("UPDATE stats SET counter=counter + 1 WHERE act_name = '" + mAction+"'");
Use this
ContentValues values=new ContentValues();
values.put("counter",1)
int rowCount=db.update("stats",values,"act_name=?",new String[]{mAction});
you get update count and do whatever you want.
Related
i have a table names "highscore"
In the table there is:
id (int) ,
name (string) ,
win(int) ,
draw(int),
loss(int).
I want to make a query that i can get the specific value win from the row , only the integer.. how can i do that? i want to handle sql injection to.
I have a method that update the win, but i need to get the win, increment the value with 1 and then update. My update method is this and it works:
public void updateWin(String playerName, int win) {
SQLiteDatabase db = this.getReadableDatabase();
ContentValues values = new ContentValues();
values.put(Constants.KEY_WIN, win);
db.update(Constants.TABLE_NAME, values, Constants.KEY_PLAYER_NAME + "= ?", new String[]{playerName});
db.close();
}
Anyone can help me please? thanx
Option 1 - Increment according to arithmetic calculation within SQL
You could base the this on the SQL (assuming the table is mytable001 and the player's name is FRED) :-
UPDATE mytable001 SET win = win +1 WHERE playername = 'FRED';
This would do away with the need to query the playername to get the current number of wins as it directly increments the value.
However, this cannot be done via the convenience update method nor a rawQuery you have utilise execSQL.
So the following could be used :-
public boolean incrementWin(String playerName) {
SQLiteDatabase db = this.getWritableDatabase();
String esc_playername = DatabaseUtils.sqlEscapeString(playerName);
String qrysql = "UPDATE " +
Constants.TABLE_NAME +
" SET " +
Constants.KEY_WIN + " = " +
Constants.KEY_WIN + " + 1" +
" WHERE " +
Constants.KEY_PLAYER_NAME + "=" + esc_playername;
db.execSQL(qrysql);
long changes = DatabaseUtils.longForQuery(db,"SELECT changes()",null);
db.close();
return changes > 0;
}
Note if the update couldn't be/ wasn't performed then it would return false.
The use of sqlEscapeString, will escape the playername and I believe offer some protection against SQL Injection.
Option 2 - Retrieve current value, calculate new, update using new :-
public boolean incWin(String playername) {
SQLiteDatabase db = this.getWritableDatabase();
String whereclause = Constants.KEY_PLAYER_NAME + "=?";
String[] wherargs = new String[]{playername};
int win = -1; // default to not update
Cursor csr = db.query(
Constants.TABLE_NAME,
null,
whereclause,
wherargs,
null,
null,
null
);
if (csr.moveToFirst()) {
win = csr.getInt(csr.getColumnIndex(Constants.KEY_WIN)) + 1;
}
csr.close();
if (win < 1) {
db.close();
return false;
}
ContentValues cv = new ContentValues();
cv.put(Constants.KEY_WIN,win);
if (db.update(Constants.TABLE_NAME,cv,whereclause,wherargs) > 0) {
db.close();
return true;
}
db.close();
return false;
}
Note if the update couldn't be/ wasn't performed then it would return false.
In my android app I have an SQLite database. with this structure:
PrivateList(idList INTEGER PRIMARY KEY, name TEXT, creationDate TEXT, active INTEGER, deactivationDate TEXT);
PrivateProduct (idProduct INTEGER PRIMARY KEY, description TEXT, quantity INTEGER, active INTEGER, additionDate TEXT);
List_Product(idList INTEGER NOT NULL, idProduct INTEGER NOT NULL, PRIMARY KEY (idList, idProduct), FOREIGN KEY(idList) REFERENCES PrivateList(idList), FOREIGN KEY(idProduct) REFERENCES PrivateProduct(idProduct));
I have an autogenerator list and elements using for to try the app:
localDB = new LocalDB(this, "localBD", null, 1);
SQLiteDatabase sqLiteDatabase = localDB.getWritableDatabase();
if (sqLiteDatabase != null){
for (int i = 0; i < 10; i++){
String a = "List" + i;
String b = "Product" + i;
Log.i("execfor", "INSERT INTO PrivateList (name, creationDate, active, deactivationDate) " + " VALUES ('" + a + "', CURRENT_TIMESTAMP, 1, null);");
sqLiteDatabase.execSQL("INSERT INTO PrivateList (name, creationDate, active, deactivationDate) " + " VALUES ('" + a + "', CURRENT_TIMESTAMP, 1, null);");
sqLiteDatabase.execSQL("INSERT INTO PrivateProduct (description, quantity, active, additionDate) " + " VALUES ('" + b + "', 3, 1, CURRENT_TIMESTAMP);");
//sqLiteDatabase.execSQL("INSERT INTO List_Product (idList, idProduct) values ");//
}
}
But I can`t find the way to get rowIds from each list and product to insert both, idlist and idproduct, into List_Product.
Thank you in advance.
The main change to facilitate grabbing the id's would be to swap from using execSQL to using insert as insert returns the id of the inserted row, execsql does not.
A little more on this here Using execSQL for INSERT operation in Android SQLite.
However, I'm not sure if you can pass CURRENT_TIMESTAMP via a ContentValues and it would result getting the current timestamp as opposed to just setting the value to the literal CURRENT_TIMESTAMP. You could use DEFAULT CURRENT_TIMEPSTAMP in the respective column definitions (as I have in the code below).
I'd suggest that you would not want a link between every list/product permutation (that would be 100 rows for you 10 List rows and 10 Product rows) as in real life you would probably not have such a scenario rather you'd have some links between the two. So in the code below I've randomly created links.
First some code from the Database Helper (for my convenience named SO45449914) for performing the inserts:-
public long insertListRow(String name,
int active) {
ContentValues cv = new ContentValues();
cv.put(LISTNAME_COL,name);
cv.put(LISTACTIVE_COL,active);
cv.put(LISTDEACTIVATIONDATE_COL,"");
return this.getWritableDatabase().insert(PRIVATELISTTABLE,null,cv);
}
public long insertProductRow(String description,int quantity, int active) {
ContentValues cv = new ContentValues();
cv.put(PRODUCTDESCRIPTION_COL,description);
cv.put(PRODUCTQUANTITY_COL,quantity);
cv.put(PRODUCTACTIVE_COL,active);
return this.getWritableDatabase().insert(PRIVATEPRODUCTTABLE,null,cv);
}
public void insertListProductLink(long listid, long productid) {
ContentValues cv = new ContentValues();
cv.put(LISTPRODUCTLIST_COL,listid);
cv.put(LISTPRODUCTPRODUCT_COL,productid);
if (this.getWritableDatabase().insertOrThrow(LISTPRODUCTTABLE,null,cv) <0) {
//handle failed insert
}
}
Notes
- I've used class variables for all columns names.
- Columns that have the current time stamp get this via the default, so there is no need to have a cv.put for those columns.
In the activity is the following code :-
void doSO45449914() {
SO45449914 dbhelper = new SO45449914(this);
int loopcount = 10;
long[] listids = new long[loopcount];
long[] productids = new long [loopcount];
for (int i=0; i < 10; i++) {
listids[i] = dbhelper.insertListRow("a" + i,1);
productids[i] = dbhelper.insertProductRow("b" + i,3,1);
}
Cursor csra = dbhelper.getWritableDatabase().query(SO45449914.PRIVATELISTTABLE,
null,null,null,null,null,null
);
Cursor csrb = dbhelper.getWritableDatabase().query(SO45449914.PRIVATEPRODUCTTABLE,
null,null,null,null,null,null
);
Log.d("SO45449914","Number of rows in LIST TABLE = " + csra.getCount());
Log.d("SO45449914","Number of rows in PRODUCTS TABLE = " + csrb.getCount());
for (long aid: listids) {
Log.d("SO45449914","LIST ID from store = " + Long.toString(aid));
}
for (long bid: productids) {
Log.d("SO45449914","PRODUCT ID from store = " + Long.toString(bid));
}
for (long lid: listids) {
for (long prdid: productids) {
if ((Math.random() * 100) > 60) {
dbhelper.insertListProductLink(lid,prdid);
Log.d("SO45449914",
"Adding link between List id(" +
Long.toString(lid) +
") and product id(" +
Long.toString(prdid) +
")"
);
}
}
}
csra.close();
csrb.close();
}
Exlapnation
The first few lines prepare long arrays based upon the number of Lists and products to be created (same number of both). Integer loopcount determines how many.
The first loop, inserts Lists and Products which use the insert method storing the returned id in the respective array element.
Two Cursors are then created for obtaining row counts, which are then written to the log. The id's as stored in the arrays are output to the log.
Two nested loops are then invoked with Products being the inner (not that it matters) and randomly (about 40% of the time) a row will be inserted into the link table. I've assumed random but you always easily adjust the algorithm to follow a pattern. It's if ((Math.random() * 100) > 60) { that determines whether or not to insert a link.
The two Cursors are then closed.
Results
Here are screen shots of the resultant tables :-
PrivateList Table
PrivateProduct Table
List_Product Table
..... (44 rows in the List_Product table)
Well, this is what I did. Despite of the fact that there is a way do the same without so many rows in List_Product table; I'd like to understand the way. (Also I had problem in the for so it didnt do what I wanted exactly).
localDB = new LocalDB(this, "localBD", null, 1);
SQLiteDatabase sqLiteDatabase = localDB.getWritableDatabase();
if (sqLiteDatabase != null){
long idList;
long idProduct;
for (int i = 0; i < 10; i++){
String a = "List" + i;
String b = "Product" + i;
ContentValues contentValuesList = new ContentValues();
contentValuesList.put("name", a);
contentValuesList.put("active", 1);
contentValuesList.put("creationDate", "CreationDate");
contentValuesList.put("deactivationDate", "");
idList = sqLiteDatabase.insert("PrivateList", null, contentValuesList);
for (int j = 0; j < 10; j++){
ContentValues contentValuesProduct = new ContentValues();
contentValuesProduct.put("description", b);
contentValuesProduct.put("active", 1);
contentValuesProduct.put("quantity", 1);
contentValuesProduct.put("additionDate", "additionDdate");
idProduct = sqLiteDatabase.insert("PrivateProduct", null, contentValuesProduct);
ContentValues contentValuesListProduct = new ContentValues();
contentValuesListProduct.put("idList", idList);
contentValuesListProduct.put("idProduct", idProduct);
sqLiteDatabase.insert("List_Product", null, contentValuesListProduct);
}
I know it could be more efficient, but that it doesn't matter now.
This is the result in the database:
PrivateList:
with 10 rows
PrivateProduct:
with 100 rows.
List_Product:
The problem was that I didn't know the existance of sqlLiteDatabase.insert(...) method.
Thanks you all.
i'm creating a contentProvider , and i wish to be able to send it multiple DB records (contentValues) to be inserted or updated to a single table using a single batch operations .
how do i do that?
batchInsert is intended only for inserting , but wouldn't it mean that insertion of something that already exists won't do anything?
also , is there a way for the update operation to use a special constraint ? for example , i need to ignore the primary key and update based on 2 other fields that together are unique.
"batchInsert is intended only for inserting" : this is true BUT you can override it in your ContentProvider to perform an UPSERT (insert/update) depending on the URI passed to batchInsert.
The following is some working code that I currently use to perform bulk inserts on time-series data (admittedly, I just delete anything that gets in the way instead of updating, but you could easily change this to your own ends.).
Also note the use of the sql transaction; this speeds up the process immensely.
#Override
public int bulkInsert(Uri uri, ContentValues[] values) {
SQLiteDatabase sqlDB = database.getWritableDatabase();
switch (match(uri)) {
case ONEPROGRAMME:
String cid = uri.getLastPathSegment();
int insertCount = 0;
int len = values.length;
if (len > 0) {
long start = values[0].getAsLong(Programme.COLUMN_START);
long end = values[len - 1].getAsLong(Programme.COLUMN_END);
String where = Programme.COLUMN_CHANNEL + "=? AND " + Programme.COLUMN_START + ">=? AND "
+ Programme.COLUMN_END + "<=?";
String[] args = { cid, Long.toString(start), Long.toString(end) };
//TODO use a compiled statement ?
//SQLiteStatement stmt = sqlDB.compileStatement(INSERT)
sqlDB.beginTransaction();
try {
sqlDB.delete(tableName(PROGRAMME_TABLE), where, args);
for (ContentValues row : values) {
if (sqlDB.insert(tableName(PROGRAMME_TABLE), null, row) != -1L) {
insertCount++;
}
}
sqlDB.setTransactionSuccessful();
} finally {
sqlDB.endTransaction();
}
}
if (insertCount > 0)
getContext().getContentResolver().notifyChange(Resolver.PROGRAMME.uri, null);
return insertCount;
default:
throw new UnsupportedOperationException("Unsupported URI: " + uri);
}
}
I am trying to have the user select a value, then search my database for that value, and return information from all rows containing that value.
I tried the following, but I am not sure how to return the results correctly to the other activity to be viewed by the user.
Cursor c = ourDatabase.rawQuery("SELECT * FROM FavoriteTable WHERE " + KEY_FAVNAME + " = '" + favoriteWorkoutName + "'", null);
I also tried this, where the int row is specifying the KEY_ROW I want to grab data from when querying. For example, the user enters the name of a favorite workout, it then searches the database for all rows containing that name, and then returns KEY_ROWS 1, 2, and 3 (which correspond to Exercise, Reps, etc). However, this only returns one row value from one row.
int row = 1;
Cursor c = ourDatabase.rawQuery("SELECT * FROM FavoriteTable WHERE " + KEY_FAVNAME + " = '" + favoriteWorkoutName + "'", null);
do{c.moveToNext();
String data = c.getString(row);
return data;
}while (row <= 3);
Any suggestions?
**EDIT:
I had already had something in my activity like that. Here is what I have there. However, I am getting an error, and it asks me to either "Change type of listCursor to Cursor", which it already is...or to "Change return type of 'GetFavoriteData' to Cursor", which is also already is.
Also, when I do get that to not have an error, I'm not sure how to use that returned data and insert it into my TextView. It will not allow me to setText(listCursor).
I ideally need to get the information from each returned row as a separate String so that I can display them the way I need to in TextViews.
In my Activity:
String favoriteWorkoutName = cfdName.getText().toString();
ExerciseDatabase choosefavorite = new ExerciseDatabase(WorkMeOutActivity.this);
try {
choosefavorite.open();
} catch (SQLException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
Cursor listCursor = choosefavorite.GetFavoriteData(favoriteWorkoutName);
choosefavorite.close();
in my DBHelper class:
public Cursor GetFavoriteData(String favoriteWorkoutName){
return ourDatabase.rawQuery("SELECT * FROM FavoriteTable WHERE " + KEY_FAVNAME + " = '" + favoriteWorkoutName + "'", null);
}
I think you misunderstood the getString() method. Have a look:
public abstract String getString (int columnIndex)
Since: API Level 1
Returns the value of the requested column as a String.
The result and whether this method throws an exception when the column value is null or the column type is not a string type is implementation-defined.
Parameters
columnIndex the zero-based index of the target column.
Returns
the value of that column as a String.
So, if you pass 1,2 or 3 inside with c.getString(row) it only gives you value of only one column(with index 1,2 or 3) from a specific row.
You can use c.getString(c.getColumnIndex(column_name)) if you are not sure about index of a column and want to get it from it's name.
Now I think you want to retrieve value of some columns(say first 3 column value of a row) from first few matching rows(like 3 rows) and use them if so use:
Cursor c = ourDatabase.rawQuery("SELECT * FROM FavoriteTable WHERE " + KEY_FAVNAME + " = '" + favoriteWorkoutName + "'", null);
String fcv="";
String scv="";
String tcv="";
if(c.getCount() == 0)
{
//no data found
}
else {
int i=0;
c.moveToFirst();
do {
fcv = fcv + c.getString(0)); //or use fcv = fcv + c.getString(c.getColumnIndex(name of first column)));
scv = fcv + c.getString(1));
tcv = fcv + c.getString(2));
i++;
} while (c.moveToNext() && i<3);
c.close();
Use the value of the strings as you like according to your need :)
Your question is not clear...
If you are trying to search all your different columns for that value your query should look more like this:
Cursor c = ourDatabase.rawQuery("SELECT * FROM FavoriteTable WHERE " + KEY_FAVNAME +
" LIKE '" + favoriteWorkoutName + "' OR " + COLUMN2 + " LIKE '" + favoriteWorkoutName +
"' OR " + COLUMN3 + " LIKE '" + favoriteWorkoutName + "'", null);
That will search columns KEY_FAVNAME, COLUMN2 and COLUMN3 and return any rows that contain favoriteWorkoutName's contents.
If you want only those from the specific column, what you first posted is correct.
EDIT
After re-reading, I'm getting a different idea of what your issue is.
You should call for the cursor in the activity in which you are going to use it. The normal method is to define a DB helper class that creates the db and contains all functions related to it (adding, deleting, updating info, getting cursors, etc). Then you instantiate that class and use it's methods to work with the db.
An example would be (used in the activity where you want to access the data):
mDbHelper = new WorkoutDB(getActivity()); // instantiate your dbhelper class
mDbHelper.open(); // use the dbhelper open method to open the db
Cursor listCursor = mDbHelper.fetchFavoriteWorkout(favoriteWorkoutName); // use the fetchFavoriteWorkout method to get your cursor
Your dbhelper class would contain a method (among many others) something like this:
public Cursor fetchFavoriteWorkout(String favoriteWorkoutName){
return ourDatabase.rawQuery("SELECT * FROM FavoriteTable WHERE " + KEY_FAVNAME + " = '" + favoriteWorkoutName + "'", null);
}
I am trying to update my sqlite database in Android.
I have this function, adjustPosition, which should adjust the position column of my rows based on the position I pass in.
For example, if position=4 and I have two rows with positions 4,5 respectively, then they should be 3,4 by the time the function returns.
public void adjustPosition(int position){
while(true){
Cursor query = mDb.rawQuery("select _ID, position from icon where position = " + position, null);
if(query != null){
if(query.getCount()==0){
query.close();
return;
}
query.moveToFirst();
long id = query.getLong(0);
int newPosition = position-1;
String sqlQuery = "update icon set position=" + newPosition + " where _ID=" + id;
boolean result = mDb.rawQuery(sqlQuery, null) != null;
String sqlQuery2 = "select position from icon where _ID="+id;
query = mDb.rawQuery(sqlQuery2, null);
query.moveToFirst();
int posn = query.getInt(0); // should equal newPosition, which is 3
position++; // have a breakpoint here
query.close();
} else {
return;
}
}
}
I have a breakpoint at the line 'positon ++;' . The value of posn should be 3, but for some reason it is 4. I have tried the SQL query using the SQLite Manager plugin in Firefox and it works fine so I don't think my SQL syntax is the problem. Do I have to commit db changes or something?