Como faço para usar instruções preparadas em SQlite no Android?

100

Como faço para usar instruções preparadas em SQlite no Android?

pupeno
fonte
9
Considere alterar a resposta aceita.
Suragch
9
concordou - considere mudar a resposta ... a mentalidade de rebanho votou positivamente na resposta aceita, quando existe uma solução melhor.
goodguys_activate
4
Pablo, por favor mude a resposta aceita ... o exemplo dado nem corre. E nossos votos negativos não são suficientes para desafiá-lo.
SparK de

Respostas:

21

Eu uso declarações preparadas no Android o tempo todo, é bastante simples:

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
jasonhudgins
fonte
81
Não sei como essa resposta pode ter tantos votos. SQLIteStatement # execute não deve ser usado para consultas Sql, apenas instruções. Verifique developer.android.com/reference/android/database/sqlite/…
simao
9
Então, como você deve usar uma instrução preparada para consultar dados?
Juan Mendes
12
Observe que SQLiteStatement.bindXXX()tem um índice baseado em 1, não baseado em 0 como a maioria dos outros.
Simulante
6
@jasonhudgins Por que não apenas substituir seu SELECT por um INSERT? Acabei de sair desta discussão, onde você confundiu um iniciante
keyser de
35
exemplo perfeito da mentalidade de rebanho que
165

Para instruções SQLite preparadas no Android, existe SQLiteStatement . As instruções preparadas ajudam a acelerar o desempenho (especialmente para instruções que precisam ser executadas várias vezes) e também ajudam a evitar ataques de injeção. Veja este artigo para uma discussão geral sobre as declarações preparadas.

SQLiteStatementdeve ser usado com instruções SQL que não retornam vários valores. (Isso significa que você não os usaria para a maioria das consultas.) Abaixo estão alguns exemplos:

Crie uma mesa

String sql = "CREATE TABLE table_name (column_1 INTEGER PRIMARY KEY, column_2 TEXT)";
SQLiteStatement stmt = db.compileStatement(sql);
stmt.execute();

O execute()método não retorna um valor, portanto, é apropriado para ser usado com CREATE e DROP, mas não se destina a ser usado com SELECT, INSERT, DELETE e UPDATE porque esses valores retornam. (Mas veja esta pergunta .)

Inserir valores

String sql = "INSERT INTO table_name (column_1, column_2) VALUES (57, 'hello')";
SQLiteStatement statement = db.compileStatement(sql);
long rowId = statement.executeInsert();

Observe que o executeInsert()método é usado em vez de execute(). Claro, você não gostaria de inserir sempre as mesmas coisas em todas as linhas. Para isso, você pode usar ligações .

String sql = "INSERT INTO table_name (column_1, column_2) VALUES (?, ?)";
SQLiteStatement statement = db.compileStatement(sql);

int intValue = 57;
String stringValue = "hello";

statement.bindLong(1, intValue); // 1-based: matches first '?' in sql string
statement.bindString(2, stringValue);  // matches second '?' in sql string

long rowId = statement.executeInsert();

Normalmente, você usa instruções preparadas quando deseja repetir algo rapidamente (como um INSERT) muitas vezes. A instrução preparada faz com que a instrução SQL não precise ser analisada e compilada todas as vezes. Você pode acelerar as coisas ainda mais usando transações . Isso permite que todas as alterações sejam aplicadas de uma vez. Aqui está um exemplo:

String stringValue = "hello";
try {

    db.beginTransaction();
    String sql = "INSERT INTO table_name (column_1, column_2) VALUES (?, ?)";
    SQLiteStatement statement = db.compileStatement(sql);

    for (int i = 0; i < 1000; i++) {
        statement.clearBindings();
        statement.bindLong(1, i);
        statement.bindString(2, stringValue + i);
        statement.executeInsert();
    }

    db.setTransactionSuccessful(); // This commits the transaction if there were no exceptions

} catch (Exception e) {
    Log.w("Exception:", e);
} finally {
    db.endTransaction();
}

Confira esses links para mais informações sobre transações e como agilizar as inserções no banco de dados.

Atualizar linhas

Este é um exemplo básico. Você também pode aplicar os conceitos da seção acima.

String sql = "UPDATE table_name SET column_2=? WHERE column_1=?";
SQLiteStatement statement = db.compileStatement(sql);

int id = 7;
String stringValue = "hi there";

statement.bindString(1, stringValue);
statement.bindLong(2, id);

int numberOfRowsAffected = statement.executeUpdateDelete();

Excluir linhas

O executeUpdateDelete()método também pode ser usado para instruções DELETE e foi introduzido na API 11. Veja este Q&A .

Aqui está um exemplo.

