I have an Android app with a ContentProvider class that queries two tables, users and items.
users table has the following columns:
_id (primary key)
online (integer)
items table has the following columns:
_id (primary key)
user_id (foreign key, maps to users._id)
name (text)
I then have a query that returns the result of the two tables joined together. In my ContentProvider, I use this code to map the column names:
SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
queryBuilder.setTables("items INNER JOIN users ON users._id=items.user_id");
Map<String, String> columnMap = new HashMap<String, String>();
columnMap.put("item_id", "items._id");
columnMap.put("user_id", "items.user_id");
columnMap.put("user_online", "users.online");
columnMap.put("item_name", "items.name");
queryBuilder.setProjectionMap(columnMap);
However, when I perform the following query, to find all items which are owned by online users:
String[] projection = {"item_name"};
String selection = "user_online=1";
Cursor cursor = getContentResolver().query(uri, projection, selection, null, null);
I get the following exception:
android.database.sqlite.SQLiteException: no such column: user_online (code 1): , while compiling: SELECT items.name FROM items INNER JOIN users ON users._id=items.user_id WHERE (user_online=1)
The problem appears to be that setProjectionMap() affects projection, but not selection.
Is there any other way of solving this problem, short of performing string manipulation on the projection to map the column names manually?
setProjectionMap is needed only because it allows to the output column names of the query.
What you do in the selection is not visible in the resulting cursor, and in any case SQLiteQueryBuilder is not smart enough to parse SQL and replace the correct column names.
Just use the original column names:
String selection = "users.online=1";
To make column aliases available in the selection, create a view for your join:
CREATE VIEW user_items AS
SELECT items._id AS item_id,
items.user_id AS user_id,
users.online AS user_online,
items.name AS item_name
FROM items INNER JOIN users ON users._id=items.user_id;
Related
I just started learning of android and come to section of Database and I inserted same record in it but now I want to fetch data from database only by name and display it in textview.
Help me
Thank You in advance
Please follow developer document.
https://developer.android.com/training/basics/data-storage/databases.html
SQLiteDatabase db = mDbHelper.getReadableDatabase();
// Define a projection that specifies which columns from the database
// you will actually use after this query.
String[] projection = {
FeedEntry._ID,
FeedEntry.COLUMN_NAME_TITLE,
FeedEntry.COLUMN_NAME_SUBTITLE
};
// Filter results WHERE "title" = 'My Title'
String selection = FeedEntry.COLUMN_NAME_TITLE + " = ?";
String[] selectionArgs = { "My Title" };
// How you want the results sorted in the resulting Cursor
String sortOrder =
FeedEntry.COLUMN_NAME_SUBTITLE + " DESC";
Cursor cursor = db.query(
FeedEntry.TABLE_NAME, // The table to query
projection, // The columns to return
selection, // The columns for the WHERE clause
selectionArgs, // The values for the WHERE clause
null, // don't group the rows
null, // don't filter by row groups
sortOrder // The sort order
);
You access data by using a query which returns a Cursor.
A Cursor is like a spreadsheet table that contains columns and rows.
You tell the query what columns you want and imply the rows that will be returned via an optional WHERE statement.
The simplest of queries is base upon the SQL SELECT * FROM <table>;. This will select all columns (i.e. * means all columns) from the table as specified by <table> ( where would be replaced by a valid table name).
If you want specific columns then ***`` should be replaced with a comma delimited list e.g.SELECT name, address FROM would return a **Cursor** containing all the rows from the table with only the **name** and **address** columns from the table specified by`.
If you want to filter the rows returned then you can add a WHERE clause. e.g. SELECT name,address FROM <table> WHERE name = 'Fred', would return a Cursor containing only the rows that have Fred as the name column with only the name and address columns.
You cannot just type the SQL statments you need to either use the SQLiteDatabase rawQuery or query methods if you need to return a cursor.
Using rawQuery
rawQuery takes two parameters, the first being the SQL as a string, the second optional arguments (not covered here, so null will be used).
To obtain a Cursor with columns name and address and with only rows that have Fred you could use, assuming the table is called mytable :-
`Cursor mycursor = db.rawQuery("SELECT name,address FROM mytable WHERE name = 'Fred'";);`
where db is an instance of an SQLiteDatabase object.
However, rawQuery is not recommended as it is open SQL injection. rather it is recommended only for situations where it has to be used.
Using query
query has a number of overload variations as can be found here SQLiteDatabase.
For this example query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) will be used.
String table, is the name of the table to be queried.
String[] columns, is an array of column names.
String selection is the where clause (null for no where clause).
String[] selectionArgs is an array of arguments that replace the ? placeholder.
The rest of the parameters will be null as these are features that are not being utilised.
As such the code could be :-
String tablename = "mytable";
String[] columns = {"name", "address"};
String whereclause = "name=?"; //note use of placeholder ?
String[] whereargs = {"Fred"};
Cursor mycursor = db.query(tablename,
columns,
whereclause,
whereargs,
null,null,null
);
where db is an instance of an SQLiteDatabase object.
Accessing the Cursor
mycursor contains the data or perhaps not if there isn't a column with the name Fred.
The number of rows in the cursor can be obtained by using:-
int rowsincursor = mycursor.getCount();
Note! A returned Cursor will not be null. (a very common mistake)
To access the data you have to move through the Cursor. Initially the Cursor is before the first row. If you only expect or want the only/first row then you can use the Cursor moveToFirst method.
See Cursor for more move... methods etc
Once the Cursor is appropriately positioned you can use Cursor get methods to get the data. e.g. getString(int columnindex) will return the data as a String. columnindex is a 0 based offset of the column to be accessed. Using the Cursor's getColumnIndex(String columnname) can be used to eliminate errors made by miscalculating offsets.
As such the following could be used to set a TextView (note intentionally over cautious)
if (mycursor.getCCount() > 0) {
if (mycursor.moveToFirst()) {
mytextview.setText(mycursor.getString(mycursor.getColumnIndex("name")));
}
}
mycursor.close() // You should always close a cursor when done with it.
I've been reading up on BaseColumns](https://developer.android.com/reference/android/provider/BaseColumns.html) in Android to help structure my database schema.
I know that _ID is a unique identifier for the row that you have to create yourself:
protected static final String SQL_CREATE = "CREATE TABLE " + TABLE_NAME + "( " +
_ID + " INTEGER PRIMARY KEY AUTOINCREMENT" + ...;
I also read that _COUNT is used to refer to the number of rows in a table.
However, when I tried using _COUNT, I got an error. Here is what I tried:
SQLiteDatabase db = TimetableDbHelper.getInstance(context).getReadableDatabase();
Cursor cursor = db.query(
SubjectsSchema.TABLE_NAME,
new String[] {SubjectsSchema._COUNT},
null, null, null, null, null);
cursor.moveToFirst();
int count = cursor.getInt(cursor.getColumnIndex(SubjectsSchema._COUNT));
cursor.close();
return count;
I'm not sure whether or not this is the correct way to use it, but I got this error:
android.database.sqlite.SQLiteException: no such column: _count (code 1): , while compiling: SELECT _count FROM subjects
How should I be using _COUNT?
In the database, there is nothing special about either _id or _count.
Your queries return an _id or _count column when the table is defined to have such a column, or when the query explicitly computes it.
Many objects of the Android framework expect a cursor to have a unique _id column, so many tables define it.
In most places, the _count is not expected to be present, so it is usually not implemented. And if it is actually needed, it can simply be computed with a subquery, like this:
SELECT _id,
[other fields],
(SELECT COUNT(*) FROM MyTable) AS _count
FROM MyTable
WHERE ...
If you want to find out the size of your own table, you are not required to use the _count name; you can execute a query like SELECT COUNT(*) FROM subjects, or, even simpler, use a helper function that does this for you.
I am stuck with one scenario in which I need to do complex query to get cursor. Don't know It is possible or not. Scenario is :
There are three tables in database.
Table Columns
Table-1 _id, name, number, ....
Table-2 _id, table1_id, col1, col2, ....
Table-3 _id, table2_id, col1, col2, ....
In these tables, when any record is inserted in table 2, corresponding table1 id is inserted in that record. Same for table2 id is inserted with table 3 record.
I want cursor for CursorAdapter to display list view of Table-3 data.
Cursor cursor = getContentResolver().query(TABLE_3_NAME, null, null, null, null);
Now need to add selection and selection args of Table-1 in this query.
Is it possible in Android?
You can use raw query to fetch from various tables. One example would be as below.
Cursor mCursor = db.rawQuery("SELECT * FROM Table-1, Table-3 " +
"WHERE Table1.id = <Whatever selection args you want> " +
"GROUP BY Table1.id", null);
This is just an example. You will have to change it as per your requirement.
You can write a SQL selection query (i.e. normal way) and you can execute using rawQuery() method.
For example:
Cursor cursor = db.rawQuery(selectQuery, null);
I have a query that joins 2 tables get the required data on my android, what picture here is the user clicks on an item and the item's ID is used to query the right data in the Database, I know I can simply use database.query() but it according to my research it is used for simply database querying only, in my case I should use rawQuery() which provides more power of the database. below is my query which links table 1 to table 2 to get the users name from table one and user last name from table 2 if the foreign key is the same as user key
Assume this is my query:
String sQuery = SELECT table1.ID, table2.userlastname FROM table1, table2 WHERE "+table1.ID = table2.foreign;
If i try to specify the user id like below it gets all data in the database table which means i should replace id with "=?" but how do I do this when I am dealing which such a query, one that uses db.rawQuery() instead of db.query()
`private Object userInfo(int id)
{
String sQuery = SELECT table1.ID, table2.userlastname
FROM table1, table2 WHERE "+table1.ID = id;
}`
Basically you replace the parameter by question marks '?' and pass them through a String array in the order they appear in the query.
String queryStr = "SELECT table1.ID, table2.userlastname
FROM table1
INNER JOIN table2 ON table1.ID = table2.foreign;
WHERE table1.ID = ?";
String[] args = new String[1];
args[0] = String.valueOf(id);
Cursor cur = db.rawQuery(queryStr, args);
it did not work until I joined table 2 like:
`String queryStr = "SELECT table1.ID, table2.userlastname
FROM table1
INNER JOIN table2 ON table1.ID = table2.foreign
WHERE table1.ID = ?";
String[] args = new String[1];
args[0] = String.valueOf(id);
Cursor cur = db.rawQuery(queryStr, args);`
I need to make a query to ContactsContract.Data table and values in CONTACT_ID column would be different (distinct).
Code:
final Uri uri = ContactsContract.Data.CONTENT_URI;
final String[] projection = new String[] {//
ContactsContract.Data.CONTACT_ID, //
ContactsContract.Data._ID, //
ContactsContract.Data.DISPLAY_NAME,//
ContactsContract.Data.LOOKUP_KEY //
};
final StringBuilder selectionBuilder = new StringBuilder();
selectionBuilder.append(ContactsContract.CommonDataKinds.GroupMembership.GROUP_ROW_ID);
selectionBuilder.append("= ? AND ");
selectionBuilder.append(ContactsContract.Data.MIMETYPE);
selectionBuilder.append("= ? ");
final String selection = selectionBuilder.toString();
final String[] selectionArgs = new String[] {//
String.valueOf(groupId), //
ContactsContract.CommonDataKinds.GroupMembership.CONTENT_ITEM_TYPE //
};
return context.getContentResolver().query(uri, projection, selection, selectionArgs, null);
First of all, I've tried to add "DISTINCT " to ContactsContract.Data.CONTACT_ID in projection. But there was an exception: java.lang.IllegalArgumentException: Invalid column DISTINCT contact_id
Then, I write this way:
"'DISTINCT "+ContactsContract.Data.CONTACT_ID+"'".
java.lang.IllegalArgumentException: Invalid column 'DISTINCT contact_id'
Then, I add to selectionBuilder:
selectionBuilder.append(" GROUP BY ").append(ContactsContract.Data.CONTACT_ID);
Once again, an exception: android.database.sqlite.SQLiteException: near "GROUP": syntax error: , while compiling: SELECT contact_id, _id, display_name, lookup FROM view_data_restricted data WHERE (1) AND (data1= ? AND mimetype= ? GROUP BY contact_id) ORDER BY display_name ASC
At last, I've append "group by" statement right after sortOrder, but:
android.database.sqlite.SQLiteException: near "GROUP": syntax error: , while compiling: SELECT contact_id, _id, display_name, lookup FROM view_data_restricted data WHERE (1) AND (data1= ? AND mimetype= ? ) ORDER BY display_name ASC GROUP BY contact_id
Is it ever possible to make query with distinct?
Maybe, I should append something to URI?
If you are targeting devices below ICS, you can use the GROUP_BY clause by adding a ) before the group by and a ( after:
selectionBuilder.append(") GROUP BY (")
As of ICS and above, the query interpretor is smarter and closes any unclosed parenthesis to prevent injection.
However, I don't see why you need distinct contact_ids here. A contact should probably have only one Data to make the association with one group, so you probably receive a different contact on each line.
Also, there may be something to do with http://developer.android.com/reference/android/provider/ContactsContract.Contacts.html#CONTENT_GROUP_URI it is not documented, but given its position, it may well be a direct access to Contacts belonging to a Group. You would use that Uri :
Uri uri = ContentUri.withAppendedId(ContactsContract.Contacts.CONTENT_GROUP_URI, groupId);
And then query it like the Contacts.CONTENT_URI