Android SQLite'da tarihlerle çalışmanın en iyi yolu [kapalı]


237

SQLite kullanan Android uygulamamda tarihlerle çalışırken bazı sorunlar yaşıyorum. Birkaç sorum var:

  1. Tarihleri ​​SQLite (metin, tamsayı, ...) içinde saklamak için ne tür kullanmalıyım?
  2. Tarihleri ​​depolamanın en iyi yolu göz önüne alındığında, ContentValues ​​kullanarak doğru şekilde nasıl depolayabilirim?
  3. Tarihi SQLite veritabanından almanın en iyi yolu nedir?
  4. Sonuçları SQLite'de bir sql seçimi yapmak, sonuçları tarihe göre sıralamak nasıl?

2
Yalnızca Calendar sınıfını ve üye zamanını kullanın (1/1/1970 tarihinden bu yana geçen milisaniye sayısını temsil eder). Zaman değerini kullanıcı tarafından okunabilir dizelere dönüştürmek için üye işlevler vardır.
slayton

Yanıtlar:


43

İçindeki tarihleri ​​saklamak için bir metin alanı kullanabilirsiniz SQLite.

Tarihleri ​​UTC biçiminde depolamak, varsayılan olarak kullanırsanız datetime('now') (yyyy-MM-dd HH:mm:ss)tarih sütununa göre sıralamaya izin verir.

Tarihleri SQLitesizden dize olarak almak daha sonra bunları Takvim veya android.text.format.DateUtils.formatDateTimeyöntemi kullanarak yerel bölgesel biçimlere gerektiği şekilde biçimlendirebilir / dönüştürebilir .

İşte kullandığım bölgesel bir biçimlendirici yöntemi;

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

63
Sorgulama tarih aralıklarını nasıl ele alırsınız?
Joe

51
"Önerilen uygulama"? Kulağa doğru gelmiyor.
shim

135
SQL kullandığım yıllarda daha önce hiç kimsenin tarihleri ​​dizeler olarak saklamayı tavsiye ettiğini hiç görmedim. Belirli bir tarih sütunu türünüz yoksa, bir tamsayı kullanın ve Unix saatinde saklayın (çağdan bu yana saniye). Sıralanabilir ve aralıklarda kullanılabilir ve kolayca dönüştürülebilir.
mikebabcock

20
Tarihleri ​​dize olarak saklamak, "bilgi" olarak almak ve almak ve göstermek istediğiniz bir şey olarak saklamak istiyorsanız iyi olur. Ancak tarihleri ​​"veri" olarak, çalışmak için bir şey olarak saklamak istiyorsanız, çağdan beri tamsayı - zaman olarak saklamayı düşünmelisiniz. Bu, tarih aralıklarını sorgulamanıza izin verecektir, bu yüzden dönüşümler vb. Hakkında endişelenmenize gerek yoktur.
Krystian

8
Sqlite dokümantasyon tarihleri saklamak için uygun bir çözüm olarak metin olarak saklanması listeleri (8601 ISO). Aslında, önce listelenir.
anderspitman

211

En iyi yol, Takvim komutunu kullanarak tarihleri ​​sayı olarak saklamaktır.

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

Bunu neden yaptın? Her şeyden önce, bir tarih aralığından değer almak kolaydır. Tarihinizi milisaniyeye dönüştürün ve ardından uygun şekilde sorgulayın. Tarihe göre sıralama da benzer şekilde kolaydır. Çeşitli formatlar arasında dönüştürme çağrıları da dahil, ben de kolay. Sonuç olarak, bu yöntemle yapmanız gereken her şeyi yapabilirsiniz, sorun yok. Ham bir değeri okumak biraz zor olacaktır, ancak kolayca makinede okunabilir ve kullanılabilir olmanın hafif dezavantajını telafi etmekten daha fazlasıdır. Ve aslında, zaman etiketini otomatik olarak bugüne kadar kolay okunacak şekilde dönüştürecek bir okuyucu oluşturmak nispeten kolaydır (Ve orada bazılarının olduğunu biliyorum).

Bundan çıkan değerlerin int değil uzun olması gerektiğini belirtmek gerekir. Sqlite tamsayı birçok şey anlamına gelebilir, 1-8 bayt arasında bir şey olabilir, ancak neredeyse tüm tarihler için 64 bit veya uzun bir süre çalışır.

