I have a very simple link table set up, I need to delete rows from all 3 tables
Table 1 - Assignment {assignment_id}
LinkTable - AssignmentTasks {assignment_id, task_id}
Table 2 - Tasks {task_id}
I can delete from Assignment and AssignmentTasks easily as I have the Id but I don't have the list of Tasks related to this assignment.
I've built a cursor which returns all the task_ids related to an assignment, but I can't remove them whilst the records in the link table refer to them. (I don't think I can as the foreign key constraint should stop me deleting rows referenced elsewhere)
Do I need to store a list of task_ids, delete the assignment_tasks records, delete the assignment record then iterate through the stored list of task_ids and delete each task ? or is there a better way of doing this ?
Or you can turn foreign key constraint checking off temporarily:
pragma foreign_keys = off;
But that shouldn't be necessary.
The other issue here is that this is currently written as a many-to-many relationship. This would imply that multiple assignments could refer to a one task. It probably wouldn't be OK to delete the task just because one of the assignments referencing it was deleted. Instead, you would need to check that the task is no longer referenced before deleting it.
Alternatively, if you really meant for each task to belong to only one assignment, you could set your schema up like this:
CREATE TABLE Assignment (assignment_id int PRIMARY KEY);
CREATE TABLE Task (
task_id int PRIMARY KEY,
assignment_id int FOREIGN KEY REFERENCES Assignment ON DELETE CASCADE
);
The ON DELETE CASCADE bit causes the Task entry to be deleted in the event that the assignment it refers to is deleted. This only works if foreign key constraints are enabled, of course. If the assignment is being deleted by a trigger or due to some other cascade, you may need to enable recursive triggers as well with pragma recursive_triggers = on.
Another possibility (if you want to retain the original schema) is to make the foreign key references in the AssignmentTask table do the cascading delete. That way those rows are deleted automatically as you delete the Tasks. Then you can delete the Assignment once all of those are taken care of.
Why not query the task_id based upon the assignment_id that you intent to delete.
You can leave the deletion of the related data to the database but that depends if you have defined cascade action onDelete when you created the relationships.
If you intend to use cascade onDelete as far as I know you need to enable that on sqlite. See this post for how to. Foreign key constraints in Android using SQLite? on Delete cascade
You can use a raw query. e.g.
delete from tasks where task_id in (select task_id from assignments where assignment_id=your_assignment_id_here)
I would also suggest using transaction for this for two reason a) so you can rollback in case the either of your queries fail. b) sqlite in general works better with transactions speedwise if you have multiple queries. So place all of your delete queires for a given action in a transaction.
Link below is for rawQuery:
http://developer.android.com/reference/android/database/sqlite/SQLiteDatabase.html#rawQuery(java.lang.String, java.lang.String[])
Related
I have a simple database schema containing 5 tables and a few foreign key relationships. Now I want to update the schema without losing the relationships.
SQLite does not support the operation I want to perform. I want to mark a column AUTOINCREMENT. So I searched and found that I need to recreate the tables.
In all the relationships, foreign key is the auto-generated row ID. This will change when I re-insert the data (right?). And the best case scenario would be having mismatched relationships. The worst case (I think) would be having foreign keys that correspond to nothing. How do I avoid this?
One way that I can think of doing this is through dumping all data into a special model designed for this very exercise. This special model will encapsulate all the relationships. Then I can start creating new tables in order of ascending dependence, from least to most. Table that has no foreign keys (but other tables use its ID) goes in first. And on inserting rows, I update the model instance.
Is there a better way of doing this? Thank you for reading.
Values in an autoincrementing column can be set to an explicit value:
INSERT INTO MyTable(MyAutoincID, Name) VALUES(42, 'hello')
Just copy the ID together with the other column values.
Furthermore, as long as you do not enable foreign key checking with PRAGMA foreign_keys or setForeignKeyConstraintsEnabled(), your DB is allowed to have inconsistent data temporarily.
I've got two SQLite databases, each with a table that I need to keep synchronized by merging rows that have the same key. The tables are laid out like this:
CREATE TABLE titles ( name TEXT PRIMARY KEY,
chapter TEXT ,
page INTEGER DEFAULT 1 ,
updated INTEGER DEFAULT 0 );
I want to be able to run the same commands on each of the two tables, with the result that for pairs of rows with the same name, whichever row has the greater value in updated will overwrite the other row completely, and rows which do not have a match are copied across, so both tables are identical when finished.
This is for an Android app, so I could feasibly do the comparisons in Java, but I'd prefer an SQLite solution if possible. I'm not very experienced with SQL, so the more explanation you can give, the more it'll help.
EDIT
To clarify: I need something I can execute at an arbitrary time, to be invoked by other code. One of the two databases is not always present, and may not be completely intact when operations on the other occur, so I don't think a trigger will work.
Assuming that you have attached the other database to your main database:
ATTACH '/some/where/.../the/other/db-file' AS other;
you can first delete all records that are to be overwritten because their updated field is smaller than the corresponding updated field in the other table:
DELETE FROM main.titles
WHERE updated < (SELECT updated
FROM other.titles
WHERE other.titles.name = main.titles.name);
and then copy all newer and missing records:
INSERT INTO main.titles
SELECT * FROM other.titles
WHERE name NOT IN (SELECT name
FROM main.titles);
To update in the other direction, exchange the main/other database names.
For this, you can use a trigger.
i.e.
CREATE TRIGGER sync_trigger
AFTER INSERT OR UPDATE OF updated ON titles
REFERENCING NEW AS n
FOR EACH ROW
DECLARE updated_match;
DECLARE prime_name;
DECLARE max_updated;
BEGIN
SET prime_name = n.name;
ATTACH database2name AS db2;
SELECT updated
INTO updated_match
FROM db2.titles t
WHERE t.name=prime_name)
IF updated_match is not null THEN
IF n.updated > updated_match THEN
SET max_updated=n.updated;
ELSE
SET max_updated=updated_match;
END IF;
UPDATE titles
SET updated=max_updated
WHERE name=prime_name;
UPDATE db2.titles
SET updated=max_updated
WHERE name=prime_name;
END IF;
END sync_trigger;
The syntax may be a little off. I don't use triggers all that often and this is a fairly complex one, but it should give you an idea of where to start at least. You will need to assign this to one database, exchanging "database2name" for the other database's name and then assign it again to the other database, swapping the "database2name" out for the other database.
Hope this helps.
In Android SQLite I had one table MyTable. By mistake I dropped it after upgrade the DB.
How can I ROLL BACK that dropped table if it's possible.
Any good answer will be accepted.
Thanks.
Dropping tables is not a recoverable action, unless performed as part of a transaction that is rolled back (which appears to be not the scenario for your particular case).
From the SQLite documentation:
The DROP TABLE statement removes a table added with the CREATE TABLE statement. The name specified is the table name.
The dropped table is completely removed from the database schema and the disk file. The table can not be recovered. All indices and triggers associated with the table are also deleted.
That's not quite the complete picture, as the behaviour under rolled-back transaction can be seen with (tested on https://sqliteonline.com/):
drop table if exists paxtable;
create table paxtable (paxcolumn integer);
insert into paxtable values (42);
begin transaction;
drop table paxtable;
rollback;
select paxcolumn from paxtable;
That shows that the table still exists after the rollback. If you commit rather than roll back (or if you remove the transactional control altogether), the table has died, expired, gone to meet its maker, shuffled off this mortal coil, <insert your favourite metaphor here>.
So, since you didn't do it as part of a rolled-back transaction (as evidenced by the fact the table has actually gone), you'll need to re-create it from scratch (or from backups if possible).
CLARIFICATION:
Although you can commit or rollback DML statements like "insert" or "delete" (provided you do it within a transaction), in general you cannot rollback a DDL statement like "alter table" or "drop table".
This is true for most databases under most circumstances: Oracle, MSSQL, mySQL, etc.
There is an exception for sqlite: if you drop table in a transaction, then a rollback will restore that table.
Otherwise (per the sqlite manual):
http://sqlite.org/lang_droptable.html
The DROP TABLE statement removes a table added with the CREATE TABLE
statement. The name specified is the table name. The dropped table is
completely removed from the database schema and the disk file. The
table can not be recovered. All indices and triggers associated with
the table are also deleted.
PS:
This link discusses "DDL", "DML" and related acronyms, if you're interested:
http://www.orafaq.com/faq/what_are_the_difference_between_ddl_dml_and_dcl_commands
I think there's two different interpretations of this question and I want to make sure both get answered and demonstrated conclusively since this is still the top search result for sqlite drop table rollback (and the links to the SQLite documentation seems misleading as well).
To the first question, you can rollback a drop table DDL action that occurs within a transaction scope, i.e.,
// with connection created
var transaction = connection.BeginTransaction();
try {
// create a table
// drop a different table
transaction.Commit(); // go ahead and commit if everything is successfully
}
catch
{
transaction.Rollback(); // rollback in case of error
}
And to confirm this is the same behavior in a language-agnostic fashion, here's the same behavior using the SQ Lite command-line shell:
sqlite3 demo
// outside transaction scope
create table tbl1( col varchar(10));
// list the current tables
.tables
// start a transaction that wraps both your DDL commands
begin transaction;
enter code here
create table tbl2 (col varchar(10));
drop table tbl1;
rollback;
.tables
The expectation is that the final list tables command, should still return tbl1 since both the create table and drop table commands were both rolled back. Another way to say this is that SQLite is not bound by the same DML/DDL distinction for what operations can be rolled back that are present in Oracle.
For the second interpretation of the question, i.e., can I recover a table dropped outside of a transaction scope (which would also entail the "Oh S#%T" experience you may have had as a developer as well as disaster recovery), the references to the SQ Lite documentation are appropriate:
The dropped table is completely removed from the database schema and the disk file. The table can not be recovered. All indices and triggers associated with the table are also deleted.
I am having a table with 'int' column. During software upgrade, I want to change it to 'long' data type. It seems SQLite does not provide an option (in alter statement) to change/modify column properties. Since I want to do an upgrade, the table might contain data which the user should not lose. So please suggest me a good method to change the column data type property without data loss.
One way suggested in the search links is to create a temporary table, copy the records from the existing table, delete the existing table, and rename the temporary table. I doubt that is efficient.
Your help appreciated!
Regards
Vivek Ragunathan
I used the follow statements to change the type of the column.
CREATE TABLE IF NOT EXISTS **TEMP_TABLE** (id integer primary key autoincrement, **col2change integer not null**, ...)
INSERT INTO TEMP_TABLE SELECT * FROM EXISTING_TABLE
DROP TABLE EXISTING_TABLE
ALTER TABLE TEMP_TABLE RENAME TO EXISTING_TABLE
I changed the int type column in the existing table to integer type. For a few hundred rows, it was reasonably fast.
SQLite3 columns do not have data types, only affinities -- there is no benefit in changing the column type from int to long.
If a wrapper program is truncating values before giving them to SQLite3, there is a way to fix this by editing the schema. It is a dangerous operation, so do it only after backing up your database. The schema is stored in the table sqlite_master; normally it is read-only, but you can modify it if you enable it with the writable_schema pragma. Be careful to only make changes that do not invalidate your data; you may change int to long int since they both have INTEGER affinity.
From SQLite documentation
It is not possible to rename a column, remove a column, or add or
remove constraints from a table.
Check this link
Please do remember that column data types are not rigid in SQLite. Check this link
Edit:
Following your comments on another answer, I guess the option you mentioned - working through the temp table - is the only one, which is not efficient off course.
you could add a new colum, copy the values form the old to the new column, delete the old column and then rename the new column to the old name
AFAIK there is no way in Android to change column data types once a table is created and used. The practiced way is to make a new table and copy the data which you read about
friends,
I am doing an Android project in my company, still some small work is remaining, I need your help to complete the task.
The problem is...
I have created two tables in which, table1 has an empty column, for purpose for saving name...
The table2 has a list of names, the objective is only the names from this list should be should be saved in the table1's empty column other than that it shouldn't accept any of the name typed manually.
You appear to want to make the list of names a validation: if the user wishes to save a name to table1, the name must already exist in table2.
Typically this would be done as in the following example, in which only the products listed in PRIZEPRODUCTS can be entered into PRIZEWINNERS table: someone could not win a Boat, for example, given the data below:
PRIZEPRODUCTS
id
productname
1|TV
2|iPad
3|backpack
PRIZEWINNERS
id
productid
winner
ALTER TABLE PRIZEWINNERS
ADD CONSTRAINT PRIZEWINNERS_PRIZEPRODUCTS_FK
FOREIGN KEY(productid) REFERENCES PRIZEPRODUCTS(id)
SQLite doesn't create the foreign key using ALTER TABLE but as part of the create-table statement. See here for the syntax. For enabling foreign key support in Android (2.2), see here.
Now, you can establish the foreign key on the [productname] column if [productname] were the key of PRIZEPRODUCTS. In other words, you could make person-name the key of the table rather than having a PersonID. But if that name is changed in the validation table, it can break the foreign key relationship, unless ON UPDATE CASCADE is enabled, but I am not sure if this is supported in Android.
I hope below query will work for you.
insert into table1(name) values (select name from table2 where id=?).
Thanks.