Tüm Ürünler
Search
Belge Merkezi

PolarDB:pg_pathman (bölüm yönetimi)

Son Güncelleme:Oct 07, 2026

Bu konu, pg_pathman uzantısının yaygın kullanım örneklerini açıklar.

Genel bilgiler

PolarDB for PostgreSQL bölümlenmiş tabloların performansını artırmak için pg_pathman uzantısını içerir. Bu uzantı bir bölüm yönetimi ve optimizasyon mekanizması sunar.

pg_pathman uzantısını oluşturun

Not

pg_pathman uzantısının bölüm yönetimi özelliklerini kullanmak için bizimle iletişime geçin.

CREATE EXTENSION IF NOT EXISTS pg_pathman;

Uzantıyı oluşturduktan sonra sürümünü görüntülemek için aşağıdaki SQL ifadesini çalıştırabilirsiniz:

SELECT extname,extversion FROM pg_extension WHERE extname = 'pg_pathman';

Aşağıdaki sonuç döner:

  extname   | extversion 
------------+------------
 pg_pathman | 1.5
(1 row)

Uzantıyı güncelleyin

PolarDB for PostgreSQL geliştirilmiş veritabanı hizmetleri sunmak amacıyla eklentilerini düzenli olarak günceller. Bir eklentiyi güncellemek için önce kümeyi en son sürüme yükseltmeniz gerekir.

Uzantı özellikleri

  • Hash bölümleme ve aralık bölümlemeyi destekler.

  • Bölümleri oluşturmak ve ana tablodan veri taşımak için işlevleri kullanan otomatik bölüm yönetimini destekler. Ayrıca, mevcut tabloları işlevlerle iliştirmenize veya ayırmanıza olanak tanıyan manuel bölüm yönetimini de destekler.

  • int, float ve date gibi özel alanlar dahil olmak üzere bölüm anahtarı sütunları için yaygın veri türlerini destekler.

  • JOIN'ler ve alt sorgular dahil olmak üzere bölümlenmiş tablolar için verimli sorgu planları oluşturur.

  • Dinamik bölüm seçimini uygulamak üzere özel plan düğümlerini, yani RuntimeAppend ve RuntimeMergeAppend kullanır.

  • PartitionFilter ekleme tetikleyicilerine verimli bir alternatif sunar.

  • Yeni bölümlerin otomatik olarak oluşturulmasını destekler. Bu özellik şu anda yalnızca aralıkla bölümlenmiş tablolar için kullanılabilir.

  • Verimliliği artırmak amacıyla copy from/to kullanarak bölümlenmiş tablolar üzerinde doğrudan okuma ve yazma işlemlerini destekler.

  • Bir tetikleyici aracılığıyla bölüm anahtarı sütunlarının güncellenmesini destekler. Olası bir performans etkisini önlemek için bölüm anahtarı sütunlarını güncellemeye ihtiyacınız yoksa bu tetikleyiciyi eklemeyin.

  • Bir bölüm oluşturulduğunda otomatik olarak tetiklenen özel geri arama işlevlerini tanımlamanıza olanak tanır.

  • İşlemleri engellemeden arka planda bölümlenmiş tablolar oluşturur ve ana tablodaki verileri bölümlere taşır.

  • Foreign Data Wrappers (FDW) desteği sunar. postgres_fdw veya başka bir FDW'yi desteklemek için pg_pathman.insert_into_fdw=(disabled | postgres | any_fdw) parametresini yapılandırabilirsiniz.

Kullanım

Daha fazla bilgi için GitHub üzerindeki projeye bakın.

İlgili görünümler ve tablolar

pg_pathman bölümlenmiş tabloları sürdürmek için işlevler kullanır ve bu tabloların durumunu kontrol etmeniz için birkaç görünüm sağlar. Görünümler şunlardır:

  • pathman_config

    CREATE TABLE IF NOT EXISTS pathman_config (
        partrel         REGCLASS NOT NULL PRIMARY KEY,  -- OID of the primary table
        attname         TEXT NOT NULL,  -- Name of the partition key column
        parttype        INTEGER NOT NULL,  -- Partitioning type (hash or range)
        range_interval  TEXT,  -- Interval for range partitions
    
        CHECK (parttype IN (1, 2)) /* check for allowed part types */ );
  • pathman_config_params

    CREATE TABLE IF NOT EXISTS pathman_config_params (
        partrel        REGCLASS NOT NULL PRIMARY KEY,  -- OID of the primary table
        enable_parent  BOOLEAN NOT NULL DEFAULT TRUE,  -- Specifies whether to filter the primary table in the optimizer
        auto           BOOLEAN NOT NULL DEFAULT TRUE,  -- Specifies whether to automatically create a new partition if it does not exist during an insert operation
        init_callback  REGPROCEDURE NOT NULL DEFAULT 0);  -- OID of the callback function for partition creation
  • pathman_concurrent_part_tasks

    -- helper SRF function
    CREATE OR REPLACE FUNCTION show_concurrent_part_tasks()  
    RETURNS TABLE (
        userid     REGROLE,
        pid        INT,
        dbid       OID,
        relid      REGCLASS,
        processed  INT,
        status     TEXT)
    AS 'pg_pathman', 'show_concurrent_part_tasks_internal'
    LANGUAGE C STRICT;
    
    CREATE OR REPLACE VIEW pathman_concurrent_part_tasks
    AS SELECT * FROM show_concurrent_part_tasks();
  • pathman_partition_list

    -- helper SRF function
    CREATE OR REPLACE FUNCTION show_partition_list()
    RETURNS TABLE (
        parent     REGCLASS,
        partition  REGCLASS,
        parttype   INT4,
        partattr   TEXT,
        range_min  TEXT,
        range_max  TEXT)
    AS 'pg_pathman', 'show_partition_list_internal'
    LANGUAGE C STRICT;
    
    CREATE OR REPLACE VIEW pathman_partition_list
    AS SELECT * FROM show_partition_list();

