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

Postgresのヒントとコツ

    毎日Postgresを使用していますか? Postgresと通信するアプリケーションコードを記述しますか?次に、作業を高速化するのに役立つ、一口サイズのSQLスニペットを確認してください。

    複数の行を1つのステートメントに挿入

    INSERTステートメントは、1つのステートメントに複数の行を挿入できます。

    INSERT INTO planets (name, gravity)
         VALUES ('earth',    9.8),
                ('mars',     3.7),
                ('jupiter', 23.1);

    INSERTでできることについて詳しくはこちらをご覧ください。

    行を挿入し、自動的に割り当てられた値を返します

    DEFAULT / serial / IDENTITY構文で自動生成された値は、RETURNING句を使用してINSERTステートメントで返すことができます。アプリケーションコードの観点からは、このようなINSERTはレコードセットを返すSELECTのように実行されます。

    -- table with 2 column values auto-generated on INSERT
    CREATE TABLE items (
        slno       serial      PRIMARY KEY,
        name       text        NOT NULL,
        created_at timestamptz DEFAULT now()
    );
    
    INSERT INTO items (name)
         VALUES ('wooden axe'),
                ('loom'),
                ('eye of ender')
      RETURNING name, slno, created_at;
    
    -- returns:
    --      name     | slno |          created_at
    -- --------------+------+-------------------------------
    --  wooden axe   |    1 | 2020-08-17 05:35:45.962725+00
    --  loom         |    2 | 2020-08-17 05:35:45.962725+00
    --  eye of ender |    3 | 2020-08-17 05:35:45.962725+00
    自動生成されたUUID主キー

    さまざまな理由で、主キーの代わりにUUIDが使用されることがあります。シリアルまたはIDENTITYの代わりにUUIDを使用する方法は次のとおりです。

    CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
    
    CREATE TABLE items (
        id    uuid DEFAULT uuid_generate_v4(),
        name  text NOT NULL
    );
    
    INSERT INTO items (name)
         VALUES ('wooden axe'),
                ('loom'),
                ('eye of ender')
      RETURNING id, name;
      
    -- returns:
    --                   id                  |     name
    -- --------------------------------------+--------------
    --  1cfaae8c-61ff-4e82-a656-99263b7dd0ae | wooden axe
    --  be043a89-a51b-4d8b-8378-699847113d46 | loom
    --  927d52eb-c175-4a97-a0b2-7b7e81d9bc8e | eye of ender

    存在しない場合は挿入、そうでない場合は更新

    Postgres 9.5以降では、アップサートできます。 ON CONFLICTconstructを直接使用する:

    CREATE TABLE parameters (
        key   TEXT PRIMARY KEY,
        value TEXT
    );
    
    -- when "key" causes a constraint violation, update the "value"
    INSERT INTO parameters (key, value) 
         VALUES ('port', '5432')
    ON CONFLICT (key) DO
                UPDATE SET value=EXCLUDED.value;
    あるテーブルから別のテーブルに行をコピーする

    INSERTステートメントには、SELECTステートメントによって値を指定できる形式があります。これを使用して、あるテーブルから別のテーブルに行をコピーします。

    -- copy between tables with similar columns 
      INSERT INTO pending_quests
    SELECT * FROM quests
            WHERE progress < 100;
    
    -- supply some values from another table, some directly
      INSERT INTO archived_quests
           SELECT now() AS archival_date, *
             FROM quests
            WHERE completed;

    テーブルの一括読み込みを検討している場合は、テキストまたはCSVファイルから行を挿入するために使用できるCOPYコマンドも確認してください。

    削除された情報を削除して返す

    RETURNINGを使用できます 一括削除ステートメントを使用して削除された行から値を返す句:

    -- return the list of customers whose licenses were deleted after expiry
    DELETE FROM licenses
          WHERE now() > expiry_date
      RETURNING customer_name;
    あるテーブルから別のテーブルに行を移動する

    DELETE .. RETURNING でCTEを使用すると、1つのステートメントで1つのテーブルから別のテーブルに行を移動できます。 :

    -- move yet-to-start todo items from 2020 to 2021
    WITH ah_well AS (
        DELETE FROM todos_2020
              WHERE NOT started
          RETURNING *
    )
    INSERT INTO todos_2021
                SELECT * FROM ah_well;
    行を更新して更新された値を返す

    RETURNING句は、UPDATEでも使用できます。この方法で返すことができるのは、更新された列の新しい値のみであることに注意してください。

    -- grant random amounts of coins to eligible players
       UPDATE players
          SET coins = coins + (100 * random())::integer
        WHERE eligible
    RETURNING id, coins;

    更新された列の元の値が必要な場合:自己結合によって可能ですが、アトミック性の保証はありません。 SELECT .. FOR UPDATEを使用してみてください 代わりに。

    いくつかのランダムな行を更新し、更新された行を返します

    テーブルからランダムな行をいくつか選択し、それらを更新して、更新された行を一度に返す方法は次のとおりです。

    WITH lucky_few AS (
        SELECT id
          FROM players
      ORDER BY random()
         LIMIT 5
    )
       UPDATE players
          SET bonus = bonus + 100 
        WHERE id IN (SELECT id FROM lucky_few)
    RETURNING id;
    別のテーブルと同じようにテーブルを作成する

    CREATE TABLE .. LIKE構文を使用して、別の列と同じ列を持つテーブルを作成します。

    CREATE TABLE to_be_audited (LIKE purchases);

    デフォルトでは、これは同様のインデックス、制約、デフォルトなどを作成しません。それを行うには、Postgresに明示的に尋ねてください:

    CREATE TABLE to_be_audited (LIKE purchases INCLUDING ALL);

    ここで完全な構文を参照してください。

    ランダムな行セットを別のテーブルに抽出します

    Postgres 9.5以降、TABLESAMPLE機能を使用してテーブルから行のサンプルを抽出できます。現在、2つのサンプリング方法があり、 bernoulli 通常、必要なもの:

    -- copy 10% of today's purchases into another table
    INSERT INTO to_be_audited
         SELECT *
           FROM purchases
    TABLESAMPLE bernoulli(10)
          WHERE transaction_date = CURRENT_DATE;

    システム テーブルサンプリングメソッドは高速ですが、均一な分布を返しません。詳細については、ドキュメントを参照してください。

    選択クエリからテーブルを作成する

    CREATE TABLE .. ASコンストラクトを使用して、SELECTクエリからテーブルを作成し、データを入力することができます。

    CREATE TABLE to_be_audited AS
          SELECT *
            FROM purchases
     TABLESAMPLE bernoulli(10)
           WHERE transaction_date = CURRENT_DATE;

    結果のテーブルは、クエリが関連付けられていないマテリアライズドビューのようなものです。 CREATE TABLE..ASの詳細についてはこちらをご覧ください。

    ログに記録されていないテーブルを作成する

    ログなし テーブルはWALレコードによってサポートされていません。つまり、このようなテーブルの更新と削除は高速ですが、クラッシュ耐性がなく、複製できません。

    CREATE UNLOGGED TABLE report_20200817 (LIKE report_v3);
    一時テーブルの作成

    一時的 テーブルは暗黙的にログに記録されないテーブルであり、存続期間は短くなります。セッションの終了時(デフォルト)またはトランザクションの終了時に自動的に自己破壊します。

    一時テーブル内のデータは、セッション間で共有できません。複数のセッションで同じ名前の一時テーブルを作成できます。

    -- temp table for duration of the session
    CREATE TEMPORARY TABLE scratch_20200817_run_12 (LIKE report_v3);
    
    -- temp table that will self-destruct after current transaction
    CREATE TEMPORARY TABLE scratch_20200817_run_12
                          (LIKE report_v3)
                          ON COMMIT DROP;
    
    -- temp table that will TRUNCATE itself after current transaction
    CREATE TEMPORARY TABLE scratch_20200817_run_12
                           (LIKE report_v3)
                           ON COMMIT DELETE ROWS;
    コメントを追加

    コメントは、データベース内の任意のオブジェクトに追加できます。 pg_dumpを含む多くのツールは、これらを理解しています。有用なコメントは、クリーンアップ中の大量の問題を回避するだけかもしれません!

    COMMENT ON INDEX idx_report_last_updated
            IS 'needed for the nightly report app running in dc-03';
    
    COMMENT ON TRIGGER tgr_fix_column_foo
            IS 'mitigates the effect of bug #4857';
    アドバイザリロック

    アドバイザリロックを使用して、同じに接続された2つのアプリ間のアクションを調整できます。 データベース。この機能を使用して、たとえば、特定の操作に対してグローバルな分散ミューテックスを実装できます。ここのドキュメントでそれについてすべて読んでください。

    -- client 1: acquire a lock 
    SELECT pg_advisory_lock(130);
    -- ... do work ...
    SELECT pg_advisory_unlock(130);
    
    -- client 2: tries to do the same thing, but mutually exclusive
    -- with client 1
    SELECT pg_advisory_lock(130); -- blocks if anyone else has held lock with id 130
    
    -- can also do it without blocking:
    SELECT pg_try_advisory_lock(130);
    -- returns false if lock is being held by another client
    -- otherwise acquires the lock then returns true

    配列、JSON配列、または文字列に集約

    Postgresは、 GROUPの値を連結する集計関数を提供します toyield配列、JSON配列、または文字列:

    -- get names of each guild, with an array of ids of players that
    -- belong to that guild
      SELECT guilds.name AS guild_name, array_agg(players.id) AS players
        FROM guilds
        JOIN players ON players.guild_id = guilds.id
    GROUP BY guilds.id;
    
    -- same but the player list is a CSV string
      SELECT guilds.name, string_agg(players.id, ',') -- ...
      
    -- same but the player list is a JSONB array
      SELECT guilds.name, jsonb_agg(players.id) -- ...
      
    -- same but returns a nice JSONB object like so:
    -- { guild1: [ playerid1, playerid2, .. ], .. }
    SELECT jsonb_object_agg(guild_name, players) FROM (
      SELECT guilds.name AS guild_name, array_agg(players.id) AS players
        FROM guilds
        JOIN players ON players.guild_id = guilds.id
    GROUP BY guilds.id
    ) AS q;
    注文のある骨材

    このトピックについて説明している間、各グループ内で集計関数に渡される値の順序を設定する方法は次のとおりです。

    -- each state with a list of counties sorted alphabetically
      SELECT states.name, string_agg(counties.name, ',' ORDER BY counties.name)
        FROM states JOIN counties
        JOIN states.name = counties.state_name
    GROUP BY states.name;

    はい、関数呼び出しparanthesis内に末尾のORDER BY句があります。はい、構文がおかしいです。

    配列とネスト解除

    ARRAYコンストラクターを使用して、それぞれが1つの列を持つ行のセットを配列に変換します。データベースドライバー(JDBCなど)は、Postgresアレイをネイティブアレイにマップできる必要があり、操作が簡単な場合があります。

    -- convert rows (with 1 column each) into a 1-dimensional array
    SELECT ARRAY(SELECT id FROM players WHERE lifetime_spend > 10000);

    unnest関数はその逆を行い、配列内の各項目を行に変換します。これらは、値のリストとの相互結合に最も役立ちます。

        SELECT materials.name || ' ' || weapons.name
          FROM weapons
    CROSS JOIN UNNEST('{"wood","gold","stone","iron","diamond"}'::text[])
               AS materials(name);
    
    -- returns:
    --     ?column?
    -- -----------------
    --  wood sword
    --  wood axe
    --  wood pickaxe
    --  wood shovel
    --  gold sword
    --  gold axe
    -- (..snip..)
    選択ステートメントをUnionと組み合わせる

    UNION構文を使用して、複数の類似したSELECTからの結果を組み合わせることができます:

    SELECT name FROM weapons
    UNION
    SELECT name FROM tools
    UNION
    SELECT name FROM materials;

    CTEを使用して、結合された結果をさらに処理します。

    WITH fight_equipment AS (
        SELECT name, damage FROM weapons
        UNION
        SELECT name, damage FROM tools
    )
      SELECT name, damage
        FROM fight_equipment
    ORDER BY damage DESC
       LIMIT 5;

    UNIONと同じように、INTERSECTおよびEXCEPTコンストラクトもあります。ドキュメントでこれらの条項の詳細をご覧ください。

    Selectのクイックフィックス:case、colesce、nullif

    CASE、COALESCE、およびNULLIFを使用して、選択したデータをすばやく「修正」します。CASEはスイッチのようなものです。 Cのような言語の場合:

    SELECT id,
           CASE WHEN name='typ0' THEN 'typo' ELSE name END
      FROM items;
      
    SELECT CASE WHEN rating='G'  THEN 'General Audiences'
                WHEN rating='PG' THEN 'Parental Guidance'
                ELSE 'Other'
           END
      FROM movies;

    COALESCEを使用して、NULLの代わりに特定の値を置き換えることができます。

    -- use an empty string if ip is not available
    SELECT nodename, COALESCE(ip, '') FROM nodes;
    
    -- try to use the first available, else use '?'
    SELECT nodename, COALESCE(ipv4, ipv6, hostname, '?') FROM nodes;

    NULLIFは逆の方法で機能し、特定の値の代わりにNULLを使用できるようにします。

    -- use NULL instead of '0.0.0.0'
    SELECT nodename, NULLIF(ipv4, '0.0.0.0') FROM nodes;
    ランダムおよびシーケンシャルテストデータの生成

    ランダムデータを生成するさまざまな方法:

    -- 100 random dice rolls
    SELECT 1+(5 * random())::int FROM generate_series(1, 100);
    
    -- 100 random text strings (each 32 chars long)
    SELECT md5(random()::text) FROM generate_series(1, 100);
    
    -- 100 random text strings (each 36 chars long)
    SELECT uuid_generate_v4()::text FROM generate_series(1, 100);
    
    -- 100 random small text strings of varying lengths
    CREATE EXTENSION IF NOT EXISTS "pgcrypto";
    SELECT gen_random_bytes(1+(9*random())::int)::text
      FROM generate_series(1, 100);
    
    -- 100 random dates in 2019
    SELECT DATE(
             DATE '2019-01-01' + ((random()*365)::int || ' days')::interval
           )
      FROM generate_series(1, 100);
      
    -- 100 random 2-column data: 1st column integer and 2nd column string
    WITH a AS (
      SELECT ARRAY(SELECT random() FROM generate_series(1,100))
    ),
    b AS (
      SELECT ARRAY(SELECT md5(random()::text) FROM generate_series(1,100))
    )
    SELECT unnest(i), unnest(j)
      FROM a a(i), b b(j);
    
    -- a daily count for 2020, generally increasing over time
    SELECT i, ( (5+random()) * (row_number() over()) )::int
      FROM generate_series(DATE '2020-01-01', DATE '2020-12-31', INTERVAL '1 day')
           AS s(i);

    bernoulliを使用する テーブルからランダムな数の行を選択するためのテーブルサンプリング:

    -- select 15% of rows from the table, chosen randomly  
         SELECT *
           FROM purchases
    TABLESAMPLE bernoulli(15)

    generate_seriesを使用します 整数、日付、およびその他の増分可能な組み込み型の順次値を生成するには:

    -- generate integers from 1 to 100
    SELECT generate_series(1, 100);
    
    -- call the generated values table as "s" with a column "i", to use in
    -- CTEs and JOINs
    SELECT i FROM generate_series(1, 100) AS s(i);
    
    -- generate multiples of 3 in different ways
    SELECT 3*i FROM generate_series(1, 100) AS s(i);
    SELECT generate_series(1, 100, 3);
    
    -- works with dates too: here are all the Mondays in 2020:
    SELECT generate_series(DATE '2020-01-06', DATE '2020-12-31', INTERVAL '1 week');
    おおよその行数を取得

    COUNT(*)の恐ろしいパフォーマンス おそらくPostgresのアーキテクチャの最も醜い副産物です。巨大なテーブルのおおよその行数が必要な場合は、統計コレクターにクエリを実行することで、完全なCOUNTを回避できます。

    SELECT relname, n_live_tup FROM pg_stat_user_tables;

    ANALYZE後の結果は正確であり、行が変更されるにつれて徐々に不正確になります。正確なカウントが必要な場合は、これを使用しないでください。

    間隔タイプ

    間隔 typeは、列のデータ型として使用できるだけでなく、 dateに加算および減算することもできます。 およびタイムスタンプ 値:

    -- get licenses that expire within the next 7 days
    SELECT id
      FROM licenses
     WHERE expiry_date BETWEEN now() - INTERVAL '7 days' AND now();
     
    -- extend expiry date
    UPDATE licenses
       SET expiry_date = expiry_date + INTERVAL '1 year'
     WHERE id = 42;
    一括挿入の制約検証をオフにする
    -- add a constraint, set as "not valid"
    ALTER TABLE players
                ADD CONSTRAINT fk__players_guilds
                               FOREIGN KEY (guild_id)
                                REFERENCES guilds(id)
                NOT VALID;
    
    -- insert lots of rows into the table
    COPY players FROM '/data/players.csv' (FORMAT CSV);
    
    -- now validate the entire table
    ALTER TABLE players
                VALIDATE CONSTRAINT fk__players_guilds;
    テーブルまたはクエリをCSVファイルにダンプする
    -- dump the contents of a table to a CSV format file on the server
    COPY players TO '/tmp/players.csv' (FORMAT CSV);
    
    -- "header" adds a heading with column names
    COPY players TO '/tmp/players.csv' (FORMAT CSV, HEADER);
    
    -- use the psql command to save to your local machine
    \copy players TO '~/players.csv' (FORMAT CSV);
    
    -- can use a query instead of a table name
    \copy ( SELECT id, name, score FROM players )
          TO '~/players.csv'
          ( FORMAT CSV );
    スキーマデザインでより多くのネイティブデータ型を使用する

    Postgresには多くの組み込みデータ型が付属しています。これらのタイプのいずれかを使用してアプリケーションに必要なデータを表すと、多くのアプリケーションコードを節約し、開発を高速化し、エラーを減らすことができます。

    たとえば、データ型pointを使用して個人の場所を表す場合 polygonとしての関心領域 、次のコマンドで、その地域の人物がいるかどうかを確認できます。

    -- the @> operator checks if the region of interest (a "polygon") contains
    -- the person's location (a "point")
    SELECT roi @> person_location FROM live_tracking;

    ここにいくつかの興味深いPostgresデータ型とそれらに関する詳細情報を見つけることができる場所へのリンクがあります:

    • Cのような列挙型
    • ジオメトリタイプ–ポイント、ボックス、ラインセグメント、ライン、パス、ポリゴン、円
    • IPv4、IPv6、およびMACアドレス
    • 範囲タイプ–整数、日付、タイムスタンプの範囲
    • 任意のタイプの値を含むことができる配列
    • UUID – UUIDを使用する必要がある場合、または129バイトのランダムな整数を処理する必要がある場合は、uuidの使用を検討してください。 タイプとuuid-oscp UUIDの保存、生成、フォーマットのための拡張機能
    • INTERVALタイプを使用した日付と時刻の間隔
    • そしてもちろん、人気の高いJSONとJSONB
    バンドルされた拡張機能

    ほとんどのPostgresインストールには、多数の標準的な「拡張機能」が含まれています。拡張機能は、コアに含まれていない機能を提供するインストール可能な(そして完全にアンインストール可能な)コンポーネントです。データベースごとにインストールできます。

    これらのいくつかは非常に便利であり、それらを知るために時間を費やす価値があります:

    • pg_stat_statements –各SQLクエリの実行に関する統計
    • auto_explain –(遅い)クエリのクエリ実行プランをログに記録する
    • postgres_fdw、dblink、file_fdw –通常のテーブルのような他のデータソース(リモートのPostgresサーバー、MySQLサーバー、サーバーのファイルシステム上のファイルなど)にアクセスする方法
    • citext –「大文字と小文字を区別しないテキスト」データ型。あらゆる場所でlower()-ingよりも効率的です
    • hstore –キーと値のデータタイプ
    • pgcrypto –SHAハッシュ関数、暗号化

    1. 2点間の距離の計算(緯度、経度)

    2. データベースの削除中にエラーが発生しました(rmdir'.test \'ができません、errno:17)

    3. MariaDBクラスターのマルチクラウドフルデータベースクラスターフェイルオーバーオプション

    4. MariaDB CEIL()の説明