How to efficiently query sqlite database multiple times on Android - android

For my application, I need to query a sqlite database around 40-50 times. I am sure that the code I wrote is very inefficient. Unfortunately, I cannot find many examples online that involves querying the database many times.
String[] entryValArray = new String[indicesList.size()];
DBHelper dbHelper = new DBHelper(MainActivity.context);
SQLiteDatabase db = dbHelper.getReadableDatabase();
for (int i = 0; i < indicesList.size(); i++) {
int moddedIndex = Integer.parseInt(indicesList.get(i), 16) % DBHelper.numEntries;
String queryStr = "select * from " + DBHelper.TBL_NAME + " where " + DBHelper.IDStr +
" = " + Integer.toString(moddedIndex);
Cursor cursor = db.rawQuery(queryStr, null);
if (cursor.moveToFirst())
entryValArray[i] = cursor.getString(1);
cursor.close();
}
Basically, I am taking a list of strings, converting them to hex values, and then modding the value to get an index into a sqlite database. This is for a password generator application.
Is there a better way to do this, especially regarding creating a cursor and then closing it in every iteration.

First of all you have to change your query string as you need only one column value but you are using
Select *
instead of
Select yourColumn
. Secondly if your indices list size is not very large you can use
IN(values ) function of db instead of
" where " + DBHelper.IDStr +" = " + Integer.toString(moddedIndex);
this will return the result in only one query you don't have to run a whole loop.

Related

SQLite Statement in Android Studio 3.5.3

