【问题标题】:Undelete and SQL-based application取消删除和基于 SQL 的应用程序
【发布时间】:2019-03-24 07:16:52
【问题描述】:

我想知道在使用 SQL 作为其底层结构来存储数据的应用程序上实现取消删除的最佳方法是什么。我问是因为在 SQL 中,一旦发出删除语句,数据就无法再恢复。然而,想象一个应用程序在列表中保存仓库项目客户和供应商数据等。您的移动应用程序可能在您的口袋里并且意外删除数据,或者您可能只是在使用用户界面时犯了错误。

解决此问题的最佳方法是什么?您可以取消 SQL,但这样应用程序会很慢,而且代码行数和代码复杂性肯定会增加。

谢谢。

【问题讨论】:

  • 在相关表中添加另一列以跟踪项目是否被“删除”。然后,您实际上不会删除任何记录;您只需通过适当地修改该列的值来软删除和取消删除。
  • @MikeM。这意味着每次我选择我都需要添加一个 where 子句来删除等于 false 。如果你更新了行,你怎么知道是否也要触摸“删除”的行。您对恢复 ON DELETE CASCADE 的感觉如何,您还必须手动执行此操作,适当地设置子表上的“已删除”列条目。
  • 如果你把东西复制到另一个表怎么办?

标签: android sql sqlite user-experience mobile-application


【解决方案1】:

您可以确保任何此类操作都需要确认。

您可以定期或在任何此类操作之前备份数据库。

您可以引入此类操作的日志记录,以允许操作回滚到某个时间点。

没有特定的最佳方法,因为最合适的方法将/可能取决于应用程序的复杂性/简单性/效率。

在所有情况下,都会有额外的代码和复杂性。

简而言之,这些考虑应该是设计的一部分。

补充评论:-

能否请您告诉我如何从原始值中复制一个值 table info 带有触发器的日志表?

考虑以下几点:-

DROP TRIGGER IF EXISTS logdelete;
DROP TABLE IF EXISTS main;
DROP TABLE IF EXISTS logtable;
CREATE TABLE IF NOT EXISTS logtable (timestamp TEXT DEFAULT CURRENT_TIMESTAMP, logaction TEXT, val1 TEXT, val2 TEXT, val3 TEXT);
CREATE TABLE IF NOT EXISTS main (id INTEGER PRIMARY KEY, val1 TEXT, val2 TEXT, val3 TEXT);
CREATE TRIGGER IF NOT EXISTS logdelete AFTER DELETE ON main
    BEGIN
        INSERT INTO logtable (logaction, val1, val2, val3) VALUES('DLT',old.val1,old.val2,old.val3);
    END
;
INSERT INTO main (val1,val2,val3) VALUES
    ('A','B','C'),('D','E','F'),('G','H','I'),('J','K','L'),('M','N','O');
SELECT * FROM main;
SELECT * FROM logtable;
DELETE FROM main WHERE val1 IN ('D','J');
SELECT * FROM main;
SELECT * FROM logtable;
INSERT INTO main (val1,val2,val3) SELECT val1,val2,val3 FROM logtable WHERE logaction = 'DLT';
UPDATE logtable SET logaction = 'DLTDONE' WHERE logaction = 'DLT';
SELECT * FROM main;
SELECT * FROM logtable;

这个

  1. 删除所有表触发器(为了方便重新运行),然后
  2. 根据主表创建日志记录表 logtable,但附加两列用于删除日期和时间(时间戳)以及所采取的操作。
  3. 创建主表 ma​​in,具有 ID 列(rowid 列的别名)和 3 列值,即 val1,val2 和 val3
  4. 当从插入 logtable 条目的 ma​​in 表中删除行时创建触发器。
  5. 在主表中插入一些行。
  6. ma​​in 表和 logtable(result1 和 result2)中选择所有行,以显示删除之前的数据。
  7. 删除一些行(根据 ID 列的第 2 和第 4 行)。
  8. ma​​in 表和 logtable(result3 和 result4)中选择所有行,以显示删除后的数据。
  9. 根据 logtable 回滚对 ma​​in 所做的删除,根据 ALL(WHERE 子句可以使用日期回滚到某个时间或一段时间)
  10. 更新 日志表(可能是一个触发器,但根据与本示例中的 ROLL BACK 相同的标准完成)以反映回滚。
  11. ma​​in 表和 logtable(result5 和 result6)中选择所有行,以显示回滚后的数据。

结果:-

1 个主要

  • 插入数据后

2 日志表

  • (空无删除)(不需要图片)

3 主要

  • 删除后(保留 3 行或原来的 5 行)

