Joining multiple tables in list item with cursor - java

I have one problem. I have a note that contains text data, and user can add multiple photos related to that note. Notes and photos are stored in separated tables, and both of data models for notes and photos extends abstract object with data that is relevant for both objects. Beside that, "note" table is foreign key for "photo" object.
Abstract class that both objects extends:
public abstract class SyncObject implements Parcelable {
public static final String ID = "_id";
public static final String DATE = "date";
public static final String IS_SAVED_TO_CLOUD = "isSavedToCloud";
public static final String TIME_STAMP = "timeStamp";
public static final String IS_DELETED = "isDeleted";
#DatabaseField(id = true, index = true, canBeNull = false, columnName = ID)
public String id;
#DatabaseField(columnName = DATE)
public String date;
#DatabaseField(columnName = IS_SAVED_TO_CLOUD)
public boolean isSavedToCloud;
#DatabaseField(columnName = TIME_STAMP)
public String timeStamp;
#DatabaseField(columnName = IS_DELETED)
public boolean isDeleted; }
Here's note class:
public class Note extends SyncObject implements Parcelable {
public static final String TITLE = "title";
public static final String DESCRIPTION = "description";
#DatabaseField(columnName = TITLE)
public String title;
#DatabaseField(columnName = DESCRIPTION)
public String description; }
And photo class:
public class Photo extends SyncObject{
public static final String FIELD_REF = "note";
public static final String PHOTO = "photo";
public static final String THUMBNAIL = "thumbnail";
#DatabaseField(columnName = PHOTO)
public String photoUrl;
#DatabaseField(columnName = THUMBNAIL)
public String thumbnailUrl;
#DatabaseField(columnName = FIELD_REF, canBeNull = false, foreign = true, foreignAutoRefresh = true)
public Note note;
So, here it begins.
I want to show photo related to note in list, but getting cursor with joining tables produced data with same fields, and querying by those fields doesn't make any sense. Basically, i want to show one note with one photo inside list.
I tried to do this with 2 cursors, but without any success. Am I making mistake in joining cursors or creating cursor in a first place?
I have Content Provider that creates 2 cursors, one for note and one for photo data. I'm able to show data in list from one at the time. Is there any way to show data from multiple(note & photo) tables with one cursor?
Creating cursor with rawQuerry created table with same fields, and i don't need fields from SyncObject for photos while showing notes in list.
Query I used:Cursor c = db.rawQuery("SELECT * FROM note INNER JOIN photos ON note = photos.note WHERE photos.note = note", null);
Thanks in advance, if someone needs more info, ill update this post, thanks!

Related

Migration from SQLite to Room DB

I am facing a problem while migrating from SQLite to Room DB.
The problem is my old SQLite schema doesn't match with the new Room DB schema in my old SQLite DB I forgot to set primaryKey to NOT NULL and also I have a column URL and that column doesn't have any type like TEXT, INTEGER or BOOLEAN.
So now when I try to migrate my SQLite to Room I got Schema don't match error and my App is published on play store with SQLite DB.
So any help will be highly appreciated.
My old SQLite DB code:
private static final String DATABASE_NAME = "mylist.db";
private static final String TABLE_NAME = "mylist_data";
private static final String POST_TITLE = "ITEM1";
private static final String POST_URL = "URL";
private static final String KEY_ID = "ID";
public BookmarksDb(Context context) {
super(context, DATABASE_NAME, null, 1);
}
#Override
public void onCreate(SQLiteDatabase db) {
String createTable = "CREATE TABLE " + TABLE_NAME + " (ID INTEGER PRIMARY KEY AUTOINCREMENT, " +
" ITEM1 TEXT," +
" URL)";
db.execSQL(createTable);
}
My new Room DB code:
#Entity(tableName = "mylist_data")
public class Bookmark {
#PrimaryKey()
#ColumnInfo(name = "ID")
private int id;
#ColumnInfo(name = "ITEM1")
private String postTitle;
#ColumnInfo(name = "URL")
private String postUrl;
Problems are:
ID is NOT NULL = false in SQLite and in Room it is true by default(can't change it)
URL column in SQLite doesn't have any type and in Room it is TEXT by default.
I don't want to lose my old data which is stored in SQLite and my app is published, so now I want to migrate to Room without losing old user data.
Please help me to solve this problem.
You have two issue, the first the NOT NULL required is due to how Room handles primatives. So instead of using int use Integer (although really you should use Long). So change the Entity to be :-
#Entity(tableName = "mylist_data")
public class Bookmark {
#PrimaryKey()
#ColumnInfo(name = "ID")
private Integer id;
#ColumnInfo(name = "ITEM1")
private String postTitle;
#ColumnInfo(name = "URL")
private String postUrl;
The second issue is the column affinity, you need to ALTER your table to suit the Entity, As you have private String postUrl; then as you have found Room expects a column type of TEXT as opposed to nothing (UNDEFINED Affinity = 1).
To circumvent this, you could run the following SQL's to convert the table to suit Room:-
DROP TABLE IF EXISTS converted_mylist_data;
DROP TABLE IF EXISTS old_mylist_data;
CREATE TABLE IF NOT EXISTS converted_mylist_data (ID INTEGER PRIMARY KEY AUTOINCREMENT, ITEM1 TEXT, URL TEXT);
INSERT INTO converted_mylist_data SELECT * FROM mylist_data; /* copies existing data into new table */
ALTER TABLE mylist_data RENAME TO old_mylist_data;
ALTER TABLE converted_mylist_data RENAME TO mylist_data;
DROP TABLE IF EXISTS old_mylist_data;
Note you can actually retrieve the SQL to create the new table from the java(generated)
Example
Run 1 creates the database (version 1) not using Room using:-
db.execSQL("CREATE TABLE mylist_data (ID INTEGER PRIMARY KEY AUTOINCREMENT,ITEM1 TEXT, URL);");
db.execSQL("INSERT INTO mylist_data VALUES(null,'item1','my url');");
The following changes are then made :-
Added the Entity
:-
#Entity(tableName = "mylist_data")
public class Bookmark {
#PrimaryKey()
#ColumnInfo(name = "ID")
private Long id; /* <<<<<<<<<< CHANGED (could be Integer) from primative to object*/
#ColumnInfo(name = "ITEM1")
private String postTitle;
#ColumnInfo(name = "URL")
private String postUrl;
public Bookmark(){}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getPostTitle() {
return postTitle;
}
public void setPostTitle(String postTitle) {
this.postTitle = postTitle;
}
public String getPostUrl() {
return postUrl;
}
public void setPostUrl(String postUrl) {
this.postUrl = postUrl;
}
}
A Dao
:-
#Dao
interface AllDao {
#Query("SELECT * FROM mylist_data")
List<Bookmark> getAll();
}
The Database with the version increased and a Migration for version 1 to 2
:-
#Database(entities = Bookmark.class,version = 2 /*<<<<<<<<<<*/,exportSchema = false)
abstract class TheDatabase extends RoomDatabase {
abstract AllDao getAllDao();
private static volatile TheDatabase instance;
public static TheDatabase getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase.class,"mylist.db")
.allowMainThreadQueries()
.addMigrations(MIGRATION_1_2)
.build();
}
return instance;
}
static final Migration MIGRATION_1_2 = new Migration(1,2) {
#Override
public void migrate(SupportSQLiteDatabase database) {
database.beginTransaction();
database.execSQL("DROP TABLE IF EXISTS converted_mylist_data;");
database.execSQL("DROP TABLE IF EXISTS oldmylist_data;");
database.execSQL("CREATE TABLE IF NOT EXISTS converted_mylist_data (ID INTEGER PRIMARY KEY AUTOINCREMENT, ITEM1 TEXT, URL TEXT);");
database.execSQL("INSERT INTO converted_mylist_data SELECT * FROM mylist_data;");
database.execSQL("ALTER TABLE mylist_data RENAME TO oldmylist_data;");
database.execSQL("ALTER TABLE main.converted_mylist_data RENAME TO mylist_data;");
database.setTransactionSuccessful();
database.endTransaction();
}
};
}
The changed invoking/using activity (from SQLite to Room with old code commented out)
:-
public class MainActivity extends AppCompatActivity {
//DBHelper db; /* Run 1 */
TheDatabase db; /* Run 2 NEW */
AllDao dao; /* Run 2 NEW */
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
/* Run 1 create the SQLite based database */
/*
db = new DBHelper(this);
db.getWritableDatabase();
*/
/* Run 2 NEW */
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
for (Bookmark b: dao.getAll()) {
Log.d("BOOKMARKINFO","ID = " + b.getId() + " PostTitle = " + b.getPostTitle() + " PostURL =" + b.getPostUrl());
}
}
}
Result :-
Successfully runs and outputs :-
D/BOOKMARKINFO: ID = 1 PostTitle = item1 PostURL =my url
i.e. data has been kept.
By default sqlite use Blob column type if type not defined in create table statement . Paragraph 3.1.3 of sqlite doc. That's why you can use #ColumnInfo(name = "URL", typeAffinity = ColumnInfo.BLOB) to solve your second problem. You declare id with type int which cant be null, try to use Integer instead int - i think it solve your first problem.
I think you have other option to migrate on room and not lose your data: use migration mechanism.

