I'm trying to do something simple here but my knowledge on databases and SQL is rusty at best.
I'm was following a tutorial here and am now trying to make my own joined table.
To explain a bit more simply, Profiles have widgets, widgets can be of multiple types.
The widget table contains an id, a name and a string widget_type. For each widget type, there will be another table for that widget_type.
So
Profile
|
|
|
Widget
|
|------------------|--------------------|
Widget1 Widget2 Widget3
The part I'm having trouble with is sharing the Id's among the widgets so that they are unique across all 3 widget tables. Heres the SQL I have below but I'm not entirely sure what I'm trying to do is called or if it can be done from within SQLite
private static final String PROFILE_DATABASE_CREATE = "create table "
+ TABLE_PROFILES + "( " + COLUMN_ID
+ " integer primary key autoincrement, " + PROFILE_COLUMN_NAME
+ " text not null);";
// Widget creation SQL statement
private static final String WIDGET_DATABASE_CREATE = "create table "
+ TABLE_WIDGETS + "(" + COLUMN_ID +"integer primary key autoincrement" + WIDGET_COLUMN_TYPE +"text not null";
Urk. Hard to do this so that the IDs in Widget1 are different from the IDs in Widget2.
A more simple way is to collapse all the tables together. The Contacts Provider that backs the People application does this. Have one table containing the data for all three widget types. Differentiate a row for a particular widget type with a unique MIME type. Add a widget by adding it to the table with the _ID value of the profile it belongs to, the MIME type for its widget type, and its data.
The trick is to give this "generic widget" table the maximum number of columns you'd need for any type of widget. Provide some columns that are the same for any widget, and then some generic columns (say DATA1 through DATA5) whose contents vary according to the type of widget. Then assign type-specific constants for each widget type
For example, for the Widget table you'd create these column name constants
private static final String _ID = "_ID";
private static final String WIDGET_NAME = "name";
private static final String WIDGET_TYPE = "MIMEtype";
then
private static final String WIDGET1_TYPE = "vnd.example.com/widget1";
private static final String WIDGET2_TYPE = "vnd.example.com/widget2";
private static final String WIDGET3_TYPE = "vnd.example.com/widget3";
and
private static final String SOME_WIDGET1_COLUMN = "DATA1";
private static final String SOME_OTHER_WIDGET2_COLUMN = "DATA2";
etc.
It really doesn't matter if you retrieve a row of MIME type "vnd.example.com/widget1" and then access the cursor using "SOME_OTHER_WIDGET2_COLUMN", but it's easier to keep track of what's going on if you use the Widget1 constants on Widget1 cursors, etc.
If you've ever wondered what all the contract classes for android.provider do, well, this is it.
Related
Hi in my senario there is a table with 4 columns and im trying to create another table with connection to the first table but i dont whay im getting this error in logcat
2019-10-28 01:04:00.853 29812-29812/com.test.fastfoodfinder E/SQLiteDatabase: Error inserting notes_main=testeststststststststs
android.database.sqlite.SQLiteException: no such table: notes (code 1 SQLITE_ERROR): , while compiling: INSERT INTO notes(notes_main) VALUES (?)
so i have created a class for my data base and this is what i have done
public class RestaurantDBHelper extends SQLiteOpenHelper {
private final static String DATABASE_NAME = "FastFood_DataBase.db";
private final static int DATABASE_VERSION = 1;
private final static String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
"(" + COLUMN_RESTAURANT_ID + " INTEGER PRIMARY KEY ," +
COLUMN_RESTAURANT_NAME + " TEXT, " +
COLUMN_RESTAURANT_ADDRESS + " TEXT, " +
COLUMN_RESTAURANT_TYPE + " INTEGER, " +
COLUMN_RESTAURANT_IMAGE + " INTEGER);";
private final static String CREATE_TABLE_NOTES = "CREATE TABLE " + TABLE_NAME_NOTES +
"(" + COLUMN_NOTES_ID + " INTEGER PRIMARY KEY, "
+ COLUMN_NOTES + " TEXT," + "FOREIGN KEY (" + COLUMN_NOTES_ID + ") REFERENCES " + TABLE_NAME +"(restaurant_id) ON DELETE CASCADE)";
public final static String DELETE_TABLE = "DROP TABLE IF EXISTS " + TABLE_NAME;
public RestaurantDBHelper(#Nullable Context context){
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
#Override
public void onCreate(SQLiteDatabase db) {
db.execSQL("PRAGMA FOREIGN_KEYS = ON;");
db.execSQL(CREATE_TABLE);
db.execSQL(CREATE_TABLE_NOTES);
}
#Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(DELETE_TABLE);
onCreate(db);
}
public void addRestaurant(Restaurant restaurant) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_RESTAURANT_NAME, restaurant.getName());
values.put(COLUMN_RESTAURANT_ADDRESS, restaurant.getAddress());
values.put(COLUMN_RESTAURANT_TYPE,restaurant.getType());
values.put(COLUMN_RESTAURANT_IMAGE, restaurant.getType());
db.insert(TABLE_NAME, null, values);
db.close();
}
public void addNotes (Restaurant restaurant) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_NOTES,restaurant.getNote());
db.insert(TABLE_NAME_NOTES,null,values);
db.close();
}
and
public class RestaurantContract {
public static class EntryRestaurants {
public final static String TABLE_NAME = "restaurants";
public final static String COLUMN_RESTAURANT_ID = "restaurant_id";
public final static String COLUMN_RESTAURANT_NAME = "restaurant_name";
public final static String COLUMN_RESTAURANT_ADDRESS = "restaurant_address";
public final static String COLUMN_RESTAURANT_TYPE = "restaurant_type";
public final static String COLUMN_RESTAURANT_IMAGE = "restaurant_image_type";
public final static String COLUMN_RESTAURANT_NOTE_ID = "note_id";
public final static String TABLE_NAME_NOTES = "notes";
public final static String COLUMN_NOTES_ID = "notes_id";
public final static String COLUMN_NOTES = "notes_main";
public final static int RESTAURANT_TYPE_DELIVERY = 1;
public final static int RESTAURANT_TYPE_SITDOWN = 2;
public final static int RESTAURANT_TYPE_TAKEAWAY = 3;
}
}
im kind a new in android so any help would be appreciated,thanks
I believe that your issue is with the onCreate method. This ONLY runs when the database is created, it does not run every time the App is run.
The easiest solution, assuming that you do not need to keep any existing data, is to either delete the App's data or to uninstall the App. After doing either rerun the App and the new table will be created as the onCreate method will then run.
Furthermore it is no use turning FOREIGN KEYS on in the onCreate method. FOREIGN KEYS need to be turned on every time the App is run. To fix this, override the onConfigure method and then use db.setForeignKeyConstraintsEnabled(true);
this is just a convenient alternative to using db.execSQL("PRAGMA FOREIGN_KEYS = ON;");, so if you prefer you could use this when overriding the onConfigure method.
e.g. add this method to the RestaurantDBHelper class :-
#Override
public void onConfigure(SQLiteDatabase db) {
super.onConfigure(db);
db.setForeignKeyConstraintsEnabled(true);
}
However, you will then have issues when trying to add notes as the child will be set to null and thus their will not be a link/map/association/reference between the added note and the restaurant.
You need to use something like :-
public long addNote(String note, long restaurantId) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(COLUMN_NOTES,note);
values.put(COLUMN_NOTES_ID,restaurantId);
return db.insert(TABLE_NAME_NOTES,null,values);
}
BUT then you may then have an issue as to determining the id of the restaurant.
BUT then you may then encounter a further issue in that you could only have one note per restaurant as the column used to reference the restaurant is defined as INTEGER PRIMARY KEY and is therefore is a UNIQUE column (the same value can only be used once (an exception is null as a null is considered to be unique to another null)).
If the requirement is for one note per restaurant then there is no need for the separate table the relationship is a one to one relationship so the value can be stored in the restaurant table.
If you want a restaurant to have multiple notes (one to many relationship) then you should not make the column INTEGER PRIMARY KEY, INTEGER would suffice. Then a number of notes could reference the same restaurant.
If you wanted a note to be able to be be applied to a number of restaurants then you'd use a third mapping/line/reference/associative table (other name probably also exist). Such a table would have two columns one to reference the restaurant and the other to reference the note. You would then have a many to many relationship between restaurants and notes (a note could be used by many restaurants and a restaurant could use many notes).
You may find The 3 Types of Relationships in Database Design helpful.
You have enabled foreign key constraints and thus must have a unique primary key for the foreign key to reference.
https://sqlite.org/foreignkeys.html#fk_indexes
says
If the database schema contains foreign key errors that require looking at more than one table definition to identify, then those errors are not detected when the tables are created. Instead, such errors prevent the application from preparing SQL statements that modify the content of the child or parent tables in ways that use the foreign keys. Errors reported when content is changed are "DML errors" and errors reported when the schema is changed are "DDL errors". So, in other words, misconfigured foreign key constraints that require looking at both the child and parent are DML errors. The English language error message for foreign key DML errors is usually "foreign key mismatch" but can also be "no such table" if the parent table does not exist. Foreign key DML errors are reported if:
The parent table does not exist, or
The parent key columns named in the foreign key constraint do not exist, or
The parent key columns named in the foreign key constraint are not the primary key of the parent table and are not subject to a unique constraint using collating sequence specified in the CREATE TABLE, or
The child table references the primary key of the parent without specifying the primary key columns and the number of primary key columns in the parent do not match the number of child key columns.
Given that you never give the restaurant_id a value when you inserting a restaurant then the primary key of the restaurants is probably always null and thus not unique
(Yes according to https://www.sqlitetutorial.net/sqlite-primary-key/ to make the current version of SQLite compatible with the earlier version, SQLite allows the primary key column to contain NULL values. )
So I would say the solution is to create restaurant entries with a unique primary key value when you insert a restaurant or get the database to generate a unique value creating the the restaurant table with the line:-
private final static String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME +
"(" + COLUMN_RESTAURANT_ID + " INTEGER PRIMARY KEY AUTOINCREMENT," +
....
One way to confirm this is download FastFood_DataBase.* files using Device Explorer in your app's database directory and then open it up in https://sqlitebrowser.org/ on your computer to confirm the contents.
I have got the error message " or expected, got 'Index'" when I was trying to create a table and I do not really understand why is the code expecting a column definition or table constraint at this line
I have tried with changing the whitespaces, however that only change the place where the error is prompted. The content of the error message does not change
This is the part that I have declared the strings
public class TaskEntry implements BaseColumns {
public static final String TABLE = "Users";
public static final String INDEX = "Index";
public static final String COL_TASK_TITLE = "title";
}
The following is my code for the creating table part
public void onCreate(SQLiteDatabase db) {
String createTable = "CREATE TABLE " + Item_contract.TaskEntry.TABLE + " ( " +
Item_contract.TaskEntry._ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
Item_contract.TaskEntry.INDEX + " INTEGER NOT NULL, " +
Item_contract.TaskEntry.COL_TASK_TITLE + " TEXT NOT NULL" + ");";
db.execSQL(createTable);
}
You cannot use INDEX as a column name as it is a keyword.
The SQL standard specifies a large number of keywords which may not be
used as the names of tables, indices, columns, databases, user-defined
functions, collations, virtual table modules, or any other named
object. The list of keywords is so long that few people can remember
them all. For most SQL code, your safest bet is to never use any
English language word as the name of a user-defined object.
SQL As Understood By SQLite - SQLite Keywords
So change
public static final String INDEX = "Index";
perhaps to
public static final String INDEX = "IX";
You could enclose the column name if you really wanted it to be INDEX e.g.
public static final String INDEX = "[Index]";
As per :-
If you want to use a keyword as a name, you need to quote it. There are four ways of quoting keywords in SQLite:
'keyword' A keyword in single quotes is a string literal.
"keyword" A keyword in double-quotes is an identifier.
[keyword] A keyword enclosed in square brackets is an identifier. This is not standard SQL. This quoting mechanism is used by MS Access and SQL Server and is included in SQLite for compatibility.
`keyword` A keyword enclosed in grave accents (ASCII code 96) is an identifier. This is not standard SQL. This quoting mechanism is used by MySQL and is included in SQLite for compatibility.
SQL As Understood By SQLite - SQLite Keywords
Note
You will have to do one of the following to get the onCreate method to run and thus alter the schema:-
Delete the App's data.
Uninstall the App.
I have an app with a RecyclerView list of CardViews. A CardView is created when a user enters some data and the data is then saved in an SQLite database. When a user enters a date with a Datepicker for a single CardView, the SQLite column "COLUMN_NOTIFTIME" saves the system time (System.currentTimeMillis() + 2 hours) using a long "notiftime" from the model class. I use the "notiftime" to fire a Notification for the user. If the user does not enter a date for a CardView, I would like the "COLUMN_NOTIFTIME" to hold a a null value or some other indicator to show that there is no data. I understand a long can't be null. So how do I save some type of value or indicator to show that "notiftime" is empty for the case where the user does not enter any date for that CardView? Or should I just change the long "notiftime" to a String and format the System time + 2 hours to a String so that I can use NULL, isEmpty(), isNull(), and putNull()?
Model class:
public class CardViewItem {
private int id;
private String duedate;
private String duetime;
private long notiftime;
}
DB class:
public class SQLiteDB extends SQLiteOpenHelper {
...
private static final String SQL_CREATE_ENTRIES =
"CREATE TABLE IF NOT EXISTS " + TABLE_NAME + "(" +
COLUMN_ID + " INTEGER PRIMARY KEY AUTOINCREMENT, " +
COLUMN_DUEDATE + " TEXT," +
COLUMN_DUETIME + " TEXT," +
COLUMN_NOTIFTIME + " INTEGER" + ")";
}
You could store any value you wished due to SQLite's flexibitlity (with one exception that of an alias of the rowid, which you ID column is, you can store any type of value in any type of column).
You could store null by not specifying a value for the column (as long as it isn't defined with the NOT NULL constraint) which would require specifying the columns to be inserted.
So in your case the SQL used for an insert would have to equate to :-
INSERT INTO your_table (due_date_column_name,due_time_column_name) VALUES('2018-01-01','10:30');
Using the insert method this would be along the lines of :-
ContentValues cv = new ContentValues();
cv.put(COLUMN_DUEDATE,"2018-01-01");
cv.put(COLUMN_DUETIME,"10:00");
long inserted_id = db.insert(TABLE_NAME,null,cv);
Instead of :-
ContentValues cv = new ContentValues();
cv.put(COLUMN_DUEDATE,"2018-01-01");
cv.put(COLUMN_DUETIME,"10:00");
cv.putCOLUMN_NOTIFTIME,calculated_notiftime_as_long);
long inserted_id = db.insert(TABLE_NAME,null,cv);
However the simplest way would be to store a long that would not be logically valid and therefore easily detectable, the same date time ( so due date + due_time - notiftime = 0), 0 would probably be the easiest, -1 is also often used to indicate nothing (generally when 0 could be valid).
This question already has answers here:
INSERT IF NOT EXISTS ELSE UPDATE?
(9 answers)
Closed 9 years ago.
I want to insert a record, unless it exists, then I want to update that record if its COLUMN_GENERATION value is less than that of the existing record. I'm not sure how to structure the query, or what command to use, even.
I setup my database like this:
public static final String TABLE_SEARCH = "SearchTable";
public static final String COLUMN_OWNER = "Owner";
public static final String COLUMN_SPOUSE = "Spouse";
public static final String COLUMN_CHILD = "Child";
public static final String COLUMN_GENERATION = "Generations";
private static final String DATABASE_NAME = "searches.db";
private static final int DATABASE_VERSION = 1;
// Database creation sql statement
private static final String DATABASE_CREATE = "CREATE TABLE "
+ TABLE_SEARCH + "("
+ COLUMN_OWNER + " INTEGER PRIMARY KEY, "
+ COLUMN_SPOUSE + " INTEGER DEFAULT 0, "
+ COLUMN_CHILD + " INTEGER DEFAULT 0, "
+ COLUMN_GENERATION
+ " INTEGER DEFAULT 0);";
And this is the query I"m using now, which just ignores conflicting entries:
values.put(SearchDatabaseHelper.COLUMN_OWNER, personID);
values.put(SearchDatabaseHelper.COLUMN_SPOUSE, spouseID);
values.put(SearchDatabaseHelper.COLUMN_CHILD, childID);
values.put(SearchDatabaseHelper.COLUMN_GENERATION, gens);
long insertID = database.insertWithOnConflict(SearchDatabaseHelper.TABLE_SEARCH, null, values, SQLiteDatabase.CONFLICT_IGNORE);
I tried putting a query to just search the database first, and insert or update as needed, but that was really slow and didn't actually work. I'd sure appreciate some help on this.
Wow-- closed down as a duplicate question. No real answers. I'm concerned that I didn't properly word the title to my question and so people just skimmed that and didn't read what I was asking.
Nobody seemed to notice the key phrase in the first paragraph of:
"I want to update that record if its COLUMN_GENERATION value is less than that of the existing record."
I can insert/update databases. The underlying question is, "Is it possible to make a single query that looks to see if a row exists in the database with my primary key, and if it doesn't, inserts, and if it does, then updates only if the stored value of one of the columns is higher than the inputted data?"
I really want to be able to do this all within a single SQLite call, as SQLite is so much faster than comparing and decision making outside of SQLite.
Read the generation of the existing record from the DB.
If it exists and has a lower generation, delete it.
If it did not exist or had a lower generation, insert the new one.
The various INSERT OR XXX commands only work for specific common cases; for your algorithm, you have to write it out.
Just write the commands correctly and wrap a transaction around them, and it will work, fast.
Working through the Notepad tutorial and have a question. When it builds the database with the fields rowid, title, and body - all three are private static final strings. But when a method like fetchnote() runs, it uses a long id to get the note. How does the method get that id if rowid is a string? I would like to modify the tutorial a bit and have an activity call a listactivity. The listactivity will display everything in the database, then, when a user clicks an item, I would like to copy just that entry into a new database and send the row id back to the calling activity. But I can't figure out how to get it to copy the entry into the new database because the tutorial uses a row id to make adjustments and my new database would be blank. Does that make sense? I'm new to this.
I think the public static final Strings you're talking about are these:
public class NotesDbAdapter {
public static final String KEY_TITLE = "title";
public static final String KEY_BODY = "body";
public static final String KEY_ROWID = "_id";
...
}
If so, then these are the column names. The column names are strings, not the values in these columns. If you look a little bit further down you'll see:
private static final String DATABASE_CREATE =
"create table notes (_id integer primary key autoincrement, "
+ "title text not null, body text not null);";
Which is how the database is created. As you can see, the "_id" column is of type "integer", which I'm guessing is the same (or at least compatible with) long.
(Edit)
The following should enable you to copy the note out of the original table and insert it into another one.
mCursor = mDbHelper.fetchNote(rowId);
title = mCursor.getString(mCursor.getColumnIndex(NotesDbAdapter.KEY_TITLE));
body = mCursor.getString(mCursor.getColumnIndex(NotesDbAdapter.KEY_BODY));
// I'm using mDbHelper2 here to indicate that you're inserting it into
// a different table.
mDbHelper2.createNote(title, body);
// And if you want to remove it from the original table as well:
mDbHelper.deleteNote(rowId);
// I'm guessing you'd have to run this to square up all the rows in the display:
fillData();
You could just set up your new database with the same columns as the first one, but without an auto-incrementing parameter on the id column. Then you just add a new entry to it with the same details as the other one.