web-dev-qa-db-ja.com

型キャストによるクエリの最適化

考えられるすべてのインデックスがあるにもかかわらず、クエリに時間がかかりすぎていました。

最終的に、私はJOIN .. ONクエリ内で列のコンテンツを別のデータ型にキャストしていました。テーブルAの列はvarchar型であり、テーブルBの一致する列はinteger型であるためです。

コードを変更して、テーブルAから必要な行を挿入し、列をvarcharからintegerにキャストする一時テーブルを使用するように変更しました。

クエリ速度が約1000倍に向上しました。

zone_site

CREATE TABLE traitements.zones_sites (
    pkid serial PRIMARY KEY,
    pkid_site integer NOT NULL,
    origine varchar(50) NOT NULL, 
    origine_id varchar(255) NOT NULL,  -- can't be converted to int
    catégorie varchar(255) NOT NULL,
    horodatage timestamp NOT NULL,
    geom geometry(MultiPolygon,2154),
    précision_contour varchar(100),
    statut varchar(100),
    détails_jsonb text,
    CONSTRAINT enforce_dims_geomloc CHECK (ST_NDims(geom) = 2)
);

CREATE INDEX zones_sites_idx_pkid_site  ON traitements.zones_sites (pkid_site);
CREATE INDEX zones_sites_géométrie ON traitements.zones_sites USING Gist (geom);
CREATE INDEX zones_sites_précision_contour ON traitements.zones_sites (précision_contour);
CREATE INDEX zones_sites_idx_catégorie ON traitements.zones_sites (catégorie);
CREATE INDEX zones_sites_idx_origine ON traitements.zones_sites (origine);
CREATE INDEX zones_sites_idx_statut ON traitements.zones_sites (statut);
ALTER TABLE  traitements.zones_sites
    ADD CONSTRAINT zones_sites_references_sites_candidats FOREIGN KEY (pkid_site)
    REFERENCES traitements.sites_candidats(pkid) ON DELETE CASCADE;

Dba.SEに関する関連質問:

残念ながら、列traitements.zones_sites.origine_idは、integer型にすることはできません。これは、いくつかの発信元からの識別子を保持しているためです。

元のクエリ:

SELECT emprises.pkid, emprises.pkid_site, emprises.origine, parcelles.idpar
FROM traitements.zones_sites AS emprises
JOIN parcelles ON parcelles.idpk::varchar = emprises.origine_id
WHERE emprises.catégorie = 'emprise_site'
AND emprises.précision_contour = 'contour_inconnu'
AND emprises.origine = 'xxxxxxxxx';

変更されたコード:

CREATE TEMPORARY TABLE temp_emprises (
    pkid serial PRIMARY KEY,
    pkid_site integer NOT NULL,
    origine varchar(50) NOT NULL, 
    idpk_parcelle integer NOT NULL
);

INSERT INTO temp_emprises (pkid, pkid_site, origine, idpk_parcelle)
SELECT pkid, pkid_site, origine, origine_id::integer 
FROM traitements.zones_sites AS emprises
WHERE emprises.catégorie = 'emprise_site'
AND emprises.précision_contour = 'contour_inconnu'
AND emprises.origine = 'xxxxxxxxx';

CREATE INDEX ON temp_emprises(idpk_parcelle);

SELECT emprises.pkid, emprises.pkid_site, emprises.origine, parcelles.idpar
FROM temp_emprises AS emprises
JOIN parcelles ON parcelles.idpk = emprises.idpk_parcelle;

より良い解決策はありますか?

1
Darth Kangooroo

適切なソリューション

あなたのコメント(あなたの質問に追加しました)は、問題の根本を開示しています

残念ながら、列_traitements.zones_sites.origine_id_はinteger型にすることはできません。これは、いくつかの起点からの識別子を保持しているため、一部が整数ではないためです。

おおよそ正規化されたDB設計は、同じ列に異なる種類のデータを混在させることを避けます。次に、真の整数値をintegerタイプの列に入れ、残りを(a)text列に入れることができます。そもそも問題はありません。

現在のデザインにこだわりながら

あなたが主張したように、列_origine_id_は、クエリ内の行の選択のために整数にキャストできます、私はこれより速い代替案を提案します:partial expression index

_CREATE INDEX zones_sites_idx_origine_part_int ON traitements.zones_sites ((origine_id::int))
WHERE catégorie = 'emprise_site'
AND   précision_contour = 'contour_inconnu'
AND   origine = 'xxxxxxxxx';
_

INSERTのtempテーブルへのキャストが機能することが保証されている場合、このインデックスも機能します。

少し変更されたクエリ:

_SELECT z.pkid, z.pkid_site, z.origine, p.idpar
FROM   traitements.zones_sites z
JOIN   parcelles               p ON p.idpk = z.origine_id::int
WHERE  z.catégorie = 'emprise_site'
AND    z.précision_contour = 'contour_inconnu'
AND    z.origine = 'xxxxxxxxx';
_

部分インデックスもクエリを完全にサポートするため、現在のソリューションは比較してスローモーションのように見えるはずです。また、シンプルで信頼性も高くなっています。

他に必要な唯一のインデックスは、parcelles(idpk)のインデックスです。
parcellesから必要な他の唯一の列はidparであるため、そのテーブルが大きく、書き込み負荷が低いため、 index-only scans それ以外の場合、parcelles(idpk, idpar)の(追加の)複数列インデックスにより、さらに高速になります。

さらに高速ですが、次の INCLUDE列を持つ真のカバーするインデックスを持つPostgres 11

コード例と関連する回答:

3

一時テーブルを作成しなかった場合でも、それがどれほど速くなるか想像してみてください。

SELECT emprises.pkid, emprises.pkid_site , emprises.origine, parcelles.idpar
    FROM traitements.zones_sites AS emprises
    JOIN parcelles
        ON parcelles.idpk = emprises.origine_id::integer
    WHERE emprises.catégorie = 'emprise_site'
    AND emprises.précision_contour = 'contour_inconnu'
    AND emprises.origine = 'xxxxxxxxx'
0
Gerard H. Pille