MySQL / SQL Incrementing a Column - java

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.

Related

Android SQLite insert method small clarification

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.

Reset sqlite squence autoincrement

I want to reset autoincrement in my table. I tried to do this, but when I created and inserted a new row an id is not 1, but was just autoincremented .
#Query("delete from sqlite_sequence where name='approval';")
void delete();
#Query("DELETE FROM approval")
void nukeTable();
How can I reset autoincrement to start from 1 again?
You can reset by update sequence after deleted rows in your-table
UPDATE SQLITE_SEQUENCE SET SEQ=0 WHERE NAME='table_name';
Reference
I don't know what Room may be bringing to the mix, but at the pure Sqlite3 level, both deleting the record from sqlite_sequence and setting its value to zero does the trick, provided all records are deleted from the table in question.
autoinc.sql3
create table ai ( id integer primary key autoincrement, value ) ;
select 'Normal insertion' ;
insert into ai(value) values ( 1 ), ( 42 ) ;
select * from ai ;
select 'Delete sequence only' ;
delete from sqlite_sequence where name = 'ai' ;
insert into ai(value) values ( 1 ), ( 42 ) ;
select * from ai ;
select 'Delete sequence and data' ;
delete from sqlite_sequence where name = 'ai' ;
delete from ai ;
insert into ai(value) values ( 1 ), ( 42 ) ;
select * from ai ;
Running this with sqlite3 < autoinc.sql3 gives the following output:
Normal insertion
1|1
2|42
Delete sequence only
1|1
2|42
3|1
4|42
Delete sequence and data
1|1
2|42
Exactly the same results are obtained if instead of deleting the record from the sequence table we reset it to zero using update sqlite_sequence set seq=0 where name = 'ai' ;.
However, as the results above show, if there are any records in the table, numbering is persisted (presumably with the highest-used-plus-one that a non autoincrement primary key uses).
Could it be something is inserting records between deleting/resetting the sequence and deleting the records from approval? Assuming Room supports it, try both operations within a single transaction.

After deleting a row I want the primary key to start from 1 again.So is it possible

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.

How to store multiple values in one field of a MySQL table column?

I want to create table stops for all stops with these columns id, name,route, lat, long, arrivaltime but I dont know how can I manage it to get the route column in the stops table? since the one route has many numbers?
{
"id": 1
"stops_name": "Amersham ",
"route": "8,4,7,34,45,8017, 57, 20,......... 30 entries"
"arrival_time": {
"mon-fri": [ "05:38", "06:07","06:37",.....50 entries],
"sat": ["05:34","06:01","06:31",...........50 entries],
"son": ["06:02","06:34","07:04",...........50 entries]
},
"stops_lat": 83.837994,
"stops_long": 18.700423
}
stt.execute("CREATE TABLE IF NOT EXISTS stops"
+ "(stop_id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY, "
+ " name varchar(30) NOT NULL, "
+ " route INT(11) NOT NULL, "
+ " lat double(10,6) NOT NULL, "
+ " longi double(10,6)NOT NULL) " );
stt.execute("CREATE TABLE IF NOT EXISTS arrivaltimes(id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY,"
+ " weekday VARCHAR(20) NOT NULL,"
+ "arrivaltime time NOT NULL,"
+ " stop_id INT, FOREIGN KEY fk_stop_id(stop_id) REFERENCES stops(stop_id) )" );
Think about it this way. Represent this:
"route": "8,4,7,34,45,8017, 57, 20,......... 30 entries"
Like this:
"route": { "8"
, "4"
, "7"
, "34"
, "45"
, ...
}
Consider representing that in a table like this:
CREATE TABLE stop_route
( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
, stop_id INT(11) NOT NULL COMMENT 'FK ref stop'
, route_id INT(11) NOT NULL COMMENT 'FK ref route'
, UNIQUE_KEY stop_route_UX1 (stop_id, route_id)
, CONSTRAINT FK_stop_route_stop (stop_id) REFERENCES stop(id)
, CONSTRAINT FK_stop_route_route (route_id) REFERENCES route(id)
}
If route is not an entity in your model, then remove the foreign key constraint.
Entity-Relationship model
I don't know your entity-relationship model.
But I suspect that there's actually a many-to-many relationship between route and stop. To resolve that, we'd introduce a new "relationship" table with foreign keys pointing to both "stop" and "route".
And I'm thinking that arrival_time is not independent of route. The question I'm asking is if every route has the same 50 arrival_time at a given stop? Or, does each route have it's own set of arrival_time for a given stop. (I'm thinking it's the latter.)
a route arrives at zero, one or more stop
a stop is serviced by zero, one or more route
We resolve the many-to-many by introducing a relationship table
route >--- route_stop ---< stop
(Personally, in terms of naming things, I think the route_stop relationship table above should be named a "stop", and the stop entity table should be a "location".
On a schedule, a vehicle makes on a particular route makes a "stop" at a particular "location", at (or close to) a particular arrival_time.) But that's just nomenclature. Just depends on how you name things.
CREATE TABLE route_stop
( id INT NOT NULL AUTO_INCREMENT PRIMARY KEY
, route_id INT NOT NULL COMMENT 'FK ref route'
, stop_id INT NOT NULL COMMENT 'FK ref stop'
, UNIQUE KEY route_stop_UX1 (route_id, stop_id)
, CONSTRAINT FK_route_stop_route FOREIGN KEY (route_id) REFERENCES route(id)
, CONSTRAINT FK_route_stop_stop FOREIGN KEY (stop_id) REFERENCES stop(id)
)
CREATE TABLE arrival_time
( id INT(11) NOT NULL AUTO_INCREMENT PRIMARY KEY
, stop_route_id INT(11) NOT NULL COMMENT 'FK ref route_stop'
, weekday VARCHAR(20) NOT NULL
, arrivaltime TIME NOT NULL
, CONSTRAINT FK_arrival_time_stop_route
FOREIGN KEY (route_stop_id) REFERENCES stop_route(id)
)
As far as I understand you want to create a relational database structure. And you want to have a relation between stops and routes table.
So assuming that one stop can belong to several routes and one route can have several stops that's many to many relationship between tables.
For instance you can take a look here
http://www.tutorialspoint.com/hibernate/hibernate_many_to_many_mapping.htm
how to implement this relation in Hibernate.

keep latest n entries in table

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

Categories