I've got a sample table:
I have a Sample table which has a "creationDate" as an atribute. What I want is a way to increment(update) the "numOfTimesUpdated" attribute each 24h since the creationdate. so lets say "creationdate" is 01.01.2021 12:12 AM => numOfTimesUpdated=0, 02.01.2021 12:12 AM => numOfTimesUpdated=1, 03.01.2021 12:12 AM => numOfTimesUpdated=3.
How can I implement something like this in the best way?
Does SQLite has some kind of background scheduler/trigger where a UPDATE Query gets automatically called? Or Is my only chance the client side(application) using smth. like an ApplicationManager?
How can I implement something like this in the best way?
You don't appear to even need a numberOfTimesUpdated column as the number of days since the creationDate can be calculated when required.
If the date/time were stored in a supported format (e.g. YYYY-MM-DD HH:MM) it is very simple.
For example consider this :-
DROP TABLE IF EXISTS table1;
CREATE TABLE IF NOT EXISTS table1 (id INTEGER PRIMARY KEY, name TEXT, creationdate TEXT);
INSERT INTO table1 VALUES
(null,'myname','2021-01-02'),(null,'anothername','2021-03-03'),(null,'andanother','2021-06-06')
;
SELECT *,strftime('%s','now')/(60 * 60 * 24) - strftime('%s',creationdate)/(60 * 60 * 24) AS numOfTimesUpdated FROM table1;
DROP TABLE IF EXISTS table1;
It :-
Drops the table if it exists
Creates the table
Inserts 3 rows with creation dates (21st Jan 2021, 3rd March 2021 and 6th June 2021)
Extracts all of the rows PLUS a calculated column with the number of days since the creation date.
Cleans up the test environment by deleting the table.
The results as run on 13th June 2021 are :-
Does SQLite has some kind of background scheduler/trigger where a UPDATE Query gets automatically called?
not time based.
Or Is my only chance the client side(application) using smth. like an ApplicationManager?
Yes, but again you don't appear to need this.
Working Room Example
The following is a working room example that implements the SQLite example above: -
The Table1 Entity :-
#Entity(tableName = "table1")
public class Table1 {
#PrimaryKey
Long id;
String name;
String creationDate;
public Table1(){}
#Ignore
public Table1(String name, String creationDate) {
this.name = name;
this.creationDate = creationDate;
}
}
Note as in theory id's can be long long instead of int has been used. As long MUST have a value Long has been used to allow autogenerated id's (without the inefficient AUTOGENERATE).
A POJO Table1WithNumberOfUpdates to get the Table1 with the additional calculated column:-
public class Table1WithNumberOfUpdates {
#Embedded
Table1 table1;
int numOfTimesUpdated;
}
A Dao AllDao to allow inserts and extracting a List of Table1WithNumberOfUpdates objects :-
#Dao
interface AllDao {
#Insert
long insert(Table1 table1);
#Query("SELECT *, strftime('%s','now')/(60 * 60 * 24) - strftime('%s',creationdate)/(60 * 60 * 24) AS numOfTimesUpdated FROM table1")
List<Table1WithNumberOfUpdates> getTable1WithNumberOfUpdatesList();
}
A standard #Database that returns an instance of the Database :-
#Database(entities = {Table1.class},exportSchema = false,version = 1)
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,
"state.db"
)
.allowMainThreadQueries()
.build();
}
return instance;
}
}
And finally some code in an Activity to add the three rows and extract the result outputting it to the log :-
public class MainActivity extends AppCompatActivity {
TheDatabase db;
AllDao dao;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Instantiate Database and get dao
db = TheDatabase.getInstance(this);
dao = db.getAllDao();
dao.insert(new Table1("myname","2021-01-02"));
dao.insert(new Table1("anothername","2021-03-03"));
dao.insert(new Table1("andanothername","2021-06-06"));
for (Table1WithNumberOfUpdates t: dao.getTable1WithNumberOfUpdatesList()) {
Log.d("TABLE1INFO","Name is " + t.table1.name + " Created = " + t.table1.creationDate + " Updates Since Creation = " + t.numOfTimesUpdated);
}
}
}
Result :-
2021-06-14 10:17:44.498 D/TABLE1INFO: Name is myname Created = 2021-01-02 Updates Since Creation = 163
2021-06-14 10:17:44.499 D/TABLE1INFO: Name is anothername Created = 2021-03-03 Updates Since Creation = 103
2021-06-14 10:17:44.499 D/TABLE1INFO: Name is andanothername Created = 2021-06-06 Updates Since Creation = 8
Days are 1 extra due to device using local time zone as opposed to the SQLite Tool used above (Navicat for SQLite).
Related
I am writing a report that would fetch data from Room database for last 7 days.
Here is my simplified application structure.
Entity Class:
#Entity(tableName = "cleaning")
data class Cleaning(
#PrimaryKey(autoGenerate = true)
val id: Long,
val task: String,
val timestamp: Date
)
TypeConverter:
class DateTypeConverter {
#TypeConverter
fun fromTimeStampToDate(timestamp: Long?): Date? {
return timestamp?.let {
Date(it)
}
}
#TypeConverter
fun fromDateToTimeStamp(date: Date?): Long? {
return date?.time?.toLong()
}
Data Access Object Class Extract:
#Query("SELECT * from cleaning WHERE (timestamp=Calendar.getInstance().time - 7) ORDER BY id DESC")
fun readSevenDaysData(): LiveData<List<CleaningRecord>>
The problem is (timestamp=Calendar.getInstance().time - 7). I do not know how to give range to extract data for last 7 days when I have stored date using Calendar.getInstance().time
Any guidance in the right direction is much appreciated.
I believe that the following will do what you want :-
#Query("SELECT * from cleaning WHERE CAST((timestamp / 1000) AS INTEGER) BETWEEN strftime('%s','now','-7 days') AND strftime('%s','now') ORDER BY id DESC;")
That is the timestamp is stored down to the millisecond so dividing by 1000 reduces the value to seconds.
strftime('%s' .... returns the time in seconds
'now' returns the current datetime
'-7 days' modifies the the value accordingly
see https://sqlite.org/lang_datefunc.html for more comprehensive information.
BETWEEN is equivalent to >= AND <=
see https://sqlite.org/lang_expr.html#the_between_operator
CAST is to ensure that like for like (integer values) are being compared.
see https://sqlite.org/lang_expr.html#cast_expressions
SQLite has no understanding of what the expression (timestamp=Calendar.getInstance().time - 7) means, even if it did it would only get the data that EXACTLY matches the time less 7 days down to the millisecond.
The was tested using your code plus or overridden by the following:-
#Dao
interface Extract {
#Insert
fun insert(cleaning: Cleaning)
#Query("SELECT * from cleaning WHERE CAST((timestamp / 1000) AS INTEGER) BETWEEN strftime('%s','now','-7 days') AND strftime('%s','now') ORDER BY id DESC;")
fun readSevenDaysData(): List<Cleaning>
}
obviously the SQL has been changed,
additionally LiveData has not been used for brevity and convenience,
and just Cleaning objects have been returned as CleaningRecord objects were not provided.
#Database(entities = [Cleaning::class], version = 1, exportSchema = false)
#TypeConverters( value = [DateTypeConverter::class])
abstract class TheDatabase: RoomDatabase() {
abstract fun getExtractDao(): Extract
companion object {
private var instance: TheDatabase? = null
fun getInstance(context: Context): TheDatabase {
if (instance == null) {
instance = Room.databaseBuilder(context,TheDatabase::class.java,"the_database.db")
.allowMainThreadQueries()
.build()
}
return instance as TheDatabase
}
}
}
The #Database annotated class, along with a getInstance function, noting that again for brevity and convenience allowMainThreadQueries has been used.
Finally an Activity that adds some data and the extracts data using the respective Query:-
class MainActivity : AppCompatActivity() {
lateinit var db: TheDatabase
lateinit var dao: Extract
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
db = TheDatabase.getInstance(this)
dao = db.getExtractDao()
dao.insert(Cleaning(100,"TASK001",Calendar.getInstance().time))
dao.insert(Cleaning(200,"TASK002",Calendar.getInstance().time))
for (c: Cleaning in dao.readSevenDaysData()) {
Log.d("DBINFO","Cleaning Task = ${c.task} ID = ${c.id} ${c.timestamp.time}")
}
}
}
resulting in :-
D/DBINFO: Cleaning Task = TASK002 ID = 200 1647400757156
D/DBINFO: Cleaning Task = TASK001 ID = 100 1647400757125
AppInspection shows :-
Additionally using App Inspection the query
SELECT *, timestamp /1000 AS timetosecond, strftime('%s','now','-7 days') AS fromtime, strftime('%s','now') AS totime from cleaning WHERE CAST((timestamp / 1000) AS INTEGER) BETWEEN strftime('%s','now','-7 days') AND strftime('%s','now') ORDER BY id DESC
was run to further show a) how useful App Inspection can be and b) what the various parts do the query do with the actual data.
i.e. the above resulted in :-
I'm planning to display an listview of CarServiceEntries.
The CarServiceEntry class contains basic data of a service:
#Entity
abstract class CarServiceEntry(
private int id;
private Date date;
private float odometer;
/*
getters and setters constructor....
*/
)
CarServiceEntry is abstract as the classes, which inherit it, have more detailied information:
#Entity
abstract class Income extends CarServiceEntry(
#Converter(....)
private PaymentType paymentType;
private float totalAmount;
/*
getters and setters constructor....
*/
)
The issue arrises with building the SQLScheme.
As I want to load all entries TOGETHER rather than making an own sqltable for each CarServiceEntry and query each table (getAllIncomes() getAllExpenses() etc), how can I load every CarServiceEntry(income,expense, service) using one sqltabletable for loading (if possible).
The current approach, which I'm not a fan of, looks like this:
CREATE TABLE CarServiceEntry(
id INTEGER PRIMARY KEY,
serviceType TEXT CHECK CONSTRAINT (....) //expense, income, service)
date,
odometer,
/*
A LOT of fields as each service has its own "unique" fields and I put it all together which I don' really like :( loading 30+ fields just for one sql statement is not something I like, unless I don't have any other option.
*/
)
I'd rather search for a solution like this:
CREATE TABLE CarServiceEntry(
id INTEGER PRIMARY KEY,
date DATE,
odometer NUMBER
)
CREATE TABLE Income INHERITS CarServiceEntry(
paymentType TEXT,
totalAmount NUMBER
/*some other fields*/
)
CREATE TABLE Expense INHERITS CarServiceEntry(
location TEXT
totalCost NUMBER
/*some other fields*/
)
==>
#Query("SELECT * FROM CarServiceEntry") //this should also return Income and Expense table
Flowable<List<CarServiceEntry>> getAllEntries();
Is there a way to it this way? Or is creating one table with a bulk of fields the only way?
According to: sql inheritance creating an FOREIGN KEY to my subentrytype tables would be kind of inheritance, however this does not solve my issue, as I would still have to need to load every table. I guess the only solution would be using one table with many null value fields?
SQLite doesn't support inheritance and I believe that it will be as simple, if not simpler, to utilise relationships which SQLite and Room support.
Creating multiple tables via room is pretty easy as is creating and handling relationships. So I would suggest taking the typical approach.
Here's an example based upon what I think that yo are trying to accomplish.
First the CarServiceEntry table (which will later have Expenses and Incomes related to it) :-
public class CarServiceEntry {
#PrimaryKey
private Long id;
private String date;
private float odometer;
public CarServiceEntry(){}
.... getters and setters removed for brevity
/* more convenient constructor (see cse3 in example) */
/* #Ignore to supress Room warning */
#Ignore
public CarServiceEntry(String date, Float odometer) {
this.date = date;
this.odometer = odometer;
}
}
note simplified so TypeConverters aren't required
Next the income table :-
#Entity(tableName = "income",
foreignKeys = {
#ForeignKey(
entity = CarServiceEntry.class,
parentColumns = "id",
childColumns = "incomeCarServiceEntryId",
onDelete = CASCADE,
onUpdate = CASCADE
)
},
indices = {#Index(
value = {"incomeCarServiceEntryId"}
)}
)
public class Income {
#PrimaryKey
private Long incomeId;
private Long incomeCarServiceEntryId;
private int paymentType;
private float totalAmount;
.... getters and setters
}
note foreign keys = { .... } nor indicies = { .... } are required but suggested that they be used as the help to ensure referential integrity
Note the additional column incomeCarServiceEntryId this is the ID of the related CarServiceEntry.
Next the expense table (pretty similar to income table):-
#Entity(tableName = "expense",
foreignKeys = {
#ForeignKey(
entity = CarServiceEntry.class,
parentColumns = {"id"},
childColumns = {"expenseCarServiceEntryId"},
onDelete = CASCADE,
onUpdate = CASCADE
)
},
indices = {
#Index(
value = {"expenseCarServiceEntryId"}
)}
)
public class Expense {
#PrimaryKey
private Long expenseId;
private long expenseCarServiceEntryId;
private String location;
private float totalCost;
.... getters and setters
}
Now a POJO (not a table) for extracting the related data (i.e CarServiceEntry with all of the related Incomes and all the related Expenses) named CarServiceEntryWithIncomeWithExpense :-
public class CarServiceEntryWithIncomeWithExpense {
#Embedded
CarServiceEntry carServiceEntry;
#Relation(entity = Income.class,parentColumn = "id",entityColumn = "incomeCarServiceEntryId")
List<Income> incomeList;
#Relation(entity = Expense.class,parentColumn = "id",entityColumn = "expenseCarServiceEntryId")
List<Expense> expenseList;
}
YES that's it
Now the Dao's (All in One) AllDao :-
#Dao
interface AllDao {
#Insert
long insert(CarServiceEntry carServiceEntry);
#Insert
long insert(Expense expense);
#Insert
long insert(Income income);
#Query("SELECT * FROM car_service_entry")
List<CarServiceEntryWithIncomeWithExpense> getAllCarServiceEntriesWithIncomesAndWithExpenses();
}
The #Database (includes singleton approach) named Database (probably better to use another name) :-
#Database(entities = {CarServiceEntry.class,Income.class,Expense.class},version = 1)
public abstract class Database extends RoomDatabase {
abstract AllDao getAllDao();
private static volatile Database instance;
public static Database getInstance(Context context) {
if (instance == null) {
instance = Room.databaseBuilder(context,Database.class,"carservice.db")
.allowMainThreadQueries()
.build();
}
return instance;
}
}
Note for brevity and convenience of the demo, the main thread is used.
Finally putting it all together and demonstrating MainActivity.
The demo adds 3 carServiceEntries, with incomes and expenses (the 3rd has none of either). It then extracts the 3 carServiceEntries with all the incomes and expenses. Traversing the extracted and outputting what has been extracted to the log.
:-
public class MainActivity extends AppCompatActivity {
Database db;
AllDao dao;
private static final String TAG = "CSEINFO";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
db = Database.getInstance(this);
dao = db.getAllDao();
/* Add a Service Entry noting it's ID */
CarServiceEntry cse1 = new CarServiceEntry();
cse1.setDate("2021-06-01");
cse1.setOdometer(5120.78F);
long cse1Id = dao.insert(cse1);
/* Add another Service Entry noting it's id */
CarServiceEntry cse2 = cse1;
cse2.setDate("2021-06-02");
cse2.setOdometer(7065.83F);
long cse2Id = dao.insert(cse2);
/* Use a single Income to add 3 Income Entries for (linked to) the 1st Service Entry */
Income incomeEntry = new Income();
incomeEntry.setIncomeCarServiceEntryId(cse1Id);
incomeEntry.setPaymentType(10);
incomeEntry.setTotalAmount(120.00F);
dao.insert(incomeEntry);
incomeEntry.setPaymentType(20);
incomeEntry.setTotalAmount(230.00F);
dao.insert(incomeEntry);
incomeEntry.setPaymentType(15);
incomeEntry.setTotalAmount(75.55F);
dao.insert(incomeEntry);
/* Use the same Income Entry to add 1 Entry for the 2nd Service Entry */
incomeEntry.setIncomeCarServiceEntryId(cse2Id);
incomeEntry.setPaymentType(25);
incomeEntry.setTotalAmount(134.56F);
dao.insert(incomeEntry);
/* Add some Expense Entries */
Expense expenseEntry = new Expense();
expenseEntry.setExpenseCarServiceEntryId(cse1Id);
expenseEntry.setLocation("London");
expenseEntry.setTotalCost(500.00F);
dao.insert(expenseEntry);
expenseEntry.setLocation("New York");
expenseEntry.setTotalCost(60.66F);
dao.insert(expenseEntry);
expenseEntry.setExpenseCarServiceEntryId(cse2Id);
expenseEntry.setLocation("Paris");
dao.insert(expenseEntry);
expenseEntry.setLocation("Hamburg");
dao.insert(expenseEntry);
expenseEntry.setLocation("Madrid");
dao.insert(expenseEntry);
dao.insert(new CarServiceEntry("2021-06-03",1765.34F));
for (CarServiceEntryWithIncomeWithExpense cse: dao.getAllCarServiceEntriesWithIncomesAndWithExpenses()) {
Log.d(
TAG,
"CSE ID = " + cse.carServiceEntry.getId() +
" Date = " + cse.carServiceEntry.getDate() +
" ODO = " + cse.carServiceEntry.getOdometer()
);
for (Income i: cse.incomeList) {
Log.d(
TAG,
"\tIncome Payment Type is " + i.getPaymentType() + " Total is " + i.getTotalAmount()
);
}
for(Expense e: cse.expenseList) {
Log.d(
TAG,
"\tExpense Location is " + e.getLocation() + " Total is " + e.getTotalCost()
);
}
}
}
}
Result
The following is output to the Log :-
2021-06-11 13:01:35.116 D/CSEINFO: CSE ID = 1 Date = 2021-06-01 ODO = 5120.78
2021-06-11 13:01:35.116 D/CSEINFO: Income Payment Type is 10 Total is 120.0
2021-06-11 13:01:35.116 D/CSEINFO: Income Payment Type is 20 Total is 230.0
2021-06-11 13:01:35.116 D/CSEINFO: Income Payment Type is 15 Total is 75.55
2021-06-11 13:01:35.116 D/CSEINFO: Expense Location is London Total is 500.0
2021-06-11 13:01:35.116 D/CSEINFO: Expense Location is New York Total is 60.66
2021-06-11 13:01:35.116 D/CSEINFO: CSE ID = 2 Date = 2021-06-02 ODO = 7065.83
2021-06-11 13:01:35.117 D/CSEINFO: Income Payment Type is 25 Total is 134.56
2021-06-11 13:01:35.117 D/CSEINFO: Expense Location is Paris Total is 60.66
2021-06-11 13:01:35.117 D/CSEINFO: Expense Location is Hamburg Total is 60.66
2021-06-11 13:01:35.117 D/CSEINFO: Expense Location is Madrid Total is 60.66
2021-06-11 13:01:35.117 D/CSEINFO: CSE ID = 3 Date = 2021-06-03 ODO = 1765.34
I am trying to have SQLite create automatic timestamps with CURRENT_TIMESTAMP.
I took the liberty of using Google's code:
// roomVersion = '2.2.2'
#Entity
public class Playlist {
#PrimaryKey(autoGenerate = true)
long playlistId;
String name;
#Nullable
String description;
#ColumnInfo(defaultValue = "normal")
String category;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String createdTime;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String lastModifiedTime;
}
#Dao
interface PlaylistDao {
#Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insert(playlist: Playlist): Long
}
This translates into an SQLite-Statement:
CREATE TABLE `Playlist` (
`playlistId` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`name` TEXT,
`description` TEXT,
`category` TEXT DEFAULT 'normal',
`createdTime` TEXT DEFAULT CURRENT_TIMESTAMP,
`lastModifiedTime` TEXT DEFAULT CURRENT_TIMESTAMP
)
I did make one insert:
mDb.playListDao().insert(Playlist().apply { name = "Test 1" })
But the timestamps are always Null.
With the DB Browser for SQLite I added another entry, here I get timestamps.
How do I insert without a Null-Timestamp in room?
(Info: createdTime is also always the same as lastModifiedTime. I think this has to be done with triggers in SQLite, but that is a different problem not to be discussed here).
You don't need to use another class, you can use #Query as an alternative to the convenience #Insert.
as per :-
There are 4 type of statements supported in Query methods: SELECT, INSERT, UPDATE, and DELETE.
Query
e.g.
#Query("INSERT INTO test_table001 (name) VALUES(:name) ")
void insert(String name);
You are also not limited to CURRENT_TIMESTAMP as the only means of getting the current timestamp you can use embedded datetime functions (as is shown below), which can store the value more efficiently and also be more flexible e.g. you could adjust the current time using modifiers such as '+7 days'.
If you consider the following :-
#Entity(tableName = "test_table001")
public class TestTable001 {
#PrimaryKey
Long id;
#ColumnInfo(defaultValue = "CURRENT_TIMESTAMP")
String dt1;
#ColumnInfo(defaultValue = "(datetime('now'))")
String dt2;
#ColumnInfo(defaultValue = "(strftime('%s','now'))")
String dt3;
String name;
}
Note that the inefficient autogenerate = true has not been used BUT as will be shown you can still have an SQLite assigned id (note that you must use the type Long/Integer as opposed to long or int)
Also note the alternative ways of getting the current date time (the latter being more efficient as the value will ultimately be stored as an Integer (max 8 bytes) rather than a more byte hungry String).
With a Dao as :-
#Dao
public interface TestTable001Dao {
#Insert()
long insert(TestTable001 testTable001);
#Query("INSERT INTO test_table001 (name) VALUES(:name) ")
long insert(String name);
#Query("SELECT * FROM test_table001")
List<TestTable001> getAllTestTable001();
}
And the following to test/demonstrate :-
public class MainActivity extends AppCompatActivity {
AppDatabase mRoomDB;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRoomDB = Room.databaseBuilder(this,AppDatabase.class,"testdb")
.allowMainThreadQueries()
.build();
TestTable001 tt01 = new TestTable001();
tt01.setName("tt01");
mRoomDB.useTestTable001().insert(tt01);
mRoomDB.useTestTable001().insert("tt02");
logAllTestTable001();
}
private void logAllTestTable001() {
for (TestTable001 tt: mRoomDB.useTestTable001().getAllTestTable001()) {
Log.d(
"TTINFO",
"ID = " + tt.getId() +
" Name = " + tt.getName() +
" Date1 = " + tt.getDt1() +
" Date2 = " + tt.getDt2() +
" Date3 = " + tt.getDt3());
}
}
}
The result is :-
2019-12-14 03:18:32.569 D/TTINFO: ID = 1 Name = tt01 Date1 = null Date2 = null Date3 = null
2019-12-14 03:18:32.569 D/TTINFO: ID = 2 Name = tt02 Date1 = 2019-12-13 16:18:32 Date2 = 2019-12-13 16:18:32 Date3 = 1576253912
Found it. Did not read the manual.
You have to create a 2nd class without the auto-set fields to insert.
public class NameAndDescription {
String name;
String description
}
I think, this is not a good idea.
If you have an autoincrement field in the DB it will get an automatically updated value when you pass 0.
Likewise the default value of the timestamp should be used when passing null or "".
I found the best solution was creating an abstract Dao that implemented the insert and update methods. I didn't get the default value to work (perhaps I was doing something wrong). Take a look at my answer here: How to implement created_at and updated_at column using Room Persistence ORM tools in android
Currently, we are using the Document API in OrientDB version 2.2. Let us suppose we have a class Company and a class Employee. Let's suppose we are interested in all Companies with at least one employee having a name from an arbitrary list. Employees are defined as LINKEDLISTs in our Company schema.
Our query would look smth like this:
select from Company where employees in (select from Employee where name in ["John", "Paul"])
Currently we have defined the following two indexes:
Company.employees (index on the employee links (their #rid)) -> dictionary hash index and
Employee.name -> notunique index
When executing the above query with explain we see that only the second index Employee.name is used, since we did not define the above indexes as a compound index. AS far as I could understand compound indexes across different classes like in our case are not supported in Orient 2.x.
Queries like this:
select from Company let $e = select from employees where name in ["John", "Paul"] where employees in $e
do not solve our problem either.
Searching across different blogs revealed two suggestions so far:
trying to define a compound index via inheritance by introducing a parent class on employee and company and defining the above two indexes on that
https://github.com/orientechnologies/orientdb/issues/5069
bundle the two queries in a batch scrip like this:
https://github.com/orientechnologies/orientdb/issues/6684
String cmd = "begin\n";
cmd += "let a = select from Employees where name " + query + "\n";
cmd += "let b = select from Company where employees in $a\n";
cmd += "COMMIT\n";
cmd += "return $b";
Suggestion 1 did not work for us.
Suggestion 2. worked. Both indexes have been used in each separate query, but then we ran into the next limitation of Orient. Batch scripts seem to be executed only synchronously, meaning that we can only get the results as a list all at once and not one by one in a lazy fashion, which in our case is a NO GO due to the memory overhead.
One naive workaround we tried is as follows:
public class OCommandAsyncScript extends OCommandScript implements OCommandRequestAsynch{
public OCommandAsyncScript(String sql, String cmd) {
super(sql, cmd);
}
#Override
public boolean isAsynchronous() {
return true;
}
private void containsAtLeastOne(final #Nonnull ODatabaseDocumentTx documentTx,
final #Nonnull Consumer<Company> matchConsumer,
final #Nonnull String queryText
) throws TimeoutException {
String cmd = "begin\n";
cmd += "let a = select from Employee where name " + queryText + "\n";
cmd += "let b = select from Company where employees in $a\n";
cmd += "COMMIT\n";
cmd += "return $b";
final OCommandHandler resultListener = new OCommandHandler(documentTx, (document -> {
final Company companies = document2model(document);
matchConsumer.accept(company);
}));
OCommandAsyncScript request = new OCommandAsyncScript("sql", cmd);
request.setResultListener(resultListener);
documentTx.command(request).execute();
...
}
}
public class OCommandHandler implements OCommandResultListener {
private final ODatabaseDocumentTx database;
private final Consumer<ODocument> matchConsumer;
public OCommandHandler(
final #Nonnull ODatabaseDocumentTx database,
final #Nonnull Consumer<ODocument> matchConsumer
) {
this.database = database;
this.matchConsumer = matchConsumer;
}
#Override
public boolean result(Object iRecord) {
if (iRecord != null) {
final ODocument document = (ODocument) iRecord;
/*
Result handler might be asynchronous, if document is loaded in a lazy mode,
database will be queries to fetch various fields. Need to activate it on the current thread.
*/
database.activateOnCurrentThread();
matchConsumer.accept(document);
}
return true;
}
...
}
The approach of defining a custom OCommandAsyncScript did not work unfortunately. When debugging the OStorageRemote class of Orient it seems that no partial results could be read, Here the respective extract from the source code:
public Object command(final OCommandRequestText iCommand) {
....
try {
OStorageRemote.this.beginResponse(network, session);
List<ORecord> temporaryResults = new ArrayList();
boolean addNextRecord = true;
byte status;
if(asynch) {
while((status = network.readByte()) > 0) {
ORecord record = (ORecord)OChannelBinaryProtocol.readIdentifiable(network);
if(record != null) {
switch(status) {
case 1:
if(addNextRecord) {
addNextRecord = iCommand.getResultListener().result(record);
database.getLocalCache().updateRecord(record);
}
break;
case 2:
if(record.getIdentity().getClusterId() == -2) {
temporaryResults.add(record);
}
database.getLocalCache().updateRecord(record);
}
}
}
}
}
Network.readbyte() is always null, hence no records could be fetched at all.
Is there any other workaround how we could execute a sql script in asynchronus mode and retrieve results in a lazy fashion preventing the generation of large lists on our application side?
I have created a two column editable TableView which the user can edit and change the data thats inside each cell. Now my question is, once the user has changed some data around in each cell, how do I then save this data or print it out in a way to add to an SQL query like this example below
INSERT INTO table_name
VALUES (Value from table,Value from table,Value from table,...);
//Editable cell
PriceColumn.setCellFactory(TextFieldTableCell.forTableColumn());
PriceColumn.setOnEditCommit(
new EventHandler<CellEditEvent<NewCustomerList, String>>() {
#Override
public void handle(CellEditEvent<NewCustomerList, String> t) {
((NewCustomerList) t.getTableView().getItems().get(
t.getTablePosition().getRow())).setPrice(t.getNewValue());
}
}
);
You could do this by getting the required values and then passing them onto the DAO class to execute the query on the DB. Example follows-
PriceColumn.setOnEditCommit(
new EventHandler<CellEditEvent<NewCustomerList, String>>() {
#Override
public void handle(CellEditEvent<NewCustomerList, String> t) {
((NewCustomerList) t.getTableView().getItems().get(t.getTablePosition().getRow())).setPrice(t.getNewValue());
String newPrice = t.getNewValue();
String uniqueIdentifier = t.getRowValue().getUniqueIdentifier(); //Unique identfier is something that uniquely identify the row. It could be the name of the object that we are pricing here.
daoObj.updatePrice(newPrice, uniqueIdentifier); //Call DAO now
}
}
);
And somewhere in the deep dark jungles of DAO class,
private final String updateQuery = "UPDATE <TABLE_NAME> SET <PRICE_COLUMN> = ? WHERE <UNIQUE_COLUMN> = ?"; //If you require multiple columns to get a unique row, add them in the where clause as well.
public void updatePrice(String newPrice, String uniqueIdentifier) {
PreparedStatement ps = con.prepareStatement(updateQuery); //con is the connection object
ps.setString(1,uniqIdentifier); //if a string
ps.setString(2,newPrice); //if a string
ps.executeQuery();
}
If this is not what you were expecting, then can you please clarify your requirement?