Comment joindre deux tables SQLite dans mon application Android?

95

Contexte

J'ai un projet Android qui a une base de données avec deux tables: tbl_questionet tbl_alternative.

Pour remplir les vues avec des questions et des alternatives, j'utilise des curseurs. Il n'y a aucun problème pour obtenir les données dont j'ai besoin jusqu'à ce que j'essaye de joindre les deux tables.

    Tbl_question  
    -------------
    _id  
    question  
    categoryid  
    Tbl_alternative
    ---------------
    _id 
    questionid 
    categoryid 
    alternative

Je veux quelque chose comme ce qui suit:

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

Voici ma tentative:

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;

Je trouve que cette façon de former des requêtes est plus difficile que le SQL ordinaire, mais j'ai reçu le conseil de l'utiliser car elle est moins sujette aux erreurs.

Question

Comment joindre deux tables SQLite dans mon application?

kakka47
la source
Quelle erreur obtenez vous? Avez-vous essayé de tester en remplaçant la chaîne concaténée par une chaîne littérale? (FWIW, je n'ai pas eu à utiliser de curseur depuis 1986 environ. Mais je ne développe pas pour Android.)
Mike Sherrill 'Cat Recall'
Je ne vois pas où / comment les colonnes de jointure interne sont spécifiées dans le bloc de code du curseur. Le SQL serait "sélectionnez la liste des cols souhaités à partir de la jointure interne T1 T2 sur T1.questionid = T2.questionid et T1.categoryid = T2.categoryid où T1.categoryid = {la valeur de catégorie souhaitée}"
Tim
C'est mon erreur: nom de colonne ambigu: _id:, lors de la compilation: SELECT DISTINCT _id, image, question, alternative, questionid FROM tbl_question INNER JOIN tbl_alternative WHERE categoryid = 2 AND _id = questionid. Je suppose donc que je dois spécifier tbl_question._id = tbl_alternative.questionid, mais je ne sais pas comment faire cela dans la manière de requête que j'utilise ci-dessus. Et je ne sais pas quoi renvoyer si j'utilise une syntaxe sql "régulière": "Select 'from tbl_question INNER JOIN tbl_alternative ON tbl_question._id = tbl_alternative.questionid AND tbl_question.categoryid = tbl_alternative.categoryid = categoryid;
kakka47
@Tim: comme vous le faites, comment former la méthode dans la classe db-helper? Ne devrais-je pas renvoyer un curseur? J'apprends au fur et à mesure, toutes les suggestions qui améliorent mon code, je suis reconnaissant!
kakka47
ici, j'ai posté la réponse stackoverflow.com/questions/31567564/…
Sagar

Réponses:

204

Vous avez besoin de la méthode rawQuery .

Exemple:

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)});

Utilisation ? liaisons au lieu de mettre des valeurs dans une requête SQL brute.

pawelzieba
la source
1
@necromancer comment voyez-vous la création d' VIEWune meilleure option que rawQuery?
Muhammad Babar
Que faut-il faire après la requête? Comment savoir quel identifiant appartient à quelle table et que faire si les deux tables avaient le même nom de colonne pour une colonne? ou s'il y a plusieurs éléments pour la deuxième table?
développeur android
@ android-developer "Que faut-il faire après la requête?" - vous obtenez un curseur, faites ce que vous voulez avec les données: P; 2. "Comment savez-vous quel identifiant ..." - c'est un JOIN, donc selon le type , vous pourriez obtenir des clés ALWAYS, JAMAIS ou PEUT-ÊTRE; 3. "... même nom de colonne pour certaines colonnes" - peut se produire, mais l'ambiguïté PEUT être supprimée. Honnêtement, je ne sais pas si vous pouvez récupérer avec "Table.column" en utilisant getColumnIndex, vous pouvez toujours calculer l'index manuellement, il suffit de le déboguer-tester; 4. "plusieurs éléments pour la deuxième table" dépend également du type de jointure.
leRobot
en supposant que la vue est une jointure externe complète d'une relation 1-à-plusieurs (par exemple RawContactsEntity ), vous pouvez obtenir des lignes avec NULL KEY sur la table "plusieurs", mais jamais sur la table "1", vous devez donc passer votre curseur comme ceci: while (cursor.moveToNext () {if (cursor.getValue (getManyTableKeyIndex ()) == NULL) {/ * ceci est une ligne sans relation, a seulement "1" table data /} else {/ c'est une relation HIT, donc il y a des données des deux tables * /}} cursor.close ();
leRobot
J'ai oublié d'ajouter un autre branchement pour les lignes "no parent many" que vous pouvez toujours obtenir avec FULL OUTER JOIN, mais généralement pas si la relation est stricte (je ne suis pas sûr que Android applique les relations). Je ne sais pas vraiment comment obtenir des colonnes ambiguës, mais vous pouvez simplement le tester sur n'importe quelle CLI SQLite en déclarant une vue avec une ambiguïté de colonne et voir le nom que ces colonnes obtiennent sur une requête "SELECT * FROM view_x". Ensuite, vous appliquez simplement cela à la saveur de l'aide Android que vous voulez (.query () / .rawQuery () ...)
leRobot
20

