REPLACE trong SQLite - Upsert (Insert hoặc Update)
Lệnh REPLACE (hoặc INSERT OR REPLACE) trong SQLite: nếu bản ghi đã tồn tại (trùng PRIMARY KEY hoặc UNIQUE constraint) thì xóa bản ghi cũ và chèn bản ghi mới. Đây là cách "thay thế toàn bộ hàng" hiệu quả.
Bước 1: Tạo dự án
Android Studio → File → New Project.
Bước 2: Giao diện (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"
tools:context=".MainActivity"
android:orientation="vertical"
android:padding="16dp">
<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" android:inputType="numberDecimal" />
<LinearLayout android:layout_width="wrap_content" android:layout_height="wrap_content" android:orientation="horizontal" android:layout_marginTop="8dp">
<Button android:id="@+id/save" android:text="Lưu (REPLACE)" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<Button android:id="@+id/refresh" android:text="Làm mới" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<Button android:id="@+id/update" android:text="Cập nhật (UPDATE)" android:layout_width="wrap_content" android:layout_height="wrap_content" />
</LinearLayout>
<ListView android:id="@+id/listView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" />
</LinearLayout>
Bước 3: MainActivity.java
package com.example.sqlitereplace;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
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 java.util.ArrayList;
public class MainActivity extends AppCompatActivity {
Button btnSave, btnRefresh, btnUpdate;
EditText edtName, edtSalary;
ListView listView;
DatabaseHelper dbHelper;
ArrayList dataList;
ArrayAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new DatabaseHelper(this);
dataList = new ArrayList<>(dbHelper.getAllContacts());
edtName = findViewById(R.id.name);
edtSalary = findViewById(R.id.salary);
listView = findViewById(R.id.listView);
btnSave = findViewById(R.id.save);
btnRefresh = findViewById(R.id.refresh);
btnUpdate = findViewById(R.id.update);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, dataList);
listView.setAdapter(adapter);
btnRefresh.setOnClickListener(v -> refreshList());
btnSave.setOnClickListener(v -> {
String name = edtName.getText().toString().trim();
String salary = edtSalary.getText().toString().trim();
if (name.isEmpty() || salary.isEmpty()) {
showError(name, salary);
return;
}
// REPLACE: Nếu tên đã có -> cập nhật, chưa có -> thêm mới
if (dbHelper.insertOrReplace(name, salary)) {
Toast.makeText(this, "Đã lưu (Replace)", Toast.LENGTH_SHORT).show();
refreshList();
} else {
Toast.makeText(this, "Lưu thất bại", Toast.LENGTH_SHORT).show();
}
});
btnUpdate.setOnClickListener(v -> {
String name = edtName.getText().toString().trim();
String salary = edtSalary.getText().toString().trim();
if (name.isEmpty() || salary.isEmpty()) {
showError(name, salary);
return;
}
// UPDATE thuần túy: chỉ cập nhật nếu đã tồn tại
if (dbHelper.update(name, salary)) {
Toast.makeText(this, "Đã cập nhật (Update)", Toast.LENGTH_SHORT).show();
refreshList();
} else {
Toast.makeText(this, "Cập nhật thất bại", Toast.LENGTH_SHORT).show();
}
});
}
private void refreshList() {
dataList.clear();
dataList.addAll(dbHelper.getAllContacts());
adapter.notifyDataSetChanged();
}
private void showError(String name, String salary) {
if (name.isEmpty()) edtName.setError("Nhập tên");
if (salary.isEmpty()) edtSalary.setError("Nhập lương");
}
}
Bước 4: DatabaseHelper.java - REPLACE vs UPDATE
package com.example.sqlitereplace;
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;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DATABASE_NAME = "salaryDatabase";
private static final String TABLE_NAME = "SalaryDetails";
private static final int DB_VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DATABASE_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
// name là UNIQUE để REPLACE hoạt động dựa trên tên
db.execSQL("CREATE TABLE " + TABLE_NAME +
"(id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT UNIQUE, " + // QUAN TRỌNG: UNIQUE constraint
"salary TEXT)");
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldV, int newV) {
db.execSQL("DROP TABLE IF EXISTS " + TABLE_NAME);
onCreate(db);
}
// REPLACE: INSERT OR REPLACE
public boolean insertOrReplace(String name, String salary) {
SQLiteDatabase db = getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("name", name);
cv.put("salary", salary);
// db.replace() = INSERT OR REPLACE
// Trả về rowId của bản ghi mới (sau khi replace)
long rowId = db.replace(TABLE_NAME, null, cv);
return rowId != -1;
}
public ArrayList getAllContacts() {
ArrayList list = new ArrayList<>();
SQLiteDatabase db = getReadableDatabase();
Cursor cursor = db.rawQuery("SELECT name FROM " + TABLE_NAME, null);
if (cursor.moveToFirst()) {
do {
list.add(cursor.getString(cursor.getColumnIndex("name")));
} while (cursor.moveToNext());
}
cursor.close();
return list;
}
// UPDATE thuần túy: chỉ sửa nếu tồn tại
public boolean update(String name, String salary) {
SQLiteDatabase db = getWritableDatabase();
ContentValues cv = new ContentValues();
cv.put("salary", salary);
int rows = db.update(TABLE_NAME, cv, "name = ?", new String[]{name});
return rows > 0;
}
public boolean deleteAll() {
return getWritableDatabase().delete(TABLE_NAME, null, null) > 0;
}
}
REPLACE vs UPDATE - So sánh chi tiết
| Đặc điểm | REPLACE (INSERT OR REPLACE) | UPDATE |
|---|---|---|
| Bản ghi chưa tồn tại | Chèn mới (INSERT) | Không làm gì (0 rows affected) |
| Bản ghi đã tồn tại | XÓA cũ → CHÈN mới | CẬP NHẬT các cột chỉ định |
| PRIMARY KEY / UNIQUE | Bắt buộc có để xác định trùng | Cần WHERE clause |
| Các cột không cung cấp | Trở về DEFAULT hoặc NULL | Giữ nguyên giá trị cũ |
| Trigger | Kích hoạt DELETE + INSERT | Kích hoạt UPDATE |
| Auto-increment ID | Thay đổi (vì xóa后再 chèn) | Giữ nguyên ID |
Cú pháp SQL thuần
-- REPLACE (SQLite extension)
REPLACE INTO SalaryDetails (name, salary) VALUES ('An', '5000');
-- Tương đương chuẩn SQL (UPSERT) - SQLite 3.24.0+
INSERT INTO SalaryDetails (name, salary) VALUES ('An', '5000')
ON CONFLICT(name) DO UPDATE SET salary = excluded.salary;
-- UPDATE thuần
UPDATE SalaryDetails SET salary = '5000' WHERE name = 'An';
Khi nào dùng REPLACE?
- Dữ liệu "cài đặt" (settings): key-value, muốn set là có, không care ID.
- Cache: key-value, luôn muốn giá trị mới nhất.
- Sync dữ liệu từ server: replace toàn bộ record.
Khi nào dùng UPDATE?
- Cập nhật một phần trường (chỉ salary, giữ name, datetime...).
- Cần giữ nguyên ID (foreign key tham chiếu).
- Có trigger ON UPDATE cần kích hoạt.
- Effective logging/audit trail.
⚠️ Cảnh báo với REPLACE
REPLACE thực chất là DELETE + INSERT. Nếu bảng có:
- Foreign key tham chiếu → có thể vi phạm ràng buộc.
- Trigger ON DELETE → sẽ chạy.
- Auto-increment ID → ID sẽ thay đổi mỗi lần replace.