Bölüm yönetimi

Aralık bölümleme

Aralık bölümleri oluşturmak için dört yönetim işlevi bulunur. Bunlardan ikisi başlangıç değeri, aralık ve bölüm sayısı belirtmenize olanak tanır. Tanımları şöyledir:

create_range_partitions(relation       REGCLASS,  -- OID of the primary table
                        attribute      TEXT,      -- Name of the partition key column
                        start_value    ANYELEMENT,  -- Start value
                        p_interval     ANYELEMENT,  -- Interval. Can be of any data type suitable for any type of partitioned table.
                        p_count        INTEGER DEFAULT NULL,   -- Number of partitions to create
                        partition_data BOOLEAN DEFAULT TRUE)   -- Specifies whether to immediately migrate data from the primary table to partitions. This is not recommended. Use the non-blocking migration function partition_table_concurrently() instead.

create_range_partitions(relation       REGCLASS,  -- OID of the primary table
                        attribute      TEXT,      -- Name of the partition key column
                        start_value    ANYELEMENT,  -- Start value
                        p_interval     INTERVAL,    -- Interval. The interval data type is used for time-based partitioned tables.
                        p_count        INTEGER DEFAULT NULL,   -- Number of partitions to create
                        partition_data BOOLEAN DEFAULT TRUE)   -- Specifies whether to immediately migrate data from the primary table to partitions. This is not recommended. Use the non-blocking migration function partition_table_concurrently() instead.

Diğer iki işlev ise başlangıç değeri, bitiş değeri ve aralık belirtmenize olanak tanır. Tanımları şöyledir:

create_partitions_from_range(relation       REGCLASS,  -- OID of the primary table
                             attribute      TEXT,      -- Name of the partition key column
                             start_value    ANYELEMENT,  -- Start value
                             end_value      ANYELEMENT,  -- End value
                             p_interval     ANYELEMENT,  -- Interval. Can be of any data type suitable for any type of partitioned table.
                             partition_data BOOLEAN DEFAULT TRUE)   -- Specifies whether to immediately migrate data from the primary table to partitions. This is not recommended. Use the non-blocking migration function partition_table_concurrently() instead.

create_partitions_from_range(relation       REGCLASS,  -- OID of the primary table
                             attribute      TEXT,      -- Name of the partition key column
                             start_value    ANYELEMENT,  -- Start value
                             end_value      ANYELEMENT,  -- End value
                             p_interval     INTERVAL,    -- Interval. The interval data type is used for time-based partitioned tables.
                             partition_data BOOLEAN DEFAULT TRUE)   -- Specifies whether to immediately migrate data from the primary table to partitions. This is not recommended. Use the non-blocking migration function partition_table_concurrently() instead.

Örneğin:

  1. Bölümlenecek ana tabloyu oluşturun ve test verilerini ekleyin.

    --- Create the primary table to be partitioned
    CREATE TABLE part_test(id int, info text, crt_time timestamp not null);  -- The partition key column must have a NOT NULL constraint  
    
    --- Insert test data to simulate a primary table that already contains data
    INSERT INTO part_test SELECT id,md5(random()::text),clock_timestamp() + (id||' hour')::interval from generate_series(1,10000) t(id); 

    Ana tablodaki verileri sorgulayın:

    SELECT * FROM part_test limit 10;

    Aşağıdaki sonuç döner:

     id |               info               |          crt_time          
    ----+----------------------------------+----------------------------
      1 | 36fe1adedaa5b848caec4941f87d443a | 2016-10-25 10:27:13.206713
      2 | c7d7358e196a9180efb4d0a10269c889 | 2016-10-25 11:27:13.206893
      3 | 005bdb063550579333264b895df5b75e | 2016-10-25 12:27:13.206904
      4 | 6c900a0fc50c6e4da1ae95447c89dd55 | 2016-10-25 13:27:13.20691
      5 | 857214d8999348ed3cb0469b520dc8e5 | 2016-10-25 14:27:13.206916
      6 | 4495875013e96e625afbf2698124ef5b | 2016-10-25 15:27:13.206921
      7 | 82488cf7e44f87d9b879c70a9ed407d4 | 2016-10-25 16:27:13.20693
      8 | a0b92547c8f17f79814dfbb12b8694a0 | 2016-10-25 17:27:13.206936
      9 | 2ca09e0b85042b476fc235e75326b41b | 2016-10-25 18:27:13.206942
     10 | 7eb762e1ef7dca65faf413f236dff93d | 2016-10-25 19:27:13.206947
    (10 rows)
  2. Bölümleri oluşturun. Her bölüm bir aylık veri aralığını içerir.

    --- Create partitions. Each partition contains data for a one-month span.  
    SELECT                                             
    create_range_partitions('part_test'::regclass,             -- OID of the primary table
                            'crt_time',                        -- Name of the partition key column
                            '2016-10-25 00:00:00'::timestamp,  -- Start value
                            interval '1 month',                -- Interval. The interval data type is used for time-based partitioned tables.
                            24,                                -- Number of partitions to create
                            false) ;                           -- Do not migrate data
  3. Engelsiz taşıma işlevini kullanarak verileri ana tablodan taşıyın.

    --- Before data migration, the data is still in the primary table
    SELECT count(*) FROM ONLY part_test;
     count 
    -------
     10000
    (1 row)
    
    --- Non-blocking migration function  
    partition_table_concurrently(relation   REGCLASS,              -- OID of the primary table
                                 batch_size INTEGER DEFAULT 1000,  -- Number of records to migrate in a single transaction batch
                                 sleep_time FLOAT8 DEFAULT 1.0)    -- The duration to sleep before retrying if acquiring row locks fails. The task exits after 60 retries.
    
    --- Use the non-blocking migration function to migrate data from the primary table
    SELECT partition_table_concurrently('part_test'::regclass,
                                 10000,
                                 1.0);
    
    --- After migration, the primary table is empty. All data has been moved to the partitions.
    SELECT count(*) FROM ONLY part_test;
     count 
    -------
         0
    (1 row)
  4. Veri taşıma tamamlandıktan sonra ana tabloyu devre dışı bırakarak yürütme planlarında görünmesini engelleyin.

    --- Disable the primary table
    SELECT set_enable_parent('part_test'::regclass, false);
    
    --- Verification
    EXPLAIN SELECT * FROM part_test WHERE crt_time = '2016-10-25 00:00:00'::timestamp;
                                       QUERY PLAN                                    
    ---------------------------------------------------------------------------------
     Append  (cost=0.00..16.18 rows=1 width=45)
       ->  Seq Scan on part_test_1  (cost=0.00..16.18 rows=1 width=45)
             Filter: (crt_time = '2016-10-25 00:00:00'::timestamp without time zone)
    (3 rows)
