SQLite Foreign Key is null AndroidStudio - java

I tried already all what i found here in Stackoverflow but nothing helped me thats is why im asking once more. I try to make a database with sqlite in android but when i opened my database to see what my programm saved i have by this foreign key always the value null.
Here is my code:
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table " + TABLE_PERSON +" (ID INTEGER PRIMARY KEY AUTOINCREMENT, NAME TEXT, IDENTIFICATION INTEGER)");
db.execSQL("create table " + TABLE_LOCATION +" (ID INTEGER PRIMARY KEY AUTOINCREMENT, LOCATION TEXT, EMPLOYEE INTEGER , FOREIGN KEY(EMPLOYEE) REFERENCES person_table(IDENTIFICATION))");
}
Screen of sqldatabse
Can someone help me?
Code of methods:
public boolean addDataToLocation(String Location){
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(COLL_2, Location);
long result = db.insert(TABLE_LOCATION, null, contentValues);
if(result == -1)
return false;
else
return true;

You appear to have two issues/misunderstandings.
The first is that specifying a foreign key doesn't automatically select a suitable value and update the column with such a value. Rather defining a foreign adds a constraint that says that the value must be a value that exists in the respective column of the parent table. If it does not then a conflict will occur.
As such you have to determine a suitable value (in your case an existing IDENTIFICATION value).
The second is that the SQLite provided with Android, by default does not have foreign key support turned on. Foreign key support can be turned on by either using
the foriegn_keys pragma to turn support on
or by using the SQliteDatabase method setForeignKeyConstraintsEnabled
noting that foreign key support cannot be turned on within a transaction. As such it is advisable to turn foreign key support on as soon as possible.
If using as sub class of SQLiteOpenHelper then it is suggested to override the onConfigure method and use the setForeignKeyConstraintsEnabled method.

Related

How to make two columns auto increment in Sqlite

I'm Using OID as a primary key with auto increment, but I want to make Txn No also auto increment. Is there any way to make it auto increment? I tried to use loop, but it seems doesn't work.
When I click the "Save" one time, Next time should be Txn No "2", but I can't think of it, because I used OID to Auto increment, so Txn No can't use it.
Here is My Code:
public class DatabaseHelper extends SQLiteOpenHelper {
public static final String DATABASE_NAME = "Person.db";
public static final String TABLE_NAME = "Person_Table";
public static final String COL_1 = "OID";
public static final String COL_2 = "TxnNo";
public static final String COL_3 = "Name";
public static final String COL_4 = "Amount";
public static final String COL_5 = "Date";
public static final String COL_6 = "Description";
public static final String COL_7 = "Description2";
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, 1);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table " + TABLE_NAME + " (OID INTEGER PRIMARY KEY AUTOINCREMENT," +
"TxnNo TEXT, Name TEXT, Amount INTEGER,Date TEXT, Description TEXT,Description2 TEXT)");
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
public boolean insertData(String TxnNo, String Name, String Amount, String Date, String Description, String Description2) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues contentValues = new ContentValues();
contentValues.put(COL_2, TxnNo);
contentValues.put(COL_3, Name);
contentValues.put(COL_4, Amount);
contentValues.put(COL_5, Date);
contentValues.put(COL_6, Description);
contentValues.put(COL_7, Description2);
long result = db.insert(TABLE_NAME, null, contentValues);
if (result == -1)
return false;
else
return true;
}
}
First thing with SQLite AUTOINCREMENT doesn't actually increment the ID, using INTEGER PRIMARY KEY will result in much the same, other than when you've had 9223372036854775807 rows added. All AUTOINCREMENT does is enforce an increasing number (so when the largest rowid is reached an SQLITE FULL exception occurs), otherwise (without AUTOINCREMENT) free lower numbers can be allocated, thus potentially circumventing the SQLITE FULL exception.
In fact what INTEGER PRIMARY KEY (with or without AUTOINCREMENT) does is make the columnn alias of the rowid (a normally hidden column that is given a unique 64 bit signed integer).
One of the rules is that only a single column can have INTEGER PRIMARY KEY (or PRIMARY KEY) coded per table. Which is the issue that you are facing.
A Solution
A way to do what you wish is to utilise a TRIGGER that is triggered when a new row is inserted and use it to update the inserted row with a value that is suffixed with the OID. e.g.
CREATE TRIGGER IF NOT EXISTS increment_tax_number
AFTER INSERT ON Person_Table
BEGIN
UPDATE Person_Table SET TxnNo = 'Txn no '||new.OID WHERE OID = new.OID;
END;
INSERT INTO Person_Table VALUES(null,'not a valid tax number as yet','Fred',15000,'2018-01-01','blah','more blah');
INSERT INTO Person_Table VALUES(null,'not a valid tax number as yet','Bert',25000,'2018-03-04','blah','more blah');
SELECT * FROM Person_Table;
For a new table the above results in :-
This solution could be incorporated by using :-
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("create table " + TABLE_NAME + " (OID INTEGER PRIMARY KEY AUTOINCREMENT," +
"TxnNo TEXT, Name TEXT, Amount INTEGER,Date TEXT, Description TEXT,Description2 TEXT)");
db.execsql("CREATE TRIGGER If NOT EXISTS increment_tax_number AFTER INSERT ON Person_Table
BEGIN
UPDATE Person_Table SET TxnNo = 'Txn no '||new.OID WHERE OID = new.OID;
END");
}
You would need to delete the App's data, uninstall the App or increase the version number (i.e. use super(context, DATABASE_NAME, null, 2)). Noting that you would lose any existing data. To not lose data would be more complicated.
A More efficient solution
Of course, there is also the option of just utilising the OID as it appears that you want the numeric part appended to Txn No, so there is no need to even have a column that is a waste. The Txn No 1, Txn No 2 etc can be generated when required.
e.g. The following will generate the Txn No purely from the OID column :-
SELECT Name, 'Txn No '||OID AS txn_no,Amount,Description,Description2 FROM Person_Table;
Resulting in :-
To incorporate this solution you don't need to do anything other than use suitable queries (although you may wish to do away with the TxnNo column)
More about AUTOINCREMENT
Using AUTOINCREMENT incurs overheads that are rarely required. The SQLite documentation includes :-
The AUTOINCREMENT keyword imposes extra CPU, memory, disk space, and
disk I/O overhead and should be avoided if not strictly needed. It is
usually not needed.
SQLite Autoincrement
The overheads are because when AUTOINCREMENT is used the algorithm used to determine the new rowid adds a second stage of getting the respective row from the sqlite_sequence table and then using the higher of this and the highest rowid in the table (without AUTOINCREMENT just the highest rowid in the table is used). So the overheads are having to maintain and access this additional table for every insert.
As such it would be more efficient to define your table with either :-
CREATE TABLE IF NOT EXISTS Person_Table (OID INTEGER PRIMARY KEY,TxnNo TEXT, Name TEXT, Amount INTEGER,Date TEXT, Description TEXT,Description2 TEXT);
If you decide to have the TxnNo column
or :-
CREATE TABLE IF NOT EXISTS Person_Table (OID INTEGER PRIMARY KEY, Name TEXT, Amount INTEGER,Date TEXT, Description TEXT,Description2 TEXT);
If using the derived Txn No (more efficient solution)
You can't have 2 AUTOINCREMENT variables in the table. But now you declere: TxnNo TEXT it's very strange.
You can look for these variants sql-auto-increment-several
It may be, Sqlite not providing autoincrement for two column.
So i have several ways
first of all you need to use "TxnNo" as INTEGER or LONG.
at the time while you insert new record get Max "TxnNo" and increase it by 1. It is possible only if your whole database is synced in local. if it's not fully synced then its may occur duplication .
Use "TxnNo" as LONG and set current time in milliseconds. this will give you unique number every time. no need to get max. I would like to prefer this way. cause i'm also using this way.

