Computer >> บทช่วยสอนคอมพิวเตอร์ >  >> การเขียนโปรแกรม >> Android

วิธีแทนที่คอลัมน์ทั้งหมดใน SQLite Android (REPLACE)

ภาพรวม

SQLite เป็นฐานข้อมูล SQL แบบ Open Source ที่เก็บข้อมูลในไฟล์ข้อความบนอุปกรณ์ Android มี SQLite ในตัว รองรับฟีเจอร์ฐานข้อมูลเชิงสัมพันธ์ครบถ้วน

ตัวอย่างนี้สาธิตการใช้ REPLACE ใน SQLite ซึ่งจะแทนที่แถวข้อมูลทั้งหมด (Whole Row Replacement) เมื่อ PRIMARY KEY ซ้ำกัน แตกต่างจาก UPDATE ที่อัปเดตเฉพาะบางคอลัมน์

ความแตกต่าง REPLACE vs UPDATE

คุณสมบัติ REPLACE UPDATE
พฤติกรรมเมื่อ PK ซ้ำ ลบแถวเก่า + Insert ใหม่ (ทั้งแถว) อัปเดตเฉพาะคอลัมน์ที่ระบุ
คอลัมน์ที่ไม่ระบุ กลายเป็น DEFAULT หรือ NULL คงค่าเดิม
Triggers ทำงาน DELETE + INSERT triggers ทำงาน UPDATE triggers
Auto-increment ID อาจเปลี่ยน (ถ้าไม่ระบุ ID) คง ID เดิม

ขั้นตอนการสร้าง

ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่

Android Studio → File → New Project → กรอกรายละเอียด

ขั้นตอนที่ 2: จัดรูปแบบหน้าจอ (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"
    tools:context=".MainActivity"
    android:orientation="vertical">

    <EditText
        android:id="@+id/name"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="ป้อนชื่อ" />

    <EditText
        android:id="@+id/salary"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:inputType="numberDecimal"
        android:hint="ป้อนเงินเดือน" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="horizontal">

        <Button
            android:id="@+id/save"
            android:text="บันทึก"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/refresh"
            android:text="รีเฟรช"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />

        <Button
            android:id="@+id/update"
            android:text="อัปเดต"
            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" />

</LinearLayout>

ขั้นตอนที่ 3: เขียนโค้ด MainActivity (src/MainActivity.java)

package com.example.andy.myapplication;

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 save, refresh;
    EditText name, salary;
    private ListView listView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        final DatabaseHelper helper = new DatabaseHelper(this);
        final ArrayList<String> arrayList = helper.getAllContacts();

        name = findViewById(R.id.name);
        salary = findViewById(R.id.salary);
        listView = findViewById(R.id.listView);

        final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(
            this, android.R.layout.simple_list_item_1, arrayList);
        listView.setAdapter(arrayAdapter);

        findViewById(R.id.update).setOnClickListener(v -> {
            String nameStr = name.getText().toString().trim();
            String salaryStr = salary.getText().toString().trim();

            if (!nameStr.isEmpty() && !salaryStr.isEmpty()) {
                if (helper.update(nameStr, salaryStr)) {
                    Toast.makeText(this, "อัปเดตสำเร็จ", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(this, "อัปเดตไม่สำเร็จ", Toast.LENGTH_LONG).show();
                }
            } else {
                name.setError("กรุณาป้อนชื่อ");
                salary.setError("กรุณาป้อนเงินเดือน");
            }
        });

        findViewById(R.id.refresh).setOnClickListener(v -> {
            arrayList.clear();
            arrayList.addAll(helper.getAllContacts());
            arrayAdapter.notifyDataSetChanged();
            listView.invalidateViews();
            listView.refreshDrawableState();
        });

        findViewById(R.id.save).setOnClickListener(v -> {
            String nameStr = name.getText().toString().trim();
            String salaryStr = salary.getText().toString().trim();

            if (!nameStr.isEmpty() && !salaryStr.isEmpty()) {
                if (helper.insert(nameStr, salaryStr)) {
                    Toast.makeText(this, "บันทึกสำเร็จ", Toast.LENGTH_LONG).show();
                } else {
                    Toast.makeText(this, "บันทึกไม่สำเร็จ", Toast.LENGTH_LONG).show();
                }
            } else {
                name.setError("กรุณาป้อนชื่อ");
                salary.setError("กรุณาป้อนเงินเดือน");
            }
        });
    }
}