Not

Aralıkla bölümlenmiş tabloları kullanırken şu en iyi uygulamaları izleyin:

  • Bölüm anahtarı sütunu NOT NULL kısıtlamasına sahip olmalıdır.

  • Mevcut tüm kayıtları kapsayacak sayıda bölüm bulunmalıdır.

  • Engelsiz taşıma işlevini kullanın.

  • Veri taşıma tamamlandıktan sonra ana tabloyu devre dışı bırakın.

Hash bölümleme

Başlangıç değeri, aralık ve bölüm sayısını belirterek hash bölümleri oluşturmanıza olanak tanıyan bir yönetim işlevi aşağıdaki gibidir:

create_hash_partitions(relation         REGCLASS,  -- OID of the primary table
                       attribute        TEXT,      -- Name of the partition key column
                       partitions_count INTEGER,   -- Number of partitions to create
                       partition_data   BOOLEAN DEFAULT TRUE)   -- Specifies whether to immediately migrate data from the primary table to partitions. This is not recommended. Use the non-blocking migration function partition_table_concurrently() instead.

Örneğin:

  1. Bölümlenecek ana tabloyu oluşturun ve test verilerini ekleyin.

    --- Create the primary table to be partitioned
    CREATE TABLE part_test(id int, info text, crt_time timestamp not null);    -- The partition key column must have a NOT NULL constraint  
    
    --- Insert test data to simulate a primary table that already contains data
    INSERT INTO part_test SELECT id,md5(random()::text),clock_timestamp() + (id||' hour')::interval FROM generate_series(1,10000) t(id); 

    Ana tablodaki verileri sorgulayın:

    SELECT * FROM part_test limit 10;

    Aşağıdaki sonuç döner:

     id |               info               |          crt_time          
    ----+----------------------------------+----------------------------
      1 | 29ce4edc70dbfbe78912beb7c4cc95c2 | 2016-10-25 10:47:32.873879
      2 | e0990a6fb5826409667c9eb150fef386 | 2016-10-25 11:47:32.874048
      3 | d25f577a01013925c203910e34470695 | 2016-10-25 12:47:32.874059
      4 | 501419c3f7c218e562b324a1bebfe0ad | 2016-10-25 13:47:32.874065
      5 | 5e5e22bdf110d66a5224a657955ba158 | 2016-10-25 14:47:32.87407
      6 | 55d2d4fd5229a6595e0dd56e13d32be4 | 2016-10-25 15:47:32.874076
      7 | 1dfb9a783af55b123c7a888afe1eb950 | 2016-10-25 16:47:32.874081
      8 | 41eeb0bf395a4ab1e08691125ae74bff | 2016-10-25 17:47:32.874087
      9 | 83783d69cc4f9bb41a3978fe9e13d7fa | 2016-10-25 18:47:32.874092
     10 | affc9406d5b3412ae31f7d7283cda0dd | 2016-10-25 19:47:32.874097
    (10 rows)
  2. Bölümleri oluşturun.

    --- Create 128 partitions
    SELECT                                              
    create_hash_partitions('part_test'::regclass,              -- OID of the primary table
                            'crt_time',                        -- Name of the partition key column
                            128,                               -- Number of partitions to create
                            false) ;                           -- Do not migrate data
  3. Engelsiz taşıma işlevini kullanarak verileri ana tablodan taşıyın.

    --- Before data migration, the data is still in the primary table
    SELECT count(*) FROM ONLY part_test;
     count 
    -------
     10000
    (1 row)
    
    --- Non-blocking migration function  
    partition_table_concurrently(relation   REGCLASS,              -- OID of the primary table
                                 batch_size INTEGER DEFAULT 1000,  -- Number of records to migrate in a single transaction batch
                                 sleep_time FLOAT8 DEFAULT 1.0)    -- The duration to sleep before retrying if acquiring row locks fails. The task exits after 60 retries.
    
    --- Use the non-blocking migration function to migrate data from the primary table
    SELECT partition_table_concurrently('part_test'::regclass,
                                 10000,
                                 1.0);
    
    --- After migration, the primary table is empty. All data has been moved to the partitions.
    SELECT count(*) FROM ONLY part_test;
     count 
    -------
         0
    (1 row)
  4. Veri taşıma tamamlandıktan sonra ana tabloyu devre dışı bırakarak yürütme planlarında görünmesini engelleyin.

    --- Disable the primary table
    SELECT set_enable_parent('part_test'::regclass, false);

    Yürütme planını doğrulayın:

    --- Query a single partition
    EXPLAIN SELECT * FROM part_test WHERE crt_time = '2016-10-25 00:00:00'::timestamp;
                                       QUERY PLAN                                    
    ---------------------------------------------------------------------------------
     Append  (cost=0.00..1.91 rows=1 width=45)
       ->  Seq Scan on part_test_122  (cost=0.00..1.91 rows=1 width=45)
             Filter: (crt_time = '2016-10-25 00:00:00'::timestamp without time zone)
    (3 rows)

    Bölümlenmiş tablodaki aşağıdaki kısıtlama, pg_pathman'ın dönüşümü otomatik olarak gerçekleştirdiğini gösterir. Buna karşılık, geleneksel kalıtım SELECT * FROM part_test WHERE crt_time = '2016-10-25 00:00:00'::timestamp; gibi bir ifade için bölüm budamayı gerçekleştiremez.

    \d+ part_test_122
                                    Table "public.part_test_122"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_122_3_check" CHECK (get_hash_part_idx(timestamp_hash(crt_time), 128) = 122)
    Inherits: part_test