DÜZENLEME: Yorumlarda belirtildiği gibi, bunu yaparsanız cursor.getLong()zaman damgasını düzgün bir şekilde almak için kullanmanız gerekir.


17
Teşekkürler adamım. Lol yanlış yazmayı düşündüm ama bulamadım. Cursor.getInt () tarafından değil, cursor.getLong () tarafından alınmalıdır. Lol kendime gülmeyi durduramaz. Tekrar teşekkürler.
Son Huy TRAN

37
  1. Bu yorumda öngörüldüğü gibi , tarihleri ​​saklamak için her zaman tamsayılar kullanırdım.
  2. Depolamak için bir yardımcı program yöntemi kullanabilirsiniz

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

    şöyle:

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
  3. Başka bir yardımcı yöntem yükleme ile ilgilenir

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

    şu şekilde kullanılabilir:

    entity.setDate(loadDate(cursor, INDEX));
  4. Tarihe göre sıralama basit SQL ORDER yantümcesidir (çünkü sayısal bir sütunumuz vardır). Aşağıdakiler azalan sipariş verecektir (en yeni tarih önce gelir):

    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
        }

Her zaman saklamak için emin olun UTC / GMT saati özellikle çalışırken, java.util.Calendarve java.text.SimpleDateFormatvarsayılan (yani cihazınızın) saat dilimi kullanmayı unutmayın. java.util.Date.Date()UTC değeri oluşturduğundan kullanımı güvenlidir.


9

SQLite, tarihleri ​​depolamak için metin, gerçek veya tamsayı veri türlerini kullanabilir. Dahası, her sorgu yaptığınızda sonuçlar format kullanılarak gösterilir %Y-%m-%d %H:%M:%S.

Şimdi, SQLite tarih / saat işlevlerini kullanarak tarih / saat değerleri ekler / güncellerseniz, gerçekten de milisaniye depolayabilirsiniz. Bu durumda, sonuçlar format kullanılarak gösterilir %Y-%m-%d %H:%M:%f. Örneğin:

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

Şimdi, zamanları gerçekten karşılaştırabildiğimizi doğrulamak için bazı sorgular yapmak:

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

Aynı kontrol edebilirsiniz SELECTkullanarak col2ve col3ve aynı sonuçları alırsınız. Gördüğünüz gibi, ikinci satır (126 milisaniye) döndürülmez.

BETWEENKapsayıcı olduğunu unutmayın , bu nedenle ...

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

... aynı seti döndürür.

Farklı tarih / saat aralıklarıyla oynamayı deneyin, her şey beklendiği gibi davranacaktır.

strftimeİşlevsiz ne olacak ?

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

Ne yaklaşık olmadan strftimeişlev ve hiçbir milisaniye?

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

Ne olmuş 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

Sadece iyi çalışıyor.

Son olarak, bir program içindeki gerçek işlemlerle uğraşırken (sqlite çalıştırılabilir dosyası kullanmadan ...)

BTW: JDBC (diğer diller hakkında emin değilim) kullanıyorum ... xerial'dan sqlite-jdbc sürücüsü v3.7.2 - belki daha yeni düzeltmeler aşağıda açıklanan davranışı değiştirir ... Android'de geliştiriyorsanız, bir jdbc sürücüsü gerekir. Tüm SQL işlemleri SQLiteOpenHelper.

JDBC bir veritabanından gerçek tarih / zaman değerlerini almak için farklı yöntemler vardır: java.sql.Date, java.sql.Time, ve java.sql.Timestamp.

İlgili yöntemler java.sql.ResultSet(tabii ki) olan getDate(..), getTime(..)ve getTimestamp()sırasıyla.

Örneğin:

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.

SQLite gerçek bir DATE / TIME / TIMESTAMP veri türüne sahip olmadığından, bu 3 yöntemin tümü, nesneler 0 ile başlatılmış gibi değerleri döndürür:

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

Yani soru şu: Tarih / Saat / Zaman Damgası nesnelerini nasıl seçebilir, ekleyebilir veya güncelleyebiliriz? Kolay bir cevap yok. Farklı kombinasyonları deneyebilirsiniz, ancak SQLite işlevlerini tüm SQL deyimlerine gömmeye zorlarlar. Metni Java programınızdaki Date nesnelerine dönüştürmek için bir yardımcı program sınıfı tanımlamak çok daha kolaydır. Ancak SQLite'nin herhangi bir tarih değerini UTC + 0000'a dönüştürdüğünü unutmayın.

