Skip to Main Content
  • Questions
  • Possible? Set up table, n maximum rows, FIFO

Breadcrumb

Question and Answer

Tom Kyte

Thanks for the question, Art.

Asked: February 27, 2006 - 11:35 am UTC

Last updated: April 06, 2006 - 9:10 am UTC

Version: 9.2.0.6

Viewed 1000+ times

You Asked

Tom, I realize that what I'm asking for might not be possible, prudent or performant, but it doesn't hurt to ask.

Is it possible to design a table with a maximum row count of, say, 10,000 rows? Then, when the limit is reached, the oldest rows would be automatically deleted in FIFO (first in, first out) fashion to make room for the most recently inserted rows. Rows would never be updated, only inserted.

This table that could "manage its own size" could be useful for logging events in instrumented batch code. You would have a "recent" record of what happened, without a) an explicit DELETE step, or b) a table that grows ad infinitum when you don't really care about older rows.

Is it feasible?

and Tom said...

No, this does not exist, we would have to implement the logic in the code itself.

It is very similar to the sys.SMON_SCN_TIME table which has 1440 rows (number of minutes in a day - they store time based entries every 5 minutes, for 5 days worth of data).

They do it by rolling around, updating the "right" row.

Rating

  (2 ratings)

Is this answer out of date? If it is, please let us know via a Comment

Comments

What about putting logic in a procedure?

Art Metzer, March 30, 2006 - 12:29 pm UTC

Tom,

Regarding this "circular log" concept, I understand that there's no way to specify a table to wrap its rows around when the number of rows hits some cap, but what would you say about using a procedure like the following to incorporate the logic?  Assume that the logging would be controlled, so the table would only be inserted into via this procedure, and obviously one row at a time.

This code was run on a 9i database.

CREATE TABLE circular_log (
    rotating_id     NUMBER
,   seq             NUMBER
,   text            VARCHAR2(100)
)
/
CREATE SEQUENCE circular_log_seq
INCREMENT BY 1
START WITH 1
CACHE 20
NOCYCLE
/
CREATE OR REPLACE PROCEDURE log_issue (p_log_text VARCHAR2)
IS
    PRAGMA      AUTONOMOUS_TRANSACTION;
    l_rotating_id          POSITIVE;
    l_max_rows  CONSTANT   PLS_INTEGER := 20;
BEGIN
    BEGIN
        -- 1. Look up the ID of the row having the maximum
        --    seq.
        -- 2. The ID of interest will be this maximum
        --    seq's ID + 1, unless we're at the limit,
        --    in which case the ID of interest becomes 1.
        SELECT CASE
               WHEN cl_max.rotating_id + 1 > l_max_rows
               THEN 1
               ELSE cl_max.rotating_id + 1
               END
        INTO   l_rotating_id
        FROM   circular_log      cl_max
        WHERE  cl_max.rotating_id =
              (SELECT a.rotating_id
               FROM  (SELECT   cl.rotating_id
                      FROM     circular_log   cl
                      ORDER BY cl.seq DESC) a
               WHERE  ROWNUM = 1)
        FOR UPDATE;
    EXCEPTION
        -- The very first time we insert into this table,
        -- the above SELECT will raise a NO_DATA_FOUND, so
        -- start counting at 1.
        WHEN NO_DATA_FOUND THEN
            l_rotating_id := 1;
    END;

    -- 3. If the ID of interest is already in the table,
    --    then just update the SEQ and TEXT columns of 
    --    that ID.
    --    If the ID of interest is not already in the table,
    --    then insert it, along with SEQ and TEXT.
    MERGE INTO circular_log         cl
    USING     (SELECT l_rotating_id rotating_id FROM DUAL) x
    ON        (cl.rotating_id = x.rotating_id)
    WHEN       MATCHED THEN
               UPDATE
               SET    cl.seq  = circular_log_seq.NEXTVAL
               ,      cl.text = p_log_text
    WHEN   NOT MATCHED THEN
               INSERT (rotating_id
               ,       seq
               ,       text
               ) VALUES (
                       x.rotating_id
               ,       circular_log_seq.NEXTVAL
               ,       p_log_text
               );
    COMMIT;
END log_issue;
/

SQL> BEGIN
  2  FOR i IN 1..24 LOOP log_issue(TO_CHAR(TO_DATE(i,'J'),'jsp')); END LOOP;
  3  END;
  4  /
 
PL/SQL procedure successfully completed.
 
SQL> SELECT * FROM circular_log ORDER BY seq DESC
  2  /
 
ROTATING_ID        SEQ TEXT
----------- ---------- -----------------------------------------------------
          4         24 twenty-four
          3         23 twenty-three
          2         22 twenty-two
          1         21 twenty-one
         20         20 twenty
         19         19 nineteen
         18         18 eighteen
         17         17 seventeen
         16         16 sixteen
         15         15 fifteen
         14         14 fourteen
         13         13 thirteen
         12         12 twelve
         11         11 eleven
         10         10 ten
          9          9 nine
          8          8 eight
          7          7 seven
          6          6 six
          5          5 five
 
20 rows selected.
 
SQL> 

Tom Kyte
March 31, 2006 - 11:35 am UTC

be more straightforward with a simple "lock table" to serialze.

have the table pre-populated with the number of rows you wanted and a timestamp.


then

update t set text = p_text, ts = systimestamp
where ts = (select min(ts) from t) and rownum=1;


so, have the N rows in there as part of the create table.

create table t ( ts, text );
insert into t select systimestamp, null from all_objects where rownum <= 20;


then, lock the table (serialize)

and update the "first oldest row" you find


so, it becomes a three line stored procedure.

Clarification on serializing

Art, April 05, 2006 - 11:51 am UTC

Tom,

To serialize, are you suggesting DBMS_LOCK.REQUEST?

Tom Kyte
April 06, 2006 - 9:10 am UTC

on this page, I believe I used "lock table" as the example, but dbms_lock may also be used if everyone uses the same API to do the work (eg: no one skips the dbms_lock call)