Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách lưu giá trị thập phân (DECIMAL) trong SQLite Android

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áoLưu trữ thực tếĐộ chính xác
INTEGERInt64 (8 byte)Chính xác tuyệt đối cho số nguyên
REALFloat64 (8 byte, IEEE 754)~15 chữ số có nghĩa, có sai số làm tròn
NUMERICTự độ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ệ.