Map additional values via rest controller

I have this Rest endpoint which returns table with IDs:
#GetMapping("pages")
public Page<ContractDTO> pages(#RequestParam(value="page") int page, #RequestParam(value="size") int size) {
return contractService.findAll(page, size).map(mapper::toDTO);
}
ContractDTO DTO:
public class ContractDTO {
private Integer id;
private String name;
private Integer gateway;
private Integer reseller_id;
private Integer acquirer_id;
private Integer terminal_id;
private Integer merchant_id;
private String descriptor;
...
}
I store into database table the ID's of each additional component like acquirer_id, terminal_id and etc.
I need to do additional SQL request like SELECT * FROM acquirers WHERE id = 3.
How I can make this after I convert the DTO object with Java?

DynamoDB Pojo to attribute value

I have a POJO defined for a dynamodb Table like this:
public class DynamoDBTablePojo {
#NonNull
private String requestId;
#NonNull
private String responseId;
private String responseBlob;
#DynamoDBHashKey(attributeName = "RequestId")
public String getRequestId(){return this.requestId;};
#DynamoDBAttribute(attributeName = "ResponseId")
public String getResponseId(){return this.responseId;};
#DynamoDBAttribute(attributeName = "ResponseBlob")
public String getResponseBlob() {return responseBlob;}
}
I want to write a function in java which will take attribute name and DynamoDBTablePojo object and returns the value of that attribute. e.g.,
String func(String attributeName, DynamoDBTablePojo dynamoDBTablePojoObj){
// returns value associated with this attribute.
}

