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;
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
add a comment |
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
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
add a comment |
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
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
android sql sqlite user-experience mobile-application
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
add a comment |
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
add a comment |
1 Answer
1
active
oldest
votes
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
- DROPS all tables triggers (for convenience of rerunning), then
- 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.
- CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3
- CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.
- INSERTS some rows into the main table.
- SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.
- DELETES some rows (2nd and 4th according to the ID column).
- SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.
- 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)
- 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.
- 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
2 logtable
- (empty as no deletetions) (image not necessary)
3 main
- after deletions (3 or the original 5 rows remain)
4 logtable
- now has 2 log entries with delete action and deleted data
5 main
- after rollback (deleted rows insert. NOTE new id's though (could set id as per original))
6 logtable
- after rollback (log entries now marked as done)
- 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: <<<<<
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 likeCREATE 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 usesDEFAULT 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 likeBEGIN 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
|
show 4 more comments
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
);
);
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
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
- DROPS all tables triggers (for convenience of rerunning), then
- 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.
- CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3
- CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.
- INSERTS some rows into the main table.
- SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.
- DELETES some rows (2nd and 4th according to the ID column).
- SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.
- 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)
- 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.
- 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
2 logtable
- (empty as no deletetions) (image not necessary)
3 main
- after deletions (3 or the original 5 rows remain)
4 logtable
- now has 2 log entries with delete action and deleted data
5 main
- after rollback (deleted rows insert. NOTE new id's though (could set id as per original))
6 logtable
- after rollback (log entries now marked as done)
- 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: <<<<<
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 likeCREATE 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 usesDEFAULT 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 likeBEGIN 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
|
show 4 more comments
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
- DROPS all tables triggers (for convenience of rerunning), then
- 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.
- CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3
- CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.
- INSERTS some rows into the main table.
- SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.
- DELETES some rows (2nd and 4th according to the ID column).
- SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.
- 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)
- 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.
- 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
2 logtable
- (empty as no deletetions) (image not necessary)
3 main
- after deletions (3 or the original 5 rows remain)
4 logtable
- now has 2 log entries with delete action and deleted data
5 main
- after rollback (deleted rows insert. NOTE new id's though (could set id as per original))
6 logtable
- after rollback (log entries now marked as done)
- 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: <<<<<
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 likeCREATE 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 usesDEFAULT 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 likeBEGIN 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
|
show 4 more comments
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
- DROPS all tables triggers (for convenience of rerunning), then
- 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.
- CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3
- CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.
- INSERTS some rows into the main table.
- SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.
- DELETES some rows (2nd and 4th according to the ID column).
- SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.
- 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)
- 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.
- 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
2 logtable
- (empty as no deletetions) (image not necessary)
3 main
- after deletions (3 or the original 5 rows remain)
4 logtable
- now has 2 log entries with delete action and deleted data
5 main
- after rollback (deleted rows insert. NOTE new id's though (could set id as per original))
6 logtable
- after rollback (log entries now marked as done)
- 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: <<<<<
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
- DROPS all tables triggers (for convenience of rerunning), then
- 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.
- CREATES the main table main, with an ID column (alias of the rowid column) and 3 columns for values namely val1, val2 and val3
- CREATES the TRIGGER for when a row is deleted from the main table that inserts a logtable entry.
- INSERTS some rows into the main table.
- SELECTS all rows from the main table and then the logtable (result1 and result2), to show the data prior to any deletions.
- DELETES some rows (2nd and 4th according to the ID column).
- SELECTS all rows from the main table and then the logtable (result3 and result4), to show the data after the deletions.
- 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)
- 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.
- 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
2 logtable
- (empty as no deletetions) (image not necessary)
3 main
- after deletions (3 or the original 5 rows remain)
4 logtable
- now has 2 log entries with delete action and deleted data
5 main
- after rollback (deleted rows insert. NOTE new id's though (could set id as per original))
6 logtable
- after rollback (log entries now marked as done)
- 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: <<<<<
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 likeCREATE 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 usesDEFAULT 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 likeBEGIN 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
|
show 4 more comments
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 likeCREATE 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 usesDEFAULT 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 likeBEGIN 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
|
show 4 more comments
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.
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
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
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Post as a guest
Required, but never shown
Sign up or log in
StackExchange.ready(function ()
StackExchange.helpers.onClickDraftSave('#login-link');
);
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
Sign up using Google
Sign up using Facebook
Sign up using Email and Password
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
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