Disallow Duplicates when adding to SQLite Database

I currently have a student grade/class input program which accepts the following inputs: Student ID, First Name, Last Name, Class ID, Class Name, Grade Point, and Letter Grade.
For obvious reasons I would like to limit the user from entering Duplicate records for the same student/course (student id, class id) pair, as well as duplicate records for the same student id and first name/last name. (Two students should not be able to fill the same ID.
Currently I have a very basic method to add this data, what is the best method to implement my intentions?:
db=openOrCreateDatabase("STUDENTGRADES", Context.MODE_PRIVATE, null);
db.execSQL("CREATE TABLE IF NOT EXISTS CUSTOMER_TABLE(studentid VARCHAR, fname VARCHAR, lname VARCHAR, classid VARCHAR, classname VARCHAR, pointgrade INTEGER, lettergrade VARCHAR);");
add.setOnClickListener(new OnClickListener() {
#Override
public void onClick(View v) {
// TODO Auto-generated method stub
if(fname.getText().toString().trim().length()==0||
lname.getText().toString().trim().length()==0 || studentid.getText().toString().trim().length()==0)
{
showMessage("Error", "Please enter First & Last Name and Student ID");
return;
}
db.execSQL("INSERT INTO CUSTOMER_TABLE VALUES('"+studentid.getText()+"','"+fname.getText()+"','"+lname.getText()+"','"+classid.getText()+"','"+classname.getText()+
"','"+pointgrade.getText()+"','"+lettergrade.getText()+"');");
showMessage("Success", "Student Record added successfully");
clearText();
}
});
You can use method insertWithOnConflict with CONFLICT_IGNORE flag, if you don't want to replace this rows. If there is no same raw, method returns -1, so you can handle it
EDITED:
At first, you need to create UNIQUE rows (in your case - class id and student id):
db.execSQL("CREATE TABLE YOURDB ("
+ "_id INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "STUDENTID TEXT UNIQUE, "
+ "CLASSID TEXT UNIQUE);");
then create variable for checkking result of method
int k = 0;
try to insert values and get result into our k
try {
SQLiteDatabase db = dbHelper.getWritableDatabase();
k = db.insertWithOnConflict("YOURDB",null, contentValues, SQLiteDatabase.CONFLICT_IGNORE);
db.close();
}
and, finally check your variable (I was made all in AsyncTask, so method for check is located in onPostExecute() method)
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
if (k==-1){
Toast.makeText(getApplicationContext(), "Same raws", Toast.LENGTH_SHORT).show();
} else {
Toast.makeText(getApplicationContext(), "not same raws", Toast.LENGTH_SHORT).show();
}
}
if there are coincidence of values, DataBase will not be updated and your k get -1 value, so, you can make Toast or something else to handle it
EDIT 2:
about ContentValue initialize:
at first, you should get values, for example, you get them from editText:
String studentIdUpdate = studentEditText.getText().toString();
String classIdUpdate = classEditText.getText().toString();
and then create ContentValues variable and put values into it
ContentValues contentValues = new ContentValues();
contentValues.put("STUDENTID", studentIdUpdate);
contentValues.put("CLASSID", classIdUpdate);
When you use a simple flat file containing all that information, it is easy to get things out of sync. That is, same student ID associated with multiple names, or same class ID associated with multiple class names. And extra work is required to keep things in sync if a student name or class name needs to be changed. Not to mention the need to minimize duplicate records. The first step to sorting out this mess is to redesign your database. For the data you mention here, I would use three tables:
Students
ID Name
------------ ---------------------------------
1 Henry
2 Molly
3 George
Classes
ID Name
------------ --------------------------------
1 Ohio History
2 US History
3 World History
Grades
StudentID ClassID Grade LetterGrade
------------ ------------ ------------ ------------
1 1 98 A
2 3 85 B
3 2 77 C
1 2 85 B
3 3 92 A
Set the primary key on Students and Classes to the ID field, and for Grades to a composite of (StudentID, ClassID). This will prevent a given student from having multiple grades for the same class, and will also prevent multiple students from having the same id. Same for classes.
Now your user interface can let the user choose a student, choose a class, then assign a grade. The letter grade can be calculated, or keyed.
Here is how I would define the tables:
create table if not exists students (
id integer primary key autoincrement,
last_name varchar,
first_name varchar);
create table if not exists classes (
id integer primary key autoincrement,
name varchar);
create table if not exists grades (
student_id integer not null,
class_id integer not null,
point_grade integer,
letter_grade varchar,
primary key (student_id, class_id),
foreign key (student_id) references students,
foreign key (class_id) references classes)
without rowid;
The foreign key constraints prevents grades from being entered for non-existent students or classes, and also prevents students or classes with grades from being deleted. There are other clauses to allow you to delete all grades for a student or class if a student or class is deleted.
The relationship between the students and classes is called a many-to-many relationship. That means that many students can be assigned to a single class, and many classes can be assigned to a single student. Not that the only keys that are auto increment are the student and class ID's. The ID's in the grades file reference the associated student and class rows. In the above data example,
Henry has grades for two classes (Ohio History (98) and US History (85)), Molly has grades for only one class (World History (85)), and George has grades for two classes (US History (77) and World History (92)).
You can create a single view that combines the students classes and grades like this:
create view if not exists student_view (
last_name, first_name, class_name, point_grade, letter_grade)
as (
select last_name, first_name, name, point_grade, letter_grade
from students
join grades on grades.student_id = students.id
join classes on classes.id = grades.class_id;

SQLite Database - RESET information

I'm currently making a code that uses a database. This is the class of the database:
public class ScriptDLL {
public static String getCreateTableCliente(){
StringBuilder sql = new StringBuilder();
sql.append(" CREATE TABLE IF NOT EXISTS CLIENTE (");
sql.append(" CODIGO INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,");
sql.append(" NOME VARCHAR (250) NOT NULL DEFAULT (''),");
sql.append(" ENDERECO VARCHAR (255) NOT NULL DEFAULT (''),");
sql.append(" EMAIL VARCHAR (200) NOT NULL DEFAULT (''),");
sql.append(" TELEFONE VARCHAR (20) NOT NULL DEFAULT ('') )");
return sql.toString();
}
}
Ok, so i want to make a SQLite code to set CODIGO back to 1. How could i write this code?
Thanks!
I would suggest to increment the version by 1 and the old table is dropped and new table is created, this will avoid failure due to uniqueness constraint if records already exist in the table(you take care of migration ofcourse). If you have specific need just to reset codigo then try the below code. It resets the value to 1 in the internal sqllite record SQLITE_SEQUENCE.
Refer documentation which explains more about autoincrement and SQLITE_SEQUENCE : https://sqlite.org/autoinc.html
public static String resetKey(){
return "UPDATE SQLITE_SEQUENCE SET seq = 1 WHERE name = CLIENTE";
}
If this is just a one-off then just Delete the App's data, or uninstall the App and rerun.
If you want to do this frequently (ignoring the fact that relying upon the column being specific values most likely indicates a flaw in the design) then:-
You could DROP and recreate the table as is often done in the onUpgrade method of the Database Helper. Doing this in the onUpgrade method could be problematic/complicated if you had multiple tables and potentially even more complicated if you had multiple versions.
You could have a specific method such as :-
public void resetCODIGO() {
this.getWritableDatabase.execSQL("DROP TABLE IF EXISTS CLIENTE;");
this.getWritableDatabase.execSQL(getCreateTableCliente());
}
DROPing a table will result in the respective row in the sqlite_sequence table being deleted by SQLite.
The sqlite_sequence table has a row per table that has a column with AUTOINCREMENT (only 1 allowed per table).
the sqlite_sequence table has two defined columns name for the table name and seq for the last inserted sequence number.
Another solution that would involve DROP and CREATE BUT does involve updating the sqlite_sequence table could be to delete all rows from the table and to then delete the respective row in the sqlite_sequence table.
Thus alternately you could have :-
public void resetCODIGO() {
this.getWritableDatabase.delete("CLIENTE");
String whereclause = "name=?";
String[] whereargs = new String[]{"CLIENTE"};
this.getWritableDatabase.delete(
"sqlite_sequence",
whereclause,
whereargs
);
}
Note the above code is in-principle code and hasn't necessarily been tested, so it may contain some errors.

Android: column _id does not exist [duplicate]

I'm having trouble with something that works in the Notepad example.
Here's the code from the NotepadCodeLab/Notepadv1Solution:
String[] from = new String[] { NotesDbAdapter.KEY_TITLE };
int[] to = new int[] { R.id.text1 };
SimpleCursorAdapter notes = new SimpleCursorAdapter(this,
R.layout.notes_row, c, from, to);
This code seems to work fine. But just to be clear, I ran the ADB
utility and run SQLite 3. I inspected the schema as follows:
sqlite> .schema
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE notes (_id integer primary key autoincrement, title text
not null, body text not null);
All seems good to me.
Now on to my application, which, as far as I can see, is basically the same with
a few minor changes. I've simplified and simplified my code, but the
problem persists.
String[] from = new String[] { "x" };
int[] to = new int[] { R.id.x };
SimpleCursorAdapter adapter = null;
try
{
adapter = new SimpleCursorAdapter(this, R.layout.circle_row, cursor, from, to);
}
catch (RuntimeException e)
{
Log.e("Circle", e.toString(), e);
}
When I run my application, I get a RuntimeException and the following prints
in LogCat from my Log.e() statement:
LogCat Message:
java.lang.IllegalArgumentException: column '_id' does not exist
So, back to SQLite 3 to see what's different about my schema:
sqlite> .schema
CREATE TABLE android_metadata (locale TEXT);
CREATE TABLE circles (_id integer primary key autoincrement, sequence
integer, radius real, x real, y real);
I don't see how I'm missing the '_id'.
What have I done wrong?
One thing that's different between my application and the Notepad example is
that I started by creating my application from scratch using the
Eclipse wizard while the sample application comes already put together. Is
there some sort of environmental change I need to make for a new application
to use a SQLite database?
I see, the documentation for CursorAdapter states:
The Cursor must include a column named _id or this class will not
work.
The SimpleCursorAdapter is a derived class, so it appears this statement applies. However, the statement is technically wrong and somewhat misleading to a newbie. The result set for the cursor must contain _id, not the cursor itself.
I'm sure this is clear to a DBA because that sort of shorthand documentation is clear to them, but for those newbies, being incomplete in the statement causes confusion. Cursors are like iterators or pointers, they contain nothing but a mechanism for transversing the data, they contain no columns themselves.
The Loaders documentation contains an example where it can be seen that the _id is included in the projection parameter.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// ...
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
This has been answered and I would like to make it more comprehensive here.
SimpleCursorAdapter requires that the Cursor's result set must include a column named exactly "_id". Don't haste to change schema if you didn't define the "_id" column in your table.
SQLite automatically added an hidden column called "rowid" for every table. All you need to do is that just select rowid explicitly and alias it as '_id' Ex.
SQLiteDatabase db = mHelper.getReadableDatabase();
Cursor cur = db.rawQuery( "select rowid _id,* from your_table", null);
Tim Wu's code really works...
If you are using db.query, then it would be like this...
db.query(TABLE_USER, new String[] {
"rowid _id",
FIELD_USERNAME,
},
FIELD_USERNAME + "=" + name,
null,
null,
null,
null);
Yes , I also change the SELECT string query to fix this issue.
String query = "SELECT t.*,t.id as _id FROM table t ";
What solved my issue with this error was that I had not included the _id column in my DB query. Adding that solved my problem.
This probably isn't relevant anymore, but I just hit the same problem today. Turns out column names are case sensitive. I had an _ID column, but Android expects an _id column.
If you read the docs on sqlite, creating any column of type INTEGER PRIMARY KEY will internally alias the ROWID, so it isn't worth the trouble of adding an alias in every SELECT, deviating from any common utilities that might take advantage of something like an enum of columns defining the table.
http://www.sqlite.org/autoinc.html
It is also more straightforward to use this as the ROWID instead of the AUTOINCREMENT option which can cause _ID can deviate from the ROWID. By tying _ID to ROWID it means that the primary key is returned from insert/insertOrThrow; if you are writing a ContentProvider you can use this key in the returned Uri.
Another way of dealing with the lack of an _id column in the table is to write a subclass of CursorWrapper which adds an _id column if necessary.
This has the advantage of not requiring any changes to tables or queries.
I have written such a class, and if it's of any interest it can be found at https://github.com/cmgharris/WithIdCursorWrapper

Hibernate get one column from a one to many connection

I want to create a java class that contains only 1 column from OneToMany ManyToOne etc. type connection not the whole row.
How can I do that?
(I'm not sure that I could express myself so I made an example)
TABLE e_skill
(
id int NOT NULL AUTO_INCREMENT,
skill_name VARCHAR (20) NOT NULL,
PRIMARY KEY (id)
);
TABLE t_person
(
id int NOT NULL AUTO_INCREMENT,
user_id int NOT NULL,
primary_skill int,
PRIMARY KEY (id),
FOREIGN KEY (primary_skill) REFERENCES e_skill(id)
);
TABLE t_secondaryskills
(
id int NOT NULL AUTO_INCREMENT,
t_person_id int NOT NULL,
skill_name int NOT NULL,
PRIMARY KEY (id),
FOREIGN KEY (t_person_id) REFERENCES t_person(id),
FOREIGN KEY (skill_name) REFERENCES e_skill(id)
);
public enum Skill {
...
}
#Entity
#Table(name = "t_person")
public class Employee {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
#Enumerated(EnumType.STRING)
//????????
//get skill_name column from e_skill
//????????
private Skill primarySkill;
#OneToMany
#Enumerated(EnumType.STRING)
//????????
//get skill_name column from e_skill
//????????
private Set<Skill> secondarySkills;
//getters setters
}
The only way I could do it now is to create a Entity to represent the e_skill table, I want to avoid that, because I only need 1 column from it.
If I understand your question correctly, you can't do what you want because of the secondary skills (because it's a collection). You can only map the primary skill name though using the #SecondaryTable annotation.
When you map things using an ORM there's no such thing as I only want a column in this scenario as you're mapping Objects, and usually in your objects you don't want to replicate data (unless they are outside your domain model). If this is unacceptable for you, I suggest you to take a look at other tools like myBtais, which gives you full control on the data you get back.
So bottom line, map your skill as an entity and live with it even if it has many columns, or choose a different tool (but not an ORM).

Categories