i am learning some sqlite tutorial for android development and i try to understand this line of code:
1 ContentValues values = new ContentValues();
2 values.put(AppContract.HeadphoneEntry.COLUMN_NAME, "Toto");
3 values.put(AppContract.HeadphoneEntry.COLUMN_BREED, "Terrier");
4 values.put(AppContract.HeadphoneEntry.COLUMN_WEIGHT, 7);
5
6
7
8 long newRowId = db.insert(AppContract.HeadphoneEntry.TABLE_NAME, null, values);
Is this line of code (on line 8) inserting my values into the table ? or i am just storing the row id into the long type variable called "newRowId" ?
I ask because the data is inserted but i don't understand why ... because i thought that i am just storing the row id on that line.
Is this line of code (on line 8) inserting my values into the table ?
-> Yeah line 8 will insert your value into the table if db is initialized with SQLiteOpenHelper.getWritableDatabase() method.
or i am just storing the row id into the long type variable called "newRowId"?
-> When you insert with SQLiteDatabase.insert() it returns the row ID of the newly inserted row, or -1 if an error occurred.
The SQLiteDatabase insert method inserts a row and returns the rowid of the inserted row as a long or in the case of a conflict -1 to indicate that the row was not inserted. So the value of newRowId will either be a positive value greater than 0 if the row was inserted or -1 if not inserted and there was no other conflict (e.g. a Foreign Key constraint conflict will result in an exception being thrown).
conflicts could be a PRIMARY KEY, UNIQUE, CHECK or NULL constraint violation.
The insert method is a convenience method that generates the underlying SQL binding any parameters and executes that. The SQL generated will be something like:-
INSERT OR IGNORE INTO the_table_name (the_name_column, the_breed_column, the_weight_column) VALUES(?,?,?);
Where:-
the_table_name will be the value as per the first parameter i.e. whatever AppContract.HeadphoneEntry.TABLE_NAME resolves to.
the_name_column will be the value as per the first value of the first in ContentValues (i.e. the value that AppContract.HeadphoneEntry.COLUMN_NAME resolves to)
the_breed_column will be value as per the first value of the second ContentValues (i.e. the value that AppContract.HeadphoneEntry.COLUMN_BREED resolves to)
the_weight_column being the first value as per the third ContentValues (i.e. the value that AppContract.HeadphoneEntry.COLUMN_WEIGHT resolves to)
The ?'s are replaced by the 2nd values of the respective ContentValues (first from the first i.e. 'Toto', second from the second i.e. 'Terrier' ....) when the SQL statement is bound. Binding statements correctly encloses the actual values in single quotes and thus protects against SQL Injection.
After executing the sqlite3_last_insert_rowid() interface is invoked the result returned with 0 being converted to -1.
If you weren't to use the convenience method, then to replicate all that it does you would have to code something like :-
db.execSQL("INSERT OR IGNORE INTO " + AppContract.HeadphoneEntry.TABLE_NAME +
"(" +
AppContract.HeadphoneEntry.COLUMN_NAME +
"," + AppContract.HeadphoneEntry.COLUMN_BREED +
"," + AppContract.HeadphoneEntry.COLUMN_WEIGHT +
") " +
"VALUES(?,?,?)",
new String[]
{
"Toto",
"Terrier",
"7"
}
);
Cursor csr = db.rawQuery("SELECT last_insert_rowid()",null);
newRowID = -1;
if (csr.moveToFirst()) {
newRowID = csr.getLong(0);
if (newRowID <= 0) {
newRowID = -1;
}
}
csr.close();
where db is an instantiated SQLiteDatabase object.
If your goal is to try to predict the next rowid to be used, then you could use :-
Cursor csr = db.rawQuery("SELECT max(rowid)+1 FROM " + AppContract.HeadphoneEntry.TABLE_NAME + ";",null);
long newRowID = 1; //
if (csr.moveToFirst()) {
newRowID = csr.getLong(0);
}
BUT BEWARE SQLite does not guarantee that the next inserted row will be 1 greater than the highest existing rowid. If you use AUTO INCREMENT then the sqlite_sequence table stores the highest used rowid for the table and the higher of that and the max(rowid) value will be used. Even then their is still no guarantee that the predicted value will be the value used. It is far better to not try to predict the value of the rowid but to retrieve it and thus for Android Java to use the convenience method.
An exception is if you delve into utilising negative rowid values when -1 may then not indicate no insertion.
Related
I am trying to add a single row to my table. It seems like it works just fine, but the row is not added to database. When i check the database there is no change on it.
I am trying to add it with insert method that SqliteDatabase provides with folowing code.
public void saveToCache(String word) {
ContentValues contentValues = new ContentValues();
contentValues.put("word", word);
long value = database.insert("CACHE",null, contentValues);
//The code below is for debug purposes, to see the if values are added
//But when executed it gives
//android.database.CursorIndexOutOfBoundsException: Index -1 requested,
//with a size of 13
Cursor cursor = database.rawQuery("SELECT * FROM CACHE",null);
for(int i=0; i< cursor.getCount();i++){
String a = cursor.getString(0); //This is where the exception above happens
}
}
The value attribute seems right, everytime I use the function, it increments by 1. But the rawQuery method below gives the error I have provided in the comment.
p.s : The table "CACHE" has only one COLUMN called word.
You need to change the loop that accesses the rows returned to the cursor:
Cursor cursor = database.rawQuery("SELECT * FROM CACHE",null);
while (cursor.moveToNext()) {
String a = cursor.getString(0);
// do something
}
After the call to rawQuery() the cursor's index is before the 1st row (if it exists).
The cursor does not advance to the first or to the next row without a call to moveToFirst() or moveToNext().
Below is a sample of what I did
{
...
Class.forName("com.mysql.jdbc.Driver").newInstance();
con=DriverManager.getConnection(ConnectionStr,"root","root");
String prepareStr="DELETE FROM customer_maintenance where id=?";
PreparedStatement pst= con.prepareStatement(prepareStr);
pst.setInt(1,id);
pst.executeUpdate();
String update_key="SET #count = 0; UPDATE customer_maintenance SET customer_maintenance.id = #count:= #count + 1;";
PreparedStatement pst1=con.prepareStatement(update_key);
pst1.executeUpdate();
System.out.println("UPDATE");
}
It is throwing the following exception
com.mysql.jdbc.exceptions.MySQLSyntaxErrorException:
You have an error in your SQL syntax
and I am not sure if it is possible to update the primary key, every time a row is deleted.
You should provide the exception you get, but the first problem I see in your code is that you may try to update a row setting ID = x, while another row could still have ID = x, thus an error for duplicate key.
That's because that update statement won't guarantee that the rows are updated following the ID order.
With the values in your example:
Start Delete Update1 Update2
ID ID ID ID
1 (del)
2 2 2 2
3 3 1 (upd) 1
4 4 4 2 (upd) <- duplicate key
Edit
Easiest fix is adding an order by clause to your query, so that the rows are update starting from the smaller ID.
You should also get rid of the ; at the end of the query.
String update_key="SET #count = 0;
UPDATE customer_maintenance
SET customer_maintenance.id = #count:= #count + 1
ORDER BY customer_maintenance.id";
If you have set auto increment and primary key for Id column while table creation. Auto increment will handle this automatically while database deletion, no need to worry about it.
Im trying to keep track of points / stats by making a column auto increment. However, it's not working as I want it. I want it to auto increment if the row gets updated, not if a new row gets added. For example, if I run the update command it will just add one to the "count" column for the row I updated. If I add a new row it'll start at 0!
Here is my code to create a table:
statement = connection.prepareStatement(
"CREATE TABLE IF NOT EXISTS stats" +
"(" +
"id varchar(100) not null," +
"count int not null auto_increment," +
"PRIMARY KEY (id)," +
"KEY (count)" +
")"
);
statement.execute();
Here is how I update to a specific row:
connection = plugin.getHikari().getConnection();
statement = connection.prepareStatement("INSERT INTO stats (id) VALUES(?) ON DUPLICATE KEY UPDATE id=?");
statement.setString(1, id.toString());
statement.setString(2, id.toString());
statement.execute();
Thanks,
- Nicster
Use Before update trigger in this case. Set default value to 1. and update/increment it by 1 on every update using trigger.
1) Remove the AUTO_INCREMENT attribute from the `count` column.
AUTO_INCREMENT isn't a suitable mechanism for what you are trying to achieve.
2) Add a DEFAULT 1 to the definition of the `count` column
When a new row is inserted into the table, and a value is not supplied for the `count` column, the default value will be assigned to the column.
3) re-write the INSERT statement to increment the `count` column when an attempt is made to add a duplicate `id` value
INSERT INTO atickets_stats (id)
VALUES ( ? )
ON DUPLICATE KEY
UPDATE count = count + 1 ;
From mysql doc (http://dev.mysql.com/doc/refman/5.7/en/example-auto-increment.html):
The AUTO_INCREMENT attribute can be used to generate a unique identity for new rows
So you have to chose a different approach:
Read current value from database
Prepare your statement updating also the count value
Problem: Concurrency
You have to synchronize your method call to make it thread-safe.
I'm not sure why you're having that problem. You're close, IMHO. All you need to do is to make count as INT NOT NULL default 0. Then you should always do INSERT ON DUPLICATE KEY UPDATE like below:
CREATE TABLE IF NOT EXISTS atickets_stats (
`id` VARCHAR(100) NOT NULL,
`count` INT(11) UNSIGNED NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
);
INSERT INTO atickets_stats (id) VALUES(1) ON DUPLICATE KEY UPDATE count = count + 1;
Note, I changed the count column to be INT(11) UNSIGNED because I assume you won't ever store negative value here. You can take out UNSIGNED if you'll have negative value.
I need to get the latest entry from my database, but not the autoincrement.
This function is in my databasehandler:
public int getLatestRouteNumber()
{
int number = 0;
SQLiteDatabase db = this.getReadableDatabase();
String query = "SELECT MAX("+ KEY_ROUTENUMBER + ") FROM " + TABLE_LOCATIONS;
Cursor c = db.rawQuery(query, null);
if (c.moveToFirst() && c != null) {
number = c.getInt(3);
}
return number;
}
it craches at the line where "number = c.getInt(3).
The third column in my database exists and has data in it.
The error I'm gettin is "Couldn't read row 0, col 3 from CursorWindow. Make sure the Cursor is initialized correctly before accessing data from it." I only need one value in the entire database, not even an entire row.
You have only one column in cursor but trying to get value of 3rd column... Tat is the error
Change it to getInt(0);
The returned result-set has nothing to do with table columns in your case. You should use
number = c.getInt(0);
Your resultset contains just one column and one row, basically a scalar value, it doesn't matter how many columns you have in your table, what you should consider here is the value you obtain from your query In your case just one value. Use
getInt(0)
have you tried something like this :
String query = "SELECT MAX("+ KEY_ROUTENUMBER + ") as my_max FROM " + TABLE_LOCATIONS;
And :
columnIndex = c.getColumnIndexOrThrow("my_max");
number = c.getInt(columnIndex );
That's better than using indexes, indexes may be wrong if the raw query changes.
getInt (3) returns the value of the 3rd column returned by the query. Your query only had 1 column.
Change getInt (3) to getInt (0)
"c.getInt(3);" does not get the value from the column in your table, it gets it from the column in your cursor.
And you only selected one column into your cursor.
So I believe it should be "c.getInt(1);"
I have an sqlite database (on android) where I want to store the latest N entries of some data.
The primary key of the table is a date field. Basically whenever I insert some row after the threshold is reached, I want to delete the oldest entry from the table.
Is there any especially clever/nice way to do this? Note that I always check the invariant (nr rows <= THRESHOLD) after each insert so we don't have to deal with anything but with deleting the oldest entry.
What I'm planning to do is basically:
insert data
if count(*) of table <= THRESHOLD: goto 4
DELETE FROM table WHERE date == (SELECT date from table order by date ASC LIMIT 1);
DONE
Note I'm using ORMlite, but since there's no user data involved I can just use raw SQL, so there shouldn't be a problem.
DELETE FROM table WHERE date = (SELECT MAX(date) from table LIMIT 1);
You can use a trigger to delete the oldest row when a new row is added, and the number of rows is over your threshold.
The count of rows can be kept in a separate accounting table to avoid a COUNT on every insert.
Here's a complete example:
create table bookkeepings (bk_name text primary key, bk_value integer not null);
insert or replace into bookkeepings values ('Max Results', 50);
insert or replace into bookkeepings values ('Qty Results', 0);
create table results
(r_timestamp text primary key default (datetime(current_timestamp)),
result text);
create trigger results_limit_trigger before insert on results"
for each row"
when (select bk_value from bookkeepings where bk_name = 'Qty Results')
>= (select bk_value from bookkeepings where bk_name = 'Max Results')
begin
delete from results
where r_timestamp = (select r_timestamp from results order by r_timestamp limit 1);
end;
create trigger results_count_insert_trigger after insert on results
for each row
begin
update bookkeepings set bk_value = bk_value + 1 where bk_name = 'Qty Results';
end;
create trigger results_count_delete_trigger after delete on results
for each row
begin
update bookkeepings set bk_value = bk_value - 1 where bk_name = 'Qty Results';
end;
How about this?
-- keep the last N records by expiration date
declare #expDate datetime
set #expDate = (select top 100 max(dt) from table order by dt asc);
delete from table where dt > #expDate