Not

Hash ile bölümlenmiş tabloları kullanırken şu en iyi uygulamaları izleyin:

  • Bölüm anahtarı sütunu NOT NULL kısıtlamasına sahip olmalıdır.

  • Engelsiz taşıma işlevini kullanın.

  • Veri taşıma tamamlandıktan sonra ana tabloyu devre dışı bırakın.

  • pg_pathman ifade biçimiyle sınırlı değildir. Bu nedenle select * from part_test where crt_time = '2016-10-25 00:00:00'::timestamp; gibi bir ifade de hash bölümleme için kullanılabilir.

  • Hash bölümleme için bölüm anahtarı sütunu int veri türüyle sınırlı değildir. Otomatik dönüşüm için bir hash işlevi kullanılır.

Verileri bölümlere taşıyın

Bölümlenmiş tabloyu oluştururken ana tablodan veri taşımadıysanız verileri taşımak için engelsiz taşıma işlevini kullanabilirsiniz.

WITH tmp AS (DELETE FROM primary_table limit xx nowait returning *) INSERT INTO partition SELECT * FROM tmp;

Alternatif olarak, satırları işaretlemek ve ardından DELETE ve INSERT işlemlerini gerçekleştirmek için aşağıdaki ifadeyi kullanabilirsiniz.

SELECT array_agg(ctid) FROM primary_table limit xx FOR UPDATE nowait;

İşlevin tanımı şöyledir:

partition_table_concurrently(relation   REGCLASS,              -- OID of the primary table
                             batch_size INTEGER DEFAULT 1000,  -- Number of records to migrate in a single transaction batch
                             sleep_time FLOAT8 DEFAULT 1.0)    -- The duration to sleep before retrying if acquiring row locks fails. The task exits after 60 retries.

Örneğin:

SELECT partition_table_concurrently('part_test'::regclass,
                             10000,
                             1.0);

Arka plandaki veri taşıma görevini görüntüleyebilirsiniz.

SELECT * FROM pathman_concurrent_part_tasks;

Aralık bölümünü ayırın

Bir bölüm çok büyük hale gelirse bunu iki bölüme ayırabilirsiniz. Bu özellik şu anda yalnızca aralıkla bölümlenmiş tablolar için kullanılabilir. Aşağıdaki işlevi kullanabilirsiniz:

split_range_partition(partition      REGCLASS,            -- OID of the partition
                      split_value    ANYELEMENT,          -- Split value
                      partition_name TEXT DEFAULT NULL)   -- Name of the new partition table created after the split

Örneğin:

  1. Aralık bölümleme örneğinden bölümlenmiş tabloyu kullanın. Tablo şeması aşağıdaki gibidir.

    \d+ part_test
                                      Table "public.part_test"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Child tables: part_test_1,
                  part_test_10,
                  part_test_11,
                  part_test_12,
                  part_test_13,
                  part_test_14,
                  part_test_15,
                  part_test_16,
                  part_test_17,
                  part_test_18,
                  part_test_19,
                  part_test_2,
                  part_test_20,
                  part_test_21,
                  part_test_22,
                  part_test_23,
                  part_test_24,
                  part_test_3,
                  part_test_4,
                  part_test_5,
                  part_test_6,
                  part_test_7,
                  part_test_8,
                  part_test_9
    
    \d+ part_test_1
                                     Table "public.part_test_1"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_1_3_check" CHECK (crt_time >= '2016-10-25 00:00:00'::timestamp without time zone AND crt_time < '2016-11-25 00:00:00'::timestamp without time zone)
    Inherits: part_test
  2. Bölümü ayırın.

    SELECT split_range_partition('part_test_1'::regclass,              -- OID of the partition
                          '2016-11-10 00:00:00'::timestamp,     -- Split value
                          'part_test_1_2');                     -- Name of the partition table

    Ayırma işleminden sonraki iki tablo aşağıdaki gibidir:

    \d+ part_test_1
                                     Table "public.part_test_1"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_1_3_check" CHECK (crt_time >= '2016-10-25 00:00:00'::timestamp without time zone AND crt_time < '2016-11-10 00:00:00'::timestamp without time zone)
    Inherits: part_test
    
    \d+ part_test_1_2 
                                    Table "public.part_test_1_2"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_1_2_3_check" CHECK (crt_time >= '2016-11-10 00:00:00'::timestamp without time zone AND crt_time < '2016-11-25 00:00:00'::timestamp without time zone)
    Inherits: part_test

    Veriler otomatik olarak yeni bölümlere taşınır.

    SELECT count(*) FROM part_test_1;
     count 
    -------
       373
    (1 row)
    
    SELECT count(*) FROM part_test_1_2;
     count 
    -------
       360
    (1 row)

    Kalıtım ilişkisi aşağıdaki gibidir:

    \d+ part_test
                                      Table "public.part_test"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Child tables: part_test_1,
                  part_test_10,
                  part_test_11,
                  part_test_12,
                  part_test_13,
                  part_test_14,
                  part_test_15,
                  part_test_16,
                  part_test_17,
                  part_test_18,
                  part_test_19,
                  part_test_1_2,    -- New table
                  part_test_2,
                  part_test_20,
                  part_test_21,
                  part_test_22,
                  part_test_23,
                  part_test_24,
                  part_test_3,
                  part_test_4,
                  part_test_5,
                  part_test_6,
                  part_test_7,
                  part_test_8,
                  part_test_9

Aralık bölümlerini birleştirin

Bu özellik şu anda yalnızca aralık bölümleme için kullanılabilir ve bölümlerin bitişik olması gerekir. Aşağıdaki işlevi çağırabilirsiniz:

