As far as I can tell, there is a plethora of possibilities, all of which are stupid in one way or another. (I assume PDO, because mysqli is more inconvenient). However, I assume Giacomos solution is among the best solutions, since it's correct, intelligent ... and boring (no offense!). So I provide some other solutions, which are most likely less performant ...
querying and updating like a mad man.
$increments = 10; // or whatever
$stmt = $db->prepare('UPDATE table SET action = action+1 WHERE id=:id');
while($increments-- > 0) {
// select the first row with the lowest action value
// now, this query - apart from the limit - is helpful for all approaches
$row = $db->query('SELECT id FROM table ORDER BY action ASC, id ASC LIMIT 1')->fetch();
$stmt->bindValue(':id', $row['id'], PDO::PARAM_INT);
$stmt->execute();
}
this solution is particularly stupid, because it needs sooo many queries. but it's probably correct (minus typos). also, execution time could be increased by not using the same prepared statement over and over again.
stored procedures
there's probably a solution with stored procedures or something. tbh i never warmed up to stored procedures, so this is essentially a placeholder.
less queries, but still stupid
as long as there are more rows than increments, just increment all rows by 1, then increment the rest.
$increments = 10;
$rowCount = $db->query('SELECT count(*) FROM table')->fetch()[0];
while($increments >= $rowCount) {
$db->query('UPDATE table SET action=action+1');
$increments -= $rowCount;
}
$stmt = $db->prepare('SELECT id FROM table ORDER BY action ASC, id ASC LIMIT :limit');
$stmt->bindValue(':limit', $increments, PDO::PARAM_INT);
$stmt->execute();
// now increase every row's action, from the result set
// yeah, I'm lazy.
even less queries, but still stupid
do the same as before, but add more than just +1, but instead ... +floor(increments/rowcount). this is essentially, what the next solution does, but it does so more efficiently ...
pre-compute target values
see Giacomos solution above ... or below, wherever it will be.
be slightly smarter
with the query I provided before (ordering by action, then by id) you could probably write foreach($rows as $currentRecord => $row){
, and the if clause would say <
instead of <=
.
compute and find the one id, where the value "flips"
(I assume, this solution is actually the fastest, if done correctly. If really done correctly, this can probably be done in the database alone ... depending on the database and database-foo)
your table has one invariant (as far as i can tell, if it doesn't hold, this solution becomes problematic and very confusing):
there is an id, for which every row with a smaller id has action value x+1
, and every id larger or equal has action value x
.
SELECT COUNT(*), action FROM table GROUP BY action
(this should return AT MOST two rows, one for x, one for x+1, with their respective counts, or just one for x or x+1, depending on how you interpret the invariant)
now, you count how many rows have value x and how many have value x+1, then you compute how many y+1 and y you will have at the end. now, if your ids are the perfect sequence starting with 1, 2, 3, ... you don't even have to query for the id, because the id == count(y+1).
UPDATE table SET action = IF(id >= :flipid, :y_plus_one, :y)
so this is probably the solution with the least amount of data exchanged between database and php.
now, if you're really smart, instead of the first select you can use this instead:
SELECT COUNT(*), SUM(action) FROM table
then add onto the sum, modulo with the count, and the rest is almost as before ^^
do something more confusing
do the same as before, but try to use relative amounts (SET action = action + IF(...)
). you'll notice, that you now need up to 2 pivot ids. and that's bat-shit crazy. don't do it. (disclaimer: i can't prove, there's no reason to actually do this, maybe there is...)
wrap it all up in transactions, because race conditions will ruin your day
the problem in and of itself is not really complicated, when you have no simultaneously acting users. With concurrent users, you have to wrap it into some transactions, lock some tables to prevent lost updates. (Some databases do this by default...)