3

My primary goal with this is to make implementing revision histories and journaling easier.

I found myself wondering if it's possible, using Flask-SQLAlchemy (or just straight up SQL), to get an auto incremented non-unique integer for mysql. I found this stack overflow post which is close to what I want to do but the question is focused on a primary key. For example, if my table had these columns,

revision_id = db.Column(db.Integer, nullable=False)
post_id = db.Column(db.Integer, nullable=False)

__table_args__ = (
    PrimaryKeyConstraint('post_id', 'revision_id'),
)

Would it be possible to create a new post that has a revision_id of 1 and a post_id of max(post_id) + 1 without the problem of two users attempting to create a post at the same time and creating the same post_id?

The advantage of this system is that it makes post history (and differencing) very simple. Whenever someone wants to modify a post I'd use the same post_id as the original and increment the revision_id (which, now that I'm typing this out, has the same problem).

Update:

Sylvain Leroux put me on the right track for how to solve my problem. I needed to set both tables as a primary key in sqlalchemy. If there's more then one primary key in sqlalchemy it doesn't assume that they're unique. Here's my current definition,

revision_id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=False, default=1)
post_id = db.Column(db.Integer, primary_key=True, nullable=False, autoincrement=True)
__table_args__ = (
    PrimaryKeyConstraint('post_id', 'revision_id'),
)

Which produces this SQL

CREATE TABLE `post` (
    revision_id INTEGER NOT NULL, 
    post_id INTEGER NOT NULL AUTO_INCREMENT, 
    PRIMARY KEY (post_id, revision_id), 
)

which allowed me to insert with and without a post_id.

All credit to Sylvain Leroux since I merely translated his answer to SQLAlchemy.

Community
  • 1
  • 1
AlexLordThorsen
  • 8,057
  • 5
  • 48
  • 103
  • sure, you can lock the table while you're doing your +1 business. but performance will be attrocious if you end up with lots of concurrent users. – Marc B Aug 12 '13 at 21:53
  • Alright, what would be a better way to implement revision history on a post then? I was thinking of the use case where it would be easy to see what the history between each edit was (being able to select two revision id's and get a difference between them)? – AlexLordThorsen Aug 12 '13 at 22:00
  • you don't need that "PrimaryKeyConstraint" at all, a table can only have one primary key constraint, so the set of all columns marked "primary_key=True" will compose a single PK constraint. – zzzeek Aug 13 '13 at 14:06
  • put the post metadata in the main post table, and then revision data in a child table. you don't need sequential ids, just "always larger" ids for the revisions. that means a standard auto_increment int int that revisions table takes care of the problem automatically. you only need sequential numbers at display time, so users see `1,2,3,...` instead of `42,89,203,...`. – Marc B Aug 13 '13 at 15:19
  • I cannot reproduce this: `'Incorrect table definition; there can be only one auto column and it must be defined as a key'` – Tjorriemorrie Jan 07 '16 at 19:02
  • Potentially fixed in your version. I should have included the SA version in the question. – AlexLordThorsen Jan 09 '16 at 00:12

1 Answers1

5

To quote a classic MySQL error message:

there can be only one auto column and it must be defined as a key:

auto_increment column must be either (in) the primary key or (in) a key (also known as index in MySQL, which may or may be unique).


As for:

SELECT MAX(id) FROM tbl INTO @new_id;
INSERT INTO tbl (id, ...) VALUES (@new_id, ....);

You clearly understand that if two concurrent requests do the same thing, you will end up having two new rows, totally unrelated, with the same ID. An you probably don't want to use table locks to avoid that pitfall.

As of myself, I would say "don't do that". Maybe that would make some things more easy in your application, but I bet that would made tons of other things far more complicated or much less robust.


But ... if the real problem if to keep the "post id" constant, remember that auto_increment could be part of a key (http://sqlfiddle.com/#!2/9b4b45/1):

create table tbl(id int auto_increment not null, 
                 rev int not null default 1,
                 content TEXT,
                 primary key(id,rev));

-- insert 3 new contents with "auto id" and default revision 1
insert into tbl (content) values ("alpha"), ("bta"), ("gamma");

Say there is an error in the item id2,rev1. You could insert a new revision:

insert into tbl (id, rev, content) values (2,2,"beta");

If you want to see the various revision of item id2:

select * from tbl where id=2 order by rev;

You will have to find how to do that with SQLAlchemy (:D), but this is definitively possible with MySQL.

prusswan
  • 6,853
  • 4
  • 40
  • 61
Sylvain Leroux
  • 50,096
  • 7
  • 103
  • 125
  • Alright, given that this isn't a very good solution what would be a good solution to implement revision histories? – AlexLordThorsen Aug 12 '13 at 22:14
  • @Rawrgulmuffins I was editing my answer while you post your comment. As you will see, you are able to have multiple rows with the same auto_increment value [*iff*](http://en.wikipedia.org/wiki/Iff) the column is *part* of a key. In your case, as you suggested, that key would be `(id,rev)`. For the SQLAlchemy part, I'm sorry, I don't have enough experience in that field to help you. – Sylvain Leroux Aug 12 '13 at 22:24
  • 1
    I updated my question with the SQLAlchemy translation of your answer. This was exactly what I was looking for. – AlexLordThorsen Aug 12 '13 at 22:57
  • `id` _must_ be the _first_ column in _some_ key. – Rick James Jun 27 '21 at 19:09
  • Do not use `max(id)`, let `AUTO_INCREMENT` do the work for you. – Rick James Jun 27 '21 at 19:09