¿Cómo implementar consultas complejas utilizando un proveedor de contenido?

Estoy preguntando esto porque no estoy muy seguro de cómo trabajar con los proveedores de contenido de Android. Tengo un subconjunto de mi base de datos con 8 tablas y tengo que crear consultas complejas para obtener algunos de los datos. Mi proveedor de contenido funciona bien con consultas sencillas. Por ejemplo, tengo una persona de la tabla en mi clase de PersonModel.java y consigo los datos usando:

String [] projection = {PersonModel.C_FIRST_NAME, PersonModel.C_LAST_NAME}; Cursor cursor = context.getContentResolver().query( MyProvider.CONTENT_URI_PERSONS, projection, null, null, null); 

Y funciona perfectamente.

En MyProvider tengo un montón de constantes de CONTENT_URI, encendido para cada una de mis tablas.

 public class MyProvider extends ContentProvider { MyDbHelper dbHelper; SQLiteDatabase db; private static final String AUTHORITY = "com.myapp.models"; //Paths for each tables private static final String PATH_PROFILE_PICTURES = "profile_pictures"; private static final String PATH_PERSONS = "persons"; private static final String PATH_USERS = "users"; .... //Content URIs for each table public static final Uri CONTENT_URI_PROFILE_PICTURES = Uri .parse("content://" + AUTHORITY + "/" + PATH_PROFILE_PICTURES); public static final Uri CONTENT_URI_PERSONS = Uri.parse("content://" + AUTHORITY + "/" + PATH_PERSONS); public static final Uri CONTENT_URI_USERS = Uri.parse("content://" + AUTHORITY + "/" + PATH_USERS); ... private static final int PROFILE_PICTURES = 1; private static final int PROFILE_PICTURE_ID = 2; private static final int PERSONS = 3; private static final int PERSON_ID = 4; private static final int USERS = 5; private static final int USER_ID = 6; private static final UriMatcher sURIMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { sURIMatcher.addURI(AUTHORITY, PATH_PROFILE_PICTURES, PROFILE_PICTURES); sURIMatcher.addURI(AUTHORITY, PATH_PROFILE_PICTURES + "/#", PROFILE_PICTURE_ID); sURIMatcher.addURI(AUTHORITY, PATH_PERSONS, PERSONS); sURIMatcher.addURI(AUTHORITY, PATH_PERSONS + "/#", PERSON_ID); sURIMatcher.addURI(AUTHORITY, PATH_USERS, USERS); sURIMatcher.addURI(AUTHORITY, PATH_USERS + "/#", USER_ID); ... } public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Uisng SQLiteQueryBuilder instead of query() method SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); // check if the caller has requested a column which does not exists //checkColumns(projection); int uriType = sURIMatcher.match(uri); switch (uriType) { case PROFILE_PICTURES: queryBuilder.setTables(ProfilePictureModel.TABLE_PROFILE_PICTURE); break; case PROFILE_PICTURE_ID: // Adding the ID to the original query queryBuilder.appendWhere(ProfilePictureModel.C_ID + "=" + uri.getLastPathSegment()); case PERSONS: queryBuilder.setTables(PersonModel.TABLE_PERSON); break; case PERSON_ID: // Adding the ID to the original query queryBuilder.appendWhere(PersonModel.C_ID + "=" + uri.getLastPathSegment()); case USERS: queryBuilder.setTables(UserModel.TABLE_USER); break; case USER_ID: // Adding the ID to the original query queryBuilder.appendWhere(UserModel.C_ID + "=" + uri.getLastPathSegment()); default: throw new IllegalArgumentException("Unknown URI: " + uri); } db = dbHelper.getWritableDatabase(); Cursor cursor = queryBuilder.query(db, projection, selection, selectionArgs, null, null, sortOrder); // make sure that potential listeners are getting notified cursor.setNotificationUri(getContext().getContentResolver(), uri); } 

Esa es una pequeña parte de mi proveedor de contenido. Así que mis preguntas son:

1) ¿Cómo implemento un rawQuery () en mi proveedor de contenido? O cómo utilizo correctamente mi queryBuilder ?, digamos que quiero ejecutar esta consulta usando varias tablas, renombrándolas y pasando también el parámetro p1.id como parámetro?

 SELECT p1.first_name, p1_last_name FROM Person p1, Person P2, Relationship r WHERE p1.id = ? AND p1.id = r.relative_id AND p2.id = r.related_id; 