--- Specify the two partitions to merge  
merge_range_partitions(partition1 REGCLASS, partition2 REGCLASS)

Bir örnek şöyledir:

  1. Birleştirme işlemini gerçekleştirmek için Aralık bölümünü ayırma örneğinden tabloları kullanın.

    SELECT merge_range_partitions('part_test_1'::regclass, 'part_test_1_2'::regclass);
    Not

    Bitişik olmayan bölümleri birleştirmeye çalışırsanız bir hata döner.

    SELECT merge_range_partitions('part_test_2'::regclass, 'part_test_12'::regclass) ;
    ERROR:  merge failed, partitions must be adjacent
    CONTEXT:  PL/pgSQL function merge_range_partitions_internal(regclass,regclass,regclass,anyelement) line 27 at RAISE
    SQL statement "SELECT public.merge_range_partitions_internal($1, $2, $3, NULL::timestamp without time zone)"
    PL/pgSQL function merge_range_partitions(regclass,regclass) line 44 at EXECUTE
  2. Birleştirmeden sonra özgün bölümlerden biri silinir.

    \d part_test_1_2
    Did not find any relation named "part_test_1_2".
    
    \d part_test_1
                 Table "public.part_test_1"
      Column  |            Type             | Modifiers 
    ----------+-----------------------------+-----------
     id       | integer                     | 
     info     | text                        | 
     crt_time | timestamp without time zone | not null
    Check constraints:
        "pathman_part_test_1_3_check" CHECK (crt_time >= '2016-10-25 00:00:00'::timestamp without time zone AND crt_time < '2016-11-25 00:00:00'::timestamp without time zone)
    Inherits: part_test
    
    SELECT count(*) FROM part_test_1;
     count 
    -------
       733
    (1 row)

Aralık bölümü ekleyin

Ana tablo zaten bölümlenmişse yeni bölümleri birkaç şekilde ekleyebilirsiniz. Bu bölümde üç yöntem açıklanmaktadır: aralık bölümü ekleme, aralık bölümü önekleme ve belirli bir başlangıç değerine sahip aralık bölümü ekleme.

Aralık bölümü ekleyin (sona)

Aralık bölümü eklediğinizde (bölümü sona eklediğinizde) bölümlenmiş tablonun ilk oluşturulması sırasında belirtilen aralık kullanılır. Aşağıda gösterildiği gibi her bölümlenmiş tablo için ilk aralığı bulmak üzere pathman_config görünümünü sorgulayabilirsiniz:

SELECT * FROM pathman_config;
  partrel  | attname  | parttype | range_interval 
-----------+----------+----------+----------------
 part_test | crt_time |        2 | 1 mon
(1 row)

Bölüm ekleme işlevi aşağıdaki gibidir. Bir tablo alanı belirtme şu anda desteklenmemektedir.

append_range_partition(parent         REGCLASS,            -- OID of the primary table
                       partition_name TEXT DEFAULT NULL,   -- Name of the new partition table. This is optional.
                       tablespace     TEXT DEFAULT NULL)   -- Tablespace for the new partition table. This is optional.

Bir örnek şöyledir:

SELECT append_range_partition('part_test'::regclass);

\d+ part_test_25
                                Table "public.part_test_25"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Check constraints:
    "pathman_part_test_25_3_check" CHECK (crt_time >= '2018-10-25 00:00:00'::timestamp without time zone AND crt_time < '2018-11-25 00:00:00'::timestamp without time zone)
Inherits: part_test

\d+ part_test_24
                                Table "public.part_test_24"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Check constraints:
    "pathman_part_test_24_3_check" CHECK (crt_time >= '2018-09-25 00:00:00'::timestamp without time zone AND crt_time < '2018-10-25 00:00:00'::timestamp without time zone)
Inherits: part_test

Aralık bölümü önekleyin (başa)

Aralık bölümünü öne eklemek için aşağıdaki işlevi kullanın:

prepend_range_partition(parent         REGCLASS,
                        partition_name TEXT DEFAULT NULL,
                        tablespace     TEXT DEFAULT NULL)

Örneğin:

SELECT prepend_range_partition('part_test'::regclass);

\d+ part_test_26
                                Table "public.part_test_26"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Check constraints:
    "pathman_part_test_26_3_check" CHECK (crt_time >= '2016-09-25 00:00:00'::timestamp without time zone AND crt_time < '2016-10-25 00:00:00'::timestamp without time zone)
Inherits: part_test

\d+ part_test_1
                                 Table "public.part_test_1"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Check constraints:
    "pathman_part_test_1_3_check" CHECK (crt_time >= '2016-10-25 00:00:00'::timestamp without time zone AND crt_time < '2016-11-25 00:00:00'::timestamp without time zone)
Inherits: part_test

Belirli bir başlangıç değeriyle bölüm ekleyin

Başlangıç ve bitiş değerlerini belirterek bir aralık bölümü ekleyebilirsiniz. Bölüm, mevcut bölümlerle çakışmadığı sürece oluşturulabilir. Bu yöntem bitişik bölümler oluşturmanızı gerektirmez. Örneğin, mevcut bölümler 2010'dan 2015'e kadar olan aralığı kapsıyorsa 2015'ten 2020'ye kadar olan aralığı kapsamadan doğrudan 2020 yılı için bir bölüm oluşturabilirsiniz. İşlev aşağıdaki gibidir:

add_range_partition(relation       REGCLASS,    -- OID of the primary table
                    start_value    ANYELEMENT,  -- Start value
                    end_value      ANYELEMENT,  -- End value
                    partition_name TEXT DEFAULT NULL,  -- Name of the partition
                    tablespace     TEXT DEFAULT NULL)  -- Tablespace where the partition is created

Örneğin:

SELECT add_range_partition('part_test'::regclass,    -- OID of the primary table
                    '2020-01-01 00:00:00'::timestamp,  -- Start value
                    '2020-02-01 00:00:00'::timestamp); -- End value