4 日志表

  • 现在有 2 个带有删除操作和已删除数据的日志条目

5 主要

  • 回滚后(插入已删除的行。但请注意新的 id(可以按照原始设置 id))

6 日志表

  • 回滚后(日志条目现在标记为完成)

  • 请注意,被标记为 DONE 的日志表行实际上是根据注释:-

在相关表中添加另一列以跟踪项目是否 “删除”。然后,您实际上不会删除任何记录;你只是 通过修改该列的值软删除和取消删除 适当的

但可能允许日志表被取消删除。

上面的Android Demo:-

DatabasHelper DBHelper.java

public class DBHelper extends SQLiteOpenHelper {

    public static final String DBNAME = "mydb";
    public static final int DBVERSION = 1;

    public static final String TBL_MAIN = "main";
    public static final String TBL_LOGTABLE = "logtable";
    public static final String TRG_MAINDELETE = "logdelete";

    public static final String COL_MAIN_ID = BaseColumns._ID;
    public static final String COl_MAIN_VAL1 = "val1";
    public static final String COL_MAIN_VAL2 = "val2";
    public static final String COL_MAIN_VAL3 = "val3";

    public static final String COL_LOGTABLE_TIMESTAMP = "timestamp";
    public static final String COl_LOGTABLE_LOGACTION = "logaction";
    public static final String COL_LOGTABLE_VAL1 = COl_MAIN_VAL1;
    public static final String COL_LOGTABLE_VAL2 = COL_MAIN_VAL2;
    public static final String COL_LOGTABLE_VAL3 = COL_MAIN_VAL3;

    public static final String LOGACTION_DLTDONE = "DLTDONE";
    public static final String LOGACTION_DELETE = "DLT";

    private String main_crtsql = "CREATE TABLE IF NOT EXISTS " + TBL_MAIN + "(" +
            COL_MAIN_ID + " INTEGER PRIMARY KEY," +
            COl_MAIN_VAL1 + " TEXT," +
            COL_MAIN_VAL2 + " TEXT, " +
            COL_MAIN_VAL3 + " TEXT" +
            ")";

    private String logtable_crtsql = "CREATE TABLE IF NOT EXISTS " + TBL_LOGTABLE + "(" +
            COL_LOGTABLE_TIMESTAMP + " TEXT DEFAULT CURRENT_TIMESTAMP, " +
            COl_LOGTABLE_LOGACTION + " TEXT, " +
            COL_LOGTABLE_VAL1 + " TEXT, " +
            COL_LOGTABLE_VAL2 + " TEXT, " +
            COL_LOGTABLE_VAL3 + " TEXT " +
            ")";


    private String logdelete_crtsql = "CREATE TRIGGER IF NOT EXISTS " + TRG_MAINDELETE +
            " AFTER DELETE ON " + TBL_MAIN +
            " BEGIN " +
            "INSERT INTO " + TBL_LOGTABLE + "(" +
            COl_LOGTABLE_LOGACTION + "," +
            COL_LOGTABLE_VAL1 + "," +
            COL_LOGTABLE_VAL2 + "," +
            COL_LOGTABLE_VAL3 +
            ")" +
            " VALUES(" +
            "'DLT'," +
            "old." + COL_LOGTABLE_VAL1 + "," +
            "old." + COL_LOGTABLE_VAL2 + "," +
            "old." + COL_LOGTABLE_VAL3 +
            ")" +
            ";" +
            " END";


    SQLiteDatabase mDB;

    public DBHelper(Context context) {
        super(context, DBNAME, null, DBVERSION);
        mDB = this.getWritableDatabase();
    }

    @Override
    public void onCreate(SQLiteDatabase db) {
        db.execSQL(logtable_crtsql);
        db.execSQL(main_crtsql);
        db.execSQL(logdelete_crtsql);
    }

