¿Cómo utilizo declaraciones preparadas en SQlite en Android?

100

¿Cómo utilizo declaraciones preparadas en SQlite en Android?

pupeño
fuente
9
Considere cambiar la respuesta aceptada.
Suragch
9
de acuerdo - considere cambiar la respuesta ... la mentalidad de rebaño votó a favor de la respuesta aceptada, cuando existe una mejor solución.
goodguys_activate
4
Pablo, por favor cambie la respuesta aceptada ... el ejemplo dado ni siquiera funciona. Y nuestros votos negativos no son suficientes para desanclarlo.
SparK

Respuestas:

21

Utilizo declaraciones preparadas en Android todo el tiempo, es bastante simple:

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
Jasonhudgins
fuente
81
No sé cómo esta respuesta puede tener tantos votos. SQLIteStatement # execute no debe usarse para consultas Sql, solo declaraciones. Consulte developer.android.com/reference/android/database/sqlite/…
simao
9
Entonces, ¿cómo se supone que debe usar una declaración preparada para consultar datos?
Juan Mendes
12
Tenga en cuenta que SQLiteStatement.bindXXX()tiene un índice basado en 1, no basado en 0 como la mayoría.
Simulante
6
@jasonhudgins ¿Por qué no reemplazar tu SELECT con un INSERT? Vengo de este hilo, donde has confundido a un principiante
keyer
35
ejemplo perfecto de la mentalidad de manada que vota y acepta una respuesta que es completamente incorrecta
165

Para las declaraciones SQLite preparadas en Android, existe SQLiteStatement . Las declaraciones preparadas lo ayudan a acelerar el rendimiento (especialmente para declaraciones que deben ejecutarse varias veces) y también ayudan a evitar ataques de inyección. Consulte este artículo para obtener una discusión general sobre declaraciones preparadas.

SQLiteStatementestá destinado a utilizarse con sentencias SQL que no devuelven varios valores. (Eso significa que no los usaría para la mayoría de las consultas). A continuación se muestran algunos ejemplos:

Crea una tabla

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

El execute()método no devuelve un valor, por lo que es apropiado usarlo con CREATE y DROP, pero no está destinado a usarse con SELECT, INSERT, DELETE y UPDATE porque estos devuelven valores. (Pero vea esta pregunta ).

Insertar valores

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

Tenga en cuenta que el executeInsert()método se utiliza en lugar de execute(). Por supuesto, no querrá ingresar siempre las mismas cosas en cada fila. Para eso puedes usar enlaces .

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

Por lo general, usa declaraciones preparadas cuando desea repetir algo rápidamente (como un INSERT) muchas veces. La declaración preparada hace que la declaración SQL no tenga que ser analizada y compilada cada vez. Puede acelerar las cosas aún más mediante transacciones . Esto permite que todos los cambios se apliquen a la vez. Aquí hay un ejemplo:

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

Consulte estos enlaces para obtener más información sobre transacciones y acelerar las inserciones de bases de datos.

Actualizar filas

Este es un ejemplo básico. También puede aplicar los conceptos de la sección anterior.

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

Eliminar filas

El executeUpdateDelete()método también se puede utilizar para declaraciones DELETE y se introdujo en la API 11. Consulte estas preguntas y respuestas .

Aquí hay un ejemplo.

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

Consulta

Normalmente, cuando ejecuta una consulta, desea recuperar un cursor con muchas filas. Sin SQLiteStatementembargo, no es para eso . No ejecuta una consulta con él a menos que solo necesite un resultado simple, como el número de filas en la base de datos, lo que puede hacer consimpleQueryForLong()

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

Por lo general, ejecutará el query()método de SQLiteDatabase para obtener un 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);

Consulte esta respuesta para obtener mejores detalles sobre las consultas.

Suragch
fuente
5
Solo un recordatorio: los métodos .bindString / .bindLong / ... están todos basados ​​en 1.
Denys Vitali
Estaba buscando bajo el capó de los métodos de conveniencia de Android como .query, .insert y .delete y noté que usan SQLiteStatement debajo del capó. ¿No sería más fácil usar métodos de conveniencia en lugar de construir sus propias declaraciones?
Nicolás Carrasco
@ NicolásCarrasco, hace un tiempo que no trabajé en esto así que ahora estoy un poco oxidado. Para consultas e inserciones individuales, actualizaciones y eliminaciones, definitivamente usaría los métodos de conveniencia. Sin embargo, si está haciendo inserciones, actualizaciones o eliminaciones masivas, consideraría declaraciones preparadas junto con una transacción. En cuanto a SQLiteStatement que se usa bajo el capó y si los métodos de conveniencia son lo suficientemente buenos, no puedo hablar de eso. Supongo que diría que si los métodos de conveniencia funcionan lo suficientemente rápido para usted, utilícelos.
Suragch
¿Por qué utiliza clearBindings ()? Vincula todos sus valores sin ninguna condición. No tiene sentido para mí establecerlos como nulos primero y luego en el valor real.
El increíble
@TheincredibleJan, no lo sé con certeza. Puede que no sea necesario y puede probarlo sin borrar los enlaces para ver si hace una diferencia. Sin embargo, dicho esto, llamar clearBindings()no solo los configura en null(ver el código fuente ). Lo veo como una limpieza del estado para que nada lo influya desde el ciclo anterior. Sin embargo, quizás eso no sea necesario. Me alegraría que alguien que sepa comentar.
Suragch
22

Si desea un cursor al regresar, entonces podría considerar algo como esto:

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

Vea este fragmento de Genscripts en caso de que desee uno más completo. Tenga en cuenta que esta es una consulta SQL parametrizada, por lo que, en esencia, es una declaración preparada.

jbaez
fuente
Pequeño error en el código anterior: debería ser "nueva Cadena [] {strCountryCode}", en lugar de "nueva Cadena {strCountryCode}".
Pierre-Luc Simard
Debe mover el cursor antes de poder recuperar los datos
Chin
9

El ejemplo de jasonhudgins no funcionará. No puede ejecutar una consulta con stmt.execute()y recuperar un valor (o a Cursor).

Solo puede precompilar declaraciones que no devuelvan filas en absoluto (como una declaración de inserción o creación de tabla) o una sola fila y columna (y use simpleQueryForLong()o simpleQueryForString()).

gallineta nórdica64
fuente
1

Para obtener un cursor, no puede usar un compiledStatement. Sin embargo, si desea utilizar una declaración SQL completamente preparada, le recomiendo una adaptación del método de jbaez ... Usando en db.rawQuery()lugar de db.query().

Aaron
fuente