在 Android Returns 错误上使用 SQLite 触发器
Using SQLite Triggers on Android Returns Error
我是 SQLite 的新手,我正在尝试创建一个应用程序,用户可以在其中创建任务并向其附加提醒。我正在使用 SQLite
数据库来保存这些项目。一切正常,现在我想实现全文搜索功能,我已经阅读了 SQLite
文档,使用 FTS4 VIRTUAL TABLE
比普通的更好。
- 所以为了保持虚拟 table 同步,我不得不使用触发器。但是在调用
execSQL("//*Trigger code*//")
后出现错误
这是我的触发器(按照文档中提到的相同顺序使用它们):
object SQLiteTriggerUtils {
fun getBeforeDeleteTrigger(mainTable : String,
ftsTable : String,
rowId : Int?) : String {
return "CREATE TRIGGER table_bd" +
" BEFORE DELETE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getBeforeUpdateTrigger(mainTable: String,
ftsTable: String,
rowId: Int?) : String {
return "CREATE TRIGGER table_bu" +
" BEFORE UPDATE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getAfterUpdateTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_au" +
" AFTER UPDATE ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
fun getAfterInsertTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_ai" +
" AFTER INSERT ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
}
这是我的数据库 onCreate() 方法:
override fun onCreate(db: SQLiteDatabase) {
val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
+ "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "$KEY_LABEL TEXT, "
+ "$KEY_DESCRIPTION TEXT, "
+ "$KEY_IMPORTANCE INTEGER,"
+ "$KEY_LOGO INTEGER,"
+ "$KEY_TO_DO_DATE TEXT,"
+ "$KEY_CREATION_DATE TEXT)")
val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
db.execSQL(CREATION_TABLE)
db.execSQL(FTS_CREATION_TABLE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
onCreate(db)
}
执行触发器 onDeleteItem() 方法的示例 :
override fun deleteItem(itemId: Int): Boolean {
var success : Boolean
writableDatabase.apply {
execSQL(SQLiteTriggerUtils.getBeforeDeleteTrigger(TABLE_NAME, FTS_TABLE_NAME, itemId))
success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
close()
}
return success
}
返回错误:
android.database.sqlite.SQLiteException: near "END": syntax error (Sqlite code 1): , while compiling: CREATE TRIGGER table_bd BEFORE DELETE ON todo_tasks BEGIN DELETE FROM fts_todo_tasks WHERE docid=1 END;, (OS error - 2:No such file or directory)
我认为你的问题是在更正了触发操作的语法错误之后,该操作在 BEGIN 和 END 之间用分号编码,按照 :-
可能是您在知道值之前尝试添加触发器(可能不是这样,具体取决于代码)。
触发器是否是一个实体,如视图、table、索引等构成架构的一部分,因此需要一个唯一的名称。所以 CREATE TRIGGER the_trigger_name ......
要求 the_trigger_name 是唯一的。看起来您可能在每次调用触发器的操作即将执行时尝试创建相同的触发器,然后由于触发器已经存在而失败。
您可以使用 CREATE TRIGGER IF NOT EXISTS the_trigger_name ......
,但是将使用现有的触发器。
因此您可能希望 DROP TRIGGER the_trigger_name
在创建触发器之前。
但是!
表示触发器的预期用途是在事件(UPDATE、DELETE 或 INSERT)发生时(实际上是紧接在其之前或之后)自动执行类似的操作。因此,触发器可以访问导致触发器被操作(触发操作)的行的列。
如果触发操作是 INSERT,则可以使用 new.column 引用正在插入的行的列,并在触发操作(在 BEGIN 和 END 之间指定的操作)中使用。
如果触发操作是 DELETE,则 old.column 可用于引用被删除行的列。
如果触发动作是UPDATE那么new.column和old.column都可以被引用
- 其中 .column 被相应的列替换。
因此当 documentation 说:-
Instead of writing separately to the full-text index and the content
table, some users may wish to use database triggers to keep the
full-text index up to date with respect to the set of documents stored
in the content table. For example, using the tables from earlier
examples:
CREATE TRIGGER t2_bu BEFORE UPDATE ON t2 BEGIN
DELETE FROM t3 WHERE docid=old.rowid;
END;
CREATE TRIGGER t2_bd BEFORE DELETE ON t2 BEGIN
DELETE FROM t3 WHERE docid=old.rowid;
END;
CREATE TRIGGER t2_au AFTER UPDATE ON t2 BEGIN
INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
END;
CREATE TRIGGER t2_ai AFTER INSERT ON t2 BEGIN
INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c);
END;
你会看到它使用了old.docid和new.rowid
因此 old.docid 将更新被删除行的 docid,new.rowid 将被插入 $TABLE_NAME[ 的行的 rowid =98=]。因此,每个触发器只需要定义一次,因为它是通用的。
因此我相信您可以使用 :-
val TRG_BD = "trigger_bd" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_BU = "trigger_bu" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_AU = "trigger_au" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_AI = "trigger_ai" //<<<<<<<<<< ADDED >>>>>>>>>>
override fun onCreate(db: SQLiteDatabase) {
val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
+ "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "$KEY_LABEL TEXT, "
+ "$KEY_DESCRIPTION TEXT, "
+ "$KEY_IMPORTANCE INTEGER,"
+ "$KEY_LOGO INTEGER,"
+ "$KEY_TO_DO_DATE TEXT,"
+ "$KEY_CREATION_DATE TEXT)")
val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
//<<<<<<<<<< ADDED FOLLOWING LINES >>>>>>>>
val BD_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BD " +
"BEFORE DELETE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.rowid; " +
"END;")
val BU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BU " +
"BEFORE UPDATE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.rowid; " +
"END;")
val AU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AU " +
"AFTER UPDATE ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " + //<<<<<<<< not sure $KEY_LABEL is correct column
"END;")
val AI_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AI " +
"AFTER INSERT ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " +
"END;")
//<<<<<<<<<< END OF ADDED LINES >>>>>>>>
db.execSQL(CREATION_TABLE)
db.execSQL(FTS_CREATION_TABLE)
db.execSQL(BD_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(BU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AI_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BD") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_AU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER If EXISTS $TRG_AI") //<<<<<<<<<< ADDDED >>>>>>>>
onCreate(db)
}
以上将替换(即不需要以下):-
object SQLiteTriggerUtils {
fun getBeforeDeleteTrigger(mainTable : String,
ftsTable : String,
rowId : Int?) : String {
return "CREATE TRIGGER table_bd" +
" BEFORE DELETE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getBeforeUpdateTrigger(mainTable: String,
ftsTable: String,
rowId: Int?) : String {
return "CREATE TRIGGER table_bu" +
" BEFORE UPDATE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getAfterUpdateTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_au" +
" AFTER UPDATE ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
fun getAfterInsertTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_ai" +
" AFTER INSERT ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
}
此外,作为触发器运行自动
override fun deleteItem(itemId: Int): Boolean {
var success : Boolean
writableDatabase.apply {
execSQL(SQLiteTriggerUtils.getBeforeDeleteTrigger(TABLE_NAME, FTS_TABLE_NAME, itemId))
success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
close()
}
return success
}
可以替换为(其他的也一样):-
override fun deleteItem(itemId: Int): Boolean {
var success : Boolean
writableDatabase.apply {
success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
close()
}
return success
}
NOTE 以上为原理代码,未经测试或运行。因此它可能包含错误。
- 我还建议触发器名称,而不是使用 au(更新后),使用更具描述性的名称,因此命名约定没有那么严格(例如,你想使用另一个更新后触发器?)。
工作示例
以下是演示触发器的工作示例(注意 Kotlin 经验不足,因此代码可能不是最好的)
DatabaseHelper.kt
val DB_VERSION = 1;
val DB_NAME = "mydb"
public class DatabaseHelper(context: Context?) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
val TABLE_NAME = "mytable"
val FTS_TABLE_NAME = "myftstable"
val TRG_BD = "trigger_bd"
val TRG_BU = "trigger_bu"
val TRG_AU = "trigger_au"
val TRG_AI = "trigger_ai"
val KEY_ID = "id";
val KEY_LABEL = "label"
val KEY_DESCRIPTION = "desctription"
val KEY_IMPORTANCE = "importance";
val KEY_LOGO = "logo";
val KEY_TO_DO_DATE = "todo_date"
val KEY_CREATION_DATE = "creation_date"
override fun onCreate(db: SQLiteDatabase) {
val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
+ "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "$KEY_LABEL TEXT, "
+ "$KEY_DESCRIPTION TEXT, "
+ "$KEY_IMPORTANCE INTEGER,"
+ "$KEY_LOGO INTEGER,"
+ "$KEY_TO_DO_DATE TEXT,"
+ "$KEY_CREATION_DATE TEXT)")
val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
//<<<<<<<<<< ADDED FOLLOWING LINES >>>>>>>>
val BD_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BD " +
"BEFORE DELETE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.$KEY_ID; " +
"END;")
val BU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BU " +
"BEFORE UPDATE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.$KEY_ID; " +
"END;")
val AU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AU " +
"AFTER UPDATE ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " + //<<<<<<<< not sure $KEY_LABEL is correct column
"END;")
val AI_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AI " +
"AFTER INSERT ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " +
"END;")
db.execSQL(CREATION_TABLE)
db.execSQL(FTS_CREATION_TABLE)
db.execSQL(BD_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(BU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AI_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BD") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_AU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER If EXISTS $TRG_AI") //<<<<<<<<<< ADDDED >>>>>>>>
onCreate(db)
}
fun insert(label: String, description: String, importance: Int, tododate: String, creationdate: String ) {
val cv = ContentValues()
cv.put(KEY_LABEL,label)
cv.put(KEY_DESCRIPTION,description)
cv.put(KEY_IMPORTANCE,importance)
cv.put(KEY_LOGO,0)
cv.put(KEY_TO_DO_DATE,tododate)
cv.put(KEY_CREATION_DATE,creationdate)
val db = this.writableDatabase
val inserted = db.insert(TABLE_NAME, null, cv )
Log.d("INSERT","INSERT result in an id of " + inserted + ".")
}
fun update(id: Long, label: String) {
val cv = ContentValues()
cv.put(KEY_LABEL,label)
val db = this.writableDatabase
val updated = db.update(TABLE_NAME,cv,"$KEY_ID =" + id,null)
Log.d("UPDATED","UPDATE resulted in " + updated + " rows being updated.")
}
fun delete(id: Long) {
val whereclause = "$KEY_ID=" + id
val db = this.writableDatabase
val deleted = db.delete(TABLE_NAME,whereclause,null)
Log.d("DELETED","DELETE resulted in " + deleted + " rows being deleted.")
}
fun logtables() {
val db = this.writableDatabase
val csr1 = db.query(TABLE_NAME, null, null, null, null, null, null)
dumpCursor(csr1)
val csr2 = db.query(FTS_TABLE_NAME,null,null,null,null,null,null)
dumpCursor(csr2)
csr1.close()
csr2.close()
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbhelper = DatabaseHelper(this)
dbhelper.insert("TEST001","Just Testing",10,"2019-01-01","2019-01-01")
dbhelper.logtables()
dbhelper.update(1,"001TEST")
dbhelper.logtables()
dbhelper.delete(1)
dbhelper.logtables()
}
}
结果(日志)
04-28 14:35:21.002 17810-17810/s.e.myapplication D/INSERT: INSERT result in an id of 1.
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@11697ce
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: id=1
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: label=TEST001
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: desctription=Just Testing
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: importance=10
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: logo=0
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: todo_date=2019-01-01
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: creation_date=2019-01-01
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@3396ef
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: label=TEST001
04-28 14:35:21.003 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.003 17810-17810/s.e.myapplication I/System.out: <<<<<
即FTS 已为 TEST001
插入一行
04-28 14:35:21.007 17810-17810/s.e.myapplication D/UPDATED: UPDATE resulted in 1 rows being updated.
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@9ce45fc
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: id=1
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: label=001TEST
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: desctription=Just Testing
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: importance=10
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: logo=0
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: todo_date=2019-01-01
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: creation_date=2019-01-01
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@30ec485
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: label=001TEST
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: <<<<<
即FTS table 已更新以反映 TEST001 已更改为 001TEST
04-28 14:35:21.011 17810-17810/s.e.myapplication D/DELETED: DELETE resulted in 1 rows being deleted.
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@10862da
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d4cb30b
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: <<<<<
非fts删除后table均为空
我是 SQLite 的新手,我正在尝试创建一个应用程序,用户可以在其中创建任务并向其附加提醒。我正在使用 SQLite
数据库来保存这些项目。一切正常,现在我想实现全文搜索功能,我已经阅读了 SQLite
文档,使用 FTS4 VIRTUAL TABLE
比普通的更好。
- 所以为了保持虚拟 table 同步,我不得不使用触发器。但是在调用
execSQL("//*Trigger code*//")
后出现错误
这是我的触发器(按照文档中提到的相同顺序使用它们):
object SQLiteTriggerUtils {
fun getBeforeDeleteTrigger(mainTable : String,
ftsTable : String,
rowId : Int?) : String {
return "CREATE TRIGGER table_bd" +
" BEFORE DELETE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getBeforeUpdateTrigger(mainTable: String,
ftsTable: String,
rowId: Int?) : String {
return "CREATE TRIGGER table_bu" +
" BEFORE UPDATE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getAfterUpdateTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_au" +
" AFTER UPDATE ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
fun getAfterInsertTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_ai" +
" AFTER INSERT ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
}
这是我的数据库 onCreate() 方法:
override fun onCreate(db: SQLiteDatabase) {
val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
+ "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "$KEY_LABEL TEXT, "
+ "$KEY_DESCRIPTION TEXT, "
+ "$KEY_IMPORTANCE INTEGER,"
+ "$KEY_LOGO INTEGER,"
+ "$KEY_TO_DO_DATE TEXT,"
+ "$KEY_CREATION_DATE TEXT)")
val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
db.execSQL(CREATION_TABLE)
db.execSQL(FTS_CREATION_TABLE)
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
onCreate(db)
}
执行触发器 onDeleteItem() 方法的示例 :
override fun deleteItem(itemId: Int): Boolean {
var success : Boolean
writableDatabase.apply {
execSQL(SQLiteTriggerUtils.getBeforeDeleteTrigger(TABLE_NAME, FTS_TABLE_NAME, itemId))
success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
close()
}
return success
}
返回错误:
android.database.sqlite.SQLiteException: near "END": syntax error (Sqlite code 1): , while compiling: CREATE TRIGGER table_bd BEFORE DELETE ON todo_tasks BEGIN DELETE FROM fts_todo_tasks WHERE docid=1 END;, (OS error - 2:No such file or directory)
我认为你的问题是在更正了触发操作的语法错误之后,该操作在 BEGIN 和 END 之间用分号编码,按照 :-
可能是您在知道值之前尝试添加触发器(可能不是这样,具体取决于代码)。
触发器是否是一个实体,如视图、table、索引等构成架构的一部分,因此需要一个唯一的名称。所以 CREATE TRIGGER the_trigger_name ......
要求 the_trigger_name 是唯一的。看起来您可能在每次调用触发器的操作即将执行时尝试创建相同的触发器,然后由于触发器已经存在而失败。
您可以使用 CREATE TRIGGER IF NOT EXISTS the_trigger_name ......
,但是将使用现有的触发器。
因此您可能希望 DROP TRIGGER the_trigger_name
在创建触发器之前。
但是!
表示触发器的预期用途是在事件(UPDATE、DELETE 或 INSERT)发生时(实际上是紧接在其之前或之后)自动执行类似的操作。因此,触发器可以访问导致触发器被操作(触发操作)的行的列。
如果触发操作是 INSERT,则可以使用 new.column 引用正在插入的行的列,并在触发操作(在 BEGIN 和 END 之间指定的操作)中使用。
如果触发操作是 DELETE,则 old.column 可用于引用被删除行的列。
如果触发动作是UPDATE那么new.column和old.column都可以被引用
- 其中 .column 被相应的列替换。
因此当 documentation 说:-
Instead of writing separately to the full-text index and the content table, some users may wish to use database triggers to keep the full-text index up to date with respect to the set of documents stored in the content table. For example, using the tables from earlier examples:
CREATE TRIGGER t2_bu BEFORE UPDATE ON t2 BEGIN DELETE FROM t3 WHERE docid=old.rowid; END; CREATE TRIGGER t2_bd BEFORE DELETE ON t2 BEGIN DELETE FROM t3 WHERE docid=old.rowid; END; CREATE TRIGGER t2_au AFTER UPDATE ON t2 BEGIN INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c); END; CREATE TRIGGER t2_ai AFTER INSERT ON t2 BEGIN INSERT INTO t3(docid, b, c) VALUES(new.rowid, new.b, new.c); END;
你会看到它使用了old.docid和new.rowid
因此 old.docid 将更新被删除行的 docid,new.rowid 将被插入 $TABLE_NAME[ 的行的 rowid =98=]。因此,每个触发器只需要定义一次,因为它是通用的。
因此我相信您可以使用 :-
val TRG_BD = "trigger_bd" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_BU = "trigger_bu" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_AU = "trigger_au" //<<<<<<<<<< ADDED >>>>>>>>>>
val TRG_AI = "trigger_ai" //<<<<<<<<<< ADDED >>>>>>>>>>
override fun onCreate(db: SQLiteDatabase) {
val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
+ "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "$KEY_LABEL TEXT, "
+ "$KEY_DESCRIPTION TEXT, "
+ "$KEY_IMPORTANCE INTEGER,"
+ "$KEY_LOGO INTEGER,"
+ "$KEY_TO_DO_DATE TEXT,"
+ "$KEY_CREATION_DATE TEXT)")
val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
//<<<<<<<<<< ADDED FOLLOWING LINES >>>>>>>>
val BD_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BD " +
"BEFORE DELETE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.rowid; " +
"END;")
val BU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BU " +
"BEFORE UPDATE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.rowid; " +
"END;")
val AU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AU " +
"AFTER UPDATE ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " + //<<<<<<<< not sure $KEY_LABEL is correct column
"END;")
val AI_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AI " +
"AFTER INSERT ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " +
"END;")
//<<<<<<<<<< END OF ADDED LINES >>>>>>>>
db.execSQL(CREATION_TABLE)
db.execSQL(FTS_CREATION_TABLE)
db.execSQL(BD_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(BU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AI_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BD") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_AU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER If EXISTS $TRG_AI") //<<<<<<<<<< ADDDED >>>>>>>>
onCreate(db)
}
以上将替换(即不需要以下):-
object SQLiteTriggerUtils {
fun getBeforeDeleteTrigger(mainTable : String,
ftsTable : String,
rowId : Int?) : String {
return "CREATE TRIGGER table_bd" +
" BEFORE DELETE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getBeforeUpdateTrigger(mainTable: String,
ftsTable: String,
rowId: Int?) : String {
return "CREATE TRIGGER table_bu" +
" BEFORE UPDATE ON $mainTable" +
" BEGIN DELETE FROM $ftsTable" +
" WHERE docid=$rowId END;"
}
fun getAfterUpdateTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_au" +
" AFTER UPDATE ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
fun getAfterInsertTrigger(
mainTable: String,
ftsTable: String,
rowId: Int?,
updatedField: String,
updatedValue: String?
) : String {
return "CREATE TRIGGER table_ai" +
" AFTER INSERT ON $mainTable" +
" BEGIN INSERT INTO $ftsTable(docid, $updatedField)" +
" VALUES($rowId, $updatedValue) END;"
}
}
此外,作为触发器运行自动
override fun deleteItem(itemId: Int): Boolean {
var success : Boolean
writableDatabase.apply {
execSQL(SQLiteTriggerUtils.getBeforeDeleteTrigger(TABLE_NAME, FTS_TABLE_NAME, itemId))
success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
close()
}
return success
}
可以替换为(其他的也一样):-
override fun deleteItem(itemId: Int): Boolean {
var success : Boolean
writableDatabase.apply {
success = delete(TABLE_NAME, "id = ?", arrayOf(itemId.toString())) > 0
close()
}
return success
}
NOTE 以上为原理代码,未经测试或运行。因此它可能包含错误。
- 我还建议触发器名称,而不是使用 au(更新后),使用更具描述性的名称,因此命名约定没有那么严格(例如,你想使用另一个更新后触发器?)。
工作示例
以下是演示触发器的工作示例(注意 Kotlin 经验不足,因此代码可能不是最好的)
DatabaseHelper.kt
val DB_VERSION = 1;
val DB_NAME = "mydb"
public class DatabaseHelper(context: Context?) :
SQLiteOpenHelper(context, DB_NAME, null, DB_VERSION) {
val TABLE_NAME = "mytable"
val FTS_TABLE_NAME = "myftstable"
val TRG_BD = "trigger_bd"
val TRG_BU = "trigger_bu"
val TRG_AU = "trigger_au"
val TRG_AI = "trigger_ai"
val KEY_ID = "id";
val KEY_LABEL = "label"
val KEY_DESCRIPTION = "desctription"
val KEY_IMPORTANCE = "importance";
val KEY_LOGO = "logo";
val KEY_TO_DO_DATE = "todo_date"
val KEY_CREATION_DATE = "creation_date"
override fun onCreate(db: SQLiteDatabase) {
val CREATION_TABLE = ("CREATE TABLE $TABLE_NAME ( "
+ "$KEY_ID INTEGER PRIMARY KEY AUTOINCREMENT, "
+ "$KEY_LABEL TEXT, "
+ "$KEY_DESCRIPTION TEXT, "
+ "$KEY_IMPORTANCE INTEGER,"
+ "$KEY_LOGO INTEGER,"
+ "$KEY_TO_DO_DATE TEXT,"
+ "$KEY_CREATION_DATE TEXT)")
val FTS_CREATION_TABLE = ("CREATE VIRTUAL TABLE $FTS_TABLE_NAME USING fts4 (content='$TABLE_NAME', $KEY_LABEL)")
//<<<<<<<<<< ADDED FOLLOWING LINES >>>>>>>>
val BD_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BD " +
"BEFORE DELETE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.$KEY_ID; " +
"END;")
val BU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_BU " +
"BEFORE UPDATE ON $TABLE_NAME " +
"BEGIN " +
"DELETE FROM $FTS_TABLE_NAME " +
"WHERE docid=old.$KEY_ID; " +
"END;")
val AU_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AU " +
"AFTER UPDATE ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " + //<<<<<<<< not sure $KEY_LABEL is correct column
"END;")
val AI_CREATION_TRIGGER = ("CREATE TRIGGER IF NOT EXISTS $TRG_AI " +
"AFTER INSERT ON $TABLE_NAME " +
"BEGIN " +
"INSERT INTO $FTS_TABLE_NAME (docid, $KEY_LABEL) VALUES(new.$KEY_ID,new.$KEY_LABEL); " +
"END;")
db.execSQL(CREATION_TABLE)
db.execSQL(FTS_CREATION_TABLE)
db.execSQL(BD_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(BU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AU_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
db.execSQL(AI_CREATION_TRIGGER) //<<<<<<<<<< ADDED >>>>>>>>>>
}
override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("DROP TABLE IF EXISTS $TABLE_NAME")
db.execSQL("DROP TABLE IF EXISTS $FTS_TABLE_NAME")
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BD") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_BU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER IF EXISTS $TRG_AU") //<<<<<<<<<< ADDDED >>>>>>>>
db.execSQL("DROP TRIGGER If EXISTS $TRG_AI") //<<<<<<<<<< ADDDED >>>>>>>>
onCreate(db)
}
fun insert(label: String, description: String, importance: Int, tododate: String, creationdate: String ) {
val cv = ContentValues()
cv.put(KEY_LABEL,label)
cv.put(KEY_DESCRIPTION,description)
cv.put(KEY_IMPORTANCE,importance)
cv.put(KEY_LOGO,0)
cv.put(KEY_TO_DO_DATE,tododate)
cv.put(KEY_CREATION_DATE,creationdate)
val db = this.writableDatabase
val inserted = db.insert(TABLE_NAME, null, cv )
Log.d("INSERT","INSERT result in an id of " + inserted + ".")
}
fun update(id: Long, label: String) {
val cv = ContentValues()
cv.put(KEY_LABEL,label)
val db = this.writableDatabase
val updated = db.update(TABLE_NAME,cv,"$KEY_ID =" + id,null)
Log.d("UPDATED","UPDATE resulted in " + updated + " rows being updated.")
}
fun delete(id: Long) {
val whereclause = "$KEY_ID=" + id
val db = this.writableDatabase
val deleted = db.delete(TABLE_NAME,whereclause,null)
Log.d("DELETED","DELETE resulted in " + deleted + " rows being deleted.")
}
fun logtables() {
val db = this.writableDatabase
val csr1 = db.query(TABLE_NAME, null, null, null, null, null, null)
dumpCursor(csr1)
val csr2 = db.query(FTS_TABLE_NAME,null,null,null,null,null,null)
dumpCursor(csr2)
csr1.close()
csr2.close()
}
}
MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dbhelper = DatabaseHelper(this)
dbhelper.insert("TEST001","Just Testing",10,"2019-01-01","2019-01-01")
dbhelper.logtables()
dbhelper.update(1,"001TEST")
dbhelper.logtables()
dbhelper.delete(1)
dbhelper.logtables()
}
}
结果(日志)
04-28 14:35:21.002 17810-17810/s.e.myapplication D/INSERT: INSERT result in an id of 1.
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@11697ce
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: id=1
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: label=TEST001
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: desctription=Just Testing
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: importance=10
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: logo=0
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: todo_date=2019-01-01
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: creation_date=2019-01-01
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@3396ef
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.002 17810-17810/s.e.myapplication I/System.out: label=TEST001
04-28 14:35:21.003 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.003 17810-17810/s.e.myapplication I/System.out: <<<<<
即FTS 已为 TEST001
插入一行04-28 14:35:21.007 17810-17810/s.e.myapplication D/UPDATED: UPDATE resulted in 1 rows being updated.
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@9ce45fc
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: id=1
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: label=001TEST
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: desctription=Just Testing
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: importance=10
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: logo=0
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: todo_date=2019-01-01
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: creation_date=2019-01-01
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.007 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@30ec485
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: 0 {
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: label=001TEST
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: }
04-28 14:35:21.008 17810-17810/s.e.myapplication I/System.out: <<<<<
即FTS table 已更新以反映 TEST001 已更改为 001TEST
04-28 14:35:21.011 17810-17810/s.e.myapplication D/DELETED: DELETE resulted in 1 rows being deleted.
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@10862da
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: <<<<<
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@d4cb30b
04-28 14:35:21.011 17810-17810/s.e.myapplication I/System.out: <<<<<
非fts删除后table均为空