I don't have a solution to what you describe, but here are some thoughts. If you make your *rank* the `id PRIMARY KEY` column, as an [alias to the ROWID][ROWID], then updating the ranks means physically moving the rows around, rebalancing the BTREE, which means a lot of IO. Very expensive. While if the *rank* is a separate and explicit column of its own, then updating it means updating the pages with actually modified rows only. If you make queries by that `rank` column, then you need an index on it though. So the index will need updating too, but then you don't need to *move* the `item` columns, as when `id` and `rank` are one-and-the-same and the PK. If you ranks change often, that's a problem indeed. You could switch to a floating point rank, to insert *in-the-middle*, w/o disturbing surrounding rows, but that only works so many times, you eventually run out of precision. But if you can detect [those conflicts when they arise][UPSERT], you've at least reduced the frequency of re-ranking the *tail/rest* of a rank-update. A similar strategy is to split your rank in two, the primary and secondary. The latter starts out at zero, and when re-ranking, you just update that one instead of the primary, again limiting the number of rows that need updating on re-ranks. Now some rows share a primary rank (after 1 or more reranking), but not a secondary one, and you need to index/order-by both ranks of course, but again, it's a strategy to limit to number of rows affected by a rerank. In both cases (REAL ranks or SPLIT ranks), once in a while you probably want to pay for a full rerank to go back to *INT-reals* or *ALL-0-sub-ranks*. I'm just thinking aloud here, I haven't tried any of the above. --DD [ROWID]: https://www.sqlite.org/lang_createtable.html#rowid [UPSERT]: https://www.sqlite.org/lang_UPSERT.html