it-swarm.it

Il modo migliore per lavorare con SQLite Android

Sto avendo qualche problema a lavorare con le date sulla mia applicazione Android che usa SQLite. Ho un paio di domande:

  1. Che tipo dovrei usare per memorizzare le date in SQLite (testo, intero, ...)?
  2. Dato il modo migliore per memorizzare le date, come posso conservarlo correttamente usando ContentValues?
  3. Qual è il modo migliore per recuperare la data dal database SQLite?
  4. Come fare una selezione SQL su SQLite, ordinando i risultati per data?
225
Filipe

È possibile utilizzare un campo di testo per memorizzare le date all'interno di SQLite.

Memorizzando le date in formato UTC, l'impostazione predefinita se si utilizza datetime('now')(yyyy-MM-dd HH:mm:ss) consentirà quindi l'ordinamento in base alla colonna della data.

Recuperando le date come stringhe da SQLite puoi quindi formattare/convertirle come richiesto nei formati regionalizzati locali usando il metodo Calendar o Android.text.format.DateUtils.formatDateTime.

Ecco un metodo formattatore regionalizzato che uso;

public static String formatDateTime(Context context, String timeToFormat) {

    String finalDateTime = "";          

    SimpleDateFormat iso8601Format = new SimpleDateFormat(
            "yyyy-MM-dd HH:mm:ss");

    Date date = null;
    if (timeToFormat != null) {
        try {
            date = iso8601Format.parse(timeToFormat);
        } catch (ParseException e) {
            date = null;
        }

        if (date != null) {
            long when = date.getTime();
            int flags = 0;
            flags |= Android.text.format.DateUtils.FORMAT_SHOW_TIME;
            flags |= Android.text.format.DateUtils.FORMAT_SHOW_DATE;
            flags |= Android.text.format.DateUtils.FORMAT_ABBREV_MONTH;
            flags |= Android.text.format.DateUtils.FORMAT_SHOW_YEAR;

            finalDateTime = Android.text.format.DateUtils.formatDateTime(context,
            when + TimeZone.getDefault().getOffset(when), flags);               
        }
    }
    return finalDateTime;
}
42
CodeChimp

Il modo migliore è quello di memorizzare le date come un numero, ricevuto utilizzando il comando Calendario.

//Building the table includes:
StringBuilder query=new StringBuilder();
query.append("CREATE TABLE "+TABLE_NAME+ " (");
query.append(COLUMN_ID+"int primary key autoincrement,");
query.append(COLUMN_DATETIME+" int)");

//And inserting the data includes this:
values.put(COLUMN_DATETIME, System.currentTimeMillis()); 

Perché questo? Prima di tutto, ottenere valori da un intervallo di date è facile. Basta convertire la data in millisecondi e quindi eseguire una query in modo appropriato. L'ordinamento per data è altrettanto facile. Anche le chiamate per la conversione tra vari formati sono altrettanto facili, come ho incluso. La linea di fondo è, con questo metodo, puoi fare tutto ciò che devi fare, senza problemi. Sarà leggermente difficile leggere un valore grezzo, ma è più che compensare quel leggero svantaggio con l'essere facilmente leggibile dalla macchina e utilizzabile. E infatti, è relativamente facile costruire un lettore (e so che ce ne sono alcuni là fuori) che convertirà automaticamente il tag temporale fino ad oggi come tale per una facile lettura.

Vale la pena ricordare che i valori che ne derivano dovrebbero essere lunghi, non int. Il numero intero in sqlite può significare molte cose, qualsiasi cosa da 1-8 byte, ma per quasi tutte le date 64 bit, o un lungo, è ciò che funziona.

EDIT: Come è stato sottolineato nei commenti, devi usare cursor.getLong() per ottenere correttamente il timestamp se lo fai.

199
PearsonArtPhoto
  1. Come presumibilmente in questo commento , userei sempre numeri interi per memorizzare le date.
  2. Per la memorizzazione, è possibile utilizzare un metodo di utilità

    public static Long persistDate(Date date) {
        if (date != null) {
            return date.getTime();
        }
        return null;
    }
    

    così:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
    
  3. Un altro metodo di utilità si occupa del caricamento

    public static Date loadDate(Cursor cursor, int index) {
        if (cursor.isNull(index)) {
            return null;
        }
        return new Date(cursor.getLong(index));
    }
    

    può essere usato in questo modo:

    entity.setDate(loadDate(cursor, INDEX));
    
  4. Ordinare per data è semplice SQL clausola ORDER (perché abbiamo una colonna numerica). Il seguente ordine decrescente (la data più recente è la prima):

    public static final String QUERY = "SELECT table._id, table.dateCol FROM table ORDER BY table.dateCol DESC";
    
    //...
    
        Cursor cursor = rawQuery(QUERY, null);
        cursor.moveToFirst();
    
        while (!cursor.isAfterLast()) {
            // Process results
        }
    

Assicurati sempre di conservare il file Ora UTC/GMT, specialmente quando si lavora con Java.util.Calendar e Java.text.SimpleDateFormat che utilizzano il fuso orario predefinito (vale a dire il proprio dispositivo). Java.util.Date.Date() è sicuro da usare in quanto crea un valore UTC.

