What is wrong with my sqlite3 CASE statement? - android

My code follows:
SELECT COUNT(_id) AS count FROM general WHERE _id = 1 CASE WHEN count > 0 THEN UPDATE general SET userGivenId = 'xxx' WHERE _id = 1 ELSE INSERT INTO general (userGivenId) VALUES ('xxx' ) END
With the error:
android.database.sqlite.SQLiteException: near "CASE": syntax error: , while compiling: SELECT COUNT(_id) AS count FROM general WHERE _id = 1 CASE WHEN count > 0 THEN UPDATE general SET userGivenId = 'xxx' WHERE _id = 1 ELSE INSERT INTO general (userGivenId) VALUES ('xxx' ) END
This is the shortest query I will use. Why I do this is because my other queries will have rows that needs to be updated but some may not be touched. Using replace will replace all the data (at least that is how it works for me on my Android phone). For instance my File class will have a filePath, but sometimes the response from the server will return null and I am only to update the File IF the server returns a new File.
Did I forget to write anything?

SQLite does not have any control logic because this would not make sense in an embedded database (there is no separate server machine/process whose asynchronous execution could improve performance).
CASE can be used only for expressions, not for entire commands.
Handle the control logic in your app:
Cursor c = db.rawQuery("SELECT 1 FROM general WHERE _id = 1", null);
if (c.moveToFirst())
db.execSQL("UPDATE general SET userGivenId = 'xxx' WHERE _id = 1");
else
db.execSQL("INSERT INTO general (userGivenId) VALUES ('xxx')");
For these particular commands, if you have a unique constraint on the _id column (and a primary key is constrained to be unique), you can use the INSERT OR REPLACE command:
INSERT OR REPLACE INTO general(_id, userGivenId) VALUES(1, 'xxx')

Related

Android SQLite additional WHERE condition after MATCH

In Android SQLite i got tabel like this
domainObjectId: String // like '9876543210'
name: String
description: String
I want to use FTS on this to search without worrying about diacritical marks, how ever i want to let user select also by typing part of object ID(ex. last 4 char)
I got select like
`SELECT * FROM tabel LEFT JOIN tabel_fts on tabel_fts.domainObjectId = tabel.domainObjectId WHERE tabel_fts MATCH '3210*' OR tabel.domainObjectId LIKE '%3210%'
But in return i get error
unable to use function MATCH in the requested context (code 1 SQLITE_ERROR);
Is this possible to add additional condition to select with MATCH?
Try to remove "MATCH" into separate "SELECT":
`SELECT * FROM tabel LEFT JOIN (select * from tabel_fts WHERE tabel_fts.domainObjectId MATCH '3210*') as tabel_fts WHERE tabel.domainObjectId LIKE '%3210%' OR table_fts.ID IS NOT NULL
By the way:
In your "WHERE tabel_fts" it seemed you've missed a column name
There is no "ON" condition in tables JOINm just "WHERE". That's OK? May be it would be better to use UNION?

Android SQLite rawquery not working for a query that works in sqlite3 command line & dbeaver

