matching multiple title in single query using like keyword - android

matching multiple title in single query using like keyword
I am trying to get all records if that matches with given titles.
below is the structure of database please see
database screenshot
when i pass single like query it returns data
#Query("SELECT * FROM task WHERE task_tags LIKE '%\"title\":\"Priority\"%'")
when i try to generate query dynamically to search multiple match it return 0 data
val stringBuilder = StringBuilder()
for (i in 0 until tags.size) {
val firstQuery = "%\"title\":\"Priority\"%"
if (i == 0) {
stringBuilder.append(firstQuery)
} else stringBuilder.append(" OR '%\"title\":\"${tags[i].title}\"%'")
}
this is function I have made
#Query("SELECT * FROM task WHERE task_tags LIKE:tagQuery ")
fun getTaskByTag(stringBuilder.toString() : String): List<Task>

The single data is fine. However, you simply cannot use the second method.
First you are omitting the space after LIKE,
Then you are omitting the full test i.e. you have task_ tags LIKE ? OR ?? when it should be task_tags LIKE ? OR task_tags LIKE ?? ....
And even then, due to the way that a parameter is handled by room the entire parameter is wrapped/encased as a single string, so the OR/OR LIKE's all become part of what is being searched for as a single test.
The correct solution, as least from a database perspective, would be to not have a single column with a JSON representation of the list of the tags, but to have a table for the tags and then, as you want a many-many relationship (a task can have many tags and a single tag could be used by many tasks) an associative table and you could then do the test using a IN clause.
As a get around though, you could utilise a RawQuery where the SQL statement is built accordingly.
As an example:-
#RawQuery
fun rawQuery(qry: SimpleSQLiteQuery): Cursor
#SuppressLint("Range")
fun getTaskByManyTags(tags: List<String>): List<Task> {
val rv = ArrayList<Task>()
val sb=StringBuilder()
var afterFirst = false
for (tag in tags) {
if (afterFirst) {
sb.append(" OR task_tags ")
}
sb.append(" LIKE '%").append(tag).append("%'")
afterFirst = true
}
if (sb.isNotEmpty()) {
val csr: Cursor = rawQuery(SimpleSQLiteQuery("SELECT * FROM task WHERE task_tags $sb"))
while (csr.moveToNext()) {
rv.add(
Task(
csr.getLong(csr.getColumnIndex("tid")),
csr.getString(csr.getColumnIndex("task_title")),
csr.getString(csr.getColumnIndex("task_tags"))))
// other columns ....
}
csr.close()
}
return rv
}
Note that the complex string with the embedded double quotes is, in this example, passed rather than built into the function (relatively simple change to incorporate) e.g. could be called using
val tasks1 = taskDao.getTaskByManyTags(listOf()) would return no tasks (handling no passed tags something you would need to decide upon)
val tasks2 = taskDao.getTaskByManyTags(listOf("\"title\":\"Priority\""))
val tasks3 = taskDao.getTaskByManyTags(listOf("\"title\":\"Priority\"","\"title\":\"Priority\"","\"title\":\"Priority\"")) obviously the tags would change
Very limited testing has been undertaken (hence just the 3 columns) but the result of running all 3 (as per the above 3 invocations) against a very limited database (basically the same row) results in the expected (as per breakpoint):-
the first returns the empty list as there are no search arguments.
the second and third both return all 4 rows as "title":"Priority" is in all 4 rows
the main reason for the 3 search args was to check the syntax of multiple args, rather than whether or not the correct selections were made.
The resultant query of the last (3 passed tags) being (as extracted from the getTaskaByManyTags function):-
SELECT * FROM task WHERE task_tags LIKE '%"title":"Priority"%' OR task_tags LIKE '%"title":"Priority"%' OR task_tags LIKE '%"title":"Priority"%'

Related

How to search for any combination of one or more words, regardless of their order using Android Room