30
schnatterer

SQLite può utilizzare tipi di dati testuali, reali o interi per memorizzare le date. Inoltre, ogni volta che si esegue una query, i risultati vengono visualizzati utilizzando il formato %Y-%m-%d %H:%M:%S.

Ora, se si inseriscono/aggiornano i valori di data/ora utilizzando le funzioni di data/ora di SQLite, è possibile memorizzare anche millisecondi. In tal caso, i risultati vengono visualizzati utilizzando il formato %Y-%m-%d %H:%M:%f. Per esempio:

sqlite> create table test_table(col1 text, col2 real, col3 integer);
sqlite> insert into test_table values (
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123')
        );
sqlite> insert into test_table values (
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.126'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.126'),
            strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.126')
        );
sqlite> select * from test_table;
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126

Ora, esegui alcune query per verificare se siamo effettivamente in grado di confrontare i tempi:

sqlite> select * from test_table /* using col1 */
           where col1 between 
               strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.121') and
               strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.125');
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123

Puoi verificare lo stesso SELECT usando col2 e col3 e otterrai gli stessi risultati. Come puoi vedere, la seconda riga (126 millisecondi) non viene restituita.

Nota che BETWEEN è inclusivo, quindi ...

sqlite> select * from test_table 
            where col1 between 
                 /* Note that we are using 123 milliseconds down _here_ */
                strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.123') and
                strftime('%Y-%m-%d %H:%M:%f', '2014-03-01 13:01:01.125');

... restituirà lo stesso set.

Prova a giocare con intervalli di date/orari diversi e tutto si comporterà come previsto.

Che dire senza la funzione strftime?

sqlite> select * from test_table /* using col1 */
           where col1 between 
               '2014-03-01 13:01:01.121' and
               '2014-03-01 13:01:01.125';
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123

Che dire senza la funzione strftime e senza millisecondi?

sqlite> select * from test_table /* using col1 */
           where col1 between 
               '2014-03-01 13:01:01' and
               '2014-03-01 13:01:02';
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126

Che dire ORDER BY?

sqlite> select * from test_table order by 1 desc;
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
sqlite> select * from test_table order by 1 asc;
2014-03-01 13:01:01.123|2014-03-01 13:01:01.123|2014-03-01 13:01:01.123
2014-03-01 13:01:01.126|2014-03-01 13:01:01.126|2014-03-01 13:01:01.126

Funziona bene.

Infine, quando si tratta di operazioni reali all'interno di un programma (senza usare l'eseguibile sqlite ...)

BTW: Sto usando JDBC (non sono sicuro degli altri linguaggi) ... il driver sqlite-jdbc v3.7.2 da xerial - Forse le versioni più recenti cambiano il comportamento spiegato di seguito ... Se stai sviluppando in Android, non hai bisogno di un driver jdbc. Tutte le operazioni SQL possono essere inviate usando SQLiteOpenHelper.

JDBC ha diversi metodi per ottenere valori di data/ora effettivi da un database: Java.sql.Date, Java.sql.Time e Java.sql.Timestamp.

I metodi correlati in Java.sql.ResultSet sono (ovviamente) getDate(..), getTime(..) e getTimestamp() rispettivamente.

Per esempio:

Statement stmt = ... // Get statement from connection
ResultSet rs = stmt.executeQuery("SELECT * FROM TEST_TABLE");
while (rs.next()) {
    System.out.println("COL1 : "+rs.getDate("COL1"));
    System.out.println("COL1 : "+rs.getTime("COL1"));
    System.out.println("COL1 : "+rs.getTimestamp("COL1"));
    System.out.println("COL2 : "+rs.getDate("COL2"));
    System.out.println("COL2 : "+rs.getTime("COL2"));
    System.out.println("COL2 : "+rs.getTimestamp("COL2"));
    System.out.println("COL3 : "+rs.getDate("COL3"));
    System.out.println("COL3 : "+rs.getTime("COL3"));
    System.out.println("COL3 : "+rs.getTimestamp("COL3"));
}
// close rs and stmt.

Poiché SQLite non ha un tipo di dati DATE/TIME/TIMESTAMP effettivo, tutti questi 3 metodi restituiscono valori come se gli oggetti fossero inizializzati con 0:

new Java.sql.Date(0)
new Java.sql.Time(0)
new Java.sql.Timestamp(0)

Quindi, la domanda è: come possiamo effettivamente selezionare, inserire o aggiornare gli oggetti Data/Ora/Indicatore data/ora? Non c'è una risposta facile. Puoi provare diverse combinazioni, ma ti costringono ad incorporare le funzioni SQLite in tutte le istruzioni SQL. È molto più semplice definire una classe di utilità per trasformare il testo in oggetti Date all'interno del tuo programma Java. Ricorda sempre che SQLite trasforma qualsiasi valore di data in UTC + 0000.

In sintesi, nonostante la regola generale di utilizzare sempre il tipo di dati corretto, o anche interi che denotano il tempo Unix (millisecondi da Epoch), trovo molto più semplice l'utilizzo del formato SQLite predefinito ('%Y-%m-%d %H:%M:%f' o in Java 'yyyy-MM-dd HH:mm:ss.SSS') piuttosto che complicare tutto il tuo SQL istruzioni con funzioni SQLite. Il primo approccio è molto più facile da mantenere.