\d+ part_test_27
                                Table "public.part_test_27"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Check constraints:
    "pathman_part_test_27_3_check" CHECK (crt_time >= '2020-01-01 00:00:00'::timestamp without time zone AND crt_time < '2020-02-01 00:00:00'::timestamp without time zone)
Inherits: part_test

Bölümü silin

  • Tek bir aralık bölümünü silmek için aşağıdaki işlevi kullanın:

    drop_range_partition(partition TEXT,   -- Name of the partition
                        delete_data BOOLEAN DEFAULT TRUE)  -- Specifies whether to delete the partition data. If false, the data is migrated to the primary table.  
    
    Drop RANGE partition and all of its data if delete_data is true.
  • Tüm bölümleri silmek ve verilerin ana tabloya taşınıp taşınmayacağını belirtmek için aşağıdaki işlevi kullanın:

    drop_partitions(parent      REGCLASS,
                    delete_data BOOLEAN DEFAULT FALSE)
    
    Drop partitions of the parent table (both foreign and local relations). 
    If delete_data is false, the data is copied to the parent table first. 
    Default is false.

Örneğin:

  • Bir bölümü silin ve verilerini ana tabloya taşıyın.

    SELECT drop_range_partition('part_test_1',false);
    SELECT drop_range_partition('part_test_2',false);

    Ana tablonun mevcut veri hacmini sorgulayın:

    SELECT count(*) FROM part_test;
     count 
    -------
     10000
    (1 row)
  • Bir bölümü ve verilerini silin. Veriler ana tabloya taşınmaz.

    SELECT drop_range_partition('part_test_3',true);

    Ana tablonun mevcut veri hacmini sorgulayın:

    SELECT count(*) FROM part_test;
     count 
    -------
      9256
    (1 row)
    
    SELECT count(*) FROM ONLY part_test;
     count 
    -------
      1453
    (1 row)
  • Tüm bölümleri silin.

    SELECT drop_partitions('part_test'::regclass, false);  -- Delete all partition tables and migrate the data to the primary table

    Ana tablodaki verileri sorgulayın:

    SELECT count(*) FROM part_test;
     count 
    -------
      9256
    (1 row)

Bölümü iliştirin

Mevcut bir tabloyu bölümlenmiş ana tabloya iliştirebilirsiniz. Mevcut tablonun, bırakılan sütunlar dahil olmak üzere ana tabloyla aynı şemaya sahip olması gerekir. Tutarlılığı kontrol etmek için pg_attribute görünümünü inceleyebilirsiniz. İşlev aşağıdaki gibidir:

attach_range_partition(relation    REGCLASS,    -- OID of the primary table
                       partition   REGCLASS,    -- OID of the partition table
                       start_value ANYELEMENT,  -- Start value
                       end_value   ANYELEMENT)  -- End value

Bir örnek şöyledir:

  1. Bölüm olarak kullanılacak bir tablo oluşturun.

    CREATE TABLE part_test_1 (like part_test including all);
  2. Mevcut tabloyu ana tabloya iliştirin.

    \d+ part_test
                                      Table "public.part_test"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    
    \d+ part_test_1
                                     Table "public.part_test_1"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    
    SELECT attach_range_partition('part_test'::regclass, 'part_test_1'::regclass, '2019-01-01 00:00:00'::timestamp, '2019-02-01 00:00:00'::timestamp);

    Bir bölümü iliştirdiğinizde kalıtım ilişkisi ve kısıtlamalar otomatik olarak oluşturulur.

    \d+ part_test_1
                                     Table "public.part_test_1"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_1_3_check" CHECK (crt_time >= '2019-01-01 00:00:00'::timestamp without time zone AND crt_time < '2019-02-01 00:00:00'::timestamp without time zone)
    Inherits: part_test

Bölümü ayırın

Bir bölümü ana tablonun kalıtım hiyerarşisinden kaldırabilirsiniz. Bu işlem verileri silmez ancak kalıtım ilişkisini ve kısıtlamaları kaldırır. İşlev aşağıdaki gibidir:

detach_range_partition(partition REGCLASS)  -- Specify the partition name to convert it into a standard table

Bir örnek şöyledir:

  1. Ana tablonun ve bölümün mevcut veri hacmini sorgulayın.

    SELECT count(*) FROM part_test;
     count 
    -------
      9256
    (1 row)
    
    SELECT count(*) FROM part_test_2;
     count 
    -------
       733
    (1 row)
  2. Bölümü ayırın.

    SELECT detach_range_partition('part_test_2');

    Ana tablonun ve bölümün mevcut veri hacmini sorgulayın.

    SELECT count(*) FROM part_test_2;
     count 
    -------
       733
    (1 row)
    
    SELECT count(*) FROM part_test;
     count 
    -------
      8523
    (1 row)

pg_pathman'ı devre dışı bırakın

Tek bir bölümlenmiş ana tablo için pg_pathman'ı devre dışı bırakabilirsiniz. İşlev aşağıdaki gibidir:

Önemli

disable_pathman_for işlemi geri alınamaz. Dikkatli kullanın.

\sf disable_pathman_for
CREATE OR REPLACE FUNCTION public.disable_pathman_for(parent_relid regclass)
 RETURNS void
 LANGUAGE plpgsql
 STRICT
AS $function$
BEGIN
        PERFORM public.validate_relname(parent_relid);

        DELETE FROM public.pathman_config WHERE partrel = parent_relid;
        PERFORM public.drop_triggers(parent_relid);

        /* Notify backend about changes */
        PERFORM public.on_remove_partitions(parent_relid);
END
$function$

Aşağıda bir örnek verilmiştir:

SELECT disable_pathman_for('part_test');

