Undelete and SQL-based applicationHow can I prevent SQL injection in PHP?How do I perform an IF…THEN in an SQL SELECT?Add a column with a default value to an existing table in SQL ServerHow to return only the Date from a SQL Server DateTime datatypeHow to concatenate text from multiple rows into a single text string in SQL server?Inserting multiple rows in a single SQL query?Is quitting an application frowned upon?How do I UPDATE from a SELECT in SQL Server?Finding duplicate values in a SQL tableHow to import an SQL file using the command line in MySQL?

How does an ARM MCU run faster than the external crystal?

How can people dance around bonfires on Lag Lo'Omer - it's darchei emori?

Can a wire having a 610-670 THz (frequency of blue light) AC frequency supply, generate blue light?

When did God say "let all the angels of God worship him" as stated in Hebrews 1:6?

Does this degree 12 genus 1 curve have only one point over infinitely many finite fields?

What is the most important source of natural gas? coal, oil or other?

Is one obligated to listen to a Rav?

What are the benefits of cryosleep?

Why is this Simple Puzzle impossible to solve?

Can you heal a summoned creature?

Employer demanding to see degree after poor code review

At what point in European history could a government build a printing press given a basic description?

Command to Search for Filenames Exceeding 143 Characters?

Rests in pickup measure (anacrusis)

Array Stutter Implementation

When do characters level up?

How do I subvert the tropes of a train heist?

Crossing US border with music files I'm legally allowed to possess

Different circular sectors as new logo of the International System

Is there a general effective method to solve Smullyan style Knights and Knaves problems? Is the truth table method the most appropriate one?

Logarithm of dependent variable is uniformly distributed. How to calculate a confidence interval for the mean?

Why do Russians call their women expensive ("дорогая")?

Why is desire the root of suffering?

Integrating an absolute function using Mathematica



Undelete and SQL-based application


How can I prevent SQL injection in PHP?How do I perform an IF…THEN in an SQL SELECT?Add a column with a default value to an existing table in SQL ServerHow to return only the Date from a SQL Server DateTime datatypeHow to concatenate text from multiple rows into a single text string in SQL server?Inserting multiple rows in a single SQL query?Is quitting an application frowned upon?How do I UPDATE from a SELECT in SQL Server?Finding duplicate values in a SQL tableHow to import an SQL file using the command line in MySQL?






.everyoneloves__top-leaderboard:empty,.everyoneloves__mid-leaderboard:empty,.everyoneloves__bot-mid-leaderboard:empty height:90px;width:728px;box-sizing:border-box;








-1















I would like to know what the best way to implement undelete would be on an app that used SQL as its underlying structure for storing data. I ask because in SQL once a delete statement is issued, the data can no longer be recovered. However, imagine an app for holding warehouse items customer and supplier data and more in a list. Your mobile app may be in your pocket and go off accidentally deleting data or you may simply make mistakes when using the user interface.



What is the best way to deal with this problem? You could do away with SQL, but then the application would be slow and lines of code and code complexity would surely increase.



Thanks.










share|improve this question

















  • 3





    Add another column in the relevant tables to track whether an item is "deleted". Then, you wouldn't actually delete any records; you'd just soft delete and undelete by modifying that column's value appropriately.

    – Mike M.
    Mar 24 at 7:29











  • @MikeM. This would mean every time I selected I would need to add a where clause for deleted equals false though. And what if you updated rows, how would you know whether to touch the 'deleted' rows as well. And how would you feel with restoring ON DELETE CASCADE stuff, you'd have to do that manually as well, setting the 'deleted' column entries on child tables appropriately.

    – Joselin Jocklingson
    Mar 25 at 13:21











  • What if you copied stuff to a different table?

    – Joselin Jocklingson
    Mar 25 at 13:21

















-1















I would like to know what the best way to implement undelete would be on an app that used SQL as its underlying structure for storing data. I ask because in SQL once a delete statement is issued, the data can no longer be recovered. However, imagine an app for holding warehouse items customer and supplier data and more in a list. Your mobile app may be in your pocket and go off accidentally deleting data or you may simply make mistakes when using the user interface.



What is the best way to deal with this problem? You could do away with SQL, but then the application would be slow and lines of code and code complexity would surely increase.



Thanks.