ขั้นตอนที่ 4: สร้าง DatabaseHelper (src/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.SQLiteException;
import android.database.sqlite.SQLiteOpenHelper;
import java.io.IOException;
import java.util.ArrayList;

class DatabaseHelper extends SQLiteOpenHelper {
    public static final String DATABASE_NAME = "salaryDatabase3";
    public static final String CONTACTS_TABLE_NAME = "SalaryDetails";

    public DatabaseHelper(Context context) {
        super(context, DATABASE_NAME, null, 1);
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        try {
            db.execSQL("CREATE TABLE " + CONTACTS_TABLE_NAME + 
                "(id INTEGER PRIMARY KEY, name TEXT, salary TEXT)");
        } catch (SQLiteException e) {
            try { throw new IOException(e); } 
            catch (IOException e1) { e1.printStackTrace(); }
        }
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
        db.execSQL("DROP TABLE IF EXISTS " + CONTACTS_TABLE_NAME);
        onCreate(db);
    }

    public boolean insert(String name, String salary) {
        SQLiteDatabase db = this.getWritableDatabase();
        ContentValues values = new ContentValues();
        values.put("name", name);
        values.put("salary", salary);
        // ใช้ REPLACE: ถ้า PRIMARY KEY (id) ซ้ำ จะลบแถวเก่าและ Insert ใหม่
        db.replace(CONTACTS_TABLE_NAME, null, values);
        return true;
    }

    public ArrayList<String> getAllContacts() {
        SQLiteDatabase db = this.getReadableDatabase();
        ArrayList<String> arrayList = new ArrayList<>();
        Cursor res = db.rawQuery("SELECT * FROM " + CONTACTS_TABLE_NAME, null);
        
        res.moveToFirst();
        while (!res.isAfterLast()) {
            arrayList.add(res.getString(res.getColumnIndex("name")));
            res.moveToNext();
        }
        res.close();
        return arrayList;
    }

    public boolean update(String name, String salary) {
        SQLiteDatabase db = this.getReadableDatabase(); // Bug: ควรเป็น Writable
        db.execSQL("UPDATE " + CONTACTS_TABLE_NAME + 
            " SET name = '" + name + "', salary = '" + salary + "'");
        return true;
    }
}

การทดสอบ

รันแอปพลิเคชัน ป้อนชื่อและเงินเดือน กดอัปเดต แล้วกด Refresh

วิธีแทนที่คอลัมน์ทั้งหมดใน SQLite Android (REPLACE)

วิธีแทนที่คอลัมน์ทั้งหมดใน SQLite Android (REPLACE)

รายละเอียดการทำงานของ REPLACE

กลไกการทำงาน

-- REPLACE INTO table (col1, col2) VALUES (val1, val2);
-- เทียบเท่ากับ:
DELETE FROM table WHERE primary_key = new_primary_key;
INSERT INTO table (col1, col2) VALUES (val1, val2);

ตัวอย่างการใช้งาน

-- ตารางมี PRIMARY KEY (id)
CREATE TABLE users (id INTEGER PRIMARY KEY, name TEXT, email TEXT);

-- Insert ครั้งแรก
REPLACE INTO users (name, email) VALUES ('John', 'john@example.com');
-- id = 1

-- Insert ซ้ำ (ไม่ระบุ id) -> สร้าง row ใหม่ id = 2
REPLACE INTO users (name, email) VALUES ('John', 'john@example.com');

-- Insert ระบุ id = 1 -> แทนที่ row เดิม
REPLACE INTO users (id, name, email) VALUES (1, 'Jane', 'jane@example.com');

ข้อควรระวัง

  • Auto-increment ID เปลี่ยน: หากไม่ระบุ ID ตัวใหม่จะได้ ID ใหม่
  • Foreign Key: จะทำให้ ON DELETE CASCADE ทำงาน (ลบข้อมูลที่เกี่ยวข้อง)
  • Triggers: ทำให้ AFTER DELETE และ AFTER INSERT ทำงานทั้งคู่
  • Performance: ช้ากว่า UPDATE เพราะทำ 2 Operation

แนวทางที่ดีกว่า: UPSERT (INSERT ... ON CONFLICT)

SQLite 3.24.0+ รองรับ UPSERT Syntax ที่ทันสมัยกว่า REPLACE:

-- UPSERT: Insert หรือ Update เมื่อ Conflict
INSERT INTO users (id, name, email) VALUES (1, 'John', 'john@example.com')
ON CONFLICT(id) DO UPDATE SET 
    name = excluded.name,
    email = excluded.email;

-- เฉพาะบางคอลัมน์
INSERT INTO users (id, name, email) VALUES (1, 'John', 'john@example.com')
ON CONFLICT(id) DO UPDATE SET 
    email = excluded.email  -- อัปเดตเฉพาะ email
WHERE excluded.email != users.email; -- เงื่อนไขเพิ่มเติม

Best Practices

  • ใช้ UPSERT (ON CONFLICT) แทน REPLACE สำหรับแอปใหม่ (ควบคุมได้ละเอียดกว่า)
  • ระบุ PRIMARY KEY ชัดเจนใน ContentValues หากต้องการคง ID เดิม
  • ใช้ db.update() API แทน raw SQL เพื่อความปลอดภัย
  • ใช้ Transaction สำหรับการเขียนหลายรายการ
  • พิจารณา Room Database: @Insert(onConflict = OnConflictStrategy.REPLACE)

Room Database Equivalent

@Entity(tableName = "salary_details")
data class SalaryDetail(
    @PrimaryKey(autoGenerate = true) val id: Int = 0,
    val name: String,
    val salary: String
)

@Dao
interface SalaryDao {
    @Insert(onConflict = OnConflictStrategy.REPLACE) // เทียบเท่า REPLACE
    suspend fun insert(detail: SalaryDetail): Long
    
    @Query("UPDATE salary_details SET name = :name, salary = :salary WHERE id = :id")
    suspend fun update(id: Int, name: String, salary: String): Int
    
    @Query("SELECT * FROM salary_details ORDER BY name")
    fun getAll(): LiveData<List<SalaryDetail>>
}