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

PostgreSQL –繰り返し値を排除する方法

    テーブル内で、値を繰り返したフィールドを一意のままにする必要がある可能性があります。
    そして、すべてを削除せずに繰り返し値を続行するにはどうすればよいですか?
    最新のものだけを残すことは可能でしょうか? ?

    ctidシステム列

    すべてのテーブルには、システムによって暗黙的に定義されたいくつかの列があり、その名前は予約されています。
    現在、システムの列は、tableoid、xmin、cmin、xmax、cmax、およびctidです。それぞれに、それらが属するテーブルのメタデータがあります。
    ctidシステム列は、行の物理的な場所のバージョンを格納することを目的としています。このバージョンは、行が更新された場合(UPDATE)、またはテーブルがVACUUM FULLを通過した場合に変更される可能性があります。
    ctidのデータ型はtidです。これは、タプルID(または行ID)を意味します。ペア(ブロック番号、ブロック内のタプルインデックス)
    テーブル内の行の物理的な場所を識別します。
    この列は常にテーブル内で一意の値を持つため、値が繰り返される行がある場合それらを排除するための基準として使用できます。

    テストテーブルの作成:

    CREATE TABLE tb_test_ctid (
        col1 int,
        col2 text);
    

    データを挿入します:

    INSERT INTO tb_test_ctid VALUES 
    (1, 'foo'),
    (2, 'bar'),
    (3, 'baz');

    現在の行を確認します:

    SELECT ctid, * FROM tb_test_ctid;
    
     ctid  | col1 | col2 
    -------+------+------
     (0,1) |    1 | foo
     (0,2) |    2 | bar
     (0,3) |    3 | baz

    行を更新します:

    UPDATE tb_test_ctid SET col2 = 'spam' WHERE col1 = 1;
    
    

    表をもう一度確認してください:

    SELECT ctid, * FROM tb_test_ctid;
    
     ctid  | col1 | col2 
    -------+------+------
     (0,2) |    2 | bar
     (0,3) |    3 | baz
     (0,4) |    1 | spam
    

    更新された行のctidも変更されていることがわかります…

    簡単なVACUUMFULLテスト:

    VACUUM FULL tb_test_ctid;

    VACUUM後のテーブルの確認:

    SELECT ctid, * FROM tb_test_ctid;
    
    
    ctid   | col1 | col2 
    -------+------+------
    (0,1)  | 2    | bar
    (0,2)  | 3    | baz
    (0,3)  | 1    | spam
    

    RETURNING句を使用して同じ行を再度更新します:

    UPDATE tb_test_ctid
        SET col2 = 'eggs'
        WHERE col1 = 1
        RETURNING ctid;
    
    
     ctid  
    -------
     (0,4)
    

    表をもう一度確認してください:

    SELECT ctid, * FROM tb_test_ctid;
    
     ctid  | col1 | col2 
    -------+------+------
     (0,2) |    2 | bar
     (0,3) |    3 | baz
     (0,4) |    1 | spam
    

    ctidを使用して繰り返し値を削除する

    フィールドに値が繰り返され、同じフィールドが後で一意になるように決定されたテーブルを想像してみてください。
    PRIMARYKEYフィールドも一意であることを忘れないでください。
    OK、そのフィールドは削除されます。
    ここで、残るこれらの繰り返し値の中から決定するための基準を確立する必要があります。
    次の場合、基準は最新の行、つまり、最高のctid値。

    新しいテストテーブルの作成:

    CREATE TABLE tb_foo(
        id_ int,  --This field will be the primary key in the future!
        letter char(1)
    );
    

    10レコードを挿入:

    INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 10), 'a';
    

    表を確認してください:

    SELECT id_, letter FROM tb_foo;
    
     id_ | letter 
    -----+--------
       1 | a
       2 | a
       3 | a
       4 | a
       5 | a
       6 | a
       7 | a
       8 | a
       9 | a
      10 | a
    
    さらに3つのレコードを挿入します:
    INSERT INTO tb_foo (id_, letter) SELECT generate_series(1, 3), 'b';
    
    

    繰り返される値を確認します:

    SELECT id_, letter FROM tb_foo WHERE id_ <= 3;
    
    
     id_ | letter  
    -----+--------
       1 | a
       2 | a
       3 | a
       1 | b
       2 | b
       3 | b
    

    テーブルのid_フィールドに繰り返し値があります…

    id_フィールドを主キーにしようとします:

    ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);
    
    
    ERROR:  could not create unique index "tb_foo_pkey"
    DETAIL:  Key (id_)=(3) is duplicated.
    

    CTEとウィンドウ関数を使用して、保持される繰り返し値を見つけます。

    WITH t AS (
    SELECT
        id_,
        count(id_) OVER (PARTITION BY id_) AS count_id,  -- Count
        ctid,
        max(ctid) OVER (PARTITION BY id_) AS max_ctid  -- Most current ctid
        
        FROM tb_foo
    )
    
    SELECT
        t.id_,
        t.max_ctid
        FROM t
        WHERE t.count_id > 1  -- Filters which values repeat
        GROUP by id_, max_ctid;
    
    
     id_ | max_ctid 
    -----+----------
       3 | (0,13)
       1 | (0,11)
       2 | (0,12)
    

    id_フィールドの一意の値をテーブルに残し、古い行を削除します:

    WITH
    
    t1 AS (
    SELECT
        id_,
        count(id_) OVER (PARTITION BY id_) AS count_id,
        ctid,
        max(ctid) OVER (PARTITION BY id_) AS max_ctid
        
        FROM tb_foo
    ),
    
    t2 AS (  -- Virtual table that filters repeated values that will remain
    SELECT t1.id_, t1.max_ctid
        FROM t1
        WHERE t1.count_id > 1
        GROUP by t1.id_, t1.max_ctid)
    
    DELETE  -- DELETE with JOIN 
        FROM tb_foo AS f
        USING t2
        WHERE 
            f.id_ = t2.id_ AND  -- tb_foo has id_ equal to t2 (repeated values)
            f.ctid < t2.max_ctid;  -- ctid is less than the maximum (most current)
    

    id_:

    の値が重複していないテーブル値を確認しています
    SELECT id_, letter FROM tb_foo;
    
     id_ | letter 
    -----+--------
       4 | a
       5 | a
       6 | a
       7 | a
       8 | a
       9 | a
      10 | a
       1 | b
       2 | b
       3 | b
    

    これで、テーブルを変更して、id_フィールドをPRIMARY KEYのままにすることができます:

    ALTER TABLE tb_foo ADD CONSTRAINT tb_foo_pkey PRIMARY KEY (id_);
    

    1. uuidを数値として保存する方法は?

    2. T-SQLのバグ、落とし穴、およびベストプラクティス–ピボットとピボット解除

    3. PHPでの入力と出力のクリーニングの聖杯?

    4. MySQL上の@GeneratedValueポリモーフィック抽象スーパークラス