share|improve this question

















  • 3





    Add another column in the relevant tables to track whether an item is "deleted". Then, you wouldn't actually delete any records; you'd just soft delete and undelete by modifying that column's value appropriately.

    – Mike M.
    Mar 24 at 7:29











  • @MikeM. This would mean every time I selected I would need to add a where clause for deleted equals false though. And what if you updated rows, how would you know whether to touch the 'deleted' rows as well. And how would you feel with restoring ON DELETE CASCADE stuff, you'd have to do that manually as well, setting the 'deleted' column entries on child tables appropriately.

    – Joselin Jocklingson
    Mar 25 at 13:21











  • What if you copied stuff to a different table?

    – Joselin Jocklingson
    Mar 25 at 13:21













-1












-1








-1








I would like to know what the best way to implement undelete would be on an app that used SQL as its underlying structure for storing data. I ask because in SQL once a delete statement is issued, the data can no longer be recovered. However, imagine an app for holding warehouse items customer and supplier data and more in a list. Your mobile app may be in your pocket and go off accidentally deleting data or you may simply make mistakes when using the user interface.



What is the best way to deal with this problem? You could do away with SQL, but then the application would be slow and lines of code and code complexity would surely increase.



Thanks.










share|improve this question














I would like to know what the best way to implement undelete would be on an app that used SQL as its underlying structure for storing data. I ask because in SQL once a delete statement is issued, the data can no longer be recovered. However, imagine an app for holding warehouse items customer and supplier data and more in a list. Your mobile app may be in your pocket and go off accidentally deleting data or you may simply make mistakes when using the user interface.



What is the best way to deal with this problem? You could do away with SQL, but then the application would be slow and lines of code and code complexity would surely increase.



Thanks.







android sql sqlite user-experience mobile-application






share|improve this question













share|improve this question











share|improve this question




share|improve this question










asked Mar 24 at 7:16









Joselin JocklingsonJoselin Jocklingson

1244




1244







  • 3





    Add another column in the relevant tables to track whether an item is "deleted". Then, you wouldn't actually delete any records; you'd just soft delete and undelete by modifying that column's value appropriately.

    – Mike M.
    Mar 24 at 7:29











  • @MikeM. This would mean every time I selected I would need to add a where clause for deleted equals false though. And what if you updated rows, how would you know whether to touch the 'deleted' rows as well. And how would you feel with restoring ON DELETE CASCADE stuff, you'd have to do that manually as well, setting the 'deleted' column entries on child tables appropriately.

    – Joselin Jocklingson
    Mar 25 at 13:21











  • What if you copied stuff to a different table?

    – Joselin Jocklingson
    Mar 25 at 13:21












  • 3





    Add another column in the relevant tables to track whether an item is "deleted". Then, you wouldn't actually delete any records; you'd just soft delete and undelete by modifying that column's value appropriately.

    – Mike M.
    Mar 24 at 7:29











  • @MikeM. This would mean every time I selected I would need to add a where clause for deleted equals false though. And what if you updated rows, how would you know whether to touch the 'deleted' rows as well. And how would you feel with restoring ON DELETE CASCADE stuff, you'd have to do that manually as well, setting the 'deleted' column entries on child tables appropriately.

    – Joselin Jocklingson
    Mar 25 at 13:21











  • What if you copied stuff to a different table?

    – Joselin Jocklingson
    Mar 25 at 13:21







3




3





Add another column in the relevant tables to track whether an item is "deleted". Then, you wouldn't actually delete any records; you'd just soft delete and undelete by modifying that column's value appropriately.

– Mike M.
Mar 24 at 7:29





Add another column in the relevant tables to track whether an item is "deleted". Then, you wouldn't actually delete any records; you'd just soft delete and undelete by modifying that column's value appropriately.

– Mike M.
Mar 24 at 7:29













@MikeM. This would mean every time I selected I would need to add a where clause for deleted equals false though. And what if you updated rows, how would you know whether to touch the 'deleted' rows as well. And how would you feel with restoring ON DELETE CASCADE stuff, you'd have to do that manually as well, setting the 'deleted' column entries on child tables appropriately.

– Joselin Jocklingson
Mar 25 at 13:21





@MikeM. This would mean every time I selected I would need to add a where clause for deleted equals false though. And what if you updated rows, how would you know whether to touch the 'deleted' rows as well. And how would you feel with restoring ON DELETE CASCADE stuff, you'd have to do that manually as well, setting the 'deleted' column entries on child tables appropriately.

– Joselin Jocklingson
Mar 25 at 13:21













What if you copied stuff to a different table?

– Joselin Jocklingson
Mar 25 at 13:21





What if you copied stuff to a different table?

– Joselin Jocklingson
Mar 25 at 13:21












1 Answer
1






active