Lo intenté haciendo esto: En mi método query () (mostrado arriba) tengo un nuevo caso, llamado GET_RELATIVES:

 case GET_RELATIVES: db = dbHelper.getWritableDatabase(); queryBuilder.setTables(PersonModel.TABLE_PERSON + " p1, " + PersonModel.TABLE_PERSON + " p2, " + RelationshipModel.TABLE_RELATIONSHIP + " r"); queryBuilder.appendWhere("p2."+PersonModel.C_ID + "=" + uri.getLastPathSegment()); queryBuilder.appendWhere("p2."+PersonModel.C_ID + "=" + "r.related_id"); queryBuilder.appendWhere("p1."+PersonModel.C_ID + "=" + "r.relative_id"); 

Así que definí un nuevo PATH, URI del CONTENIDO y lo agrego al UriMatcher, como esto:

 private static final String PATH_GET_RELATIVES = "get_relatives"; public static final Uri CONTENT_URI_GET_RELATIVES = Uri .parse("content://" + AUTHORITY + "/" + PATH_GET_RELATIVES); private static final int GET_RELATIVES = 22; private static final UriMatcher sURIMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { ... sURIMatcher.addURI(AUTHORITY, PATH_GET_RELATIVES, GET_RELATIVES); } 

Pero esto no parece funcionar así que creo que probablemente estoy definiendo algo malo en mi proveedor de contenido o dentro del método de consulta.

2) No estoy muy seguro de cuál es el punto de tener para cada tabla una constante denominada TABLE_ID y añadiéndola al switch-case. ¿Para qué se utiliza? ¿Cómo lo llamo?

Espero que nadie me puede ayudar con esto, gracias de antemano!

En realidad encontré la respuesta a mi pregunta en el lugar más obvio: la documentación de Android.

Primera Pregunta: Implementar un rawQuery. ¿Fue así:

Dentro de mi switch-case en el proveedor de contenido agregué un nuevo URI, que para mí es un JOIN entre tablas, así que creé una nueva constante de ContentUri para él, un nuevo ID, lo registré en el UriMatcher y luego escribí el RawQuery. Así que MyProvider ahora parece un poco bit como esto:

 public class MyProvider extends ContentProvider { ... // JOIN paths private static final String PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = "relationship_join_person_get_relatives"; ... public static final Uri CONTENT_URI_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = Uri .parse("content://" + AUTHORITY + "/" + PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES); ... private static final int RELATIONSHIP_JOIN_PERSON_GET_RELATIVES = 21; private static final UriMatcher sURIMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { ... //JOINS sURIMatcher.addURI(AUTHORITY, PATH_RELATIONSHIP_JOIN_PERSON_GET_RELATIVES + "/#", RELATIONSHIP_JOIN_PERSON_GET_RELATIVES); ... public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { // Uisng SQLiteQueryBuilder instead of query() method SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder(); // check if the caller has requested a column which does not exists //checkColumns(projection); int uriType = sURIMatcher.match(uri); switch (uriType) { ... case RELATIONSHIP_JOIN_PERSON_GET_RELATIVES: db = dbHelper.getWritableDatabase(); String[] args = {String.valueOf(uri.getLastPathSegment())}; Cursor cursor = db.rawQuery( "SELECT p1.first_name, p1.last_name " + "FROM Person p1, Person p2, Relationship r " + "WHERE p1.id = r.relative_id AND " + "p2.id = r.related_id AND " + "p2.id = ?", args); cursor.setNotificationUri(getContext().getContentResolver(), uri); return cursor; ... } 

Y para llamar al método query () y pasar el id ad un parámetro lo hice en mi controlador:

 String[] projection = { PersonModel.C_FIRST_NAME, PersonModel.C_LAST_NAME }; Cursor cursor = context.getContentResolver().query( ContentUris.withAppendedId( AkdemiaProvider.CONTENT_URI_RELATIONSHIP_JOIN_PERSON_GET_RELATED, id), projection, null, null, null); 

Segunda pregunta: Tener la constante TABLE_ID es útil para tener una consulta para cada tabla pasando un id como parámetro, no sabía cómo llamar al método de consulta pasando tal id y así es como la documentación para desarrolladores de Android explica cómo hacerlo Utilizando ContentUris.withAppendedId

 // Request a specific record. Cursor managedCursor = managedQuery( ContentUris.withAppendedId(Contacts.People.CONTENT_URI, 2), projection, // Which columns to return. null, // WHERE clause. null, // WHERE clause value substitution People.NAME + " ASC"); // Sort order. 

Yo ustedes quieren ver toda la documentación ir a este enlace.

Espero que esto ayude a cualquier otra persona que tenga el mismo problema para entender ContentProvider, ContentUris y todo eso 🙂

Debajo del código trabajó para mí. Dentro del proveedor de contenido de su aplicación:

 public static final String PATH_JOIN_TWO_TABLES = "my_path"; public static final Uri URI_JOIN_TWO_TABLES = Uri.parse("content://" + AUTHORITY + "/" + PATH_JOIN_TWO_TABLES); private static final int ID_JOIN_TWO_TABLES = 1001; private static final UriMatcher sURIMatcher = new UriMatcher( UriMatcher.NO_MATCH); static { sURIMatcher.addURI(AUTHORITY, PATH_JOIN_TWO_TABLES + "/#", ID_JOIN_TWO_TABLES ); } @Nullable @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection,String[] selectionArgs, String sortOrder, CancellationSignal cancellationSignal) { int uriType = sURIMatcher.match(uri); switch (uriType) { case ID_JOIN_TWO_TABLES: return getWritableDatabase() .rawQuery("select * from " + "table_one" + " LEFT OUTER JOIN " + "table_two" + " ON (" + "table_one.ID" + " = " + "table_two.id" + ")", null); } return super.query(uri, projection, selection, selectionArgs, sortOrder, cancellationSignal); } 

Y al hacer la consulta dentro de su actividad o fragmento:

  Cursor cursor = getActivity().getContentResolver() .query(ContentUris.withAppendedId(MYContentProvider.URI_JOIN_TWO_TABLES, MyContentProvider.ID_JOIN_TWO_TABLES), null, null, null, null); 

Espero que funcione para ti.

Para consultas simples use selectionArgs en ContentProvider. Funciona como abajo

 String[] args = { "first string", "[email protected]" }; Cursor cursor = db.query("TABLE_NAME", null, "name=? AND email=?", args, null); 

Tener el TABLE_ID dentro de para crear una consulta diferente para cada tabla.

Consulte la clase siguiente para todas las tablas múltiples en los proveedores de contenido

  1. Tutorial 1 de Vogella
  2. Tutorial 2 de Vogella
  3. Prácticas recomendadas para exponer varias tablas mediante proveedores de contenido en Android
  • Cómo crear sqlite declaración preparada en OrmLite?
  • Android SQLite - Nueva tabla VS. Nueva base de datos
  • Base de datos de diccionarios sin conexión para la aplicación de Android
  • ¿Cómo acceder a una base de datos sqlite existente en Android?
  • Base de datos H2 vs SQLite en Android
  • GreenDAO soporta múltiples relaciones entre tablas
  • ¿Cómo puedo obtener información sobre la columna causó restricción de clave externa en android sqlite?
  • SQLITE INSERT con ID manual (clave principal)
  • Android Guardar imágenes en una tarjeta de memoria o en una tarjeta SD
  • ¿Puedo hacer que SQLiteDatabase se queje de los parámetros que faltan?
  • Active Android es propenso a las inyecciones de SQL. ¿Alguna solución conocida?
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.