Cách tốt nhất để làm việc với ngày trong Android SQLite [đã đóng]


237

Tôi gặp một số khó khăn khi làm việc với ngày trên ứng dụng Android sử dụng SQLite. Tôi có một vài câu hỏi:

  1. Tôi nên sử dụng loại nào để lưu trữ ngày tháng trong SQLite (văn bản, số nguyên, ...)?
  2. Đưa ra cách tốt nhất để lưu trữ ngày làm cách nào để lưu trữ đúng cách bằng ContentValues?
  3. Cách tốt nhất để lấy ngày từ cơ sở dữ liệu SQLite là gì?
  4. Làm thế nào để thực hiện chọn sql trên SQLite, sắp xếp kết quả theo ngày?

2
Chỉ cần sử dụng lớp Lịch và thời gian thành viên của nó (đại diện cho số mili giây đã trôi qua kể từ 1/1/1970). Có các hàm thành viên để biến đổi giá trị thời gian thành các chuỗi người dùng có thể đọc được.
slayton

Câu trả lời:


43

Bạn có thể sử dụng một trường văn bản để lưu trữ ngày trong SQLite.

Lưu trữ ngày ở định dạng UTC, mặc định nếu bạn sử dụng datetime('now') (yyyy-MM-dd HH:mm:ss)sẽ cho phép sắp xếp theo cột ngày.

Lấy ngày dưới dạng chuỗi từ SQLitebạn sau đó có thể định dạng / chuyển đổi chúng theo yêu cầu thành định dạng khu vực cục bộ bằng Lịch hoặc android.text.format.DateUtils.formatDateTimephương thức.

Đây là một phương pháp định dạng khu vực hóa mà tôi sử dụng;

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
Làm thế nào bạn sẽ xử lý phạm vi ngày truy vấn?
Joe

51
"Đề nghị thực hành"? Nghe có vẻ không đúng.
shim

135
Trong những năm tôi đã sử dụng SQL, tôi chưa từng thấy ai trước đây khuyên bạn nên lưu trữ ngày dưới dạng chuỗi. Nếu bạn không có loại cột ngày cụ thể, hãy sử dụng số nguyên và lưu trữ trong thời gian Unix (giây kể từ kỷ nguyên). Nó có thể sắp xếp và có thể sử dụng trong phạm vi và dễ dàng chuyển đổi.
mikebabcock

20
Lưu trữ ngày dưới dạng chuỗi là tốt nếu bạn muốn lưu trữ nó dưới dạng "thông tin", một cái gì đó bạn lấy và hiển thị. Nhưng nếu bạn muốn lưu trữ ngày dưới dạng "dữ liệu", một cái gì đó để làm việc, bạn nên xem xét việc lưu trữ nó dưới dạng số nguyên - thời gian kể từ thời đại. Điều này sẽ cho phép bạn truy vấn phạm vi ngày, theo tiêu chuẩn, do đó bạn không phải lo lắng về chuyển đổi, v.v. Lưu trữ ngày vì chuỗi rất hạn chế và tôi thực sự muốn biết ai đã đề xuất thực hành này làm quy tắc chung.
Kirtian

8
Các sqlite tài liệu danh sách lưu trữ dưới dạng văn bản (ISO 8601) như một giải pháp khả thi để lưu trữ ngày. Trên thực tế, nó được liệt kê đầu tiên.
anderspitman

211

Cách tốt nhất là lưu trữ ngày dưới dạng số, nhận được bằng cách sử dụng lệnh Lịch.

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

Tại sao làm điều này? Trước hết, nhận giá trị từ một phạm vi ngày là dễ dàng. Chỉ cần chuyển đổi ngày của bạn thành mili giây, và sau đó truy vấn thích hợp. Sắp xếp theo ngày cũng tương tự dễ dàng. Các cuộc gọi để chuyển đổi giữa các định dạng khác nhau cũng dễ dàng, như tôi đã bao gồm. Điểm mấu chốt là, với phương pháp này, bạn có thể làm bất cứ điều gì bạn cần làm, không có vấn đề gì. Sẽ hơi khó để đọc một giá trị thô, nhưng nó nhiều hơn tạo nên nhược điểm nhỏ với việc dễ dàng đọc và sử dụng máy. Và trên thực tế, việc xây dựng một trình đọc tương đối dễ dàng (Và tôi biết có một số ngoài đó) sẽ tự động chuyển đổi thẻ thời gian thành ngày để dễ đọc.

Điều đáng nói là các giá trị xuất phát từ điều này phải dài chứ không phải int. Số nguyên trong sqlite có thể có nghĩa là nhiều thứ, bất cứ thứ gì từ 1 - 8 byte, nhưng đối với hầu hết tất cả các ngày 64 bit, hoặc dài, là những gì hoạt động.