Une autre manière consiste à construire une vue qui est ensuite interrogée comme une table. Dans de nombreux gestionnaires de bases de données, l'utilisation d'une vue peut améliorer les performances.

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;

Cela vient de mémoire, il peut donc y avoir des problèmes de syntaxe. http://www.sqlite.org/lang_createview.html

Je mentionne cette approche car vous pouvez alors utiliser SQLiteQueryBuilder avec la vue, car vous avez laissé entendre qu'elle était préférée.

phreed
la source
4
En ce qui concerne les SGBDR comme Oracle, les vues peuvent vous apporter des gains de performances, mais d'après mon expérience, elles ne sont utilisées que lorsque cela est nécessaire à des fins de performance ou de reporting. Ou pour le dire autrement, vous ne créez pas de vue pour chaque requête de jointure que vous utilisez. Et dans SQLite en particulier, je ne pense pas qu'il y ait de gain de performance - selon la réponse à cette question, SQLite réécrit la requête à l'aide d'une sous-requête. stackoverflow.com/questions/25577407/performance-penalty-for-unused-view#25580319 L'API d'Android pourrait limiter ce que fait l'OP, mais rawQuery()c'est la bonne réponse.
spaaarky21
13

En plus de la réponse de @ pawelzieba, qui est certainement correcte, joindre deux tables, alors que vous pouvez utiliser un INNER JOINcomme celui-ci

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

via une requête brute comme celle-ci -

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
);

en raison de la prise en charge rétrocompatible de SQLite de la manière primitive d'interroger, nous transformons cette commande en ceci -

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

et donc pouvoir profiter de la méthode d'assistance 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
);

Pour un article de blog détaillé, consultez ce http://blog.championswimmer.in/2015/12/doing-a-table-join-in-android-without-using-rawquery

Arnav Gupta
la source
1
Je ne comprends pas d'où vient l'Utils.concat.
nicoqueijo le
1
C'est une bonne approche mais ne fonctionnera pas si un nom de colonne dans une table est identique à un nom de colonne dans une autre table. Comme si les deux tables ont une colonne ayant un ID de nom, alors cela lanceraandroid.database.sqlite.SQLiteException: ambiguous column name
Anup Sharma
8

«Colonne ambiguë» signifie généralement que le même nom de colonne apparaît dans au moins deux tableaux; le moteur de base de données ne peut pas dire lequel vous voulez. Utilisez des noms de table complets ou des alias de table pour supprimer l'ambiguïté.

Voici un exemple que j'ai eu dans mon éditeur. Cela vient du problème de quelqu'un d'autre, mais cela devrait avoir un sens de toute façon.

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 'Rappel de chat'
la source
Oui, je m'en doutais. Mais je ne savais pas comment spécifier les tables, car la manière: DBTABLE_QUESTION.KEY_QUESTION, ne fonctionnait pas. Mais lorsque j'utilise la méthode rawQuery, il était facile de définir à quelle table appartenait la colonne.
kakka47