Wie verbinde ich zwei SQLite-Tabellen in meiner Android-Anwendung?

95

Hintergrund

Ich habe ein Android-Projekt, das eine Datenbank mit zwei Tabellen hat: tbl_questionund tbl_alternative.

Um die Ansichten mit Fragen und Alternativen zu füllen, verwende ich Cursor. Es gibt keine Probleme beim Abrufen der benötigten Daten, bis ich versuche, die beiden Tabellen zu verbinden.

    Tbl_question  
    -------------
    _Ich würde  
    Frage  
    Kategorie ID  
    Tbl_alternative
    ---------------
    _Ich würde 
    fragend 
    Kategorie ID 
    Alternative

Ich möchte so etwas wie das Folgende:

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

Das ist mein Versuch:

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;

Ich finde diesen Weg zum Erstellen von Abfragen schwieriger als normales SQL, habe aber den Rat erhalten, diesen Weg zu verwenden, da er weniger fehleranfällig ist.

Frage

Wie verbinde ich zwei SQLite-Tabellen in meiner Anwendung?

kakka47
quelle
Welchen Fehler bekommen Sie? Haben Sie versucht zu testen, indem Sie die verkettete Zeichenfolge durch ein Zeichenfolgenliteral ersetzt haben? (FWIW, ich musste seit 1986 oder so keinen Cursor mehr verwenden. Aber ich entwickle nicht für Android.)
Mike Sherrill 'Cat Recall'
Ich sehe nicht, wo / wie die inneren Join-Spalten im Cursor-Codeblock angegeben werden. Die SQL wäre "Auswahl der gewünschten Spaltenliste aus T1 innerem Join T2 auf T1.questionid = T2.questionid und T1.categoryid = T2.categoryid, wobei T1.categoryid = {der gewünschte Kategoriewert}"
Tim
Dies ist mein Fehler: mehrdeutiger Spaltenname: _id: beim Kompilieren: SELECT DISTINCT _id, Bild, Frage, Alternative, questionid FROM tbl_question INNER JOIN tbl_alternative WHERE categoryid = 2 AND _id = questionid. Ich gehe also davon aus, dass ich tbl_question._id = tbl_alternative.questionid angeben muss, aber ich weiß nicht, wie ich dies in der oben verwendeten Abfragemethode tun soll. Und ich weiß nicht , was zurück , wenn ich eine "reguläre" SQL-Syntax verwenden: „Select" aus tbl_question INNER JOIN tbl_alternative ON tbl_question._id = tbl_alternative.questionid UND tbl_question.categoryid = tbl_alternative.categoryid = categoryid;
kakka47
@ Tim: Wie bilde ich die Methode in der Klasse db-helper? Sollte ich keinen Cursor zurückgeben? Ich lerne im Laufe der Zeit Vorschläge, die meinen Code verbessern, für die ich dankbar bin!
Kakka47
hier habe ich Antwort stackoverflow.com/questions/31567564/…
Sagar

Antworten:

204

Sie benötigen die rawQuery- Methode.

Beispiel:

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

Verwenden ? Bindungen, anstatt Werte in eine SQL-Rohabfrage einzufügen.

