Come si utilizzano le istruzioni preparate in SQlite in Android?


100

Come si utilizzano le istruzioni preparate in SQlite in Android?


9
Valuta la possibilità di modificare la risposta accettata.
Suragch

9
d'accordo - considera la possibilità di cambiare la risposta ... la mentalità del gregge ha votato a favore della risposta accettata, quando esiste una soluzione migliore.
goodguys_activate

4
Pablo, per favore cambia la risposta accettata ... l'esempio dato non funziona nemmeno. E i nostri voti negativi non sono sufficienti per sbloccarlo.
SparK

Risposte:


21

Uso sempre dichiarazioni preparate in Android, è abbastanza semplice:

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();

81
Non so come questa risposta possa avere così tanti voti. SQLIteStatement # execute non deve essere utilizzato per le query SQL, ma solo per le istruzioni. Si prega di controllare developer.android.com/reference/android/database/sqlite/…
simao

9
Allora come dovresti usare un'istruzione preparata per interrogare i dati?
Juan Mendes

12
Nota che SQLiteStatement.bindXXX()ha un indice basato su 1, non basato su 0 come la maggior parte di essi.
Simulazione

6
@jasonhudgins Perché non sostituire semplicemente SELECT con un INSERT? Sono appena uscito da questo thread, dove hai confuso un principiante
keyser

35
perfetto esempio della mentalità del gregge che vota e accetta una risposta completamente errata

165

Per le istruzioni SQLite preparate in Android c'è SQLiteStatement . Le istruzioni preparate consentono di accelerare le prestazioni (soprattutto per le istruzioni che devono essere eseguite più volte) e aiutano anche a evitare attacchi di iniezione. Vedi questo articolo per una discussione generale sulle dichiarazioni preparate.

SQLiteStatementè pensato per essere utilizzato con istruzioni SQL che non restituiscono più valori. (Ciò significa che non li useresti per la maggior parte delle query.) Di seguito sono riportati alcuni esempi:

Crea una tabella

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

Il execute()metodo non restituisce un valore, quindi è appropriato da utilizzare con CREATE e DROP ma non è inteso per essere utilizzato con SELECT, INSERT, DELETE e UPDATE perché questi restituiscono valori. (Ma vedi questa domanda .)

Inserisci valori

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

Si noti che il executeInsert()metodo viene utilizzato anziché execute(). Ovviamente non vorresti inserire sempre le stesse cose in ogni riga. Per questo puoi usare le associazioni .

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

Di solito usi affermazioni preparate quando vuoi ripetere velocemente qualcosa (come un INSERT) molte volte. L'istruzione preparata fa in modo che l'istruzione SQL non debba essere analizzata e compilata ogni volta. Puoi velocizzare ulteriormente le cose utilizzando le transazioni . Ciò consente di applicare tutte le modifiche contemporaneamente. Ecco un esempio:

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

Dai un'occhiata a questi collegamenti per ulteriori informazioni utili sulle transazioni e per velocizzare gli inserimenti nel database.

Aggiorna righe

Questo è un semplice esempio. Puoi anche applicare i concetti della sezione precedente.

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

Elimina righe

Il executeUpdateDelete()metodo può essere utilizzato anche per le istruzioni DELETE ed è stato introdotto nell'API 11. Vedere questa domanda e risposta .

Ecco un esempio.

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

Query

Normalmente quando si esegue una query, si desidera ripristinare un cursore con molte righe. Non è questo ciò che SQLiteStatementserve, però. Non esegui una query con esso a meno che tu non abbia bisogno solo di un risultato semplice, come il numero di righe nel database, che puoi fare consimpleQueryForLong()

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

Di solito eseguirai il query()metodo di SQLiteDatabase per ottenere un cursore.

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

Vedi questa risposta per maggiori dettagli sulle query.


5
Solo un promemoria: i metodi .bindString / .bindLong / ... sono tutti basati su 1.
Denys Vitali

Stavo cercando sotto il cofano di metodi di convenienza Android come .query, .insert e .delete e ho notato che usano SQLiteStatement sotto il cofano. Non sarebbe più semplice usare solo metodi convenienti invece di costruire le proprie dichiarazioni?
Nicolás Carrasco

@ NicolásCarrasco, è passato un po 'di tempo dall'ultima volta che ci ho lavorato, quindi ora sono un po' arrugginito. Per query e singoli inserimenti, aggiornamenti ed eliminazioni, utilizzerei sicuramente i metodi convenienti. Tuttavia, se si eseguono inserimenti, aggiornamenti o eliminazioni di massa, prenderei in considerazione dichiarazioni preparate insieme a una transazione. Per quanto riguarda SQLiteStatement utilizzato sotto il cofano e se i metodi di convenienza sono abbastanza buoni, non posso parlarne. Immagino che direi, se i metodi di convenienza sono abbastanza veloci per te, allora usali.
Suragch

Perché usi clearBindings ()? Leghi tutti i tuoi valori senza alcuna condizione. Non ha senso per me impostarli prima su null e sul valore reale.
L'incredibile

@TheincredibleJan, non lo so per certo. Potrebbe non essere necessario e puoi provarlo senza cancellare le associazioni per vedere se fa la differenza. Tuttavia, detto questo, la chiamata clearBindings()non li imposta solo su null(vedi il codice sorgente ). Lo considero come un ripulire lo stato in modo che nulla lo stia influenzando dal ciclo precedente. Forse non è necessario, però. Sarei felice per qualcuno che sa commentare.
Suragch

22

Se vuoi un cursore al ritorno, potresti prendere in considerazione qualcosa del genere:

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

Vedi questo frammento Genscripts nel caso in cui ne desideri uno più completo. Si noti che questa è una query SQL parametrizzata, quindi in sostanza è un'istruzione preparata.


Piccolo errore nel codice precedente: dovrebbe essere "new String [] {strCountryCode}," invece di "new String {strCountryCode}".
Pierre-Luc Simard

È necessario spostare il cursore prima di poter recuperare i dati
Chin

9

L'esempio di jasonhudgins non funzionerà. Non puoi eseguire una query con stmt.execute()e recuperare un valore (o un Cursor).

È possibile precompilare solo istruzioni che non restituiscono alcuna riga (come un'istruzione di inserimento o di creazione di una tabella) o una singola riga e colonna (e utilizzare simpleQueryForLong()o simpleQueryForString()).


1

Per ottenere un cursore, non puoi usare un compiledStatement. Tuttavia, se si desidera utilizzare un'istruzione SQL completamente preparata, consiglio un adattamento del metodo jbaez ... Usingdb.rawQuery() invece di db.query().

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.