I have been able to achieve a basic search capability in Android Room + FTS with the following query as an example in my Dao:
#Query("SELECT * FROM Conversation JOIN ConversationFts ON Conversation.id == ConversationFts.id WHERE ConversationFts.title LIKE :text GROUP BY Conversation.id")
public abstract DataSource.Factory<Integer, Conversation> search(String text);
Where the text is passed along between percentage characters, as such %lorem%.
This example works perfectly for the search of a single word and I want to expand this to be able to search for one or more words with the condition that they do not need to be in the order they are entered, but they must have a match for all the words. This means this has to be an AND and not OR situation.
I've found a few examples of SQL queries, but they require a specific query tailored for each case, for example: LIKE text AND LIKE another AND LIKE, etc... which is not a solution.
How can this be achieved? And to make it clear, I am not looking for a raw query solution, I want to stick to Room as much as possible, otherwise, I'd rather just use this as it is than to resort to raw queries.
EDIT: Adding an example per request
I search for do the results returned include all matches that contain do in the title, even if it is a partial match
I search for do and test the results returned include all matches that contain do and test, but in no specific order, even if they are partial matches.
However, if just one of them cannot be found in the text then it will not be returned in the results. For example, if do is found, but test is not then it will not be part of the results.
I think there is no way to do that except creating a raw query dynamically. You can write another method, something like this:
public abstract class ConversationDao {
public DataSource.Factory<Integer, Conversation> search(String text) {
StringBuilder builder = new StringBuilder();
String[] words = text.split("\\s+");
if (words.length > 0) {
builder.append(" WHERE ");
}
for (int i = 0; i < words.length; i++) {
builder.append("ConversationFts.title LIKE %").append(words[i]).append("%");
if (i < words.length - 1) {
builder.append(" AND ");
}
}
SupportSQLiteQuery query = new SimpleSQLiteQuery(
"SELECT * FROM Conversation JOIN ConversationFts ON Conversation.id == ConversationFts.id"
+ builder.toString()
+ " GROUP BY Conversation.id"
);
return search(query);
}
#RawQuery
public abstract DataSource.Factory<Integer, Conversation> search(SupportSQLiteQuery query);
}

Firestore query StartAt(DocumentReference) not giving proper result

