Database Forum / DB2 Topics / April 2008
generate sequence number
|
|
Thread rating:  |
Frank Swarbrick - 28 Mar 2008 21:41 GMT Take the following DDL:
------------------------------------------------ -- DDL Statements for table "TEST "."MEMOS" ------------------------------------------------
CREATE TABLE "TEST "."MEMOS" ( "BRCH" DECIMAL(3,0) NOT NULL , "ACCT" DECIMAL(7,0) NOT NULL , "DATE" DATE NOT NULL , "SEQ_NBR" INTEGER NOT NULL , "TIME" TIME NOT NULL , "TEXT" VARCHAR(255) NOT NULL ) IN "USERSPACE1" ;
-- DDL Statements for primary key on Table "TEST "."MEMOS"
ALTER TABLE "TEST "."MEMOS" ADD CONSTRAINT "MEMOS_PK" PRIMARY KEY ("BRCH", "ACCT", "DATE", "SEQ_NBR");
And the following before trigger
CREATE TRIGGER test.memo_seq NO CASCADE BEFORE INSERT ON test.memos REFERENCING NEW AS n FOR EACH ROW MODE DB2SQL BEGIN ATOMIC SET n.date = CURRENT DATE;-- SET n.time = CURRENT TIME;-- SET n.seq_nbr = ( SELECT CASE WHEN max_seq IS NULL THEN 1 ELSE max_seq + 1 END AS next_seq FROM ( SELECT MAX(memos.seq_nbr) AS MAX_SEQ FROM test.memos AS memos WHERE memos.brch = n.brch AND memos.acct = n.acct AND memos.date = CURRENT DATE ) );-- END;
The goal, and this works, is that the first memo row for any branch/account for any particular day will be automatically assigned SEQ_NBR of 1. After that each row inserted for the same branch/account will be assigned the number following the highest SEQ_NBR value.
This works if I insert one row at a time, ie:
DELETE FROM test.memos; INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is a memo!'); INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!'); INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!!'); DELETE FROM test.memos WHERE brch = 543 AND acct = 2870819 AND seq_nbr = 2; INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!!!'); SELECT * FROM test.memos; DELETE FROM test.memos WHERE brch = 543 AND acct = 2870819 AND seq_nbr = 4; INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!!!!'); SELECT * FROM test.memos;
BRCH ACCT DATE SEQ_NBR TIME TEXT ----- --------- ---------- ----------- -------- ------------------------- 543. 2870819. 03/28/2008 1 14:36:07 This is a memo! 543. 2870819. 03/28/2008 3 14:36:07 This is another memo!!! 543. 2870819. 03/28/2008 4 14:36:07 This is another memo!!!!
3 record(s) selected.
BRCH ACCT DATE SEQ_NBR TIME TEXT ----- --------- ---------- ----------- -------- ------------------------- 543. 2870819. 03/28/2008 1 14:36:07 This is a memo! 543. 2870819. 03/28/2008 3 14:36:07 This is another memo!!! 543. 2870819. 03/28/2008 4 14:36:07 This is another memo!!!!!
3 record(s) selected.
However it doesn't like this:
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is a memo!') , (543, 2870819, 'This is another memo!!') , (543, 2870819, 'This is another memo!!!');
SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "1" constrains
table "TEST.MEMOS" from having duplicate values for the index key. SQLSTATE=23505
I am guessing it's because the result of SELECT MAX(memos.seq_nbr) AS MAX_SEQ FROM test.memos AS memos WHERE memos.brch = n.brch AND memos.acct = n.acct AND memos.date = CURRENT DATE
is the same in all three cases because the first row hasn't actually been inserted when the trigger is fired for the second row.
Any thoughts how to make this work?
Any other thoughts about the entire process?
Thanks, Frank
Frank Swarbrick - 28 Mar 2008 23:10 GMT >>> On 3/28/2008 at 2:41 PM, in message <47ED038E.6F0F.0085.0@efirstbank.com>,
> Take the following DDL: > [quoted text clipped - 28 lines] > FOR EACH ROW > MODE DB2SQL CREATE TRIGGER test.memo_seq NO CASCADE BEFORE INSERT ON test.memos REFERENCING NEW AS n FOR EACH ROW MODE DB2SQL BEGIN ATOMIC SET n.date = CURRENT DATE; SET n.time = CURRENT TIME; SET n.seq_nbr = ( SELECT CASE WHEN max_seq IS NULL THEN 1 ELSE max_seq + 1 END AS next_seq FROM ( SELECT MAX(memos.seq_nbr) AS MAX_SEQ FROM test.memos AS memos WHERE memos.brch = n.brch AND memos.acct = n.acct AND memos.date = CURRENT DATE ) ); END
Frank Swarbrick - 28 Mar 2008 23:19 GMT I don't know why my SQL is some times cut off, but to continue...
The intention is that the trigger will generate a unique sequence number for each memo added for the same branch/account/date.
It works when I insert one row per insert, eg:
---------------------------------------------------------------------------- -- DELETE FROM test.memos DB20000I The SQL command completed successfully.
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is a memo!') DB20000I The SQL command completed successfully.
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!') DB20000I The SQL command completed successfully.
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!!') DB20000I The SQL command completed successfully.
DELETE FROM test.memos WHERE brch = 543 AND acct = 2870819 AND seq_nbr = 2 DB20000I The SQL command completed successfully.
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!!!') DB20000I The SQL command completed successfully.
SELECT * FROM test.memos
BRCH ACCT DATE SEQ_NBR TIME TEXT ----- --------- ---------- ----------- -------- ------------------------- 543. 2870819. 03/28/2008 1 14:36:07 This is a memo! 543. 2870819. 03/28/2008 3 14:36:07 This is another memo!!! 543. 2870819. 03/28/2008 4 14:36:07 This is another memo!!!!
3 record(s) selected.
DELETE FROM test.memos WHERE brch = 543 AND acct = 2870819 AND seq_nbr = 4 DB20000I The SQL command completed successfully.
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is another memo!!!!!') DB20000I The SQL command completed successfully.
SELECT * FROM test.memos
BRCH ACCT DATE SEQ_NBR TIME TEXT ----- --------- ---------- ----------- -------- ------------------------- 543. 2870819. 03/28/2008 1 14:36:07 This is a memo! 543. 2870819. 03/28/2008 3 14:36:07 This is another memo!!! 543. 2870819. 03/28/2008 4 14:36:07 This is another memo!!!!!
3 record(s) selected.
However it does not work if I try to insert more than one row in a single insert:
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'This is a memo!') , (543, 2870819, 'This is another memo!!') , (543, 2870819, 'This is another memo!!!') DB21034E The command was processed as an SQL statement because it was not a
valid Command Line Processor command. During SQL processing it returned: SQL0803N One or more values in the INSERT statement, UPDATE statement, or foreign key update caused by a DELETE statement are not valid because the primary key, unique constraint or unique index identified by "1" constrains
table "TEST.MEMOS" from having duplicate values for the index key. SQLSTATE=23505
I believe what is happening is that each time the trigger is 'run' for this multi-row insert the insert of the previous row has not yet been committed, and so the max_seq resulting from the nested table expression query always returns the same value; that is the max_seq prior to the insert of the first of the multi-row rows.
Any idea how to get around this? I was hoping that because of the FOR EACH ROW I wouldn't have this problem, but alas.
Any other thoughts?
Frank
n 3/28/2008 at 4:10 PM, in message <47ED185D.6F0F.0085.0@efirstbank.com>,
>>>> On 3/28/2008 at 2:41 PM, in message > <47ED038E.6F0F.0085.0@efirstbank.com>, [quoted text clipped - 54 lines] > ); > END Lennart - 29 Mar 2008 00:09 GMT On Mar 28, 11:19 pm, "Frank Swarbrick" <Frank.Swarbr...@efirstbank.com> wrote:
> I don't know why my SQL is some times cut off, but to continue... > [quoted text clipped - 145 lines] > > ); > > END Frank, would the following be an option? (I'm not sure I understand the meaning of your p.k). The idea is to have an underlaying table with an identity column, and a view that calculates the seq_nbr. Using instead of triggers on the view will make it transparent for the users:
DROP TABLE TEST.MEMOS_TMP; CREATE TABLE TEST.MEMOS_TMP ( BRCH DECIMAL(3,0) NOT NULL , ACCT DECIMAL(7,0) NOT NULL , DATE DATE DEFAULT CURRENT_DATE NOT NULL , X_SEQ_NBR INTEGER NOT NULL GENERATED ALWAYS AS IDENTITY, TIME TIME DEFAULT CURRENT_TIME NOT NULL, TEXT VARCHAR(255) NOT NULL ) IN USERSPACE1;
ALTER TABLE TEST.MEMOS_TMP ADD CONSTRAINT MEMOS_PK PRIMARY KEY (X_SEQ_NBR);
DROP VIEW TEST.MEMOS; CREATE VIEW TEST.MEMOS AS select BRCH, ACCT, DATE, row_number() over ( partition by BRCH,ACCT,DATE order by X_SEQ_NBR ) as SEQ_NBR, TIME, TEXT from TEST.MEMOS_TMP;
DROP TRIGGER TEST.memo_seq; CREATE TRIGGER TEST.memo_seq INSTEAD OF INSERT ON TEST.MEMOS REFERENCING NEW AS n FOR EACH ROW MODE DB2SQL insert into TEST.MEMOS_TMP (BRCH, ACCT, TEXT) values (N.BRCH, N.ACCT, N.TEXT);
-- Add update/delete triggers as well
INSERT INTO test.memos (BRCH, ACCT, TEXT) VALUES (543, 2870819, 'Memo') , (543, 2870819, 'Memo again') , (543, 2870819, 'Once around');
select * from test.memos; BRCH ACCT DATE SEQ_NBR TIME TEXT ----- --------- ---------- -------------------- -------- --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- 543. 2870819. 03/29/2008 1 00:03:12 Memo 543. 2870819. 03/29/2008 2 00:03:12 Memo again 543. 2870819. 03/29/2008 3 00:03:12 Once around
3 record(s) selected.
/Lennart
Serge Rielau - 29 Mar 2008 00:42 GMT > Take the following DDL: > [quoted text clipped - 123 lines] > is the same in all three cases because the first row hasn't actually been > inserted when the trigger is fired for the second row. Correct. The standard defines a before row trigger as a before statement trigger with a loop over OLD/NEW TABLE. So only when all rows are processed does the workflow continue to the next before trigger and ultimately the insert/delete/update. This is to avoid mutating table conflicts.
Nwo in your case you have two choices: * ask yourself why you need one sequence per branch as opposed to just use a global sequence which can be implemented via either an IDENTITY property or a SEQUENCE. * Use the scratchpad counter scalar function in sqllib/samples/c.
If you google back to pre DB2 V7.2 posts you will find several posts on that topic. http://publib.boulder.ibm.com/infocenter/db2luw/v8/index.jsp?topic=/com.ibm.db2. udb.doc/ad/c0009033.htm
Cheers Serge
 Signature Serge Rielau DB2 Solutions Development IBM Toronto Lab