\d+ part_test
                                  Table "public.part_test"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Child tables: part_test_10,
              part_test_11,
              part_test_12,
              part_test_13,
              part_test_14,
              part_test_15,
              part_test_16,
              part_test_17,
              part_test_18,
              part_test_19,
              part_test_20,
              part_test_21,
              part_test_22,
              part_test_23,
              part_test_24,
              part_test_25,
              part_test_26,
              part_test_27,
              part_test_28,
              part_test_29,
              part_test_3,
              part_test_30,
              part_test_31,
              part_test_32,
              part_test_33,
              part_test_34,
              part_test_35,
              part_test_4,
              part_test_5,
              part_test_6,
              part_test_7,
              part_test_8,
              part_test_9

\d+ part_test_10
                                Table "public.part_test_10"
  Column  |            Type             | Modifiers | Storage  | Stats target | Description 
----------+-----------------------------+-----------+----------+--------------+-------------
 id       | integer                     |           | plain    |              | 
 info     | text                        |           | extended |              | 
 crt_time | timestamp without time zone | not null  | plain    |              | 
Check constraints:
    "pathman_part_test_10_3_check" CHECK (crt_time >= '2017-06-25 00:00:00'::timestamp without time zone AND crt_time < '2017-07-25 00:00:00'::timestamp without time zone)
Inherits: part_test

pg_pathman uzantısını devre dışı bıraktıktan sonra kalıtım ilişkileri ve kısıtlamalar değişmeden kalır. Tek fark, pg_pathman uzantısının artık yürütme planında özel bir tarama sağlamamasıdır. Uzantı devre dışı bırakıldıktan sonraki yürütme planı aşağıdaki gibidir:

EXPLAIN SELECT * FROM part_test WHERE crt_time='2017-06-25 00:00:00'::timestamp;
                                   QUERY PLAN                                    
---------------------------------------------------------------------------------
 Append  (cost=0.00..16.00 rows=2 width=45)
   ->  Seq Scan on part_test  (cost=0.00..0.00 rows=1 width=45)
         Filter: (crt_time = '2017-06-25 00:00:00'::timestamp without time zone)
   ->  Seq Scan on part_test_10  (cost=0.00..16.00 rows=1 width=45)
         Filter: (crt_time = '2017-06-25 00:00:00'::timestamp without time zone)
(5 rows)

Gelişmiş bölüm yönetimi

Ana tabloyu devre dışı bırakın

Ana tablodaki tüm veriler bölümlere taşındıktan sonra ana tabloyu devre dışı bırakabilirsiniz. İşlev aşağıdaki gibidir:

set_enable_parent(relation REGCLASS, value BOOLEAN)

Include/exclude parent table into/from query plan. 

In original PostgreSQL planner parent table is always included into query plan even if it's empty which can lead to additional overhead. 

You can use disable_parent() if you are never going to use parent table as a storage. 

Default value depends on the partition_data parameter that was specified during initial partitioning in create_range_partitions() or create_partitions_from_range() functions. 

If the partition_data parameter was true then all data have already been migrated to partitions and parent table disabled. 

Otherwise it is enabled.

Örneğin:

SELECT set_enable_parent('part_test', false);

Bölümleri otomatik olarak genişletin

Aralıkla bölümlenmiş tablolar için otomatik bölüm oluşturmayı etkinleştirebilirsiniz. Yeni eklenen veriler mevcut bölümlerin aralığına düşmezse yeni bir bölüm otomatik olarak oluşturulur.

set_auto(relation REGCLASS, value BOOLEAN)

Enable/disable auto partition propagation (only for RANGE partitioning). 

It is enabled by default.

Örneğin:

  1. Test tablosunun mevcut bölümlerini sorgulayın.

    \d+ part_test
                                      Table "public.part_test"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Child tables: part_test_10,
                  part_test_11,
                  part_test_12,
                  part_test_13,
                  part_test_14,
                  part_test_15,
                  part_test_16,
                  part_test_17,
                  part_test_18,
                  part_test_19,
                  part_test_20,
                  part_test_21,
                  part_test_22,
                  part_test_23,
                  part_test_24,
                  part_test_25,
                  part_test_26,
                  part_test_3,
                  part_test_4,
                  part_test_5,
                  part_test_6,
                  part_test_7,
                  part_test_8,
                  part_test_9
    
    \d+ part_test_26
                                    Table "public.part_test_26"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_26_3_check" CHECK (crt_time >= '2018-09-25 00:00:00'::timestamp without time zone AND crt_time < '2018-10-25 00:00:00'::timestamp without time zone)
    Inherits: part_test
    
    \d+ part_test_25
                                    Table "public.part_test_25"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Check constraints:
        "pathman_part_test_25_3_check" CHECK (crt_time >= '2018-08-25 00:00:00'::timestamp without time zone AND crt_time < '2018-09-25 00:00:00'::timestamp without time zone)
    Inherits: part_test
  2. Mevcut bölümlerin aralığının dışındaki bir değeri ekleyin. Bu işlem, ilk kurulum sırasında belirtilen aralığa göre gerekli sayıda bölümü otomatik olarak oluşturur. Bu işlemin uzun sürebileceğini unutmayın.

    INSERT INTO part_test VALUES (1,'test','2222-01-01'::timestamp);

    Mevcut bölüm durumunu sorgulayabilirsiniz:

    \d+ part_test
                                      Table "public.part_test"
      Column  |            Type             | Modifiers | Storage  | Stats target | Description 
    ----------+-----------------------------+-----------+----------+--------------+-------------
     id       | integer                     |           | plain    |              | 
     info     | text                        |           | extended |              | 
     crt_time | timestamp without time zone | not null  | plain    |              | 
    Child tables: part_test_10,
                  part_test_100,
                  part_test_1000,
                  part_test_1001,
                  ......
Not

Aralıkla bölümlenmiş tablolar için otomatik bölüm oluşturmayı etkinleştirmemenizi öneririz. Çok sayıda bölümün otomatik olarak oluşturulması oldukça zaman alıcı olabilir.

Geri arama işlevleri