oldest

votes


















2














You could make sure that any such action requires confirmation.



You could backup the database on a regular basis or prior to any such actions.



You could introduce logging of such actions that allowed actions to be rolled back to a point in time.



There is no specific best way, as what is most suitable would/could depend upon the complexity/simplicity/efficiency of the App.



In all cases, there would be additional code and complexity.



In short, such considerations should be part of the design.



Additional re comment :-




Could you please show me how I could copy a value from the original
table info the log table with the trigger?




Consider the following :-



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;


This



  1. DROPS all tables triggers (for convenience of rerunning), then

  2. CREATES the logging table logtable as per the main table BUT with two additional columns for the date and time (timestamp) of the deletion and the action taken.

  3. CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3

  4. CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.

  5. INSERTS some rows into the main table.

  6. SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.

  7. DELETES some rows (2nd and 4th according to the ID column).

  8. SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.

  9. ROLLS BACK the deletions made to the main according to the logtable, according to ALL (WHERE clause could use the date for roll back to a time or over a period)

  10. UPDATES the logtable (could be a trigger, but done according to the same criteria as the ROLL BACK in this example) to reflect the roll backs.

  11. SELECTS all rows from the main table and then the logtable (result5 and result6), to show the data after the roll back.

Results :-



1 main



  • after inserting data

enter image description here



2 logtable



  • (empty as no deletetions) (image not necessary)

3 main



  • after deletions (3 or the original 5 rows remain)

enter image description here



4 logtable



  • now has 2 log entries with delete action and deleted data

enter image description here