Frank Swarbrick - 01 Apr 2008 00:46 GMT This is an response to messages from both Lennart and Serge.
Lennart, that is an interesting idea. Not sure how you banged it out so quickly. :-) I don't think it is what I want, though. The point of the SEQ_NBR field is to create a unique key that can be used to update or delete any particular memo. I think with your method if two users retrieved all memos for a particular account and then both attempted to delete the same memo, the first one would delete the correct one, but the second would end up deleting the "new number 2", as it were. In other words, if user one deleted memo 2, it would make the original number 3 now be number 2. Then when the second user attempted to delete number 2 it would delete the new number 2. I just tried it and this is indeed what occured.
As for Serge's questions: * "ask yourself why you need one sequence per branch as opposed to just use a global sequence which can be implemented via either an IDENTITY property or a SEQUENCE."
This is certainly a valid question. The thing I don't like about IDENTITY and SEQUENCE is that they have an upper bounds, but (probably) one that will not be reached for a long time. I'm not sure how to plan for that far in the future when the sequence wants to roll over. I guess if BIGINT is used then that number is so large that we won't get there in my lifetime, so maybe it doesn't matter, but...
Anyway, currently we have these memos in DL/I segments, which doesn't offer the flexibility of DB2. What we do when adding a memo is we attempt to add a record for the customer's branch/account number, the current date, and a sequence of 1. If that fails because it already exists we try sequence 2, etc, until we find one that doesn't already exist. I was trying to emulate this within DB2, which I know isn't always the best idea. And it isn't. But that was my first thought.
My latest thought is to bypass the whole 'sequence' thing entirely, and rather to use the new RID_BIT(tablename) function. (And while I'm at it probably the ROW CHANGE TIMESTAMP function as well).
So I'll do a SELECT like this: SELECT RID_BIT(memos) , ROW CHANGE TIMESTAMP FOR memos , date , time , text FROM test.memos AS memos WHERE brch = :branch-nbr AND acct = :acct-nbr
I'll assign my own temporary sequence numbers, if necessary (probably not necessary unless the user wants them numbered for some reason). But when the user choses to delete a particular memo I'll just do:
DELETE FROM test.memos AS memos WHERE RID_BIT(memos) = :rid-bit AND ROW CHANGE TIMESTAMP FOR memos = :rct
And something similar for UPDATE.
Thoughts?
Frank
Serge Rielau - 01 Apr 2008 02:21 GMT > This is an response to messages from both Lennart and Serge. > [quoted text clipped - 53 lines] > > And something similar for UPDATE. The intent for these functions is for use with optimitsic locking. So as long as you are OK with that you;re cool. E.g. you make get, in rare cases, false positives (no match).
Cheers Serge
 Signature Serge Rielau DB2 Solutions Development IBM Toronto Lab
Dave Hughes - 02 Apr 2008 09:48 GMT > As for Serge's questions: > * "ask yourself why you need one sequence per branch as opposed to [quoted text clipped - 8 lines] > that we won't get there in my lifetime, so maybe it doesn't matter, > but... It seems many people have a problem comprehending in real terms just how enormous the upper bounds of sequences can be. Let's be generous and assume you need one new sequence number every single millisecond (regardless of whether that number is committed or thrown away). With this frequency:
1. An INTEGER sequence starting at 1 with no cycle (the default) will expire in 24 and a bit days (2^31 / (24*60*60*1000))
2. A BIGINT sequence starting at 1 with no cycle will expire in just under 300,000 millenia (2^63 / (1000*365.25*24*60*60*1000))
3. A DECIMAL(31) sequence starting at 1 with no cycle will expire in a little over 3E17 millenia
In other words, it's not difficult to pick a sequence type that will never encounter rollover (at least not in any realistic time frame). Sequences also shouldn't have any locking issues (provided a sufficiently large cache of values is used). The only annoyance with them is they can be tricky to reload if you ever need to perform a manual database migration (i.e. with db2move - under DB2 8 db2move doesn't generate the right modifiers for loading generated columns - not sure if that's fixed in later versions).
Personally, I'd just stick with sequences. Don't worry about throwing values away (I've yet to see a good reason for having contiguous sequence numbers), and don't worry about rollover :-)
Cheers,
Dave.
|
|
|