¿Cómo puedo unir dos tablas SQLite en mi aplicación de Android?

95

Antecedentes

Tengo un proyecto de Android que tiene una base de datos con dos tablas: tbl_questiony tbl_alternative.

Para poblar las vistas con preguntas y alternativas, estoy usando cursores. No hay problemas para obtener los datos que necesito hasta que intento unir las dos tablas.

    Tbl_question  
    -------------
    _carné de identidad  
    pregunta  
    categoria ID  
    Tbl_alternative
    ---------------
    _carné de identidad 
    questionid 
    categoria ID 
    alternativa

Quiero algo como lo siguiente:

SELECT tbl_question.question, tbl_alternative.alternative where 
categoryid=tbl_alternative.categoryid AND tbl_question._id = 
tbl_alternative.questionid.` 

Este es mi intento:

public Cursor getAlternative(long categoryid) {
            String[] columns = new String[] { KEY_Q_ID, KEY_IMAGE, KEY_QUESTION, KEY_ALT, KEY_QID};
             String whereClause = KEY_CATEGORYID + "=" + categoryid +" AND "+ KEY_Q_ID +"="+ KEY_QID;
             Cursor cursor = mDb.query(true, DBTABLE_QUESTION + " INNER JOIN "+ DBTABLE_ALTERNATIVE, columns, whereClause, null, null, null, null, null);
             if (cursor != null) {
                  cursor.moveToFirst();
             }
             return cursor;

Encuentro que esta forma de formular consultas es más difícil que el SQL normal, pero he recibido el consejo de usar esta forma ya que es menos propenso a errores.

Pregunta

¿Cómo puedo unir dos tablas SQLite en mi aplicación?

kakka47
fuente
¿Qué error estás recibiendo? ¿Intentó probar reemplazando la cadena concatenada con una cadena literal? (FWIW, no he tenido que usar un cursor desde 1986 más o menos. Pero no desarrollo para Android.)
Mike Sherrill 'Cat Recall'
No veo dónde / cómo se especifican las columnas de unión interna en el bloque de código del cursor. El SQL sería "seleccionar la lista de columnas deseadas de la unión interna de T1 T2 en T1.questionid = T2.questionid y T1.categoryid = T2.categoryid donde T1.categoryid = {el valor de categoría deseado}"
Tim
Este es mi error: nombre de columna ambiguo: _id:, mientras se compila: SELECT DISTINCT _id, image, question, Alternative, questionid FROM tbl_question INNER JOIN tbl_alternative WHERE categoryid = 2 AND _id = questionid. Así que supongo que necesito especificar tbl_question._id = tbl_alternative.questionid, pero no sé cómo hacer esto en la forma de consulta que uso arriba. Y no sé qué devolver si uso una sintaxis sql "regular": "Seleccionar 'de tbl_question INNER JOIN tbl_alternative ON tbl_question._id = tbl_alternative.questionid AND tbl_question.categoryid = tbl_alternative.categoryid = categoryid;
kakka47
@Tim: la forma en que lo haces, ¿cómo formo el método en la clase db-helper? ¿No debería devolver un cursor? Estoy aprendiendo sobre la marcha, ¡estoy agradecido por cualquier sugerencia que mejore mi código!
kakka47
aquí he publicado la respuesta stackoverflow.com/questions/31567564/…
Sagar

Respuestas:

204

Necesita el método rawQuery .

Ejemplo:

private final String MY_QUERY = "SELECT * FROM table_a a INNER JOIN table_b b ON a.id=b.other_id WHERE b.property_id=?";

db.rawQuery(MY_QUERY, new String[]{String.valueOf(propertyId)});

Utilizar ? enlaces en lugar de poner valores en una consulta SQL sin formato.

pawelzieba
fuente
1
@necromancer ¿cómo ves la creación de VIEWuna mejor opción que rawQuery?
Muhammad Babar
¿Qué se debe hacer después de la consulta? ¿Cómo sabe qué id pertenece a qué tabla y qué pasa si ambas tablas tienen el mismo nombre de columna para alguna columna? o si hay varios elementos para la segunda tabla?
desarrollador de Android
@ android-developer "¿Qué se debe hacer después de la consulta?" - obtienes un Cursor, haz lo que quieras con los datos: P; 2. "¿Cómo saber qué id ..." - es un JOIN, por lo que dependiendo del tipo , puede obtener SIEMPRE, NUNCA o QUIZÁS claves pobladas; 3. "... el mismo nombre de columna para alguna columna" - puede suceder, pero la ambigüedad PUEDE eliminarse. Honestamente, no sé si puede buscar con "Table.column" usando getColumnIndex. Siempre puede calcular el índice manualmente, simplemente depurarlo y probarlo; 4. "Varios elementos para la segunda tabla" también depende del tipo de combinación.
leRobot
asumiendo que la Vista es una COMPLETA UNIÓN EXTERIOR de una relación de 1 a muchos (por ejemplo, RawContactsEntity ), puede obtener filas con CLAVE nula en la tabla "muchos", pero nunca en la tabla "1", por lo que debe ramificar el paso del cursor así: while (cursor.moveToNext () {if (cursor.getValue (getManyTableKeyIndex ()) == NULL) {/ * esta es una fila sin relación, solo tiene datos de tabla "1" /} else {/ esto es una relación HIT, por lo que hay datos de ambas tablas * /}} cursor.close ();
leRobot
Olvidé agregar otra bifurcación para las filas "sin padres muchas" que aún puede obtener con FULL OUTER JOIN, pero generalmente no lo hará si la relación es estricta (no estoy seguro de si Android hace cumplir las relaciones). Realmente no sé cómo obtener columnas ambiguas, pero puede probarlo en cualquier CLI de SQLite declarando una vista con ambigüedad de columna y ver qué nombre obtienen esas columnas en una consulta "SELECT * FROM view_x". Luego, simplemente aplique eso a cualquier versión de ayuda de Android que desee (.query () / .rawQuery () ...)
leRobot
20

Una forma alternativa es construir una vista que luego se consulta como una tabla. En muchos administradores de bases de datos, el uso de una vista puede resultar en un mejor rendimiento.

CREATE VIEW xyz SELECT q.question, a.alternative  
   FROM tbl_question AS q, tbl_alternative AS a
  WHERE q.categoryid = a.categoryid 
    AND q._id = a.questionid;

Esto es de memoria, por lo que puede haber algunos problemas sintácticos. http://www.sqlite.org/lang_createview.html

Menciono este enfoque porque luego puede usar SQLiteQueryBuilder con la vista, ya que insinuó que era la preferida.

phreed
fuente
4
Cuando se trata de RDBMS como Oracle, las vistas pueden brindarle algunas ganancias de rendimiento, pero en mi experiencia, solo se usan cuando es necesario para fines de rendimiento o informes. O, para decirlo de otra manera, no crea una vista para cada consulta de combinación que usa. Y en SQLite en particular, no creo que haya ninguna ganancia de rendimiento; de acuerdo con la respuesta a esta pregunta, SQLite reescribe la consulta usando una subconsulta. stackoverflow.com/questions/25577407/performance-penalty-for-unused-view#25580319 La API de Android podría limitar lo que está haciendo el OP, pero rawQuery()es la respuesta correcta.
spaaarky21
13

Además de la respuesta de @ pawelzieba, que definitivamente es correcta, para unir dos tablas, mientras que puedes usar una INNER JOINcomo esta

SELECT * FROM expense INNER JOIN refuel
ON exp_id = expense_id
WHERE refuel_id = 1

a través de una consulta sin procesar como esta -

String rawQuery = "SELECT * FROM " + RefuelTable.TABLE_NAME + " INNER JOIN " + ExpenseTable.TABLE_NAME
        + " ON " + RefuelTable.EXP_ID + " = " + ExpenseTable.ID
        + " WHERE " + RefuelTable.ID + " = " +  id;
Cursor c = db.rawQuery(
        rawQuery,
        null
);

debido a la compatibilidad con versiones anteriores de SQLite de la forma primitiva de consulta, convertimos ese comando en esto:

SELECT *
FROM expense, refuel
WHERE exp_id = expense_id AND refuel_id = 1

y, por lo tanto, poder aprovechar el método auxiliar SQLiteDatabase.query ()

Cursor c = db.query(
        RefuelTable.TABLE_NAME + " , " + ExpenseTable.TABLE_NAME,
        Utils.concat(RefuelTable.PROJECTION, ExpenseTable.PROJECTION),
        RefuelTable.EXP_ID + " = " + ExpenseTable.ID + " AND " + RefuelTable.ID + " = " +  id,
        null,
        null,
        null,
        null
);

Para obtener una publicación de blog detallada, consulte este http://blog.championswimmer.in/2015/12/doing-a-table-join-in-android-without-using-rawquery

Arnav Gupta
fuente
1
No entiendo de dónde viene Utils.concat.
nicoqueijo
1
Este es un buen enfoque, pero no funcionará si cualquier nombre de columna en una tabla es el mismo que el nombre de una columna en otra tabla. Como si ambas tablas tuvieran una columna con ID de nombre, esto arrojaríaandroid.database.sqlite.SQLiteException: ambiguous column name
Anup Sharma
8

"Columna ambigua" generalmente significa que el mismo nombre de columna aparece en al menos dos tablas; el motor de la base de datos no puede decir cuál desea. Utilice nombres de tablas completos o alias de tablas para eliminar la ambigüedad.

Aquí hay un ejemplo que tuve en mi editor. Es del problema de otra persona, pero debería tener sentido de todos modos.

select P.* 
from product_has_image P
inner join highest_priority_images H 
        on (H.id_product = P.id_product and H.priority = p.priority)
Mike Sherrill 'Cat Recall'
fuente
Sí, lo sospechaba. Pero no sabía cómo especificar las tablas, ya que la forma: DBTABLE_QUESTION.KEY_QUESTION, no funcionó. Pero cuando utilizo la forma rawQuery fue fácil definir a qué tabla pertenecía cada columna.
kakka47