In my firestore database,there are 12+ documents.I am getting the first 3 documents correctly by calling the below function on button click. But on the secondclick, though the documentReference is passed correctly, its not retrieving any data.The querySnapshot size is coming 0. What could be the problem.
Given below is the declaration
private val db: FirebaseFirestore = FirebaseFirestore.getInstance()
private val colRef: CollectionReference = db.collection("Notebook")
private var lastResult: DocumentReference? = null
private lateinit var query: Query
and below is the onButtonClick code :
private fun loadNoteNew() {
#Suppress("SENSELESS_COMPARISON", "LiftReturnOrAssignment")
if (lastResult == null) {
query = colRef.orderBy("priority")
.limit(3)
} else {
Log.i(TAG, "Start ${lastResult!!.id}")
query = colRef.orderBy("priority")
.startAfter(lastResult)
.limit(3)
}
Log.i(TAG, "before get")
query.get()
.addOnSuccessListener { querySnapshot ->
var data = ""
Log.i(TAG, "querySnapshot Size : ${querySnapshot.size()}")
if (lastResult != null) {
Log.i(TAG, "querySnapshot ID : ${lastResult!!.id}")
}
for (snapshot in querySnapshot) {
val note = snapshot.toObject(Note::class.java)
note.id = snapshot.id
val title = note.title
val desc = note.description
val priority = note.priority
data += "${note.id} \nTitle =$title \nDescription = $desc\nPriority : $priority\n\n"
}
if (querySnapshot.size() > 0) {
data += "---------------\n\n"
textView_loadData.append(data)
lastResult = querySnapshot.documents[querySnapshot.size() - 1].reference
Log.i(TAG, lastResult!!.id)
}
}
}
Given below is the logcat for first click
I/FireStoreExample: before get
I/FireStoreExample: querySnapshot Size : 3
I/FireStoreExample: P9hIw4Ai7w4IHP6H3ew3
and given below is the logcat of second click
I/FireStoreExample: Start P9hIw4Ai7w4IHP6H3ew3
I/FireStoreExample: before get
I/FireStoreExample: querySnapshot Size : 0
I/FireStoreExample: querySnapshot ID : P9hIw4Ai7w4IHP6H3ew3
Please help me find out,where i am getting it wrong.
Thanks
The second query result is empty because of a misunderstanding on the semantics of query pagination using startAt and startAfter methods.
Let's say the Notebook collection contains N documents. When you make the first query you're asking for the first 3 documents ordered by the priority field so the query is returning documents 1..3. Then upon the second click you're expecting the query to return the next 3 results so indeed you're expecting documents 4..6. The keypoint here is that both startAt and startAfter paginate based on the value of the ordered field rather than with the last document retrieved. Overall the semantics of startAt and startAfter are roughly as follows.
orderby(X).startAt(Y) => Return documents whose X field is greater than or equal Y
orderby(X).startAfter(Y) => Return documents whose X field is strictly greater than Y
With that in mind, let's examine what the code is actually doing when you make the second query:
// At the end of the first query...
lastResult = querySnapshot.documents[querySnapshot.size() - 1].reference
// Second query
query = colRef.orderBy("priority")
.startAfter(lastResult)
.limit(3)
In the code above you're asking for the documents whose "priority" field is greater than document reference "P9hIw4Ai7w4IHP6H3ew3" and indeed there are no documents greater than that, therefore the result set is empty. Here is api reference for both.
There is yet another thing to note. Because these methods filter upon the fields value the position of the cursor could be ambiguous. For instance, if you have 4 documents with priority 3 and already retrieved the leading three if you set startAfter(3) you'll be missing a document. Similarly, if startAt(3) were to be made you'll get back the same three documents. This is also pointed out in the documentation. All in all you have a couple of options to make this work as intended:
Add another orderby in another field so that documents are uniquely identified by the combination so to prevent any cursor ambiguity and be able to use startAfter with guarantees. Next snippet build upon the doc samples and your code.
// first query
query = colRef.orderBy("priority")
.orderBy("AnotherField")
.limit(3)
// Save last document
lastResult = querySnapshot.documents[querySnapshot.size() - 1]
// Second and next queries
query = colRef.orderBy("priority")
.orderBy("AnotherField")
.startAfter(lastResult)
.limit(3)
Lastly remember that it might be simpler to just query all the documents if they're not many and delay optimizations until they become a performance issue.

Can you chain multiple whereEqualTo operations in one query in pieces in Firestore

In my app, I have a multiple-choice dialog with various filter options that the user should be able to choose in order to filter the database based on the rarity field of the documents. Since there are many options in the filter dialog, covering each case by hand would take ages if we take into account all the possible combinations of the filters. With that in mind, I tried creating a starting query as you can see below and then I iterate through the list of filters selected by the user and try to add a whereEqualTo("rarity",filter) operation to the query for each filter. I noticed that you can't concatenate queries like with normal variables e.g. var i += 5 so i would like to know if there is any solution to this kind of issue. Can you actually apply multiple whereEqualTo operations in the same query in steps/pieces without overriding the previously applied operations on that same query?
Here's what I've tried after receiving the filters selected by the user in my FilterActivity.kt class:
class FilterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_filter)
val db = FirebaseFirestore.getInstance()
val filters:ArrayList<String>? = intent.getStringArrayListExtra("filterOptions")
Log.d("FilterActivity", "filter options $filters")
var query = db.collection("Cards").orderBy("resID")
for(filter in filters!!) {
query = query.whereEqualTo("rarity",filter)
}
query.get().addOnSuccessListener { querySnapshot ->
if(querySnapshot.isEmpty) Log.d("FilterActivity","is empty")
for(doc in querySnapshot.documents) {
Log.d("FilterActivity", "${doc.getString("name")} - ${doc.getString("rarity")}")
}
}
}
}
Basically you are trying a do OR operation, where you are retrieving all documents, in which rarity fields matched any of the value in array.
You are try new firebase whereIn operation where you can pass array of values, but theres a limitation of max 10 values in filter
FirebaseFirestore.getInstance()
.collection("Cards")
.orderBy("resID")
.whereIn("rarity",filters!!.toList())
.get().addOnSuccessListener { querySnapshot ->
if (querySnapshot.isEmpty) Log.d("FilterActivity", "is empty")
for (doc in querySnapshot.documents) {
Log.d("FilterActivity", "${doc.getString("name")} - ${doc.getString("rarity")}")
}
}
filters arraylist can contain max 10 values
Can you chain multiple whereEqualTo operations in one query in pieces in Firestore
You can chain as may whereEqualTo operations as you need.
The problem in your code += operator. There is no way you can make an addition/concatenation of two Query objects. To solve this, please change the following line of code:
query += query.whereEqualTo("rarity",filter)
to
query = query.whereEqualTo("rarity",filter)

