sql >> データベース >  >> RDS >> PostgreSQL

PostgreSQL:複数列の一意性制約に基づく自動インクリメント

    MySQLのMyISAMテーブルのようにPostgreSQLが「複数列のインデックスの2番目の列で」インクリメントをサポートしていれば便利です

    ええ、でもそうすることで、MyISAMはテーブル全体をロックすることに注意してください。これにより、同時トランザクションを気にすることなく、最大の+1を安全に見つけることができます。

    Postgresでは、テーブル全体をロックすることなく、これを行うこともできます。アドバイザリーロックとトリガーで十分です:

    CREATE TYPE animal_grp AS ENUM ('fish','mammal','bird');
    
    CREATE TABLE animals (
        grp animal_grp NOT NULL,
        id INT NOT NULL DEFAULT 0,
        name varchar NOT NULL,
        PRIMARY KEY (grp,id)
    );
    
    CREATE OR REPLACE FUNCTION animals_id_auto()
        RETURNS trigger AS $$
    DECLARE
        _rel_id constant int := 'animals'::regclass::int;
        _grp_id int;
    BEGIN
        _grp_id = array_length(enum_range(NULL, NEW.grp), 1);
    
        -- Obtain an advisory lock on this table/group.
        PERFORM pg_advisory_lock(_rel_id, _grp_id);
    
        SELECT  COALESCE(MAX(id) + 1, 1)
        INTO    NEW.id
        FROM    animals
        WHERE   grp = NEW.grp;
    
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql STRICT;
    
    CREATE TRIGGER animals_id_auto
        BEFORE INSERT ON animals
        FOR EACH ROW WHEN (NEW.id = 0)
        EXECUTE PROCEDURE animals_id_auto();
    
    CREATE OR REPLACE FUNCTION animals_id_auto_unlock()
        RETURNS trigger AS $$
    DECLARE
        _rel_id constant int := 'animals'::regclass::int;
        _grp_id int;
    BEGIN
        _grp_id = array_length(enum_range(NULL, NEW.grp), 1);
    
        -- Release the lock.
        PERFORM pg_advisory_unlock(_rel_id, _grp_id);
    
        RETURN NEW;
    END;
    $$ LANGUAGE plpgsql STRICT;
    
    CREATE TRIGGER animals_id_auto_unlock
        AFTER INSERT ON animals
        FOR EACH ROW
        EXECUTE PROCEDURE animals_id_auto_unlock();
    
    INSERT INTO animals (grp,name) VALUES
        ('mammal','dog'),('mammal','cat'),
        ('bird','penguin'),('fish','lax'),('mammal','whale'),
        ('bird','ostrich');
    
    SELECT * FROM animals ORDER BY grp,id;
    

    これにより、次のようになります。

      grp   | id |  name   
    --------+----+---------
     fish   |  1 | lax
     mammal |  1 | dog
     mammal |  2 | cat
     mammal |  3 | whale
     bird   |  1 | penguin
     bird   |  2 | ostrich
    (6 rows)
    

    注意点が1つあります。アドバイザリロックは、解放されるまで、またはセッションが期限切れになるまで保持されます。トランザクション中にエラーが発生した場合、ロックは保持され、手動で解放する必要があります。

    SELECT pg_advisory_unlock('animals'::regclass::int, i)
    FROM generate_series(1, array_length(enum_range(NULL::animal_grp),1)) i;
    

    Postgres 9.1では、ロック解除トリガーを破棄し、pg_advisory_lock()呼び出しをpg_advisory_xact_lock()に置き換えることができます。その1つは、トランザクションの終了まで自動的に保持され、解放されます。

    別のメモとして、私は古き良きシーケンスを使用することに固執します。これにより、データを見ると見栄えが悪くても、処理が速くなります。

    最後に、(年、月)コンボごとの一意のシーケンスは、主キーがシリアルであり、(年、月)の値に一意の制約があるテーブルを追加することによっても取得できます。



    1. MariaDBでのTO_BASE64()のしくみ

    2. Slick2.0の一般的なCRUD操作

    3. 9.6最も恐ろしいパッチトーナメント

    4. Pythonの問題manage.pymigrate->psycopg2という名前のモジュールがありません