try {

    db.beginTransaction();
    String sql = "DELETE FROM " + table_name +
            " WHERE " + column_1 + " = ?";
    SQLiteStatement statement = db.compileStatement(sql);

    for (Long id : words) {
        statement.clearBindings();
        statement.bindLong(1, id);
        statement.executeUpdateDelete();
    }

    db.setTransactionSuccessful();

} catch (SQLException e) {
    Log.w("Exception:", e);
} finally {
    db.endTransaction();
}

Inquerir

Normalmente, quando você executa uma consulta, deseja obter um cursor de volta com muitas linhas. Mas não SQLiteStatementé para isso. Você não executa uma consulta com ele, a menos que precise apenas de um resultado simples, como o número de linhas no banco de dados, o que pode ser feito comsimpleQueryForLong()

String sql = "SELECT COUNT(*) FROM table_name";
SQLiteStatement statement = db.compileStatement(sql);
long result = statement.simpleQueryForLong();

Normalmente, você executará o query()método SQLiteDatabase para obter um cursor.

SQLiteDatabase db = dbHelper.getReadableDatabase();
String table = "table_name";
String[] columnsToReturn = { "column_1", "column_2" };
String selection = "column_1 =?";
String[] selectionArgs = { someValue }; // matched to "?" in selection
Cursor dbCursor = db.query(table, columnsToReturn, selection, selectionArgs, null, null, null);

Veja esta resposta para maiores detalhes sobre as dúvidas.

Suragch
fonte
5
Apenas um lembrete: os métodos .bindString / .bindLong / ... são todos baseados em 1.
Denys Vitali
Eu estava procurando nos bastidores os métodos de conveniência do Android, como .query, .insert e .delete, e percebi que eles usam SQLiteStatement nos bastidores. Não seria mais fácil usar apenas métodos de conveniência em vez de construir suas próprias instruções?
Nicolás Carrasco
@NicolásCarrasco, já faz um tempo que não trabalho nisso, estou um pouco enferrujado agora. Para consultas e inserções, atualizações e exclusões únicas, eu definitivamente usaria os métodos de conveniência. No entanto, se você estiver fazendo inserções, atualizações ou exclusões em massa, eu consideraria instruções preparadas junto com uma transação. Quanto ao SQLiteStatement sendo usado nos bastidores e se os métodos de conveniência são bons o suficiente, não posso falar sobre isso. Eu diria que, se os métodos de conveniência estão funcionando rápido o suficiente para você, use-os.
Suragch
Por que você usa clearBindings ()? Você vincula todos os seus valores sem qualquer condição. Não faz sentido para mim defini-los como nulos primeiro e o com o valor real.
O incrível janeiro
@TheincredibleJan, não tenho certeza. Pode não ser necessário e você pode tentar sem limpar as ligações para ver se faz diferença. No entanto, dito isso, chamar clearBindings()não apenas os define como null(consulte o código-fonte ). Eu vejo isso como uma limpeza do estado para que nada o influencie do loop anterior. Talvez isso não seja necessário. Eu ficaria feliz se alguém que sabe comentar.
Suragch,
22

Se você quiser um cursor no retorno, pode considerar algo assim:

SQLiteDatabase db = dbHelper.getWritableDatabase();

public Cursor fetchByCountryCode(String strCountryCode)
{
    /**
     * SELECT * FROM Country
     *      WHERE code = US
     */
    return cursor = db.query(true, 
        "Country",                        /**< Table name. */
        null,                             /**< All the fields that you want the 
                                                cursor to contain; null means all.*/
        "code=?",                         /**< WHERE statement without the WHERE clause. */
        new String[] { strCountryCode },    /**< Selection arguments. */
        null, null, null, null);
}

/** Fill a cursor with the results. */
Cursor c = fetchByCountryCode("US");

/** Retrieve data from the fields. */
String strCountryCode = c.getString(cursor.getColumnIndex("code"));

/** Assuming that you have a field/column with the name "country_name" */
String strCountryName = c.getString(cursor.getColumnIndex("country_name"));

Veja este snippet Genscripts caso queira um mais completo. Observe que esta é uma consulta SQL parametrizada, portanto, em essência, é uma instrução preparada.

Jbaez
fonte
Pequeno erro no código acima: deveria ser "new String [] {strCountryCode}," ao invés de "new String {strCountryCode}".
Pierre-Luc Simard
Você precisa mover o cursor antes de recuperar os dados
Chin
9

O exemplo de jasonhudgins não funcionará. Você não pode executar uma consulta com stmt.execute()e obter um valor (ou a Cursor) de volta.

Você só pode pré-compilar instruções que não retornem nenhuma linha (como uma instrução de inserção ou criação de tabela) ou uma única linha e coluna (e use simpleQueryForLong()ou simpleQueryForString()).

redfish64
fonte
1

Para obter um cursor, você não pode usar um compiledStatement. No entanto, se você quiser usar uma instrução SQL totalmente preparada, recomendo uma adaptação do método de jbaez ... Usando em db.rawQuery()vez de db.query().

Aaron
fonte