我在Android上开发并维护Google Tasks应用。目前,我有一个包含列表和任务的类,按顺序包含在ArrayList
中。我正在考虑切换到SQLite以更好地构建应用程序。我不确定在数据库中存储任务顺序的最佳和最简单的方法是什么。
现在,我可以简单地在不同索引处的remove
和add
项,显然其他列行索引的顺序正确。使用SQLite数据库,我可以在每行中存储位置编号,每次移动任务时,都会相应地更新以下行。
该系统的问题是并发性和可维护性:如果一个行的位置被更新(并且下面的行的位置递增/递减)同时发生任务同步,它可能会变得混乱,因为同步正在改变排名也是。
我可以添加Mutex锁,但我不想这样做,我希望用户能够在同步时同时更新数据,如果发生冲突,将丢弃其中一个新位置,没有乱七八糟的行。
我的问题:在SQLite数据库中存储和更新订单的最佳方法是什么?
答案 0 :(得分:0)
“最好的方式”我不知道。
但是,与另一个RDBMS一样,为此添加列是一个很好的解决方案。
处理该列将意味着某些代码,但在SQLite中,您可以使用触发器在数据库中实现此逻辑。
下面是一个示例,使用后台和执行魔术的视图:
-- Main table, where data belongs
CREATE TABLE _t(n, o);
-- Table view, which will handle all inserts, updates and deletes
CREATE VIEW t AS SELECT * FROM _t;
-- Triggers:
-- Raise error when inserting invalid index (out of bounds or non integer)
CREATE TRIGGER t_ins_err INSTEAD OF INSERT ON t
WHEN NEW.o<1 OR NEW.o>(SELECT COUNT()+1 FROM _t) OR CAST(NEW.o AS INT) <> NEW.o
BEGIN SELECT RAISE(ABORT, 'Invalid index!'); END;
-- Increments all indexes when new row inserted in middle of table
CREATE TRIGGER t_ins INSTEAD OF INSERT ON t
WHEN NEW.o BETWEEN 1 AND (SELECT COUNT() FROM _t)+1
BEGIN
UPDATE _t SET o=o+1 WHERE o>=NEW.o;
INSERT INTO _t VALUES(NEW.n, NEW.o);
END;
-- Insert row in last when supplied index is NULL
CREATE TRIGGER t_ins_last INSTEAD OF INSERT ON t
WHEN NEW.o IS NULL
BEGIN
INSERT INTO _t VALUES(NEW.n, (SELECT COUNT()+1 FROM _t));
END;
-- Decrements indexes when item is removed
CREATE TRIGGER t_del INSTEAD OF DELETE ON t
BEGIN
DELETE FROM _t WHERE o=OLD.o;
UPDATE _t SET o=o-1 WHERE o>OLD.o;
END;
-- Raise error when updating to invalid index
CREATE TRIGGER t_upd_err INSTEAD OF UPDATE OF o ON t
WHEN NEW.o NOT BETWEEN 1 AND (SELECT COUNT() FROM _t) OR CAST(NEW.o AS INT)<>NEW.o OR NEW.o IS NULL;
BEGIN SELECT RAISE(ABORT, 'Invalid index!'); END;
-- Decrements indexes when item is moved up
CREATE TRIGGER t_upd_up INSTEAD OF UPDATE OF o ON t
WHEN NEW.o BETWEEN OLD.o+1 AND (SELECT COUNT() FROM t)
BEGIN
UPDATE _t SET o=NULL WHERE o=OLD.o;
UPDATE _t SET o=o-1 WHERE o BETWEEN OLD.o AND NEW.o;
UPDATE _t SET o=NEW.o WHERE o IS NULL;
END;
-- Increments indexes when item is moved down
CREATE TRIGGER t_upd_down INSTEAD OF UPDATE OF o ON t
WHEN NEW.o BETWEEN 1 AND OLD.o-1
BEGIN
UPDATE _t SET o=NULL WHERE o=OLD.o;
UPDATE _t SET o=o+1 WHERE o BETWEEN NEW.o AND OLD.o;
UPDATE _t SET o=NEW.o WHERE o IS NULL;
END;
-- Tests:
INSERT INTO t(n) VALUES('a1');
INSERT INTO t(n) VALUES('b1');
INSERT INTO t(n) VALUES('c1');
INSERT INTO t(n) VALUES('d1');
INSERT INTO t VALUES('e1', 5);
INSERT INTO t VALUES('z1', 20);
SELECT * FROM t ORDER BY o;
INSERT INTO t VALUES('b2', 2);
SELECT * FROM t ORDER BY o;
DELETE FROM t WHERE n='b1';
SELECT * FROM t ORDER BY o;
UPDATE t SET o=4 WHERE o=2;
SELECT * FROM t ORDER BY o;
UPDATE t SET o=3 WHERE o=5;
SELECT * FROM t ORDER BY o;
答案 1 :(得分:0)
向每行添加sort_position
值的问题在于它无法以原子方式更新,因为要在列表中交换两个项目,您需要更改它们的两个位置。当更改属于同一行时,数据库在保证原子性方面要好得多。
更好的方法是将排序列视为优先级 - 值可以是稀疏的,因此可以在两个相邻的列之间放置行,并且两个项可以具有相同的值,而不是一致性问题。如果值很稀疏,您只需更改一个值就可以交换两个项目,因此可以更好地处理并发更新(尽管如果两个代理尝试重新排列列表的相同部分,您仍可能会遇到意外情况。)
处理它的另一种方法可能是将排序存储为不同表中的一个值 - 例如作为项ID列表。这具有允许更精确地控制排序而不会从交错更新中获得乱码状态的优点,但是更复杂(处理丢失或未知的项目,如何同时处理两者更新)。除非你真的需要,我不会尝试这个。