PnX-SI / GeoNature

Application de saisie et de synthèse des observations faune et flore
GNU General Public License v3.0
104 stars 102 forks source link

Synthèse - vue synthese_for_export #755

Closed jbrieuclp closed 4 years ago

jbrieuclp commented 5 years ago

Salut, C'est juste pour signaler que le WITH de la vue gn_synthese.v_synthese_for_export n'a pas lieu d'être. Au contraire il flingue les capacité de la base lors de l'exécution de cette requête. Si les jointures sont réalisées directement dans la requête (donc sans WITH) le cost lié à cette requête est 4.5 fois moindre. Je signale ça après avoir eu un out of memory pour une extraction départementale, extraction faite sous pgadmin, pas depuis l'appli.

La requête modifiée :

CREATE OR REPLACE VIEW gn_synthese.v_synthese_for_export AS 
 SELECT s.id_synthese AS "idSynthese",
    s.unique_id_sinp AS "permId",
    s.unique_id_sinp_grp AS "permIdGrp",
    s.count_min AS "denbrMin",
    s.count_max AS "denbrMax",
    s.meta_v_taxref AS "vTAXREF",
    s.sample_number_proof AS "sampleNumb",
    s.digital_proof AS "preuvNum",
    s.non_digital_proof AS "preuvNoNum",
    s.altitude_min AS "altMin",
    s.altitude_max AS "altMax",
    public.ST_astext(s.the_geom_4326) AS wkt,
    s.date_min AS "dateDebut",
    s.date_max AS "dateFin",
    s.validator AS validateur,
    s.observers AS observer,
    s.id_digitiser AS id_digitiser,
    s.determiner AS detminer,
    s.comment_context AS "obsCtx",
    s.comment_description AS "obsDescr",
    s.meta_create_date,
    s.meta_update_date,
    d.id_dataset AS "jddId",
    d.dataset_name AS "jddCode",
    d.id_acquisition_framework,
    t.cd_nom AS "cdNom",
    t.cd_ref AS "cdRef",
    s.nom_cite AS "nomCite",
    public.ST_x(public.ST_transform(s.the_geom_point, 2154)) AS x_centroid,
    public.ST_y(public.ST_transform(s.the_geom_point, 2154)) AS y_centroid,
    COALESCE(s.meta_update_date, s.meta_create_date) AS lastact,
    public.ST_asgeojson(s.the_geom_4326) AS geojson_4326,
    public.ST_asgeojson(s.the_geom_local) AS geojson_local,
    object_nature.label_default AS "ObjGeoTyp",
    grp_typ.label_default AS "methGrp",
    obs_meth.label_default AS "obsMeth",
    obs_technique.label_default AS "obsTech",
    bio_status.label_default AS "ocEtatBio",
    naturalness.label_default AS "ocNat",
    exist_proof.label_default AS "preuveOui",
    diffusion_level.label_default AS "difNivPrec",
    life_stage.label_default AS "ocStade",
    sex.label_default AS "ocSex",
    obj_count.label_default AS "objDenbr",
    type_count.label_default AS "denbrTyp",
    sensitivity.label_default AS "sensiNiv",
    observation_status.label_default AS "statObs",
    blurring.label_default AS "dEEFlou",
    source_status.label_default AS "statSource",
    info_geo_type.label_default AS "typInfGeo"
   FROM gn_synthese.synthese s
        JOIN taxonomie.taxref t ON t.cd_nom = s.cd_nom
        JOIN gn_meta.t_datasets d ON d.id_dataset = s.id_dataset
        JOIN gn_synthese.t_sources sources ON sources.id_source = s.id_source
        LEFT JOIN ref_nomenclatures.t_nomenclatures object_nature ON s.id_nomenclature_geo_object_nature = object_nature.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures grp_typ ON s.id_nomenclature_grp_typ = grp_typ.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures obs_meth ON s.id_nomenclature_obs_meth = obs_meth.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures obs_technique ON s.id_nomenclature_obs_technique = obs_technique.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures bio_status ON s.id_nomenclature_bio_status = bio_status.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures bio_condition ON s.id_nomenclature_bio_condition = bio_condition.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures naturalness ON s.id_nomenclature_naturalness = naturalness.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures exist_proof ON s.id_nomenclature_exist_proof = exist_proof.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures diffusion_level ON s.id_nomenclature_diffusion_level = diffusion_level.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures life_stage ON s.id_nomenclature_life_stage = life_stage.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures sex ON s.id_nomenclature_sex = sex.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures obj_count ON s.id_nomenclature_obj_count = obj_count.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures type_count ON s.id_nomenclature_type_count = type_count.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures sensitivity ON s.id_nomenclature_sensitivity = sensitivity.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures observation_status ON s.id_nomenclature_observation_status = observation_status.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures blurring ON s.id_nomenclature_blurring = blurring.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures source_status ON s.id_nomenclature_source_status = source_status.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures info_geo_type ON s.id_nomenclature_info_geo_type = info_geo_type.id_nomenclature
        LEFT JOIN ref_nomenclatures.t_nomenclatures determination_method ON s.id_nomenclature_determination_method = determination_method.id_nomenclature;