Kotlin / SQLite delete() in batch issue

I would like to delete mutiple items from SQLite in batch basing on their ID column.
What I have is a HashMap which contains objects which one of field is pID (unique ID in DB).
So, here's my code:
/*
Delete rows from DB
*/
val selection = "${BaseColumns._ID} = ?"
// Create a list of product ID's to delete
val dbDeletor = dbHelper.writableDatabase
// Temp array to store ID's in String format
val tempIDs = ArrayList<String>()
// Loop through array of items to be deleted
for(i in ProductsRecyclerAdapter.productsToDeleteArray)
tempIDs.add(i.value.pID.toString())
// Perform deletion in DB
val deletedRowsCount = dbDeletor.delete(ProductsEntry.TABLE_NAME, selection, tempIDs.toTypedArray())
// Show snackbar with count of deleted items
Snackbar.make(mainCoordinatorLayout, "Products deleted: $deletedRowsCount", Snackbar.LENGTH_SHORT).show()
Everything works great when I'm deleting only 1 item but if tempIDs array contains 2 or more I'm receiving following Exception:
Too many bind arguments. 3 arguments were provided but the statement needs 1 arguments.
Maybe the reason is that I'm converting pID of type Long into String in order to delete rows in batch? I did not find any other solution. Please take a look and comment.
Your query looks somewhat like that:
DELETE FROM ProductsEntry.TABLE_NAME WHERE BaseColumns._ID = ?
There is only 1 argument ? but you're passing 3 values (IDs). You want to use IN statement instead, and print your params separated by comma:
// IN instead of equal to compare multiple values
val selection = "${BaseColumns._ID} IN (?)"
// your code to obtain IDs here
// .....
// combine all values into single string, ie. 1, 2, 3, 4 and wrap it as an array
val selectionArg = arrayOf(tempIDs.joinToString())
// Perform deletion in DB
val deletedRowsCount = dbDeletor.delete(ProductsEntry.TABLE_NAME, selection, selectionArg)

How to check if record exists or not using Anko?

I am learning to fetching data from sqlite using anko. I can print the data successfully (if the record exist) but my application always crash when the data doesn't exist.
the error says:
parseSingle accepts only cursors with a single entry
I know exactly the meaning of error, I just dont know how to solve it.
here is the code for query:
fun getUserByUid(uid: Int): UserModel
{
val data = context.database.use {
val db = context.database.readableDatabase
val columns = UserModel.COLUMN_ID + "," + UserModel.COLUMN_NAME + "," + UserModel.COLUMN_API_KEY
val query = db.select(UserModel.TABLE_NAME, columns)
.whereArgs("(uid = {userId})",
"userId" to uid)
query.exec {
val rowParser = classParser<UserModel>()
parseSingle(rowParser) // this line that trigger error exception
}
}
return data
}
I tried to find count function in query or rowParser variable to check if the record exist or not but could not find it.
From the wiki page.
https://github.com/Kotlin/anko/wiki/Anko-SQLite#parsing-query-results
Parsing query results
So we have some Cursor, and how can we parse it into regular classes? Anko provides functions parseSingle, parseOpt and parseList to do it much more easily.
Method Description
parseSingle(rowParser): T Parse exactly one row
parseOpt(rowParser): T? Parse zero or one row
parseList(rowParser): List Parse zero or more rows
Note that parseSingle() and parseOpt() will throw an exception if the received Cursor contains more than one row.

Categories

Resources