Jak korzystać z przygotowanych wyciągów w SQlite w Androidzie?

100

Jak korzystać z przygotowanych wyciągów w SQlite w Androidzie?

pupeno
źródło
9
Rozważ zmianę zaakceptowanej odpowiedzi.
Suragch
9
zgodził się - rozważ zmianę odpowiedzi ... mentalność stada głosowała za zaakceptowaną odpowiedzią, gdy istnieje lepsze rozwiązanie.
goodguys_activate
4
Pablo, zmień zaakceptowaną odpowiedź ... podany przykład nawet nie działa. A nasze głosy w dół nie wystarczą, aby go odpiąć.
SparK

Odpowiedzi:

21

Cały czas korzystam z przygotowanych wyciągów w Androidzie, to dość proste:

SQLiteDatabase db = dbHelper.getWritableDatabase();
SQLiteStatement stmt = db.compileStatement("INSERT INTO Country (code) VALUES (?)");
stmt.bindString(1, "US");
stmt.executeInsert();
jasonhudgins
źródło
81
Nie wiem, jak ta odpowiedź może mieć tyle głosów. SQLIteStatement # execute nie powinno być używane do zapytań Sql, tylko do instrukcji. Sprawdź developer.android.com/reference/android/database/sqlite/ ...
simao,
9
W takim razie jak masz wykorzystać przygotowane zestawienie do odpytywania danych?
Juan Mendes
12
Zauważ, że SQLiteStatement.bindXXX()indeks ma wartość 1, a nie 0, jak większość z nich.
Symulant
6
@jasonhudgins Dlaczego po prostu nie zamienić swojego SELECT na INSERT? Właśnie wyszedłem z tego wątku, w którym
pomyliłeś
35
doskonały przykład mentalności stadnej, która pozytywnie ocenia i akceptuje odpowiedź całkowicie niepoprawną
165

Dla przygotowanych instrukcji SQLite w systemie Android jest SQLiteStatement . Przygotowane instrukcje pomagają przyspieszyć działanie (szczególnie w przypadku instrukcji, które muszą być wykonywane wielokrotnie), a także pomagają uniknąć ataków polegających na iniekcji. Zobacz ten artykuł, aby zapoznać się z ogólną dyskusją na temat przygotowanych wypowiedzi.

SQLiteStatementjest przeznaczony do użytku z instrukcjami SQL, które nie zwracają wielu wartości. (Oznacza to, że nie użyjesz ich do większości zapytań). Poniżej znajduje się kilka przykładów:

Utwórz tabelę

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

execute()Sposób nie zwraca wartość tak, że jest odpowiedni do użycia z tworzenia i usuwania, lecz nie są przeznaczone do stosowania z wybranych przez użytkownika, Wstawianie, usuwanie i UPDATE, ponieważ obie te wartości. (Ale spójrz na to pytanie .)

Wstaw wartości

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

Zwróć uwagę, że executeInsert()metoda jest używana zamiast execute(). Oczywiście nie chciałbyś zawsze wpisywać te same rzeczy w każdym wierszu. Do tego możesz użyć wiązań .

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

Zwykle używasz przygotowanych instrukcji, gdy chcesz szybko coś powtórzyć (np. INSERT) wiele razy. Przygotowana instrukcja sprawia, że ​​instrukcja SQL nie musi być za każdym razem analizowana i kompilowana. Możesz jeszcze bardziej przyspieszyć, korzystając z transakcji . Pozwala to na jednoczesne zastosowanie wszystkich zmian. Oto przykład:

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

Sprawdź te linki, aby uzyskać więcej przydatnych informacji na temat transakcji i przyspieszenia wstawiania do bazy danych.

Zaktualizuj wiersze

To jest podstawowy przykład. Możesz również zastosować koncepcje z powyższej sekcji.

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

Usuń wiersze