Après on peut pimper un peu plus cette requête en intégrant le statut de validation :

SELECT
[...]
valid_status.label_default AS "validStat"
FROM [...]
LEFT JOIN ref_nomenclatures.t_nomenclatures valid_status ON s.id_nomenclature_valid_status = valid_status.id_nomenclature

En ajoutant les commune(s) qui intersecte(nt) la geométrie (attention au id_type communal et au group by) :

SELECT
[...]
string_agg(DISTINCT ((commune.area_name::text || ' ('::text) || "left"(commune.area_code::text, 2)) || ')'::text, ', '::text) AS communes,
FROM [...]
LEFT JOIN (
    gn_synthese.cor_area_synthese cor_s_commune ON s.id_synthese = cor_s_commune.id_synthese
    INNER JOIN ref_geo.l_areas comm_area ON cor_s_commune.id_area = comm_area.id_area AND comm_area.id_type = 25
) commune ON s.id_synthese = commune.id_synthese
GROUP BY [tous les champs]
jbdesbas commented 5 years ago

Après avoir testé rapidement ta requête : un cost divisé par 10 (sur un faible nombre de données) ! Merci !

jbrieuclp commented 5 years ago

Merci pour le retour parce que je me suis aperçu entre temps que pour la partie ajout des communes c'était pas top... Je modifie dans le post initial mais voici une correction pour encore réduire le cost par 10..

[Reprise du premier post avec correction] En ajoutant les commune(s) qui intersecte(nt) la geométrie (attention au id_type communal et au group by) :

SELECT
[...]
string_agg(DISTINCT ((commune.area_name::text || ' ('::text) || "left"(commune.area_code::text, 2)) || ')'::text, ', '::text) AS communes,
FROM [...]
LEFT JOIN (
    gn_synthese.cor_area_synthese cor_s_commune ON s.id_synthese = cor_s_commune.id_synthese
    INNER JOIN ref_geo.l_areas comm_area ON cor_s_commune.id_area = comm_area.id_area AND comm_area.id_type = 25
) commune ON s.id_synthese = commune.id_synthese
GROUP BY [tous les champs]

La première version faisait un LEFT JOIN avec toutes les données gn_synthese.cor_area_synthese ce qui renvoyait un grand nombre de ligne, ligne invisible du fait du GROUP BY. Le LEFT JOIN (INNER JOIN) ne retourne que les cor_area_synthèse propres aux communes.

amandine-sahl commented 5 years ago

Pour extraire les données sur les aires géographique de façon générique il y a également une technique un peu bourine :