    @Override
    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

    }

    public long addMain(String val1, String val2, String val3) {
        ContentValues cv = new ContentValues();
        cv.put(COl_MAIN_VAL1,val1);
        cv.put(COL_MAIN_VAL2,val2);
        cv.put(COL_MAIN_VAL3,val3);
        return mDB.insert(TBL_MAIN,null,cv);
    }

    public int deleteMain(String val) {
        String whereclause = COl_MAIN_VAL1 + "=?";
        String[] whereargs = new String[]{val};
        return mDB.delete(TBL_MAIN,whereclause,whereargs);
    }

    //INSERT INTO main (val1,val2,val3) SELECT val1,val2,val3 FROM logtable WHERE logaction = 'DLT';
    //UPDATE logtable SET logaction = 'DLTDONE' WHERE logaction = 'DLT';
    public void rollbackMain() {
        String columns = " (" + COl_MAIN_VAL1 + "," + COL_MAIN_VAL2 + "," +COL_LOGTABLE_VAL3 + ")";
        String values = " SELECT " + COL_LOGTABLE_VAL1 + "," + COL_LOGTABLE_VAL2 + "," + COL_LOGTABLE_VAL3 +
                " FROM " + TBL_LOGTABLE + " WHERE " + COl_LOGTABLE_LOGACTION + "='" + LOGACTION_DELETE + "'";
        String insertsql = "INSERT INTO " + TBL_MAIN + columns + values;
        mDB.beginTransaction();
        mDB.execSQL(insertsql);
        ContentValues cv = new ContentValues();
        cv.put(COl_LOGTABLE_LOGACTION,LOGACTION_DLTDONE);
        String wherecluase = COl_LOGTABLE_LOGACTION + "=?";
        String[] whereargs = new String[]{LOGACTION_DELETE};
        mDB.update(TBL_LOGTABLE,cv,wherecluase,whereargs);
        mDB.setTransactionSuccessful();
        mDB.endTransaction();
    }

    public void logtables() {
        Cursor csr = mDB.query(TBL_MAIN,null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
        csr = mDB.query(TBL_LOGTABLE,null,null,null,null,null,null);
        DatabaseUtils.dumpCursor(csr);
        csr.close();
    }
}

一个活动 - MainActivity.java

public class MainActivity extends AppCompatActivity {

    DBHelper mDBHlpr;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mDBHlpr = new DBHelper(this);
        // Empty main and logtable
        mDBHlpr.getWritableDatabase().delete(DBHelper.TBL_MAIN,null,null);
        mDBHlpr.getWritableDatabase().delete(DBHelper.TBL_LOGTABLE,null,null);
        // Add some data
        mDBHlpr.addMain("Fred","Banana","Rock");
        mDBHlpr.addMain("Mary","Orange","Scissors");
        mDBHlpr.addMain("Sue","Apple","Paper");
        mDBHlpr.logtables();
        //Delete some data
        mDBHlpr.deleteMain("Mary");
        mDBHlpr.deleteMain("MrNobody");
        mDBHlpr.deleteMain("Sue");
        mDBHlpr.logtables();
        //Rollback
        mDBHlpr.rollbackMain();
        mDBHlpr.logtables();
    }
}

结果

(等同于之前的结果,尽管数据不同)

1.

2019-03-27 12:44:37.136  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@7f0608d
2019-03-27 12:44:37.137  I/System.out: 0 {
2019-03-27 12:44:37.137  I/System.out:    _id=1
2019-03-27 12:44:37.137  I/System.out:    val1=Fred
2019-03-27 12:44:37.137  I/System.out:    val2=Banana
2019-03-27 12:44:37.137  I/System.out:    val3=Rock
2019-03-27 12:44:37.137  I/System.out: }
2019-03-27 12:44:37.137  I/System.out: 1 {
2019-03-27 12:44:37.137  I/System.out:    _id=2
2019-03-27 12:44:37.137  I/System.out:    val1=Mary
2019-03-27 12:44:37.137  I/System.out:    val2=Orange
2019-03-27 12:44:37.137  I/System.out:    val3=Scissors
2019-03-27 12:44:37.137  I/System.out: }
2019-03-27 12:44:37.137  I/System.out: 2 {
2019-03-27 12:44:37.137  I/System.out:    _id=3
2019-03-27 12:44:37.137  I/System.out:    val1=Sue
2019-03-27 12:44:37.138  I/System.out:    val2=Apple
2019-03-27 12:44:37.138  I/System.out:    val3=Paper
2019-03-27 12:44:37.138  I/System.out: }
2019-03-27 12:44:37.138  I/System.out: <<<<<

2.

2019-03-27 12:44:37.138  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@8d24242
2019-03-27 12:44:37.138  I/System.out: <<<<<

3.

2019-03-27 12:44:37.140  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@2c28253
2019-03-27 12:44:37.140  I/System.out: 0 {
2019-03-27 12:44:37.140  I/System.out:    _id=1
2019-03-27 12:44:37.141  I/System.out:    val1=Fred
2019-03-27 12:44:37.141  I/System.out:    val2=Banana
2019-03-27 12:44:37.141  I/System.out:    val3=Rock
2019-03-27 12:44:37.141  I/System.out: }
2019-03-27 12:44:37.141  I/System.out: <<<<<

4.