Geri arama işlevi, her bölüm oluşturulduğunda otomatik olarak tetiklenen bir işlevdir. Örneğin, DDL mantıksal çoğaltma için bir geri arama işlevi kullanarak DDL ifadelerini bir tabloda kaydedebilirsiniz. Geri arama ayarlama işlevi aşağıdaki gibidir:

set_init_callback(relation REGCLASS, callback REGPROC DEFAULT 0)

Set partition creation callback to be invoked for each attached or created partition (both HASH and RANGE). 

The callback must have the following signature: 

part_init_callback(args JSONB) RETURNS VOID. 

Parameter arg consists of several fields whose presence depends on partitioning type:

/* RANGE-partitioned table abc (child abc_4) */
{
    "parent":    "abc",
    "parttype":  "2",
    "partition": "abc_4",
    "range_max": "401",
    "range_min": "301"
}

/* HASH-partitioned table abc (child abc_0) */
{
    "parent":    "abc",
    "parttype":  "1",
    "partition": "abc_0"
}

Örneğin:

  1. Bir geri arama işlevi oluşturun.

    CREATE OR REPLACE FUNCTION f_callback_test(jsonb) RETURNS void AS
    $$
    DECLARE
    BEGIN
      CREATE TABLE if NOT EXISTS rec_part_ddl(id serial primary key, parent name, parttype int, partition name, range_max text, range_min text);
      if ($1->>'parttype')::int = 1 then
        raise notice 'parent: %, parttype: %, partition: %', $1->>'parent', $1->>'parttype', $1->>'partition';
        INSERT INTO rec_part_ddl(parent, parttype, partition) values (($1->>'parent')::name, ($1->>'parttype')::int, ($1->>'partition')::name);
      elsif ($1->>'parttype')::int = 2 then
        raise notice 'parent: %, parttype: %, partition: %, range_max: %, range_min: %', $1->>'parent', $1->>'parttype', $1->>'partition', $1->>'range_max', $1->>'range_min';
        INSERT INTO rec_part_ddl(parent, parttype, partition, range_max, range_min) values (($1->>'parent')::name, ($1->>'parttype')::int, ($1->>'partition')::name, $1->>'range_max', $1->>'range_min');
      END if;
    END;
    $$ LANGUAGE plpgsql strict;
  2. Bir test tablosu hazırlayın.

    CREATE TABLE tt(id int, info text, crt_time timestamp not null);
    
    --- Set the callback function for the test table
    SELECT set_init_callback('tt'::regclass, 'f_callback_test'::regproc);
    
    --- Create partitions
    SELECT                                                           
    create_range_partitions('tt'::regclass,                    -- OID of the primary table
                            'crt_time',                        -- Name of the partition key column
                            '2016-10-25 00:00:00'::timestamp,  -- Start value
                            interval '1 month',                -- Interval. The interval data type is used for time-based partitioned tables.
                            24,                                -- Number of partitions to create
                            false) ;
  3. Geri arama işlevinin çağrılıp çağrılmadığını kontrol edin.

    SELECT * FROM rec_part_ddl;

    Aşağıdaki sonuç döner:

     id | parent | parttype | partition |      range_max      |      range_min      
    ----+--------+----------+-----------+---------------------+---------------------
      1 | tt     |        2 | tt_1      | 2016-11-25 00:00:00 | 2016-10-25 00:00:00
      2 | tt     |        2 | tt_2      | 2016-12-25 00:00:00 | 2016-11-25 00:00:00
      3 | tt     |        2 | tt_3      | 2017-01-25 00:00:00 | 2016-12-25 00:00:00
      4 | tt     |        2 | tt_4      | 2017-02-25 00:00:00 | 2017-01-25 00:00:00
      5 | tt     |        2 | tt_5      | 2017-03-25 00:00:00 | 2017-02-25 00:00:00
      6 | tt     |        2 | tt_6      | 2017-04-25 00:00:00 | 2017-03-25 00:00:00
      7 | tt     |        2 | tt_7      | 2017-05-25 00:00:00 | 2017-04-25 00:00:00
      8 | tt     |        2 | tt_8      | 2017-06-25 00:00:00 | 2017-05-25 00:00:00
      9 | tt     |        2 | tt_9      | 2017-07-25 00:00:00 | 2017-06-25 00:00:00
     10 | tt     |        2 | tt_10     | 2017-08-25 00:00:00 | 2017-07-25 00:00:00
     11 | tt     |        2 | tt_11     | 2017-09-25 00:00:00 | 2017-08-25 00:00:00
     12 | tt     |        2 | tt_12     | 2017-10-25 00:00:00 | 2017-09-25 00:00:00
     13 | tt     |        2 | tt_13     | 2017-11-25 00:00:00 | 2017-10-25 00:00:00
     14 | tt     |        2 | tt_14     | 2017-12-25 00:00:00 | 2017-11-25 00:00:00
     15 | tt     |        2 | tt_15     | 2018-01-25 00:00:00 | 2017-12-25 00:00:00
     16 | tt     |        2 | tt_16     | 2018-02-25 00:00:00 | 2018-01-25 00:00:00
     17 | tt     |        2 | tt_17     | 2018-03-25 00:00:00 | 2018-02-25 00:00:00
     18 | tt     |        2 | tt_18     | 2018-04-25 00:00:00 | 2018-03-25 00:00:00
     19 | tt     |        2 | tt_19     | 2018-05-25 00:00:00 | 2018-04-25 00:00:00
     20 | tt     |        2 | tt_20     | 2018-06-25 00:00:00 | 2018-05-25 00:00:00
     21 | tt     |        2 | tt_21     | 2018-07-25 00:00:00 | 2018-06-25 00:00:00
     22 | tt     |        2 | tt_22     | 2018-08-25 00:00:00 | 2018-07-25 00:00:00
     23 | tt     |        2 | tt_23     | 2018-09-25 00:00:00 | 2018-08-25 00:00:00
     24 | tt     |        2 | tt_24     | 2018-10-25 00:00:00 | 2018-09-25 00:00:00
    (24 rows)