How to populate a complex list with sublist from SQLite?

I have a structure of lists that I would like to populate from database. I've already mapped all tables and insertion methods. However I don't know what is the best approach to get all those data and populate my Java Objects. I have this structure:
public class DailySchedule {
private long id;
private List<Task> tasks;
private List<Plan> plans;
}
public class Task {
private long id;
private long duration;
private String description;
private Date date;
}
public class Plan {
private long id;
private String description;
private Date date;
private List<User> users;
}
public class User {
private long id;
private String name;
}
Should I create a huge INNERJOIN and get all the data (including repeated info) and try to populate the Java Objects or I should get all the Id's from each table and perform a loop in Java(using cursor) and perform Selects by ID's and populate the tables?
I assume you have below tables
daily_schedule
task
plan
user
Code
public List<DailySchedule> getDailySchedules(){
SQLiteDatabase db = getReadableDataBase();
Cursor dailyScheduleCursor = db.rawQuery(“Select * from daily_schedule ”);
List<DailySchedule> all = new ArrayList<>();
While(dailyScheduleCursor.next()){
DailySchedule dailySchedule = new DailySchedule();
dailySchedule.setId(dailyScheduleCursor.getLong(0));
dailySchedule.setTasks(getTasks(dailyScheduleCursor.getLong(0)));
dailySchedule.setPlans(getTasks(dailyScheduleCursor.getLong(0)));
all.add(dailySchedule);
}
db.close();
}
public List<Task> getTasks(int id ){
SQLiteDatabase db = getReadableDataBase();
Cursor taskCursor = db.rawQuery(“Select * from tasks where id =’”+id+”’ ”);
List<Task> all = new ArrayList<>();
While(taskCursor.next()){
Task task = new task();
task.setId(taskCursor.getLong(0));
// set other attributes
all.add(task);
}
db.close();
return all;
}
public List<Plan> getPlan(int id ){
getUser(id);// get all plans
// set user
return plans;
}
public User(int id){
//create user from data
return user;
}
Then call getDailySchedules() from somewhere.
Note If you want to see your SQLite databse content, use
AndroidDBvieweR

How to build query with selecting by value of foreign object's field

What is the best way for querying by using the value of foreign object's field?
Suppose I have these three classes.
UnitResult class which describes amount of Units:
#DatabaseTable
public class UnitResult {
public static final String ID_FIELD_NAME = "id";
public static final String UNIT_COLUMN_NAME = "unit";
public static final String RESULT_COLUMN_NAME = "result";
#DatabaseField(generatedId = true, columnName = ID_FIELD_NAME)
public Integer id;
#DatabaseField(foreign = true, canBeNull = false, columnName = UNIT_COLUMN_NAME)
public Unit unit;
#DatabaseField(canBeNull = true, columnName = RESULT_COLUMN_NAME)
public Integer result = null;
}
Unit class which describes certain Units in a market (for example jiuce, snack etc.):
#DatabaseTable
public class Unit {
public static final String ID_FIELD_NAME = "id";
public static final String TYPE_FIELD_NAME = "type";
#DatabaseField(id = true, columnName = ID_FIELD_NAME)
public int id;
#DatabaseField(canBeNull = false, columnName = TYPE_FIELD_NAME)
public UnitType type;
}
And Enum of Unit type:
public enum UnitType {
JUICES,
DRINKS,
SNACKS,
NPD;
}
So how can I query all UnitResult where Unit type is UnitType.JUICES?
So how can I query all UnitResult where Unit type is UnitType.JUICES?
The way to do this in ORMLite is to use the `Where.in(...) with a sub-query:
// setup our sub-query on the Unit table first
QueryBuilder<Unit,Integer> uQb = unitDao.queryBuilder();
uQb.where().eq(Unit.TYPE_FIELD_NAME, UnitType.JUICES);
// outer query on UnitResult table
QueryBuilder<UnitResult,Integer> urQb = unitResultDao.queryBuilder();
// in using the sub-query
urQb.where().in(UnitResult.UNIT_COLUMN_NAME, uQb);
List<UnitResult> results = urQb.query();

Categories