2019-03-27 12:44:37.142  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@fb8f790
2019-03-27 12:44:37.142  I/System.out: 0 {
2019-03-27 12:44:37.142  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.142  I/System.out:    logaction=DLT
2019-03-27 12:44:37.142  I/System.out:    val1=Mary
2019-03-27 12:44:37.142  I/System.out:    val2=Orange
2019-03-27 12:44:37.142  I/System.out:    val3=Scissors
2019-03-27 12:44:37.143  I/System.out: }
2019-03-27 12:44:37.143  I/System.out: 1 {
2019-03-27 12:44:37.143  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.143  I/System.out:    logaction=DLT
2019-03-27 12:44:37.143  I/System.out:    val1=Sue
2019-03-27 12:44:37.143  I/System.out:    val2=Apple
2019-03-27 12:44:37.143  I/System.out:    val3=Paper
2019-03-27 12:44:37.143  I/System.out: }
2019-03-27 12:44:37.144  I/System.out: <<<<<

5.

2019-03-27 12:44:37.145  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@68f7889
2019-03-27 12:44:37.145  I/System.out: 0 {
2019-03-27 12:44:37.146  I/System.out:    _id=1
2019-03-27 12:44:37.146  I/System.out:    val1=Fred
2019-03-27 12:44:37.146  I/System.out:    val2=Banana
2019-03-27 12:44:37.146  I/System.out:    val3=Rock
2019-03-27 12:44:37.146  I/System.out: }
2019-03-27 12:44:37.146  I/System.out: 1 {
2019-03-27 12:44:37.146  I/System.out:    _id=2
2019-03-27 12:44:37.146  I/System.out:    val1=Mary
2019-03-27 12:44:37.146  I/System.out:    val2=Orange
2019-03-27 12:44:37.146  I/System.out:    val3=Scissors
2019-03-27 12:44:37.146  I/System.out: }
2019-03-27 12:44:37.146  I/System.out: 2 {
2019-03-27 12:44:37.146  I/System.out:    _id=3
2019-03-27 12:44:37.146  I/System.out:    val1=Sue
2019-03-27 12:44:37.146  I/System.out:    val2=Apple
2019-03-27 12:44:37.146  I/System.out:    val3=Paper
2019-03-27 12:44:37.146  I/System.out: }
2019-03-27 12:44:37.146  I/System.out: <<<<<

6.

2019-03-27 12:44:37.147  I/System.out: >>>>> Dumping cursor android.database.sqlite.SQLiteCursor@4c6408e
2019-03-27 12:44:37.147  I/System.out: 0 {
2019-03-27 12:44:37.147  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.147  I/System.out:    logaction=DLTDONE
2019-03-27 12:44:37.147  I/System.out:    val1=Mary
2019-03-27 12:44:37.147  I/System.out:    val2=Orange
2019-03-27 12:44:37.147  I/System.out:    val3=Scissors
2019-03-27 12:44:37.147  I/System.out: }
2019-03-27 12:44:37.147  I/System.out: 1 {
2019-03-27 12:44:37.147  I/System.out:    timestamp=2019-03-27 01:44:37
2019-03-27 12:44:37.148  I/System.out:    logaction=DLTDONE
2019-03-27 12:44:37.148  I/System.out:    val1=Sue
2019-03-27 12:44:37.148  I/System.out:    val2=Apple
2019-03-27 12:44:37.148  I/System.out:    val3=Paper
2019-03-27 12:44:37.148  I/System.out: }
2019-03-27 12:44:37.148  I/System.out: <<<<<

【讨论】:

  • 介绍您提到的日志记录和回滚的最佳方式是什么。回滚是否可以不影响独立的后续操作,这样如果我删除 A,然后 B,然后 C,我可以取消删除 B,从而删除 A 和 C?不确定您是否在谈论 SQL 中的事务。
  • @JoselinJocklingson 不是事务,而是一个日志表,(可能有一个或多个触发器插入的行)。类似于CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END。其中 mylogtable 有列 logaction 和另一个用于时间戳的列,在其定义中使用 DEFAULT CURRENT_TIMESTAMP
  • @JoselinJocklingson 任何你想要的,它只是行动的一个指标。当然,如果是删除,那么您还想存储已删除的内容以便恢复。该代码只是为了展示如何创建一个在删除一行后执行的简单触发器。
  • 即将推出,但如果我记得,我可能会提供一个非常基本的示例。但是,您所做的只是将列(值)称为 old.column,因此,更多信息类似于 BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER
  • @JoselinJocklingson 请参阅上述链接中的部分使用 BEFORE 触发器的注意事项。非常感谢您在回答中打勾,因为我相信您从回答中收获颇丰。
猜你喜欢
  • 1970-01-01
  • 2023-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-28
相关资源
最近更新 更多