How to check a sequence efficiently for used and unused values in PostgreSQL

前端 未结 4 1754
暖寄归人
暖寄归人 2020-12-21 11:36

In PostgreSQL (9.3) I have a table defined as:

CREATE TABLE charts
( recid serial NOT NULL,
  groupid text NOT NULL,
  chart_number integer NOT NULL,
  \"tim         


        
4条回答
  •  滥情空心
    2020-12-21 12:21

    Sequence numbers usually have no meaning, so why worry? But if you really want this, then follow the below, cumbersome procedure. Note that it is not efficient; the only efficient option is to forget about the holes and use the sequence.

    In order to avoid having to scan the charts table on every insert, you should scan the table once and store the unused chart_number values in a separate table:

    CREATE TABLE charts_unused_chart_number AS
      SELECT seq.unused
      FROM (SELECT max(chart_number) FROM charts) mx,
           generate_series(1, mx(max)) seq(unused)
      LEFT JOIN charts ON charts.chart_number = seq.unused
      WHERE charts.recid IS NULL;
    

    The above query generates a contiguous series of numbers from 1 to the current maximum chart_number value, then LEFT JOINs the charts table to it and find the records where there is no corresponding charts data, meaning that value of the series is unused as a chart_number.

    Next you create a trigger that fires on an INSERT on the charts table. In the trigger function, pick a value from the table created in the step above:

    CREATE FUNCTION pick_unused_chart_number() RETURNS trigger AS $$
    BEGIN
      -- Get an unused chart number
      SELECT unused INTO NEW.chart_number FROM charts_unused_chart_number LIMIT 1;
    
      -- If the table is empty, get one from the sequence
      IF NOT FOUND THEN
        NEW.chart_number := next_val(charts_chartnumber_seq);
      END IF;
    
      RETURN NEW;
    END;
    $$ LANGUAGE plpgsql;
    
    CREATE TRIGGER tr_charts_cn
    BEFORE INSERT ON charts
    FOR EACH ROW EXECUTE PROCEDURE pick_unused_chart_number();
    

    Easy. But the INSERT may fail because of some other trigger aborting the procedure or any other reason. So you need a check to ascertain that the chart_number was indeed inserted:

    CREATE FUNCTION verify_chart_number() RETURNS trigger AS $$
    BEGIN
      -- If you get here, the INSERT was successful, so delete the chart_number
      -- from the temporary table.
      DELETE FROM charts_unused_chart_number WHERE unused = NEW.chart_number;
    END;
    $$ LANGUAGE plpgsql;
    
    CREATE TRIGGER tr_charts_verify
    AFTER INSERT ON charts
    FOR EACH ROW EXECUTE PROCEDURE verify_chart_number();
    

    At a certain point the table with unused chart numbers will be empty whereupon you can (1) ALTER TABLE charts to use the sequence instead of an integer for chart_number; (2) delete the two triggers; and (3) the table with unused chart numbers; all in a single transaction.

提交回复
热议问题