Как мне соединить две таблицы SQLite в моем приложении для Android?

95

Задний план

У меня есть проект Android, в котором есть база данных с двумя таблицами: tbl_questionи tbl_alternative.

Чтобы заполнить представления вопросами и альтернативами, я использую курсоры. Нет проблем с получением нужных мне данных, пока я не попытаюсь объединить две таблицы.

    Tbl_question  
    -------------
    _мне бы  
    вопрос  
    categoryid  
    Tbl_alternative
    ---------------
    _мне бы 
    вопросид 
    categoryid 
    альтернатива

Я хочу что-то вроде следующего:

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

Это моя попытка:

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;

Я считаю, что этот способ формирования запросов сложнее, чем обычный SQL, но я получил совет использовать этот способ, поскольку он менее подвержен ошибкам.

Вопрос

Как мне объединить две таблицы SQLite в моем приложении?

kakka47
источник
Какую ошибку вы получаете? Вы пробовали тест, заменяя объединенную строку строковым литералом? (FWIW, мне не приходилось использовать курсор с 1986 года или около того. Но я не занимаюсь разработкой для Android.)
Майк Шерилл «Cat Recall»
Я не вижу, где и как указываются столбцы внутреннего соединения в блоке кода курсора. SQL будет «выбрать желаемый-список-столбцов из внутреннего соединения T1 T2 на T1.questionid = T2.questionid и T1.categoryid = T2.categoryid, где T1.categoryid = {желаемое значение категории}»
Тим
Это моя ошибка: неоднозначное имя столбца: _id: при компиляции: SELECT DISTINCT _id, image, question, alternate, questionid FROM tbl_question INNER JOIN tbl_alternative WHERE categoryid = 2 AND _id = questionid. Итак, я предполагаю, что мне нужно указать tbl_question._id = tbl_alternative.questionid, но я не знаю, как это сделать в способе запроса, который я использовал выше. И я не знаю, что возвращать, если я использую «обычный» sql-синтаксис: «Выбрать» из tbl_question ВНУТРЕННЕЕ СОЕДИНЕНИЕ tbl_alternative ON tbl_question._id = tbl_alternative.questionid И tbl_question.categoryid = tbl_alternative.categoryid = categoryid;
kakka47
@Tim: как вы это делаете, как мне сформировать метод в классе db-helper? Не возвращать курсор? Я учусь по ходу дела, я благодарен за любые предложения по улучшению моего кода!
kakka47
здесь я опубликовал ответ stackoverflow.com/questions/31567564/…
Sagar

Ответы:

204

Вам нужен метод rawQuery .

Пример:

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

Использовать? привязки вместо помещения значений в необработанный запрос sql.