TODO: Controllerò i risultati quando uso getDate/getTimestamp all'interno di Android (API15 o superiore) ... forse il driver interno è diverso da sqlite-jdbc ...

8
miguelt

Di solito (come faccio in mysql/postgres) memorizzo le date in int (mysql/post) o testo (sqlite) per memorizzarle nel formato timestamp.

Quindi li convertirò in oggetti Date ed eseguirò azioni basate su TimeZone dell'utente

3
StErMi

Il modo migliore per memorizzare datein SQlite DB serve per memorizzare DateTimeMilliseconds corrente. Di seguito è riportato lo snippet di codice per farlo_

  1. Ottieni DateTimeMilliseconds
public static long getTimeMillis(String dateString, String dateFormat) throws ParseException {
    /*Use date format as according to your need! Ex. - yyyy/MM/dd HH:mm:ss */
    String myDate = dateString;//"2017/12/20 18:10:45";
    SimpleDateFormat sdf = new SimpleDateFormat(dateFormat/*"yyyy/MM/dd HH:mm:ss"*/);
    Date date = sdf.parse(myDate);
    long millis = date.getTime();

    return millis;
}
  1. Inserisci i dati nel tuo DB
public void insert(Context mContext, long dateTimeMillis, String msg) {
    //Your DB Helper
    MyDatabaseHelper dbHelper = new MyDatabaseHelper(mContext);
    database = dbHelper.getWritableDatabase();

    ContentValues contentValue = new ContentValues();
    contentValue.put(MyDatabaseHelper.DATE_MILLIS, dateTimeMillis);
    contentValue.put(MyDatabaseHelper.MESSAGE, msg);

    //insert data in DB
    database.insert("your_table_name", null, contentValue);

   //Close the DB connection.
   dbHelper.close(); 

}

Now, your data (date is in currentTimeMilliseconds) is get inserted in DB .

Il passo successivo è, quando si desidera recuperare i dati dal DB, è necessario convertire i rispettivi millisecondi di data e ora nella data corrispondente. Di seguito è riportato lo snippet di codice di esempio per eseguire lo stesso

  1. Converti millisecondi di data nella stringa di data.
public static String getDate(long milliSeconds, String dateFormat)
{
    // Create a DateFormatter object for displaying date in specified format.
    SimpleDateFormat formatter = new SimpleDateFormat(dateFormat/*"yyyy/MM/dd HH:mm:ss"*/);

    // Create a calendar object that will convert the date and time value in milliseconds to date.
    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(milliSeconds);
    return formatter.format(calendar.getTime());
}
  1. Ora, finalmente recupera i dati e vedi come funziona ...
public ArrayList<String> fetchData() {

    ArrayList<String> listOfAllDates = new ArrayList<String>();
    String cDate = null;

    MyDatabaseHelper dbHelper = new MyDatabaseHelper("your_app_context");
    database = dbHelper.getWritableDatabase();

    String[] columns = new String[] {MyDatabaseHelper.DATE_MILLIS, MyDatabaseHelper.MESSAGE};
    Cursor cursor = database.query("your_table_name", columns, null, null, null, null, null);

    if (cursor != null) {

        if (cursor.moveToFirst()){
            do{
                //iterate the cursor to get data.
                cDate = getDate(cursor.getLong(cursor.getColumnIndex(MyDatabaseHelper.DATE_MILLIS)), "yyyy/MM/dd HH:mm:ss");

                listOfAllDates.add(cDate);

            }while(cursor.moveToNext());
        }
        cursor.close();

    //Close the DB connection.
    dbHelper.close(); 

    return listOfAllDates;

}

Spero che questo possa aiutare tutti! :)

2
Rupesh Yadav

1 - Esattamente come ha detto StErMi.

2 - Si prega di leggere questo: http://www.vogella.de/articles/AndroidSQLite/article.html

3 -

Cursor cursor = db.query(TABLE_NAME, new String[] {"_id", "title", "title_raw", "timestamp"}, 
                "//** YOUR REQUEST**//", null, null, "timestamp", null);

vedere qui:

Query () in SQLiteDatabase

4 - vedi risposta 3

1
Waza_Be

Preferisco questo. Questo non è il modo migliore, ma una soluzione veloce.

//Building the table includes:
StringBuilder query= new StringBuilder();
query.append("CREATE TABLE "+TABLE_NAME+ " (");
query.append(COLUMN_ID+"int primary key autoincrement,");
query.append(COLUMN_CREATION_DATE+" DATE)");

//Inserting the data includes this:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
values.put(COLUMN_CREATION_DATE,dateFormat.format(reactionGame.getCreationDate())); 

// Fetching the data includes this:
try {
   Java.util.Date creationDate = dateFormat.parse(cursor.getString(0);
   YourObject.setCreationDate(creationDate));
} catch (Exception e) {
   YourObject.setCreationDate(null);
}
1
lidox
"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";
0
Suresh Mewara