การใช้ฟังก์ชัน unlikely() ใน SQLite บน Android
ฟังก์ชัน unlikely() ใน SQLite เป็น Query Planner Hint ใช้บอก Query Optimizer ว่าเงื่อนไขนี้น่าจะเป็นเท็จ (False) เพื่อช่วยให้ Planner เลือก Execution Plan ที่เหมาะสมกว่า
ขั้นตอนที่ 1: สร้างโปรเจกต์ใหม่
สร้างโปรเจกต์ Android ใหม่ใน Android Studio
ขั้นตอนที่ 2: ออกแบบ Layout (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"
android:padding="16dp">
<EditText
android:id="@+id/etName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="ป้อนชื่อ" />
<EditText
android:id="@+id/etSalary"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:hint="ป้อนเงินเดือน"
android:inputType="numberDecimal" />
<LinearLayout
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="16dp"
android:orientation="horizontal">
<Button android:id="@+id/btnSave" android:text="บันทึก" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<Button android:id="@+id/btnRefresh" android:text="รีเฟรช" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<Button android:id="@+id/btnUpdate" android:text="อัปเดต" android:layout_width="wrap_content" android:layout_height="wrap_content" />
<Button android:id="@+id/btnDelete" 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"
android:layout_marginTop="16dp">
</ListView>
</LinearLayout>
ขั้นตอนที่ 3: MainActivity.java
package com.example.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 {
private EditText etName, etSalary;
private ListView listView;
private ArrayAdapter<String> adapter;
private DatabaseHelper dbHelper;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
dbHelper = new DatabaseHelper(this);
ArrayList<String> dataList = dbHelper.getUnlikelyNames();
etName = findViewById(R.id.etName);
etSalary = findViewById(R.id.etSalary);
listView = findViewById(R.id.listView);
adapter = new ArrayAdapter<>(this, android.R.layout.simple_list_item_1, dataList);
listView.setAdapter(adapter);
findViewById(R.id.btnDelete).setOnClickListener(v -> {
Toast.makeText(this, dbHelper.deleteAll() ? "ลบสำเร็จ" : "ลบไม่สำเร็จ", Toast.LENGTH_LONG).show();
});
findViewById(R.id.btnUpdate).setOnClickListener(v -> {
String name = etName.getText().toString().trim();
String salary = etSalary.getText().toString().trim();
if (!name.isEmpty() && !salary.isEmpty()) {
Toast.makeText(this, dbHelper.update(name, salary) ? "อัปเดตสำเร็จ" : "อัปเดตไม่สำเร็จ", Toast.LENGTH_LONG).show();
} else {
etName.setError("กรุณาป้อนชื่อ");
etSalary.setError("กรุณาป้อนเงินเดือน");
}
});
findViewById(R.id.btnRefresh).setOnClickListener(v -> {
dataList.clear();
dataList.addAll(dbHelper.getUnlikelyNames());
adapter.notifyDataSetChanged();
});
findViewById(R.id.btnSave).setOnClickListener(v -> {
String name = etName.getText().toString().trim();
String salary = etSalary.getText().toString().trim();
if (!name.isEmpty() && !salary.isEmpty()) {
Toast.makeText(this, dbHelper.insert(name, salary) ? "บันทึกสำเร็จ" : "บันทึกไม่สำเร็จ", Toast.LENGTH_LONG).show();
} else {
etName.setError("กรุณาป้อนชื่อ");
etSalary.setError("กรุณาป้อนเงินเดือน");
}
});
}
}
ขั้นตอนที่ 4: DatabaseHelper.java - ใช้ unlikely() ใน Query
package com.example.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;
public class DatabaseHelper extends SQLiteOpenHelper {
private static final String DB_NAME = "salaryDatabase";
private static final String TABLE_NAME = "SalaryDetails";
private static final int DB_VERSION = 1;
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, DB_VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
String sql = "CREATE TABLE " + TABLE_NAME + " (id INTEGER PRIMARY KEY AUTOINCREMENT, " +
"name TEXT, salary REAL, 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);
}
public boolean insert(String name, String salary) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("salary", salary);
return db.insert(TABLE_NAME, null, values) != -1;
}
// จุดสำคัญ: ใช้ unlikely() ใน WHERE clause
public ArrayList<String> getUnlikelyNames() {
SQLiteDatabase db = getReadableDatabase();
ArrayList<String> list = new ArrayList<>();
// unlikely(name = 'specific_value') - บอก Planner ว่าเงื่อนไขนี้น่าจะเป็นเท็จ
// แต่ในที่นี้ใช้ unlikely(name) แบบ standalone - จะคืนค่า argument เดิม (name)
// ใช้จริงควรเป็น: WHERE unlikely(name = 'rare_value')
Cursor cursor = db.rawQuery(
"SELECT unlikely(name) as name_value FROM " + TABLE_NAME, null);
if (cursor.moveToFirst()) {
do {
list.add("unlikely(name) = " + cursor.getString(0));
} while (cursor.moveToNext());
}
cursor.close();
return list;
}
public boolean update(String name, String salary) {
SQLiteDatabase db = getWritableDatabase();
ContentValues values = new ContentValues();
values.put("name", name);
values.put("salary", salary);
return db.update(TABLE_NAME, values, "name = ?", new String[]{name}) > 0;
}
public boolean deleteAll() {
return getWritableDatabase().delete(TABLE_NAME, null, null) > 0;
}
}
คำอธิบาย unlikely() และ likely()
Syntax
unlikely(X) -- คืนค่า X, แต่บอก Planner ว่า X น่าจะเป็น FALSE (0, null, '') likely(X) -- คืนค่า X, แต่บอก Planner ว่า X น่าจะเป็น TRUE (non-zero, non-null)
การทำงานจริง
- คืนค่า: ค่าของ argument X เดิม (identity function)
- ผลต่อ Query Plan: ให้ Hint ให้ Query Planner ประเมินความน่าจะเป็น (Selectivity)
- unlikely(X) → Planner คิดว่า X เป็น FALSE → อาจเลือก Index Scan แทน Full Table Scan
- likely(X) → Planner คิดว่า X เป็น TRUE → อาจเลือก Full Table Scan หรือ Join order ต่างออกไป
ตัวอย่างการใช้งานจริง (Best Practice)
1. ใน WHERE Clause - กรองข้อมูลที่หายาก
-- หาลูกค้าที่มีสถานะ 'VIP' (น่าจะน้อย - unlikely) SELECT * FROM customers WHERE unlikely(status = 'VIP'); -- หาสินค้าที่หมดสต็อก (น่าจะน้อย) SELECT * FROM products WHERE unlikely(stock = 0);
2. ร่วมกับ Index
CREATE INDEX idx_status ON customers(status); -- Planner อาจเลือก Index Scan เพราะ unlikely() บอกว่า match น้อย SELECT * FROM customers WHERE unlikely(status = 'VIP');
3. ใน JOIN Condition
SELECT * FROM orders o JOIN customers c ON unlikely(o.customer_id = c.id AND c.type = 'premium');
ตัวอย่างผลลัพธ์จากตัวอย่างโค้ด
Query: SELECT unlikely(name) FROM SalaryDetails
เนื่องจาก unlikely() คืนค่า argument เดิม ผลลัพธ์จะเหมือน SELECT name:
unlikely(name) = Sairam unlikely(name) = John unlikely(name) = Jane
ความแตกต่าง unlikely() vs CASE WHEN
| Feature | unlikely()/likely() | CASE WHEN |
|---|---|---|
| เปลี่ยนค่าผลลัพธ์ | ไม่ (คืนค่าเดิม) | ใช่ (กำหนดค่าใหม่ได้) |
| ส่งผลต่อ Query Plan | ใช่ (Hint ให้ Planner) | ไม่โดยตรง |
| ใช้ใน WHERE | ได้ | ได้ |
| Standard SQL | ไม่ (SQLite extension) | ใช่ |
ข้อควรระวัง
- SQLite Extension: unlikely()/likely() เป็นเฉพาะ SQLite ไม่ใช่ Standard SQL
- ไม่เปลี่ยนผลลัพธ์: คืนค่า argument เดิมเสมอ - ใช้ได้แค่ Hint
- Planner อาจละเลย: Query Planner ไม่บังคับต้องปฏิบัติตาม Hint
- ใช้ผิดที่อาจช้าลง: Hint ผิดอาจทำให้เลือก Plan ที่แย่กว่า
- ทดสอบด้วย EXPLAIN QUERY PLAN: ตรวจสอบว่า Plan ดีขึ้นจริงหรือไม่
การตรวจสอบ Query Plan
EXPLAIN QUERY PLAN SELECT * FROM table WHERE unlikely(col = 'rare');
ดูว่า Planner ใช้ Index หรือ Full Scan และ Estimated rows เท่าไหร่
สรุป
unlikely() และ likely() เป็น Advanced Optimization Hints สำหรับ SQLite Expert ใช้ปรับ Effectiveness ของ Query Plan เมื่อรู้สถิติข้อมูล (Data Distribution) ล่วงหน้า สำหรับแอปทั่วไปมักไม่จำเป็นต้องใช้