pawelzieba
источник
1
@necromancer, как вы видите создание VIEWлучшего варианта, чем rawQuery?
Мухаммад Бабар
Что делать после запроса? Как узнать, какой идентификатор принадлежит какой таблице, и что, если обе таблицы имеют одинаковое имя столбца для некоторого столбца? или если для второй таблицы есть несколько элементов?
разработчик Android
@ android-developer "Что делать после запроса?" - вы получаете курсор, делайте с данными все, что хотите: P; 2. «Как узнать, какой идентификатор…» - это СОЕДИНЕНИЕ, поэтому в зависимости от типа вы можете получить ВСЕГДА, НИКОГДА или МОЖЕТ БЫТЬ заполненные ключи; 3. «... одно и то же имя столбца для некоторого столбца» - может случиться, но двусмысленность МОЖЕТ быть устранена. Честно говоря, я не знаю, можно ли выполнить выборку с помощью "Table.column" с помощью getColumnIndex. Вы всегда можете рассчитать индекс вручную, просто проверьте его отладкой; 4. «Несколько элементов для второй таблицы» также зависит от типа соединения.
leRobot 04
предполагая, что View представляет собой ПОЛНОЕ ВНЕШНЕЕ СОЕДИНЕНИЕ отношения «1 ко многим» (например, RawContactsEntity ), вы можете получить строки с нулевым KEY в таблице «многие», но никогда в таблице «1», поэтому вам следует перейти к переходу курсора вот так: while (cursor.moveToNext () {if (cursor.getValue (getManyTableKeyIndex ()) == NULL) {/ * это строка, не имеющая отношения, только данные таблицы «1» /} else {/ это отношение HIT, поэтому есть данные из обеих таблиц * /}} cursor.close ();
leRobot
Я забыл добавить еще одно ветвление для строк «без родительского множества», которые вы все еще можете получить с ПОЛНЫМ ВНЕШНИМ СОЕДИНЕНИЕМ, но обычно этого не произойдет, если отношение строгое (я не уверен, что Android обеспечивает соблюдение отношений). На самом деле не знаю, как получить неоднозначные столбцы, но вы можете просто проверить это на любом SQLite CLI, объявив представление с неоднозначностью столбцов и посмотреть, какое имя эти столбцы получают в запросе «SELECT * FROM view_x». Затем вы просто применяете это к любому помощнику Android, который хотите (.query () / .rawQuery () ...)
leRobot
20

Альтернативный способ - создать представление, которое затем запрашивается, как таблица. Во многих менеджерах баз данных использование представления может повысить производительность.

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;

Это из памяти, поэтому могут быть некоторые синтаксические проблемы. http://www.sqlite.org/lang_createview.html

Я упоминаю этот подход, потому что тогда вы можете использовать SQLiteQueryBuilder с представлением, поскольку вы подразумевали, что это предпочтительнее.

фрид
источник
4
Когда дело доходит до СУБД, таких как Oracle, представления могут дать вам некоторый прирост производительности, но, по моему опыту, они используются только тогда, когда это необходимо для целей производительности или отчетности. Или, другими словами, вы не создаете представление для каждого используемого запроса на соединение. И, в частности, в SQLite, я не верю, что есть какой-либо прирост производительности - согласно ответу на этот вопрос, SQLite переписывает запрос, используя подзапрос. stackoverflow.com/questions/25577407/performance-penalty-for-unused-view#25580319 API Android может ограничивать то, что делает OP, но rawQuery()это правильный ответ.
spaaarky21
13

В дополнение к ответу @ pawelzieba, который определенно верен, для объединения двух таблиц, в то время как вы можете использовать INNER JOINтакой

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

через необработанный запрос, подобный этому -

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

из-за обратной совместимости SQLite с поддержкой примитивного способа запросов, мы превращаем эту команду в следующую:

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

и, следовательно, иметь возможность воспользоваться вспомогательным методом 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
);

Подробное сообщение в блоге можно найти на http://blog.championswimmer.in/2015/12/doing-a-table-join-in-android-without-using-rawquery

Арнав Гупта
источник
1
Я не понимаю, откуда взялся Utils.concat.
nicoqueijo
1
Это хороший подход, но он не будет работать, если имя любого столбца в одной таблице совпадает с именем столбца в другой таблице. Например, если в обеих таблицах есть столбец с идентификатором имени, то это вызоветandroid.database.sqlite.SQLiteException: ambiguous column name
Ануп Шарма
8

«Неоднозначный столбец» обычно означает, что одно и то же имя столбца встречается как минимум в двух таблицах; механизм базы данных не может сказать, какой из них вам нужен. Используйте полные имена таблиц или псевдонимы таблиц, чтобы устранить двусмысленность.

Вот пример, который у меня был в моем редакторе. Это из-за чужой проблемы, но в любом случае должно иметь смысл.

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)
Майк Шерилл, "Отзыв кошки"
источник
Да, я это подозревал. Но я не знал, как указать таблицы, поскольку способ DBTABLE_QUESTION.KEY_QUESTION не работал. Но когда я использовал метод rawQuery, было легко определить, какой таблице какой столбец принадлежит.
kakka47