-- Exemple sur les zonages parcs
 SELECT 
    s.id_synthese, 
    a.jname ->> 'COM' AS commune,
    COALESCE(a.jcode ->> 'ZC', a.jcode ->> 'AA', a.jcode ->> 'PEC', 'Hors parc') AS zone_pnc,
    a.jname ->> 'MASSIF' AS massif,
    a.jname ->> 'ZB' AS zone_bioge,
    FROM gn_synthese.synthese s
    JOIN LATERAL ( 
        SELECT d_1.id_synthese,
            json_object_agg(d_1.type_code, d_1.o_name) AS jname,
            json_object_agg(d_1.type_code, d_1.o_code) AS jcode
        FROM ( 
            SELECT sa.id_synthese,
                    ta.type_code,
                    string_agg(DISTINCT a_1.area_name::text, ','::text) AS o_name,
                    string_agg(DISTINCT a_1.area_code::text, ','::text) AS o_code
            FROM gn_synthese.cor_area_synthese sa
            JOIN ref_geo.l_areas a_1 ON sa.id_area = a_1.id_area
            JOIN ref_geo.bib_areas_types ta ON ta.id_type = a_1.id_type
            WHERE sa.id_synthese = s.id_synthese
            GROUP BY sa.id_synthese, ta.type_code
        ) d_1
        GROUP BY d_1.id_synthese
    ) a ON true
jbrieuclp commented 5 years ago

Tu arrives à réduire le cost de la requête avec ça ??

camillemonchicourt commented 4 years ago

Le premier sujet du with et des jointures a été revu depuis un moment, merci pour le retour.

On vient de reprendre complètement la vue d'export dans https://github.com/PnX-SI/GeoNature/pull/1124 (en cours de finalisation dans develop et à cette occasion on a vu avec @amandine-sahl pour y ajouter les communes.

Pour uniquement ajouter les communes, sans devoir faire un GROUP BY avec tous les champs de la vue, @amandine-sahl a proposé :

SELECT 
        s.id_synthese, 
         communes
    FROM gn_synthese.synthese s
    LEFT OUTER JOIN (
        SELECT id_synthese , string_agg(DISTINCT area_name, ', ') AS communes
        FROM  gn_synthese.cor_area_synthese cas
        LEFT OUTER JOIN ref_geo.l_areas a_1 ON cas.id_area = a_1.id_area
        JOIN ref_geo.bib_areas_types ta ON ta.id_type = a_1.id_type AND ta.type_code ='COM'
        GROUP BY id_synthese 
    ) sa ON sa.id_synthese = s.id_synthese

https://explain.depesz.com/s/2SYb

Par contre si on veut plusieurs types de zonages, elle suggère :

WITH areas AS (
    SELECT ta.id_type, type_code, id_area, a_1.area_code, a_1.area_name 
    FROM ref_geo.bib_areas_types ta
    JOIN ref_geo.l_areas a_1 ON ta.id_type = a_1.id_type
    WHERE  ta.type_code in ('DEP', 'COM', 'M1')
)
SELECT 
    s.id_synthese ,
    a.jname ->> 'DEP' AS "nomDepartement",
    a.jcode ->> 'DEP' AS "codeDepartement",
    a.jname ->> 'COM' AS "nomCommune",
    a.jcode ->> 'COM' AS "codeCommune",
    a.jcode ->> 'M10' AS "codeMaille"
   FROM gn_synthese.synthese s
   LEFT JOIN LATERAL ( 
        SELECT 
            d_1.id_synthese,
            json_object_agg(d_1.type_code, d_1.o_name) AS jname,
            json_object_agg(d_1.type_code, d_1.o_code) AS jcode
           FROM ( 
               SELECT 
                   sa.id_synthese,
                ta.type_code,
                string_agg(ta.area_name, '|') AS o_name,
            string_agg(ta.area_code, '|') AS o_code
        FROM gn_synthese.cor_area_synthese sa
        JOIN areas ta ON ta.id_area = sa.id_area
        WHERE sa.id_synthese = s.id_synthese
        GROUP BY sa.id_synthese, ta.type_code
     ) d_1
     GROUP BY d_1.id_synthese
) a ON TRUE;

https://explain.depesz.com/s/T2W5

C'est cette deuxième solution qui a été retenue pour le nouvel export DEE en cours d'ajout dans les vues du module Export (https://github.com/PnX-SI/gn_module_export/commit/7ef0bf0ee8550efce00d13ec8e21a49c153d81a4)