Ta executeUpdateDelete()metoda może być również używana do instrukcji DELETE i została wprowadzona w API 11. Zobacz to pytanie i odpowiedź .

Oto przykład.

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

Pytanie

Zwykle po uruchomieniu zapytania chcesz odzyskać kursor z wieloma wierszami. SQLiteStatementJednak nie do tego służy. Nie uruchamiasz z nim zapytania, chyba że potrzebujesz tylko prostego wyniku, takiego jak liczba wierszy w bazie danych, którą możesz zrobićsimpleQueryForLong()

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

Zwykle query()w celu uzyskania kursora uruchomisz metodę 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);

Zobacz tę odpowiedź, aby uzyskać szczegółowe informacje na temat zapytań.

Suragch
źródło
5
Dla przypomnienia: wszystkie metody .bindString / .bindLong / ... są oparte na 1.
Denys Vitali
Szukałem pod maską wygodnych metod Androida, takich jak .query, .insert i .delete, i zauważyłem, że pod maską używają SQLiteStatement. Czy nie byłoby łatwiej po prostu skorzystać z wygodnych metod zamiast budować własne stwierdzenia?
Nicolás Carrasco,
@ NicolásCarrasco, minęło trochę czasu, odkąd nad tym pracowałem, więc jestem teraz trochę zardzewiały. W przypadku zapytań i pojedynczych wstawień, aktualizacji i usuwania zdecydowanie użyłbym wygodnych metod. Jeśli jednak robisz masowe wstawienia, aktualizacje lub usunięcia, rozważyłbym przygotowane zestawienia wraz z transakcją. Jeśli chodzi o używanie SQLiteStatement pod maską i czy metody wygody są wystarczająco dobre, nie mogę o tym mówić. Myślę, że powiedziałbym, że jeśli wygodne metody działają wystarczająco szybko, użyj ich.
Suragch
Dlaczego używasz clearBindings ()? Wiążesz wszystkie swoje wartości bez żadnych warunków. Nie ma dla mnie sensu ustawianie ich najpierw na zero, a na rzeczywistą wartość.
Niesamowity
@TheincredibleJan, nie wiem na pewno. Może to nie być konieczne i możesz spróbować bez czyszczenia wiązań, aby sprawdzić, czy to robi różnicę. Jednak to powiedziawszy, wywołanie clearBindings()nie tylko ustawia je na null(zobacz kod źródłowy ). Patrzę na to jako oczyszczanie stanu, dzięki czemu nic nie wpływa na niego z poprzedniej pętli. Może to jednak nie jest konieczne. Byłbym zadowolony, gdyby ktoś wiedział, że może to skomentować.
Suragch
22

Jeśli chcesz mieć kursor po powrocie, możesz rozważyć coś takiego:

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

Zobacz ten fragment Genscripts na wypadek, gdybyś chciał mieć pełniejszy. Zauważ, że jest to sparametryzowane zapytanie SQL, więc w zasadzie jest to przygotowana instrukcja.

jbaez
źródło
Mały błąd w powyższym kodzie: powinno to być „new String [] {strCountryCode}” zamiast „new String {strCountryCode}”.
Pierre-Luc Simard
Musisz przesunąć kursor, zanim będziesz mógł pobrać dane
Chin,
9

Przykład jasonhudgins nie zadziała. Nie można wykonać zapytania zi stmt.execute()odzyskać wartość (lub a Cursor).

Możesz prekompilować tylko instrukcje, które albo nie zwracają żadnych wierszy (takich jak instrukcja wstawiania lub tworzenia tabeli), albo pojedynczy wiersz i kolumnę (i użyj simpleQueryForLong()lub simpleQueryForString()).

karmazyn64
źródło
1

Aby uzyskać kursor, nie możesz użyć compiledStatement. Jeśli jednak chcesz skorzystać z w pełni przygotowanej instrukcji SQL, polecam adaptację metody jbaez ... Używanie db.rawQuery()zamiast db.query().

Aaron
źródło