EDIT: Như đã được chỉ ra trong các bình luận, bạn phải sử dụng cursor.getLong()để lấy dấu thời gian đúng cách nếu bạn làm điều này.


17
Cảm ơn anh chàng. Tôi đã nghĩ về việc gõ sai nhưng tôi không thể tìm thấy nó. Nó phải được truy xuất bởi con trỏ.getLong (), không phải bởi con trỏ.getInt (). Lol không thể ngừng cười với chính mình. Cảm ơn một lần nữa.
Sơn Huy TRẦN

37
  1. Như được cho là trong nhận xét này , tôi luôn sử dụng số nguyên để lưu trữ ngày.
  2. Để lưu trữ, bạn có thể sử dụng một phương thức tiện ích

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

    như vậy

    ContentValues values = new ContentValues();
    values.put(COLUMN_NAME, persistDate(entity.getDate()));
    long id = db.insertOrThrow(TABLE_NAME, null, values);
    
  3. Một phương pháp tiện ích khác đảm nhận việc tải

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

    có thể được sử dụng như thế này:

    entity.setDate(loadDate(cursor, INDEX));
  4. Sắp xếp theo ngày là mệnh đề SQL ORDER đơn giản (vì chúng ta có một cột số). Sau đây sẽ đặt hàng giảm dần (đó là ngày mới nhất đi trước):

    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
        }
    

Luôn đảm bảo lưu trữ thời gian UTC / GMT , đặc biệt là khi làm việc với java.util.Calendarjava.text.SimpleDateFormatsử dụng múi giờ mặc định (tức là thiết bị của bạn). java.util.Date.Date()là an toàn để sử dụng vì nó tạo ra một giá trị UTC.


9

SQLite có thể sử dụng các kiểu dữ liệu văn bản, thực hoặc số nguyên để lưu trữ ngày. Thậm chí, bất cứ khi nào bạn thực hiện một truy vấn, kết quả được hiển thị bằng định dạng %Y-%m-%d %H:%M:%S.

Bây giờ, nếu bạn chèn / cập nhật giá trị ngày / giờ bằng các hàm ngày / giờ SQLite, bạn thực sự có thể lưu trữ cả mili giây. Nếu đó là trường hợp, kết quả được hiển thị bằng cách sử dụng định dạng %Y-%m-%d %H:%M:%f. Ví dụ:

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

Bây giờ, thực hiện một số truy vấn để xác minh xem chúng tôi có thực sự có thể so sánh thời gian không:

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

Bạn có thể kiểm tra tương tự SELECTsử dụng col2col3và bạn sẽ nhận được kết quả tương tự. Như bạn có thể thấy, hàng thứ hai (126 mili giây) không được trả lại.

Lưu ý rằng đã BETWEENbao gồm, do đó ...

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

... sẽ trả lại cùng một bộ.

Hãy thử chơi xung quanh với các phạm vi ngày / giờ khác nhau và mọi thứ sẽ hoạt động như mong đợi.

Còn về strftimechức năng thì sao?

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

Điều gì về không có strftimechức năng và không có mili giây?

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

Thế còn 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

Hoạt động tốt.

Cuối cùng, khi xử lý các hoạt động thực tế trong một chương trình (không sử dụng tệp thực thi sqlite ...)

BTW: Tôi đang sử dụng JDBC (không chắc chắn về các ngôn ngữ khác) ... trình điều khiển sqlite-jdbc v3.7.2 từ xerial - có thể các phiên bản mới hơn thay đổi hành vi được giải thích bên dưới ... Nếu bạn đang phát triển trong Android, bạn không cần một trình điều khiển jdbc. Tất cả các hoạt động SQL có thể được gửi bằng cách sử dụng SQLiteOpenHelper.

JDBC có phương pháp khác nhau để có được giá trị ngày / thời gian thực tế từ một cơ sở dữ liệu: java.sql.Date, java.sql.Time, và java.sql.Timestamp.

Các phương pháp có liên quan trong java.sql.ResultSetlà (rõ ràng) getDate(..), getTime(..)getTimestamp()tương ứng.

Ví dụ:

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.

Vì SQLite không có kiểu dữ liệu DATE / TIME / TIMESTAMP thực tế, cả 3 phương thức này trả về giá trị như thể các đối tượng được khởi tạo với 0:

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