pawelzieba
quelle
1
@necromancer Wie sehen Sie VIEWeine bessere Option als rawQuery?
Muhammad Babar
Was ist nach der Abfrage zu tun? Woher wissen Sie, welche ID zu welcher Tabelle gehört und was, wenn beide Tabellen für eine Spalte denselben Spaltennamen hatten? oder wenn es mehrere Elemente für die zweite Tabelle gibt?
Android-Entwickler
@ android-developer "Was ist nach der Abfrage zu tun?" - Sie erhalten einen Cursor, machen Sie mit den Daten, was Sie wollen: P; 2. "Woher wissen Sie, welche ID ..." - es handelt sich um eine VERBINDUNG. Je nach Typ erhalten Sie möglicherweise IMMER, NIE oder möglicherweise ausgefüllte Schlüssel. 3. "... der gleiche Spaltenname für eine Spalte" - kann vorkommen, aber Mehrdeutigkeiten können beseitigt werden. Ehrlich gesagt weiß ich nicht, ob Sie mit "Table.column" mit getColumnIndex abrufen können. Sie können den Index immer manuell berechnen, testen Sie ihn einfach per Debug; 4. "Mehrere Elemente für die zweite Tabelle" hängt auch vom Verknüpfungstyp ab.
LeRobot
Angenommen, die Ansicht ist ein FULL OUTER JOIN einer 1-zu-viele-Beziehung (z. B. RawContactsEntity ), können Sie Zeilen mit null KEY in der Tabelle "many", aber niemals in der Tabelle "1" abrufen , sodass Sie Ihren Cursor-Durchgang verzweigen sollten wie folgt: while (cursor.moveToNext () {if (cursor.getValue (getManyTableKeyIndex ()) == NULL) {/ * Dies ist eine Zeile ohne Beziehung, hat nur "1" Tabellendaten /} else {/ this is eine Beziehung HIT, so gibt es Daten aus beiden Tabellen * /}} cursor.close ();
leRobot
Ich habe vergessen, eine weitere Verzweigung für "no parent many" -Zeilen hinzuzufügen, die Sie immer noch mit FULL OUTER JOIN erhalten können, aber normalerweise nicht, wenn die Beziehung streng ist (ich bin nicht sicher, ob Android Beziehungen erzwingt). Sie wissen nicht genau, wie Sie mehrdeutige Spalten erhalten, aber Sie können sie einfach auf jeder SQLite-CLI testen, indem Sie eine Ansicht mit Spaltenmehrdeutigkeit deklarieren und sehen, welchen Namen diese Spalten in einer Abfrage "SELECT * FROM view_x" erhalten. Dann wenden Sie das einfach auf die gewünschte Android-Helfer-Variante an (.query () / .rawQuery () ...)
leRobot
20

Eine alternative Möglichkeit besteht darin, eine Ansicht zu erstellen, die dann wie eine Tabelle abgefragt wird. In vielen Datenbankmanagern kann die Verwendung einer Ansicht zu einer besseren Leistung führen.

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;

Dies ist aus dem Speicher, so dass es einige syntaktische Probleme geben kann. http://www.sqlite.org/lang_createview.html

Ich erwähne diesen Ansatz, weil Sie dann SQLiteQueryBuilder mit der Ansicht verwenden können, da Sie impliziert haben, dass dies bevorzugt wurde.

phreed
quelle
4
Wenn es um RDBMS wie Oracle geht, können Ansichten zu Leistungssteigerungen führen. Nach meiner Erfahrung werden sie jedoch nur verwendet, wenn dies für Leistungs- oder Berichtszwecke erforderlich ist. Oder anders ausgedrückt: Sie erstellen nicht für jede von Ihnen verwendete Join-Abfrage eine Ansicht. Insbesondere in SQLite glaube ich nicht, dass es einen Leistungsgewinn gibt. Gemäß der Antwort auf diese Frage schreibt SQLite die Abfrage mithilfe einer Unterabfrage neu. stackoverflow.com/questions/25577407/performance-penalty-for-unused-view#25580319 Die Android-API kann die Funktionsweise des OP einschränken, ist jedoch rawQuery()die richtige Antwort.
spaaarky21
13

Neben @ pawelzieba Antwort, die auf jeden Fall richtig ist, zwei Tabellen zu verbinden, während Sie ein verwenden können , INNER JOINwie dies

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

über rohe Abfrage wie diese -

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

Aufgrund der abwärtskompatiblen Unterstützung von SQLite für die primitive Art der Abfrage verwandeln wir diesen Befehl in Folgendes:

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

und daher in der Lage sein, die SQLiteDatabase.query () - Hilfsmethode zu nutzen

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

Einen detaillierten Blog-Beitrag finden Sie unter http://blog.championswimmer.in/2015/12/doing-a-table-join-in-android-without-using-rawquery

Arnav Gupta
quelle
1
Ich verstehe nicht, woher die Utils.concat kommt.
Nicoqueijo
1
Dies ist ein guter Ansatz, funktioniert jedoch nicht, wenn ein Spaltenname in einer Tabelle mit einem Spaltennamen in einer anderen Tabelle identisch ist. Wenn beide Tabellen eine Spalte mit der Namens-ID haben, wird dies ausgelöstandroid.database.sqlite.SQLiteException: ambiguous column name
Anup Sharma
8

"Mehrdeutige Spalte" bedeutet normalerweise, dass derselbe Spaltenname in mindestens zwei Tabellen vorkommt. Das Datenbankmodul kann nicht sagen, welches Sie möchten. Verwenden Sie vollständige Tabellennamen oder Tabellenaliasnamen, um die Mehrdeutigkeit zu beseitigen.

Hier ist ein Beispiel, das ich zufällig in meinem Editor hatte. Es ist von einem anderen Problem, sollte aber trotzdem Sinn machen.

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'
quelle
Ja, das habe ich vermutet. Aber ich wusste nicht, wie ich die Tabellen angeben sollte, da der Weg: DBTABLE_QUESTION.KEY_QUESTION nicht funktionierte. Aber wenn ich die rawQuery-Methode verwende, war es einfach zu definieren, zu welcher Tabelle welche Spalte gehört.
Kakka47