Как использовать подготовленные операторы в SQlite в Android?

100

Как использовать подготовленные операторы в SQlite в Android?

пупено
источник
9
Попробуйте изменить принятый ответ.
Suragch
9
согласился - подумайте об изменении ответа ... стадное мышление поддержало принятый ответ, когда существует лучшее решение.
goodguys_activate
4
Пабло, пожалуйста, измените принятый ответ ... данный пример даже не запускается. И наших голосов против не достаточно, чтобы открепить его.
SparK

Ответы:

21

Я постоянно использую подготовленные операторы в Android, это довольно просто:

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
Джейсонхаджинс
источник
81
Я не знаю, как за этот ответ может быть столько голосов. SQLIteStatement # execute не следует использовать для запросов Sql, только для операторов. Пожалуйста, проверьте developer.android.com/reference/android/database/sqlite/…
simao,
9
Тогда как вы должны использовать подготовленный оператор для запроса данных?
Хуан Мендес
12
Обратите внимание, что SQLiteStatement.bindXXX()индекс основан на 1, а не на 0, как у большинства.
Simulant
6
@jasonhudgins Почему бы просто не заменить SELECT на INSERT? Я только что пришел из этой
ветки
35
идеальный пример менталитета стада, который поддерживает и принимает совершенно неверный ответ
165

Для подготовленных операторов SQLite в Android есть SQLiteStatement . Подготовленные операторы помогают повысить производительность (особенно для операторов, которые необходимо выполнять несколько раз), а также помогают избежать атак с использованием инъекций. См. Эту статью для общего обсуждения подготовленных операторов.

SQLiteStatementпредназначен для использования с операторами SQL, которые не возвращают несколько значений. (Это означает, что вы не будете использовать их для большинства запросов.) Ниже приведены несколько примеров:

Создать таблицу

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

Этот execute()метод не возвращает значение, поэтому его можно использовать с CREATE и DROP, но он не предназначен для использования с SELECT, INSERT, DELETE и UPDATE, поскольку эти возвращаемые значения. (Но посмотрите на этот вопрос .)

Вставить значения

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

Обратите внимание, что executeInsert()метод используется, а не execute(). Конечно, вы не хотели бы всегда вводить одни и те же данные в каждую строку. Для этого можно использовать привязки .

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

Обычно вы используете подготовленные операторы, когда хотите быстро повторить что-то (например, INSERT) много раз. Подготовленный оператор делает так, что оператор SQL не нужно каждый раз анализировать и компилировать. Вы можете ускорить процесс еще больше, используя транзакции . Это позволяет применить все изменения сразу. Вот пример:

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

Ознакомьтесь с этими ссылками, чтобы получить более подробную информацию о транзакциях и ускорении вставки в базу данных.

Обновить строки

Это простой пример. Вы также можете применить концепции из раздела выше.

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

Удалить строки

Этот executeUpdateDelete()метод также может использоваться для операторов DELETE и был представлен в API 11. См. Эти вопросы и ответы .

Вот пример.

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

Запрос

Обычно, когда вы запускаете запрос, вы хотите вернуть курсор с большим количеством строк. Однако это не то SQLiteStatement, для чего. Вы не запускаете с ним запрос, если вам не нужен только простой результат, например количество строк в базе данных, что вы можете сделать сsimpleQueryForLong()

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

Обычно вы запускаете query()метод SQLiteDatabase, чтобы получить курсор.

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

См. Этот ответ для получения более подробной информации о запросах.

Suragch
источник
5
Напоминаем: все методы .bindString / .bindLong / ... основаны на единице.
Денис Виталий
Я заглянул под капот удобных методов Android, таких как .query, .insert и .delete, и заметил, что они используют SQLiteStatement под капотом. Разве не было бы проще использовать удобные методы вместо создания собственных операторов?
Николас Карраско
@ NicolásCarrasco, я давно не работал над этим, так что теперь я немного заржавел. Для запросов и одиночных вставок, обновлений и удалений я бы определенно использовал удобные методы. Однако, если вы выполняете массовые вставки, обновления или удаления, я бы рассмотрел подготовленные операторы вместе с транзакцией. Что касается того, что SQLiteStatement используется под капотом, и достаточно ли хороши удобные методы, я не могу об этом говорить. Думаю, я бы сказал, что если удобные методы работают для вас достаточно быстро, используйте их.
Suragch 06
Почему вы используете clearBindings ()? Вы связываете все свои ценности без каких-либо условий. Для меня не имеет смысла сначала устанавливать для них null, а для реального значения.
Невероятный
@TheincredibleJan, я точно не знаю. Возможно, в этом нет необходимости, и вы можете попробовать это, не очищая привязки, чтобы увидеть, имеет ли это значение. Однако при этом вызов clearBindings()не просто устанавливает их null(см. Исходный код ). Я смотрю на это как на очистку состояния, чтобы ничего не влияло на него из предыдущего цикла. Хотя, возможно, в этом нет необходимости. Буду рад, если кто-нибудь прокомментирует.
Suragch
22

Если вам нужен курсор при возврате, вы можете рассмотреть что-то вроде этого:

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

См. Этот фрагмент Genscripts, если вам нужен более полный. Обратите внимание, что это параметризованный SQL-запрос, поэтому, по сути, это подготовленный оператор.

jbaez
источник
Небольшая ошибка в приведенном выше коде: это должно быть «new String [] {strCountryCode}» вместо «new String {strCountryCode}».
Пьер-Люк Симар
Вам нужно переместить курсор, прежде чем вы сможете получить данные
Чин
9

Пример с jasonhudgins не работает. Вы не можете выполнить запрос stmt.execute()и вернуть значение (или Cursor).

Вы можете предварительно скомпилировать только операторы, которые либо не возвращают никаких строк (например, оператор вставки или создания таблицы), либо одну строку и столбец (и используйте simpleQueryForLong()или simpleQueryForString()).

морской окунь64
источник
1

Чтобы получить курсор, вы не можете использовать compiledStatement. Однако, если вы хотите использовать полностью подготовленный оператор SQL, я рекомендую адаптировать метод jbaez ... Использование db.rawQuery()вместо db.query().

Аарон
источник