I'm a newbie with Android Studio so please be patient... This forum often leads me with suggestions and examples (as a reader), but today I decided to ask for help:
Since hours, I try to build an SQLite statement in Android Studio: There is a column COLUMN_LAST_ATTEMPT with date and time as String, e.g. 2020-01-09 17:23, see screenshot, and I want to get the newest date (without time) from the table, e.g. 2020-09-01. I tried various options but I can't get it to run.
What I need is an Android SQLite Statement for
SELECT MAX(SUBSTR(last_attempt,11,20)) FROM quiz_questions
(which runs on DBBrowser), where 'last attempt' is a column of table 'quiz_questions', screenshot of that column in table 'quiz_questions'
I tried the following rawQueries, none of them works:
In QuizDBHelper-Class
//...
final QuizDbHelper dbHelper = QuizDbHelper.getInstance(this);
//...
public String newestQuiz(){
db = getReadableDatabase();
String result = null;
Cursor cursor = db.rawQuery("SELECT MAX(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ") FROM "
+ QuizContract.QuestionsTable.TABLE_NAME, null);
//Cursor cursor = db.rawQuery("SELECT MAX(SUBSTR(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT +
// ",11,20)) FROM " + QuizContract.QuestionsTable.TABLE_NAME, null);
//Cursor cursor = db.rawQuery("SELECT " + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + " FROM " +
// QuizContract.QuestionsTable.TABLE_NAME, null);
if(cursor.moveToFirst()){
do {
result = cursor.getString(c.getColumnIndex(QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT));
} while (cursor.moveToNext());
}
cursor.close();
return result;
}
In Statistics-Class
String LastUse = dbHelper.newestQuiz();
LastUsage.setText("Letzte Challenge: " + LastUse);
//LastUsage is a TextView in activity_Statistics.xml
//attached with LastUsage = findViewById(R.id.text_lastUsage);
Either the SQLite statements are totally wrong or I make (basic?) mistakes in statistics class. I need ...newbie help!
I need something like Select column from table where substring of date-Entry == newest
Your issue appear to be column names. That is a Cursor only contains the columns extracted, not all the columns from the table. Although you are basing your query on the column as per QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT that will not be the column name in the cursor.
Rather it will will MAX(SUBSTR(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT +
// ",11,20))
The simplest way of managing this is to give the column in the Cursor a specific name using AS. As such perhaps use :-
Cursor cursor = db.rawQuery("SELECT MAX(" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ") AS " + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + " FROM "
+ QuizContract.QuestionsTable.TABLE_NAME, null);
However, you may prefere to use a column name (AS ????) specififc to the situation e.g.
........ AS max_" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT + ........
You would then have to use :-
result = cursor.getString(c.getColumnIndex("max_" + QuizContract.QuestionsTable.COLUMN_LAST_ATTEMPT));
Alternately, as it's just a single value/column that is returned in the cursor you could use the column offset of 0, in which case the column name is irrelevant as long as it is valid. However, using offsets is not typically recommended due to the lack of validation of the column being accessed.
re the comment :-
I just need the date part
As the date is a recognised DateTime format (and also that such formats are directly sortable/orderable), use max(date(column_name)) or even max(column_name).

SQLite Indexing?

My app is using an external SQLite database. The database is created using DB Browser for SQLite software. I am using the following method to query my table with the column ENGLISH (same as en_word). However, problem is the query is slow when my database become large.
public static final String ENGLISH = "en_word";
public static final String TABLE_NAME = "words";
String sql = "SELECT * FROM " + TABLE_NAME +
" WHERE " + ENGLISH + " LIKE ? ORDER BY LENGTH(" + ENGLISH + ") LIMIT 100";
SQLiteDatabase db = initializer.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.rawQuery(sql, new String[]{"%" + englishWord.trim() + "%"});
List<Bean> wordList = new ArrayList<Bean>();
while(cursor.moveToNext()) {
String english = cursor.getString(1);
String mal = cursor.getString(2);
wordList.add(new Bean(english, mal));
}
return wordList;
} catch (SQLiteException exception) {
exception.printStackTrace();
return null;
} finally {
if (cursor != null)
cursor.close();
}
I tried to create index using DB Browser for SQLite.
CREATE INDEX `kindx` ON `words` ( `en_word` )
However, do I need to modify my code so that my app will query the database using this index? If so, how to do that?
The problem is that SQLite, like most relational databases, can use an index when the parameter to a 'like' clause ends with a wildcard, it cannot use an index when the parameter begins with a wildcard.
So, for this type of query, the index will not be used, and you wind up with a full table scan. This is why it is slower with a large number of rows.
You are actually attempting to do what is known as "full text search", which is not really possible to do efficiently without database features to support it directly.
I have not tried it, but I see that SQLite does have full-text search capabilities, and that it is supported on Android. See Full text search example in Android for an example.

How to Fetch data from database by passing two numbers as parameters?

i want to get some data from my database by passing 2 values like
getdata("1","5");
My function in database and the id is stored as String in database.
getdata(String FromMember_id,String ToMember_id){
}
I have also cast the id to get data but I didnt get data
Query is
"SELECT * FROM MilkCollection WHERE cast(member_code as REAL)
BETWEEN '"+ FromMember_id + "' AND '" +ToMember_id+ "' ";
When is use this Query without casting
"SELECT * FROM MilkCollection WHERE member_code '"+ FromMember_id + "'
AND '" + ToMember_id + "'"
and when i dont cast the member_code and do the same query it shows data but it shows the other data other id like 17,18,19 i guess its taking the starting value of 17,18,19 because there is 1 in starting of them.
As i understand your issue, you need to type cast your passing values to REAL which sure gives you a workaround solutions.
"SELECT * FROM MilkCollection WHERE cast(member_code as REAL) >='"+ FromMember_id + "' AND cast(member_code as REAL) <= '" + ToMember_id + "' ";
Description: Just because you are passing string values using your member function which causes uncertainty while fetching data from your database.
Here is some working code for sending objects for a get. You can compare and see where you have taken a misstep.
public String getPreachLogString(long day, String time) {
String selectQuery;
SQLiteDatabase db;
Cursor cursor;
String myPath = DATABASE_PATH + DATABASE_NAME;
db = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
selectQuery = "SELECT * FROM TABLE WHERE day = " + day + " AND time = \"" + time + "\"";
cursor = db.rawQuery(selectQuery, null);
To use strings in search I wrap string in \" where you wrap in '. Not sure why you are storing member IDs as string, assuming you have letters involved, but try casting member_code as TEXT to make a logical comparison between the 2 objects.
What you are trying to accomplish is possible I do it all the time. You just need to compare properly.
public ArrayList<String> getMonthlyWork(int year, int month) {
long thismonth = 0;
long nextmonth = 0;
String selectQuery;
SQLiteDatabase db;
ArrayList<String> result = new ArrayList<>();
SimpleDateFormat formatDate = new SimpleDateFormat("dd-MM-yyyy", Locale.US);
String myPath = DATABASE_PATH + DATABASE_NAME;
Calendar c = Calendar.getInstance();
c.set(year, month, 1);
thismonth = c.getTimeInMillis();
if (month < 11) {
c.set(Calendar.MONTH, month + 1);
} else {
c.set(Calendar.MONTH, 0);
c.set(Calendar.YEAR, year + 1);
}
nextmonth = c.getTimeInMillis();
db = SQLiteDatabase.openDatabase(myPath, null, SQLiteDatabase.OPEN_READONLY);
selectQuery = "SELECT * FROM TABLE WHERE day >= " + thismonth + " AND day < " + nextmonth;
Seems like John Bravdo already given you a working example
Let me address following 2 areas:
1. Why following SQL is not returning result?
Code:
"SELECT * FROM MilkCollection WHERE cast(member_code as REAL)
BETWEEN '"+ FromMember_id + "' AND '" +ToMember_id+ "' ";
With parameter value:
SELECT * FROM MilkCollection WHERE cast(member_code as REAL)
BETWEEN 1 AND 5;
Answer: Your table does not has any record containing value 1 to 5, but it does has record with value 17, 18, 19. Try pass the value 17 and 19 instead of 1 and 5, then it should return value. Moreover, test the SQL in database directly to confirm it does return data before checking in your program
Poor performance
Using a function call on any indexed column, e.g. cast(member_code as REAL), will not able to make use of index (regular index). Therefore, this SQL going to scan the entire table. You will start to see poor performance as the table size grow. Therefore, you need to use a column that is integer type in order to filter by integer value. You can add this column later after you have bandwidth. If the table is just a 1MB in disk, then you need to consider how many concurrent users are going to trigger this SQL call. If there are 1000 users running it concurrently, then it will cause 1GB of disk I/O (although DB engine could cache it) and it will be slow as well

Fastest way to search through strings stored in sqlite database

I have large number of strings, approximately 15,000 that I stored in a SQLite database using the following code:
void addKey(String key, String value, String table) {
SQLiteDatabase db = this.getWritableDatabase();
ContentValues values = new ContentValues();
values.put(KEY_KEY, key); // Contact Name
values.put(KEY_VALUE, value); // Contact Phone
// Inserting Row
db.insert(table, null, values);
db.close(); // Closing database connection
}
And then i search through that database using the following method in order to pick out any strings that match the key im looking for:
public String searchKeyString(String key, String table){
String rtn = "";
Log.d("searchKeyString",table);
// Select All Query
String selectQuery = "SELECT * FROM " + table;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Log.d("searchKeyString","searching");
if(cursor.getString(1).equals(key))
rtn = rtn + "," + cursor.getString(2);
} while (cursor.moveToNext());
}
cursor.close();
db.close();
Log.d("searchKeyString","finish search");
return rtn;
}
The goal is to do this in real time as the user is typing on the keep board so response time is key and the way it stands now it takes over a second to run through the search.
I considered reading all of the items into an array list initially and sorting through that which might be faster, but i thought an array list of that size might cause memory issues. What is the best way to search through these entries in my database?
A couple of things you can do...
Change the return to a StringBuilder until the end.
Only use a readable version of the database (that's probably not making much difference though)
Do not get a new instance of the database every time, keep it opened until you don't need it anymore
Query for only what you need with the "WHERE" argument in the SQL query.
See the code below with some changes:
// move this somewhere else in your Activity or such
SQLiteDatabase db = this.getReadableDatabase();
public String searchKeyString(String key, String table){
StringBuilder rtn = new StringBuilder();
Log.d("searchKeyString",table);
// Select All Query
String selectQuery = "SELECT * FROM " + table + " WHERE KEY_KEY=?";
Cursor cursor = db.rawQuery(selectQuery, new String[] {key});
// you can change it to
// db.rawQuery("SELECT * FROM "+table+" WHERE KEY_KEY LIKE ?", new String[] {key+"%"});
// if you want to get everything starting with that key value
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
do {
Log.d("searchKeyString","searching");
rtn.append(",").append(cursor.getString(2));
} while (cursor.moveToNext());
}
cursor.close();
Log.d("searchKeyString","finish search");
return rtn.toString();
}
Note even if you want this to happen in "real-time" for the user, you will still need to move this to a separate Thread or ASyncTask or you are going to run into problems....
You should consider using SELECT * FROM your-table LIMIT 50, for example. And you can put two buttons "Back", "Next" on your view. If every page has max 50 items, the user is at page 1, and he taps "Next", then you can use this query:
SELECT * FROM your-table LIMIT 50 OFFSET 50
If your table contains most of text-data, and you want to integrate search deeply into your app, consider using virtual table with FTS.
Let sqlite do the hard lifting.
First off, add an index to the field you're searching for, if you don't have one already. Secondly, don't do a SELECT all with manual table scan, but rather use a query in the form
SELECT column_value
FROM my_table
WHERE column_key LIKE "ABC%"
This returns the least amount of data, and the sql engine uses the index.
i dunno about better but maybe it'd be faster to make queries for the selected strings one by one.
public String searchKeyString(String key, String table){
String rtn = "";
Log.d("searchKeyString",table);
// Select All Query
String selectQuery = "SELECT * FROM " + table + "WHERE column_1 = " + key;
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor = db.rawQuery(selectQuery, null);
// looping through all rows and adding to list
if (cursor.moveToFirst()) {
rtn = rtn + "," + cursor.getString(2);
}
cursor.close();
db.close();
Log.d("searchKeyString","finish search");
return rtn;
}
EDIT:
Well i dunno how those custom keyboard apps do it, but those AutoCompleteTextViews are hooked up to adapters. you could just as easily make a cursorAdapter and hook your auto-complete view to it.
http://www.outofwhatbox.com/blog/2010/11/android-autocompletetextview-sqlite-and-dependent-fields/
http://www.opgenorth.net/blog/2011/09/06/using-autocompletetextview-and-simplecursoradapter-2/

Complex Update Query with Nested Select Android SQLite

android noob... I have two tables, with a one to many relationship between country_tbl and city_tbl, and I would like to concatenate values from city_tbl.landmark_col with GROUP_CONCAT() and INSERT all the landmark_col values as a single String into country_tbl.all_landmarks column. The SQL seems to require a nested SELECT to concatenate the landmark_col values before passing them to the country_tbl... something like:
UPDATE country_tbl
SET country_tbl.all_landmarks = (SELECT landmarks_col FROM
(SELECT country_id, group_concat(landmarks_col)
FROM city_tbl INNER JOIN country_tbl
ON country_tbl.country_id = city_tbl.country_id
GROUP BY country_tbl.country_id)
AS country_landmarks
WHERE country_tbl.country_id = country_landmarks.country_id)
WHERE
EXISTS (
SELECT *
FROM country_landmarks
WHERE country_tbl.country_id = country_landmarks.country_id
);
Not sure if nested select statements are even supported or if just too resource intensive... there must be a better way, as it seems like using rawQuery is not the best solution. Not sure if I should be creating temporary tables, using ContentProviders, or passing a cursor...?
I answered this by splitting up the long SQL query into two parts. First I created a subquery with a SQLiteQueryBuilder and ran using rawQuery to get a two column cursor with the location_id and the group_concat values for the landmark_names. I was then able to cycle through the cursor to update the country table with each of the appropriate concatenated values of all the landmark names for that country.
The query below is a tad more complicated than the question above (which I simplified before posting), only because I had to join a landmarks list table with another landmark_type table by the landmark_type_id, and my real goals was to concatenate the shorter list of landmark_type by country, not the long list of all the landmark_names by country. Anyway, it works.
public void UpdateCountryLandmarks() throws SQLException {
Cursor c = null;
String subquery = SQLiteQueryBuilder.buildQueryString(
// include distinct
true,
// FROM tables
LANDMARK_TYPE_TABLE + "," + LANDMARKS_TABLE,
// two columns (one of which is a group_concat()
new String[] { LANDMARKS_TABLE + "." + LOCATION_ID + ", group_concat(" + LANDMARK_TYPE_TABLE + "." + LANDMARK_TYPE + ",\", \") AS " + LANDMARK_NAMES },
// where
LANDMARK_TYPE_TABLE + "." + LANDMARK_ID + "=" + LANDMARKS_TABLE + "." + LANDMARK_TYPE_ID,
// group by
LANDMARKS_TABLE + "." + LOCATION_ID, null, null, null);
c = mDb.rawQuery(subquery, null);
if (c.moveToFirst()) {
do {
String locationId = c.getString(c.getColumnIndex(LOCATION_ID));
String landmarkNames = c.getString(c.getColumnIndex(LANDMARK_NAMES));
ContentValues cv = new ContentValues();
cv.put(LANDMARK_NAMES, landmarkNames);
mDb.update(COUNTRY_TABLE, cv, LOCATION_ID + "=" + locationId, null);
} while (c.moveToNext());
}
c.close();
}

Categories

Resources