Özet olarak, her zaman doğru veri türünü kullanmak için genel kurala veya Unix zamanını (çağdan beri milisaniye) ifade eden tamsayılara rağmen, tüm SQL ifadelerinizi karmaşıklaştırmak yerine varsayılan SQLite biçimini ( '%Y-%m-%d %H:%M:%f'veya Java'da 'yyyy-MM-dd HH:mm:ss.SSS') kullanmaktan çok daha kolay buluyorum SQLite fonksiyonları. Önceki yaklaşımın sürdürülmesi çok daha kolaydır.

YAPILACAKLAR: Android içinde (API15 veya daha iyisi) getDate / getTime / getTimestamp kullanırken sonuçları kontrol edeceğim ... belki dahili sürücü sqlite-jdbc'den farklı ...


1
SQLite'ın dahili depolama motoru göz önüne alındığında, örneklerinizin ima ettiğiniz etkiye sahip olduğuna ikna olmadım: Motor, "belirtilen SQL türüne bakılmaksızın herhangi bir sütunda depolama türü değerlerin depolanmasına izin veriyor" ( books.google) gibi görünüyor. de /… ). Bana öyle geliyor ki Real vs. Integer vs. Text örneğinizde olan şey şudur: SQLite metni tüm ağaç sütunlarında Metin olarak saklar. Yani, doğal olarak sonuçların hepsi iyi, depolama hala boşa. Sadece bir Tamsayı kullanılmışsa, milisaniyeyi kaybetmelisiniz. Sadece söyleyerek ...
marco

Aslında, test_table'dan bir SELECT datetime (col3, 'unixepoch') yaparak söylediklerimi doğrulayabilirsiniz. Bu, örnekleriniz için boş satırlar gösterecektir ... test uğruna gerçek bir Tamsayı eklemediğiniz sürece. Örneğin, col3 değeri 37 olan bir satır ekleyecekseniz, yukarıdaki SELECT ifadesi şunu gösterir: 1970-01-01 00:00:37. Dolayısıyla, tüm tarihlerinizi metin dizesi olarak verimsiz bir şekilde saklamakta sorun yoksa, önerdiğiniz gibi yapmayın.
marco

Bu cevabı gönderdiğimden beri uzun zaman oldu ... belki SQLite güncellendi. Aklıma gelen tek şey, önerilerinize karşı SQL deyimlerini tekrar yürütmektir.
miguelt

3

Genellikle (mysql / postgres ile yaptığım gibi) tarihleri, zaman damgası biçiminde saklamak için int (mysql / post) veya text (sqlite) olarak saklarım.

Sonra bunları Date nesnelerine dönüştüreceğim ve TimeZone kullanıcısına dayalı eylemler gerçekleştireceğim


3

Mağazada en iyi yolu dateiçinde SQLite DB akımını saklamaktır DateTimeMilliseconds. Bunu yapmak için kod snippet'i aşağıdadır_

  1. Almak 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. Veriyi DB'nize ekleyin
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 .

Bir sonraki adım, DB'den veri almak istediğinizde, ilgili tarih saatini milisaniye olarak ilgili tarihe dönüştürmeniz gerekir. Aşağıda aynısını yapmak için örnek kod snippet'i bulunmaktadır_

  1. Tarih milisaniyesini tarih dizesine dönüştürün.
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. Şimdi, Sonunda verileri getir ve çalışmasını gör ...
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;

}

Umarım bu yardımcı olacaktır! :)


SQLite uzun veri tipini desteklemez. EDIT: Benim hatam, INTEGER 8 bayt uzunluğunda, bu yüzden bu veri türünü desteklemelidir.
Antonio Vlasic


1

Bunu tercih ederim. Bu en iyi yol değil, hızlı bir çözüm.

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

0
"SELECT  "+_ID+" ,  "+_DESCRIPTION +","+_CREATED_DATE +","+_DATE_TIME+" FROM "+TBL_NOTIFICATION+" ORDER BY "+"strftime(%s,"+_DATE_TIME+") DESC";
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.