5 main



  • after rollback (deleted rows insert. NOTE new id's though (could set id as per original))

enter image description here



6 logtable



  • after rollback (log entries now marked as done)

enter image description here



  • Note that the logtable rows being marked as DONE is effectively as per the comment :-


Add another column in the relevant tables to track whether an item is
"deleted". Then, you wouldn't actually delete any records; you'd just
soft delete and undelete by modifying that column's value
appropriately




but potentially allowing the logtable to be UNDELETED.



Android Demo of the above:-



The 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();




An activity - 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();




Result



(equivalent to previous results albeit differnt data)



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: <<<<<






share|improve this answer

























  • What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

    – Joselin Jocklingson
    Mar 25 at 13:25






  • 2





    @JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

    – MikeT
    Mar 25 at 18:42






  • 2





    @JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

    – MikeT
    Mar 26 at 21:02






  • 1





    about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

    – MikeT
    Mar 26 at 21:52






  • 1





    @JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

    – MikeT
    Mar 27 at 8:30











Your Answer






StackExchange.ifUsing("editor", function ()
StackExchange.using("externalEditor", function ()
StackExchange.using("snippets", function ()
StackExchange.snippets.init();
);
);
, "code-snippets");

StackExchange.ready(function()
var channelOptions =
tags: "".split(" "),
id: "1"
;
initTagRenderer("".split(" "), "".split(" "), channelOptions);

StackExchange.using("externalEditor", function()
// Have to fire editor after snippets, if snippets enabled
if (StackExchange.settings.snippets.snippetsEnabled)
StackExchange.using("snippets", function()
createEditor();
);

else
createEditor();

);

function createEditor()
StackExchange.prepareEditor(
heartbeatType: 'answer',
autoActivateHeartbeat: false,
convertImagesToLinks: true,
noModals: true,
showLowRepImageUploadWarning: true,
reputationToPostImages: 10,
bindNavPrevention: true,
postfix: "",
imageUploader:
brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
allowUrls: true
,
onDemand: true,
discardSelector: ".discard-answer"
,immediatelyShowMarkdownHelp:true
);



);













draft saved

draft discarded


















StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55321539%2fundelete-and-sql-based-application%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown

























1 Answer
1






active

oldest

votes








1 Answer
1






active

oldest

votes









active

oldest

votes






active

oldest

votes









2














You could make sure that any such action requires confirmation.



You could backup the database on a regular basis or prior to any such actions.



You could introduce logging of such actions that allowed actions to be rolled back to a point in time.



There is no specific best way, as what is most suitable would/could depend upon the complexity/simplicity/efficiency of the App.



In all cases, there would be additional code and complexity.



In short, such considerations should be part of the design.



Additional re comment :-




Could you please show me how I could copy a value from the original
table info the log table with the trigger?




Consider the following :-



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;


This



  1. DROPS all tables triggers (for convenience of rerunning), then

  2. CREATES the logging table logtable as per the main table BUT with two additional columns for the date and time (timestamp) of the deletion and the action taken.

  3. CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3

  4. CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.

  5. INSERTS some rows into the main table.

  6. SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.

  7. DELETES some rows (2nd and 4th according to the ID column).

  8. SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.

  9. ROLLS BACK the deletions made to the main according to the logtable, according to ALL (WHERE clause could use the date for roll back to a time or over a period)

  10. UPDATES the logtable (could be a trigger, but done according to the same criteria as the ROLL BACK in this example) to reflect the roll backs.

  11. SELECTS all rows from the main table and then the logtable (result5 and result6), to show the data after the roll back.

Results :-



1 main



  • after inserting data

enter image description here



2 logtable



  • (empty as no deletetions) (image not necessary)

3 main



  • after deletions (3 or the original 5 rows remain)

enter image description here



4 logtable



  • now has 2 log entries with delete action and deleted data

enter image description here



5 main



  • after rollback (deleted rows insert. NOTE new id's though (could set id as per original))

enter image description here



6 logtable



  • after rollback (log entries now marked as done)

enter image description here



  • Note that the logtable rows being marked as DONE is effectively as per the comment :-


Add another column in the relevant tables to track whether an item is
"deleted". Then, you wouldn't actually delete any records; you'd just
soft delete and undelete by modifying that column's value
appropriately




but potentially allowing the logtable to be UNDELETED.



Android Demo of the above:-



The 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();




An activity - 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();




Result



(equivalent to previous results albeit differnt data)



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: <<<<<






share|improve this answer

























  • What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

    – Joselin Jocklingson
    Mar 25 at 13:25






  • 2





    @JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

    – MikeT
    Mar 25 at 18:42






  • 2





    @JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

    – MikeT
    Mar 26 at 21:02






  • 1





    about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

    – MikeT
    Mar 26 at 21:52






  • 1





    @JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

    – MikeT
    Mar 27 at 8:30















2














You could make sure that any such action requires confirmation.



You could backup the database on a regular basis or prior to any such actions.



You could introduce logging of such actions that allowed actions to be rolled back to a point in time.



There is no specific best way, as what is most suitable would/could depend upon the complexity/simplicity/efficiency of the App.



In all cases, there would be additional code and complexity.



In short, such considerations should be part of the design.



Additional re comment :-




Could you please show me how I could copy a value from the original
table info the log table with the trigger?




Consider the following :-



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;


This



  1. DROPS all tables triggers (for convenience of rerunning), then

  2. CREATES the logging table logtable as per the main table BUT with two additional columns for the date and time (timestamp) of the deletion and the action taken.

  3. CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3

  4. CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.

  5. INSERTS some rows into the main table.

  6. SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.

  7. DELETES some rows (2nd and 4th according to the ID column).

  8. SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.

  9. ROLLS BACK the deletions made to the main according to the logtable, according to ALL (WHERE clause could use the date for roll back to a time or over a period)

  10. UPDATES the logtable (could be a trigger, but done according to the same criteria as the ROLL BACK in this example) to reflect the roll backs.

  11. SELECTS all rows from the main table and then the logtable (result5 and result6), to show the data after the roll back.

Results :-



1 main



  • after inserting data

enter image description here



2 logtable



  • (empty as no deletetions) (image not necessary)

3 main



  • after deletions (3 or the original 5 rows remain)

enter image description here



4 logtable



  • now has 2 log entries with delete action and deleted data

enter image description here



5 main



  • after rollback (deleted rows insert. NOTE new id's though (could set id as per original))

enter image description here



6 logtable



  • after rollback (log entries now marked as done)

enter image description here



  • Note that the logtable rows being marked as DONE is effectively as per the comment :-


Add another column in the relevant tables to track whether an item is
"deleted". Then, you wouldn't actually delete any records; you'd just
soft delete and undelete by modifying that column's value
appropriately




but potentially allowing the logtable to be UNDELETED.



Android Demo of the above:-



The 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();




An activity - 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();




Result



(equivalent to previous results albeit differnt data)



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: <<<<<






share|improve this answer

























  • What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

    – Joselin Jocklingson
    Mar 25 at 13:25






  • 2





    @JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

    – MikeT
    Mar 25 at 18:42






  • 2





    @JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

    – MikeT
    Mar 26 at 21:02






  • 1





    about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

    – MikeT
    Mar 26 at 21:52






  • 1





    @JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

    – MikeT
    Mar 27 at 8:30













2












2








2







You could make sure that any such action requires confirmation.



You could backup the database on a regular basis or prior to any such actions.



You could introduce logging of such actions that allowed actions to be rolled back to a point in time.



There is no specific best way, as what is most suitable would/could depend upon the complexity/simplicity/efficiency of the App.



In all cases, there would be additional code and complexity.



In short, such considerations should be part of the design.



Additional re comment :-




Could you please show me how I could copy a value from the original
table info the log table with the trigger?




Consider the following :-



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;


This



  1. DROPS all tables triggers (for convenience of rerunning), then

  2. CREATES the logging table logtable as per the main table BUT with two additional columns for the date and time (timestamp) of the deletion and the action taken.

  3. CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3

  4. CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.

  5. INSERTS some rows into the main table.

  6. SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.

  7. DELETES some rows (2nd and 4th according to the ID column).

  8. SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.

  9. ROLLS BACK the deletions made to the main according to the logtable, according to ALL (WHERE clause could use the date for roll back to a time or over a period)

  10. UPDATES the logtable (could be a trigger, but done according to the same criteria as the ROLL BACK in this example) to reflect the roll backs.

  11. SELECTS all rows from the main table and then the logtable (result5 and result6), to show the data after the roll back.

Results :-



1 main



  • after inserting data

enter image description here



2 logtable



  • (empty as no deletetions) (image not necessary)

3 main



  • after deletions (3 or the original 5 rows remain)

enter image description here



4 logtable



  • now has 2 log entries with delete action and deleted data

enter image description here



5 main



  • after rollback (deleted rows insert. NOTE new id's though (could set id as per original))

enter image description here



6 logtable



  • after rollback (log entries now marked as done)

enter image description here



  • Note that the logtable rows being marked as DONE is effectively as per the comment :-


Add another column in the relevant tables to track whether an item is
"deleted". Then, you wouldn't actually delete any records; you'd just
soft delete and undelete by modifying that column's value
appropriately




but potentially allowing the logtable to be UNDELETED.



Android Demo of the above:-



The 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();




An activity - 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();




Result



(equivalent to previous results albeit differnt data)



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: <<<<<






share|improve this answer















You could make sure that any such action requires confirmation.



You could backup the database on a regular basis or prior to any such actions.



You could introduce logging of such actions that allowed actions to be rolled back to a point in time.



There is no specific best way, as what is most suitable would/could depend upon the complexity/simplicity/efficiency of the App.



In all cases, there would be additional code and complexity.



In short, such considerations should be part of the design.



Additional re comment :-




Could you please show me how I could copy a value from the original
table info the log table with the trigger?




Consider the following :-



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;


This



  1. DROPS all tables triggers (for convenience of rerunning), then

  2. CREATES the logging table logtable as per the main table BUT with two additional columns for the date and time (timestamp) of the deletion and the action taken.

  3. CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3

  4. CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.

  5. INSERTS some rows into the main table.

  6. SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.

  7. DELETES some rows (2nd and 4th according to the ID column).

  8. SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.

  9. ROLLS BACK the deletions made to the main according to the logtable, according to ALL (WHERE clause could use the date for roll back to a time or over a period)

  10. UPDATES the logtable (could be a trigger, but done according to the same criteria as the ROLL BACK in this example) to reflect the roll backs.

  11. SELECTS all rows from the main table and then the logtable (result5 and result6), to show the data after the roll back.

Results :-



1 main



  • after inserting data

enter image description here



2 logtable



  • (empty as no deletetions) (image not necessary)

3 main



  • after deletions (3 or the original 5 rows remain)

enter image description here



4 logtable



  • now has 2 log entries with delete action and deleted data

enter image description here



5 main



  • after rollback (deleted rows insert. NOTE new id's though (could set id as per original))

enter image description here



6 logtable



  • after rollback (log entries now marked as done)

enter image description here



  • Note that the logtable rows being marked as DONE is effectively as per the comment :-


Add another column in the relevant tables to track whether an item is
"deleted". Then, you wouldn't actually delete any records; you'd just
soft delete and undelete by modifying that column's value
appropriately




but potentially allowing the logtable to be UNDELETED.



Android Demo of the above:-



The 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();




An activity - 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();




Result



(equivalent to previous results albeit differnt data)



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: <<<<<







share|improve this answer














share|improve this answer



share|improve this answer








edited Mar 27 at 1:54

























answered Mar 24 at 10:14









MikeTMikeT

20.2k112844




20.2k112844












  • What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

    – Joselin Jocklingson
    Mar 25 at 13:25






  • 2





    @JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

    – MikeT
    Mar 25 at 18:42






  • 2





    @JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

    – MikeT
    Mar 26 at 21:02






  • 1





    about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

    – MikeT
    Mar 26 at 21:52






  • 1





    @JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

    – MikeT
    Mar 27 at 8:30

















  • What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

    – Joselin Jocklingson
    Mar 25 at 13:25






  • 2





    @JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

    – MikeT
    Mar 25 at 18:42






  • 2





    @JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

    – MikeT
    Mar 26 at 21:02






  • 1





    about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

    – MikeT
    Mar 26 at 21:52






  • 1





    @JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

    – MikeT
    Mar 27 at 8:30
















What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

– Joselin Jocklingson
Mar 25 at 13:25





What is the best way to introduce the logging and rollbacks you mention. Can a rollback leave independent subsequent operations unaffected, so that if I delete A, then B, then C, I can undelete B leaving A and C deleted? Not sure if you're talking about transactions in SQL.

– Joselin Jocklingson
Mar 25 at 13:25




2




2





@JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

– MikeT
Mar 25 at 18:42





@JoselinJocklingson not transactions, but a log table, (perhaps with rows inserted by a trigger or triggers). Something like CREATE TRIGGER log_delete AFTER DELETE ON mymaintable BEGIN INSERT INTO mylogtable (logaction) VALUES ('dlt'); END. Where mylogtable has columns logaction and another for the timestamp which uses DEFAULT CURRENT_TIMESTAMP in it's definition.

– MikeT
Mar 25 at 18:42




2




2





@JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

– MikeT
Mar 26 at 21:02





@JoselinJocklingson anything you want, it's just an indicator of the action. Of course if it's a delete then you'd also want to store what has been deleted so that it can be restored. The code was just to show how you create a simple trigger that is executed after a row is deleted.

– MikeT
Mar 26 at 21:02




1




1





about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

– MikeT
Mar 26 at 21:52





about to go out but if I remember I'll perhaps provide a very basic example. However, all you do is refer to the column (value) as old.column so, something like BEGIN INSERT INTO mylogtable (logaction,value_to_save) VALUES('dlt',old.the_column); END See CREATE TRIGGER for more.

– MikeT
Mar 26 at 21:52




1




1





@JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

– MikeT
Mar 27 at 8:30





@JoselinJocklingson see the section in the above link Cautions On The Use Of BEFORE triggers. I'd appreciate you tick the answer as I believe that you have gained a quite a lot from the answer.

– MikeT
Mar 27 at 8:30



















draft saved

draft discarded
















































Thanks for contributing an answer to Stack Overflow!


  • Please be sure to answer the question. Provide details and share your research!

But avoid


  • Asking for help, clarification, or responding to other answers.

  • Making statements based on opinion; back them up with references or personal experience.

To learn more, see our tips on writing great answers.




draft saved


draft discarded














StackExchange.ready(
function ()
StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f55321539%2fundelete-and-sql-based-application%23new-answer', 'question_page');

);

Post as a guest















Required, but never shown





















































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown

































Required, but never shown














Required, but never shown












Required, but never shown







Required, but never shown







Popular posts from this blog

Kamusi Yaliyomo Aina za kamusi | Muundo wa kamusi | Faida za kamusi | Dhima ya picha katika kamusi | Marejeo | Tazama pia | Viungo vya nje | UrambazajiKuhusu kamusiGo-SwahiliWiki-KamusiKamusi ya Kiswahili na Kiingerezakuihariri na kuongeza habari

Swift 4 - func physicsWorld not invoked on collision? The Next CEO of Stack OverflowHow to call Objective-C code from Swift#ifdef replacement in the Swift language@selector() in Swift?#pragma mark in Swift?Swift for loop: for index, element in array?dispatch_after - GCD in Swift?Swift Beta performance: sorting arraysSplit a String into an array in Swift?The use of Swift 3 @objc inference in Swift 4 mode is deprecated?How to optimize UITableViewCell, because my UITableView lags

Access current req object everywhere in Node.js ExpressWhy are global variables considered bad practice? (node.js)Using req & res across functionsHow do I get the path to the current script with Node.js?What is Node.js' Connect, Express and “middleware”?Node.js w/ express error handling in callbackHow to access the GET parameters after “?” in Express?Modify Node.js req object parametersAccess “app” variable inside of ExpressJS/ConnectJS middleware?Node.js Express app - request objectAngular Http Module considered middleware?Session variables in ExpressJSAdd properties to the req object in expressjs with Typescript