Como faço para unir duas tabelas SQLite em meu aplicativo Android?


95

fundo

Tenho um projeto Android que possui um banco de dados com duas tabelas: tbl_questione tbl_alternative.

Para preencher as visualizações com perguntas e alternativas, estou usando cursores. Não há problemas em obter os dados de que preciso até tentar unir as duas tabelas.

    Tbl_question  
    -------------
    _Eu iria  
    questão  
    Categoria ID  
    Tbl_alternative
    ---------------
    _Eu iria 
    questionid 
    Categoria ID 
    alternativo

Eu quero algo como o seguinte:

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

Esta é minha tentativa:

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;

Acho que essa maneira de formar consultas é mais difícil do que o SQL regular, mas recebi o conselho para usar dessa forma, pois é menos sujeita a erros.

Questão

Como faço para unir duas tabelas SQLite em meu aplicativo?


que erro você está tendo? Você tentou testar substituindo a string concatenada por uma string literal? (FWIW, eu não tenho que usar um cursor desde 1986 ou então. Mas eu não desenvolvo para Android.)
Mike Sherrill 'Cat Recall'

Não vejo onde / como as colunas de junção interna estão sendo especificadas no bloco de código do cursor. O SQL seria "selecionar a lista-cols desejada da junção interna T1 T2 em T1.questionid = T2.questionid e T1.categoryid = T2.categoryid onde T1.categoryid = {o valor da categoria desejada}"
Tim

Este é o meu erro: nome de coluna ambíguo: _id:, durante a compilação: SELECT DISTINCT _id, imagem, questão, alternativa, questionid FROM tbl_question INNER JOIN tbl_alternative WHERE categoryid = 2 AND _id = questionid. Portanto, presumo que preciso especificar tbl_question._id = tbl_alternative.questionid, mas não sei como fazer isso na forma de consulta que uso acima. E eu não sei o que para voltar se eu usar um "regular" sql-sintaxe: "Select" de tbl_question INNER JOIN tbl_alternative ON tbl_question._id = tbl_alternative.questionid E tbl_question.categoryid = tbl_alternative.categoryid = categoryid;
kakka47

@Tim: da maneira que você faz, como eu formo o método na classe db-helper? Não devo retornar um cursor? Estou aprendendo enquanto prossigo, agradeço qualquer sugestão que torne meu código melhor!
kakka47 de

Respostas:


204

Você precisa do método rawQuery .

Exemplo:

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

Usar ? ligações em vez de colocar valores em consulta sql bruta.


1
@necromancer como você vê a criação de uma VIEWopção melhor do que rawQuery?
Muhammad Babar

O que deve ser feito após a consulta? Como você sabe qual id pertence a qual tabela, e o que aconteceria se ambas as tabelas tivessem o mesmo nome de coluna para alguma coluna? ou se houver vários itens para a segunda tabela?
desenvolvedor do Android

@ android-developer "O que deve ser feito após a consulta?" - você obtém um Cursor, faça o que quiser com os dados: P; 2. "Como você sabe qual id ..." - é um JOIN, portanto, dependendo de qual tipo , você pode obter chaves populadas SEMPRE, NUNCA ou TALVEZ; 3. "... mesmo nome de coluna para alguma coluna" - pode acontecer, mas a ambigüidade PODE ser removida. Honestamente, não sei se você pode buscar com "Table.column" usando getColumnIndex. Você sempre pode calcular o índice manualmente, apenas depure-o teste; 4. "vários itens para a segunda mesa" também depende do tipo de junção.
leRobot

assumindo que a View é um FULL OUTER JOIN de uma relação de 1 para muitos (por exemplo, RawContactsEntity ), você pode obter linhas com chave nula na tabela "muitos", mas nunca na tabela "1", então você deve ramificar a passagem do cursor assim: while (cursor.moveToNext () {if (cursor.getValue (getManyTableKeyIndex ()) == NULL) {/ * esta é uma linha sem relação, tem apenas "1" dados da tabela /} else {/ isto é uma relação HIT, então há dados de ambas as tabelas * /}} cursor.close ();
leRobot

Esqueci de adicionar outra ramificação para linhas "sem muitos" que você ainda pode obter com FULL OUTER JOIN, mas normalmente não se a relação for estrita (não tenho certeza se o Android impõe relacionamentos). Não sei realmente como obter colunas ambíguas, mas você pode apenas testá-lo em qualquer CLI SQLite declarando uma visão com ambigüidade de coluna e ver que nome essas colunas obtêm em uma consulta "SELECT * FROM view_x". Em seguida, basta aplicar isso a qualquer tipo de auxiliar do Android que você quiser (.query () / .rawQuery () ...)
leRobot

20

Uma forma alternativa é construir uma visão que é então consultada como uma tabela. Em muitos gerenciadores de banco de dados, o uso de uma visualização pode resultar em melhor desempenho.

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;

Isso é de memória, portanto, pode haver alguns problemas sintáticos. http://www.sqlite.org/lang_createview.html

Menciono essa abordagem porque então você pode usar SQLiteQueryBuilder com a visão, pois você deu a entender que era a preferida.


4
Quando se trata de RDBMSs como o Oracle, as visualizações podem fornecer alguns ganhos de desempenho, mas, em minha experiência, elas são usadas apenas quando necessário para fins de desempenho ou relatório. Ou, dito de outra forma, você não cria uma visualização para cada consulta de junção que usa. E no SQLite em particular, não acredito que haja ganho de desempenho - de acordo com a resposta a esta pergunta, o SQLite reescreve a consulta usando uma subconsulta. stackoverflow.com/questions/25577407/performance-penalty-for-unused-view#25580319 A API do Android pode ser limitante para o que o OP está fazendo, mas rawQuery()é a resposta certa.
spaaarky21

13

Além da resposta de @pawelzieba, que definitivamente está correta, juntar duas tabelas, enquanto você pode usar um INNER JOINcomo este

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

via consulta bruta 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
);

por causa do suporte compatível com versões anteriores do SQLite da forma primitiva de consulta, transformamos esse comando neste -

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

e, portanto, ser capaz de obter vantagem do 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 uma postagem detalhada no blog, verifique esta http://blog.championswimmer.in/2015/12/doing-a-table-join-in-android-without-using-rawquery


1
Não entendo de onde vem o Utils.concat.
nicoqueijo

1
Esta é uma boa abordagem, mas não funcionará se qualquer nome de coluna em uma tabela for igual a um nome de coluna em outra tabela. Por exemplo, se ambas as tabelas tiverem uma coluna com ID de nome, isso vai lançarandroid.database.sqlite.SQLiteException: ambiguous column name
Anup Sharma

8

"Coluna ambígua" geralmente significa que o mesmo nome de coluna aparece em pelo menos duas tabelas; o mecanismo de banco de dados não sabe qual você deseja. Use nomes de tabela completos ou aliases de tabela para remover a ambiguidade.

Aqui está um exemplo que por acaso tive em meu editor. É problema de outra pessoa, mas deve fazer sentido de qualquer maneira.

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)

Sim, eu suspeitei disso. Mas não sabia como especificar as tabelas, pois da forma: DBTABLE_QUESTION.KEY_QUESTION não funcionava. Mas quando eu uso o método rawQuery, é fácil definir a qual tabela qual coluna pertence.
kakka47 de
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.