I am writing an android app for a condo complex to aid locating utilities for residents. I have multiple tables and am having trouble retrieving data from 1 of them, but only for 1 query out of 3. Note that I am using SQLiteDatabase.rawquery. Also, the query works in sqlite3 command line and dbeaver. The first usage of the app should have the column 'yours' all 0's (boolean false) and let the user later set theirs to 1 (true). So, first call should return no rows. Later calls 1 row.
Table definition is as follows:
CREATE TABLE units (
building int4 NOT NULL,
unit int4 NULL,
stack int4 NULL,
unit_id int4 NOT NULL,
stack_id int4 NOT NULL,
yours int4 NULL,
CONSTRAINT units_pk PRIMARY KEY (unit_id));
Query that fails in android but works in command line & dbeaver is as follows. I copied the non-args version straight into command line and it works aside from adding the semicolon. The args variation did not work either.
String query = "SELECT * FROM units WHERE yours = 1";
//String query = "SELECT * FROM units WHERE yours = ?";
//String[] args = new String[]{"1"};
SQLiteDatabase db = this.getReadableDatabase();
Cursor cursor = null;
try {
cursor = db.rawQuery(query, null);
//cursor = db.rawQuery(query, args);
The following 2 queries work, one with the asterisk (*) wildcard.
List<String> units = new ArrayList<>();
String query = "SELECT unit FROM units WHERE building = " + buildingSelected
+ " ORDER BY unit";
SQLiteDatabase db = this.getWritableDatabase();
Cursor cursor;
try {
cursor = db.rawQuery(query, null);
cursor = db.rawQuery("SELECT * FROM units ORDER BY unit_id", null);
I am at a loss as to what is wrong and can't find an answer on the internet based upon my questions.
10/2/2019 Added the following to explain "not working".
From sqlite3 command line, ran the SQL statement to select all rows where yours = 1. No rows come back as expected since all rows have yours = 0 (false). Then update 1 row to set yours to 1. Ran same SQL statement and get 1 row as expected.
SQLite version 3.28.0 2019-04-16 19:49:53
Enter ".help" for usage hints.
sqlite> .tables
electric_meters gas_meters units water_valves
sqlite> SELECT * FROM units WHERE yours = 1;
sqlite>
sqlite> UPDATE units SET yours = 1 WHERE unit_id = 4561205;
sqlite> SELECT * FROM units WHERE yours = 1;
4561|205|5|4561205|45615|1
sqlite>
Run UPDATE again to put row back to yours = 0 to run app in studio. Updated code after rawQuery call to getCount() in variable rows.
cursor = db.rawQuery(query, null);
//cursor = db.rawQuery(query, args);
int rows = cursor.getCount();
And while I am gathering this information, I found a code error that was causing issues with the debug output. While the data looks valid with the right formats, I am not expecting rows to return but the 2 rows returned do have yours = 1. Note the code error was after the rawQuery call and during the extraction of the values in cursor into a Unit class instance.
This is probably bad form to change my question in the middle. The database I am looking at through sqlite3 command line is in the assets directory of the project. The app is using the one on the AVD under /data/user/0/.... They might be different. I have done a clean project a few times. I thought that would reset everything, including updating the database the app uses in the AVD. Can someone tell me how to make sure the database in the project gets repopulated into the AVD data structure?
I found a solution to my issue. Turns out the database in the AVD file structure was not getting updated when I thought it would: clean project, synchronise files, etc. The AVD database had values for yours = 1, so it was responding correctly. Ended up deleting the databases and then uploading from the assets project folder. Probably a better way, but I could not find it. Thanks.

android update database column based on the current column value

In android, SQLiteDatabase has a update function
update(String table, ContentValues values, String whereClause, String[] whereArgs)
new values in put in values
If I want to update a column A by adding one to it, how should I prepare the ContentValues values variable? I don't think the following would work.
cv.put("A", "A" + 1);
I can sure run execSQL with raw sql, but it does not return num of row updated
If you'd execute a raw query, something like this should work to increment the current value in the column:
UPDATE table_name SET column_a = column_a + 1 WHERE _id = 1
(where 1 is just an example to illustrate how to apply it to a specific row)
The same probably wouldn't work with ContentValues, since (as the name indicates) it takes the values to set the column to. That means it needs to have been evaluated before building the ContentValues, whereas with a raw query the value isn't evaluated until the query actually runs on the database.
You can of course retrieve the current value first and then increment that accordingly when issuing an update; that requires a select query first. Quite commonly though, you're working with objects in Java, where the column value for a row is bound up to a member field of the object. If you've got a setup like that, then you probably already have the current value at the moment you want to run an update query.
As such, it would just look somewhat like:
SomeObject object = ...;
cv.put("column_a", object.getSomeValue() + 1);
(where I'm assuming object.getSomeValue() will return an int)
// edit: here's some more examples for the raw query approach:
SQLite - increase value by a certain number
// edit2: You've edited your original question and added:
I can sure run execSQL with raw sql, but it does not return num of
row updated
If knowing how many rows the query changed is a must, then you can potentially leverage the changes() function. It still means you're going to have to run a second query though.
SELECT changes() FROM table_name
The docs say:
The changes() function returns the number of database rows that were
changed or inserted or deleted by the most recently completed INSERT,
DELETE, or UPDATE statement, exclusive of statements in lower-level
triggers. The changes() SQL function is a wrapper around the
sqlite3_changes() C/C++ function and hence follows the same rules for
counting changes.
Alternatively, you could look into the rawQuery() method that takes an SQL statement and returns the result as a Cursor. Not sure if it that even works for an update query, or whether the result would be anything sensible, but if you're really lucky, you may find that Cursor.getCount() gives you the number of affected rows.
To expand upon #MH's solution, there actually is a way to do a raw update AND get back the number of rows updated (because I'm doing the same thing in one of my projects). You have to use a compiled SQLiteStatement and then call the method executeUpdateDelete(). From the documentation:
public int executeUpdateDelete ()
Execute this SQL statement, if the the number of rows affected by execution of this SQL statement is of any importance to the caller - for example, UPDATE / DELETE SQL statements.
Returns
the number of rows affected by this SQL statement execution.
See the following sample code where I add a new column to my table and then update each column similarly to how you were asking:
db.beginTransaction();
try {
db.execSQL("ALTER TABLE " + TABLE_NAME + " ADD COLUMN "
+ COLUMN_NAME_LOCALTIME + " INTEGER");
String stmtString = "UPDATE " + TABLE_NAME + " SET "
+ COLUMN_NAME_LOCALTIME + "="
+ COLUMN_NAME_TIME + "+ (" + tzOffset + ")";
SQLiteStatement sqlStmt = db.compileStatement(stmtString);
int rows = sqlStmt.executeUpdateDelete();
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
I'm using a transaction here because in case I add the new column but CANNOT update the values, I want everything to rollback so I can attempt something else.

I'm trying to compile a query to cope with the no rows found, but using SELECT CASE blocks bindargs

with a simple query, using bindargs works fine, but as soon as I include select case ..... in the query it always fails "Can't pass bindargs for this sql", if I test with all the '?'s replaced with fixed values, it compiles and runs OK, so it does seem to be that argument binding cannot cope with selects embedded inside select case. Is this true?
the query I am compiling is:
(SELECT CASE
WHEN ((SELECT _id FROM point2D WHERE x=? AND y=?) IS NULL ) THEN 0
ELSE (SELECT _id FROM point2D WHERE x=? AND y=? LIMIT 1))
and the table is setup with
create table if not exists point2D ( _id INTEGER PRIMARY KEY AUTOINCREMENT,
x REAL, y REAL )
I only want a single compiled query that returns the key to an existing matching record, or a known 'its not there' value, but of course a simple query like
SELECT _id FROM point2D WHERE x=? AND y=? LIMIT 1
will cause an exception with simpleQueryForLong when there is nothing found (which is quite expensive in cpu time)
I cannot use insert or update, because update actually replaces the old row with a new row (and hence a new _id) which screws up all the other stuff pointing to this.
You have kept the actual parameter code a secret, but I'd guess that you provide only two parameters although the query has four.
You can use one parameter multiple times if you give it a name or a number:
(SELECT CASE
WHEN ((SELECT _id FROM point2D WHERE x=?1 AND y=?2) IS NULL ) THEN 0
ELSE (SELECT _id FROM point2D WHERE x=?1 AND y=?2 LIMIT 1))
However, for this particular query, you'd better use the ifnull function:
(SELECT ifnull((SELECT _id FROM point2D WHERE x=? AND y=?), 0))
(And I doubt that the outer subquery is actually needed.)

Delete rows with inner join?

I have a SQLITE database with two tables. Table A has an integer timestamp and another integer column containing a row id referring to a row in table B which has two timestamps.
I want to delete all rows in table A where it's timestamp does not lie between the two timestamps in table B, and the ROWID is equal to X.
Here is what I have at the moment but I am getting a syntax error:
DELETE FROM network
WHERE ROWID in (
SELECT ROWID
FROM track
INNER JOIN network ON (track.ROWID = network.trackId)
WHERE network.timestamp > track.stopTime OR network.timestamp < track.startTime
AND network.trackId = X
You don't have a closing parenthesis for your subselect. Try this:
DELETE FROM network
WHERE ROWID in (
SELECT ROWID
FROM track
INNER JOIN network ON (track.ROWID = network.trackId)
WHERE network.timestamp > track.stopTime OR network.timestamp < track.startTime
AND network.trackId = X
)
If that doesn't work, try posting your actual syntax error.

Categories

Resources