MySQL Update/Insert trigger auto-increment over group by
I have a form in which a user can click on a flag (=1 if yes or 0 if not) to create a receipt number (receiptno). I want this to be automated through the database – thus triggers came to mind.
I want to create an auto-increment over a group.
Schema:
CREATE TABLE booking
(BuchungId int, `category` int, `year` int, `flag` int, receiptno int)
;
INSERT INTO booking
(BuchungId,`category`, `year`, `flag`, receiptno)
VALUES
(1, 1, 2016, 1, NULL),
(2, 1, 2017, 1, NULL),
(3, 1, 2017, 0, NULL),
(4, 2, 2017, 1, NULL),
(5, 2, 2017, 1, NULL),
(6, 1, 2017, 1, NULL),
(7, 1, 2017, 0, NULL),
(8, 1, 2017, 1, NULL)
;
I have created the body of a trigger:
CREATE TRIGGER trigger1
BEFORE UPDATE
ON booking
FOR EACH ROW
BEGIN
IF (new.Flag = 1) THEN
SET New.receiptno = COALESCE(old.receiptno,0)+1;
end IF;
end
Now I would like to update (or insert) the receiptno over a group by on category and year.
Thus eventually resulting in a table like:
(1, 1, 2016, 1, 1),
(2, 1, 2017, 1, 1),
(3, 1, 2017, 0, NULL),
(4, 2, 2017, 1, 1),
(5, 2, 2017, 1, 2),
(6, 1, 2017, 1, 2),
(7, 1, 2017, 0, NULL),
(8, 1, 2017, 1, 3)
Best Answer
One option is not to have a distinct set of unique receipt numbers per category, per year. Instead, simply use the
BuchungID
as the receipt number (I'm assuming that value is unique in your table, and may very well be an actual autoincrement column in the actual database). This should guarantee a unique ID is already available, whether the user wants a receipt number or not.However, I'll assume that you've got some business rule that prevents this simple solution from working, and you really need to generate distinct receipt numbers within each category/year pairing.
Simply looking at the maximum current
receiptno
and adding 1 doesn't guarantee uniqueness - it would be possible for two users to request a receipt number at the same time, both to check the current max for the category and year before eitherreceiptno
is updated, and thus to get the same receipt number.My next thought was to use sequences, but there are two problems with that. First, it looks like MySQL doesn't have a sequence object. Second, since you would have to have a sequence for each category and year, you'd either have to use dynamic SQL to set which sequence to use (not allowed in triggers), or you'd have to hard-code all the sequence names into your query (complicating the query significantly, and forcing you to update it any time a new category or year is added).
However, we can get around that by using a sequence table. This is a table that has a row for each "sequence" you want, with column(s) that identify the sequences (
category
andyear
, in your case), and others that define how the sequence works (starting value, ending value, increment, etc.).I found an article online that provides the code to set this all up, including some options I hadn't thought of.
To make things best fit your situation, I'd modify their code as follows:
category
andyear
.nextval
function, have it automatically create a new sequence if there isn't one for thecategory
andyear
you pass in.(Note: the following code is untested, and it's just my tweaks of the code in the article)
In your trigger, you'd use this:
It's important to note that, as written,
sequence_cur_val
is actually the next value that the sequence will assign, not the last value it did assign. So, when you querysequence_data
and see a current value of 71, then the highest assigned value for that category and year is 70. Also, if the current value goes over the maximum value, then it is set to NULL (or, if you change the default setting ofsequence_cycle
, to the minimum value for the sequence - in your case, you definitely don't want to do that). So, you should probably add a check to see if the function returns NULL, and error out appropriately; otherwise, no receipt number will be set.Please read the article (and the comments below it) for more details on how this is all meant to work.
Also note that the function must be run inside a transaction, to ensure that you don't get duplicate sequence numbers. If it's only going to be run inside a trigger, then you may not need to explicitly start a transaction inside the function itself.
Actually, I'd be inclined to rearrange the
nextval
code a little, to make sure we've done our update or insert (which should lock down the value to our session) before I try to set the value. I believe that version would look like this:(Again, untested code).
Hope this provides help, or at least a direction to consider.