Giới thiệu
Ví dụ này minh họa cách lưu trữ giá trị thập phân (số thực) trong SQLite Android. SQLite không có kiểu DECIMAL chính xác như SQL Server/MySQL, nhưng hỗ trợ REAL (double 64-bit) và NUMERIC.
Bước 1: Tạo dự án mới
Mở Android Studio, chọn File → New Project và điền đầy đủ thông tin cần thiết.
Bước 2: Thiết kế giao diện (res/layout/activity_main.xml)
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="https://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity">
<EditText
android:id="@+id/name"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nhập tên" />
<EditText
android:id="@+id/salary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="Nhập lương (ví dụ: 1000000.50)"
android:inputType="numberDecimal" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal">
<Button
android:id="@+id/save"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lưu" />
<Button
android:id="@+id/refresh"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Làm mới" />
<Button
android:id="@+id/update"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Cập nhật" />
<Button
android:id="@+id/delete"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Xóa tất cả" />
</LinearLayout>
<ListView
android:id="@+id/listView"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>
Bước 3: MainActivity.java
package com.example.andy.myapplication;
import android.os.Bundle;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.Toast;
import androidx.appcompat.app.AppCompatActivity;
import java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
Button save, refresh, update, delete;
EditText name, salary;
ListView listView;
DatabaseHelper helper;
ArrayAdapter arrayAdapter;
ArrayList arrayList;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
helper = new DatabaseHelper(this);
arrayList = new ArrayList<>(helper.getAllContacts());
name = findViewById(R.id.name);
salary = findViewById(R.id.salary);
listView = findViewById(R.id.listView);
save = findViewById(R.id.save);
refresh = findViewById(R.id.refresh);
update = findViewById(R.id.update);
delete = findViewById(R.id.delete);
arrayAdapter = new ArrayAdapter<>(this,
android.R.layout.simple_list_item_1, arrayList);
listView.setAdapter(arrayAdapter);
delete.setOnClickListener(v -> {
if (helper.deleteAll()) {
Toast.makeText(this, "Đã xóa", Toast.LENGTH_LONG).show();
refreshList();
} else {
Toast.makeText(this, "Xóa thất bại", Toast.LENGTH_LONG).show();
}
});
update.setOnClickListener(v -> {
String n = name.getText().toString().trim();
String s = salary.getText().toString().trim();
if (!n.isEmpty() && !s.isEmpty()) {
if (helper.update(n, s)) {
Toast.makeText(this, "Đã cập nhật", Toast.LENGTH_LONG).show();
refreshList();
} else {
Toast.makeText(this, "Cập nhật thất bại", Toast.LENGTH_LONG).show();
}
} else {
name.setError("Nhập tên");
salary.setError("Nhập lương");
}
});
refresh.setOnClickListener(v -> refreshList());
save.setOnClickListener(v -> {
String n = name.getText().toString().trim();
String s = salary.getText().toString().trim();
if (!n.isEmpty() && !s.isEmpty()) {
if (helper.insert(n, s)) {
Toast.makeText(this, "Đã thêm", Toast.LENGTH_LONG).show();
refreshList();
name.setText("");
salary.setText("");
} else {
Toast.makeText(this, "Thêm thất bại", Toast.LENGTH_LONG).show();
}
} else {
name.setError("Nhập tên");
salary.setError("Nhập lương");
}
});
}
private void refreshList() {
arrayList.clear();
arrayList.addAll(helper.getAllContacts());
arrayAdapter.notifyDataSetChanged();
}
}
Bước 4: DatabaseHelper.java
package com.example.andy.myapplication;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "salaryDatabase5";
private static final String TABLE_NAME = "SalaryDetails";
private static final int DATABASE_VERSION = 1;
DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DATABASE_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// SQLite: DECIMAL(4,2) được hiểu là NUMERIC/REAL
// Thực tế lưu là REAL (8-byte float) - không đảm bảo độ chính xác thập phân tuyệt đối
String sql = "CREATE TABLE " + TABLE_NAME + " (" +
"id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, " +
"salary REAL, " + -- Dùng REAL cho số thực
"datetime DEFAULT CURRENT_TIMESTAMP)";
db.execSQL(sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
boolean insert(String name, String salary) {
SQLiteDatabase db = getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("name", name);
try {
cv.put("salary", Double.parseDouble(salary));
} catch (NumberFormatException e) {
cv.put("salary", salary);
}
long result = db.replace(TABLE_NAME, null, cv);
return result != -1;
}
ArrayList getAllContacts() {
SQLiteDatabase db = getReadableDatabase();
ArrayList list = new ArrayList<>();
String query = "SELECT (id || ' : ' || name || ' : ' || salary || ' : ' || datetime) AS fullname FROM " + TABLE_NAME;
Cursor res = db.rawQuery(query, null);
if (res.moveToFirst()) {
do {
list.add(res.getString(res.getColumnIndexOrThrow("fullname")));
} while (res.moveToNext());
}
res.close();
return list;
}
boolean update(String name, String salary) {
SQLiteDatabase db = getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("name", name);
try {
cv.put("salary", Double.parseDouble(salary));
} catch (NumberFormatException e) {
cv.put("salary", salary);
}
int rows = db.update(TABLE_NAME, cv, null, null); // CẢNH BÁO: không WHERE
return rows > 0;
}
boolean deleteAll() {
SQLiteDatabase db = getWritableDatabase();
int rows = db.delete(TABLE_NAME, null, null);
return rows > 0;
}
}
Chạy ứng dụng
Kết nối thiết bị, nhấn Run. Nhập lương có phần thập phân (ví dụ: 1000000.50), nhấn Lưu, sau đó Làm mới.
Kết quả
ListView hiển thị: "1 : Nguyễn Văn A : 1000000.5 : 2024-01-15 10:30:00"
Giải thích về kiểu số trong SQLite
| Kiểu khai báo | Lưu trữ thực tế | Độ chính xác |
|---|---|---|
| INTEGER | Int64 (8 byte) | Chính xác tuyệt đối cho số nguyên |
| REAL | Float64 (8 byte, IEEE 754) | ~15 chữ số có nghĩa, có sai số làm tròn |
| NUMERIC | Tự động (INTEGER/REAL/TEXT) | Tùy giá trị nhập |
| DECIMAL(p,s) | NUMERIC (bỏ qua p,s) | Không đảm bảo độ chính xác cố định |
Lưu ý quan trọng về tiền tệ
KHÔNG nên dùng REAL/FLOAT cho tiền tệ do sai số floating-point (ví dụ: 0.1 + 0.2 = 0.30000000000000004).
Giải pháp tốt nhất: Lưu số nguyên (cents/đồng) trong INTEGER.
// Lưu 1.000.000,50 VNĐ → lưu 100000050 (đồng)
// Hiển thị: format chia 100
long salaryInCents = 100000050L;
String display = String.format("%,.2f", salaryInCents / 100.0); // "1,000,000.50"
Hoặc dùng BigDecimal trong Java, lưu dưới dạng TEXT trong SQLite.
Lưu ý: Code gốc khai báo "salary DECIMAL(4,2)" - SQLite chấp nhận nhưng bỏ qua (4,2), lưu thực tế là REAL. Đã sửa rõ ràng dùng REAL và cảnh báo về tiền tệ.