Vì vậy, câu hỏi là: làm thế nào chúng ta thực sự có thể chọn, chèn hoặc cập nhật các đối tượng Ngày / Thời gian / Dấu thời gian? Không có câu trả lời dễ dàng. Bạn có thể thử các kết hợp khác nhau, nhưng chúng sẽ buộc bạn nhúng các hàm SQLite trong tất cả các câu lệnh SQL. Việc định nghĩa một lớp tiện ích để chuyển đổi văn bản thành các đối tượng Date trong chương trình Java của bạn dễ dàng hơn nhiều. Nhưng luôn nhớ rằng SQLite biến đổi bất kỳ giá trị ngày nào thành UTC + 0000.

Tóm lại, mặc dù quy tắc chung là luôn sử dụng đúng loại dữ liệu hoặc thậm chí các số nguyên biểu thị thời gian Unix (mili giây kể từ epoch), tôi thấy dễ dàng hơn khi sử dụng định dạng SQLite mặc định ( '%Y-%m-%d %H:%M:%f'hoặc trong Java 'yyyy-MM-dd HH:mm:ss.SSS') thay vì làm phức tạp tất cả các câu lệnh SQL của bạn Hàm SQLite. Cách tiếp cận trước đây là dễ dàng hơn nhiều để duy trì.

TODO: Tôi sẽ kiểm tra kết quả khi sử dụng getDate / getTime / getTimestamp bên trong Android (API15 hoặc tốt hơn) ... có thể trình điều khiển nội bộ khác với sqlite-jdbc ...


1
Với công cụ lưu trữ nội bộ của SQLite, tôi không tin rằng các ví dụ của bạn có tác dụng mà bạn ngụ ý: Có vẻ như công cụ "cho phép lưu trữ bất kỳ giá trị được lưu trữ nào trong bất kỳ cột nào bất kể loại SQL đã khai báo" ( Books.google. de / khoan ). Tôi nghe có vẻ như trong ví dụ Real vs Integer vs Text của bạn, điều đang xảy ra là: SQLite chỉ lưu trữ văn bản dưới dạng Văn bản trong tất cả các cột của cây. Vì vậy, tự nhiên kết quả đều tốt, lưu trữ vẫn lãng phí. Nếu chỉ một số nguyên được sử dụng, thì bạn nên mất một phần nghìn giây. Chỉ cần nói ...
marco

Trên thực tế, bạn có thể xác nhận những gì tôi vừa nói bằng cách thực hiện một datetime (col3, 'unixepoch') TỪ test_table. Điều này sẽ hiển thị các hàng trống cho các ví dụ của bạn ... trừ khi, vì mục đích thử nghiệm, bạn chèn một số nguyên thực tế. Ví dụ: nếu bạn đã thêm một hàng có giá trị col3 37, câu lệnh SELECT ở trên sẽ hiển thị: 1970-01-01 00:00:37. Vì vậy, trừ khi bạn thực sự ổn với việc lưu trữ tất cả các ngày của bạn không hiệu quả như chuỗi văn bản, đừng làm như bạn đề xuất.
marco

Đã lâu rồi tôi mới đăng câu trả lời này ... có lẽ SQLite đã được cập nhật. Điều duy nhất tôi có thể nghĩ là thực hiện lại các câu lệnh SQL so với các đề xuất của bạn.
Miguelt

3

Thông thường (giống như tôi làm trong mysql / postgres) Tôi lưu trữ ngày trong int (mysql / post) hoặc văn bản (sqlite) để lưu trữ chúng ở định dạng dấu thời gian.

Sau đó, tôi sẽ chuyển đổi chúng thành các đối tượng Date và thực hiện các hành động dựa trên TimeZone của người dùng


3

Cách tốt nhất để lưu trữ datetrong SQlite DB là lưu trữ hiện tại DateTimeMilliseconds. Dưới đây là đoạn mã để làm như vậy_

  1. Nhận được 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. Chèn dữ liệu vào DB của bạn
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 .

Bước tiếp theo là, khi bạn muốn lấy dữ liệu từ DB, bạn cần chuyển đổi mili giây thời gian ngày tương ứng thành ngày tương ứng. Dưới đây là đoạn mã mẫu để làm tương tự_

  1. Chuyển đổi ngày mili giây thành chuỗi ngày.
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. Bây giờ, Cuối cùng lấy dữ liệu và xem nó hoạt động ...
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;

}

Hy vọng điều này sẽ giúp tất cả! :)


SQLite không hỗ trợ kiểu dữ liệu dài. EDIT: Lỗi của tôi, INTEGER dài 8 byte, vì vậy nó nên hỗ trợ kiểu dữ liệu này.
Antonio Vlasic


1

Tôi thích điều này. Đây không phải là cách tốt nhất, nhưng là một giải pháp nhanh chóng.

//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";
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.