Home | Contact Us | FAQ | Search & Site Map | Link to Us
Sign In | Join | Other 45 Sites in Network
Home
Discussion Groups
Database Servers
DB2InformixIngresMS SQLOraclePervasive.SQLPostgreSQLProgressSybase
Desktop Databases
FileMakerFoxProMS AccessParadox
General
General DB TopicsDatabase Theory
Related Topics
Java Development.NET DevelopmentVB DevelopmentMore Topics ...

Database Forum / DB2 Topics / April 2008

Tip: Looking for answers? Try searching our database.

generate sequence number

Thread view: 
Enable EMail Alerts  Start New Thread
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.
 
Sign In
Join
My Latest Posts
My Monitored Threads
My Blog
My Photo Gallery
My Profile
My Homepage

Start New Thread
Enable EMail Alerts
Rate this Thread



©2008 Advenet LLC   Privacy Policy - Terms of Use
This website includes both content owned or controlled by Advenet as well as content owned or controlled by third parties.