Skip to content

Commit

Permalink
Utils - correction orthographiques
Browse files Browse the repository at this point in the history
  • Loading branch information
mdouchin committed Mar 7, 2024
1 parent 52f59d0 commit 5d8ff16
Showing 1 changed file with 123 additions and 111 deletions.
234 changes: 123 additions & 111 deletions docs/utils.md
Original file line number Diff line number Diff line change
Expand Up @@ -255,113 +255,6 @@ WHERE schema_name NOT IN ('pg_catalog', 'information_schema')
ORDER BY pg_schema_size(schema_name) DESC;
```

## Tester les différences entre 2 tables de même structure

Nous souhaitons **comparer deux tables de la base**, par exemple une table de communes en 2021 `communes_2021` et une table de communes en 2022 `communes_2022`.

On peut utiliser une fonction qui utilise les possibilités du format hstore pour comparer les données entre elles.

```sql
-- On ajoute le support du format hstore
CREATE EXTENSION IF NOT EXISTS hstore;

-- On crée la fonction de comparaison
DROP FUNCTION compare_tables(text,text,text,text,text,text[]);
CREATE OR REPLACE FUNCTION compare_tables(
p_schema_name_a text,
p_table_name_a text,
p_schema_name_b text,
p_table_name_b text,
p_common_identifier_field text,
p_excluded_fields text[]

) RETURNS TABLE(
uid text,
status text,
table_a_values hstore,
table_b_values hstore
)
LANGUAGE plpgsql
AS $_$
DECLARE
sqltemplate text;
BEGIN

-- Compare data
sqltemplate = '
SELECT
coalesce(ta."%1$s", tb."%1$s") AS "%1$s",
CASE
WHEN ta."%1$s" IS NULL THEN ''not in table A''
WHEN tb."%1$s" IS NULL THEN ''not in table B''
ELSE ''table A != table B''
END AS status,
CASE
WHEN ta."%1$s" IS NULL THEN NULL
ELSE (hstore(ta.*) - ''%6$s''::text[]) - (hstore(tb) - ''%6$s''::text[])
END AS values_in_table_a,
CASE
WHEN tb."%1$s" IS NULL THEN NULL
ELSE (hstore(tb.*) - ''%6$s''::text[]) - (hstore(ta) - ''%6$s''::text[])
END AS values_in_table_b
FROM "%2$s"."%3$s" AS ta
FULL JOIN "%4$s"."%5$s" AS tb
ON ta."%1$s" = tb."%1$s"
WHERE
(hstore(ta.*) - ''%6$s''::text[]) != (hstore(tb.*) - ''%6$s''::text[])
OR (ta."%1$s" IS NULL)
OR (tb."%1$s" IS NULL)
';

RETURN QUERY
EXECUTE format(sqltemplate,
p_common_identifier_field,
p_schema_name_a,
p_table_name_a,
p_schema_name_b,
p_table_name_b,
p_excluded_fields
);

END;
$_$;
```

Cette fonction attend en paramètres

* le schéma de la **table A**. Ex: `referentiels`
* le nom de la **table A**. Ex: `communes_2021`
* le schéma de la **table B**. Ex: `referentiels`
* le nom de la **table B**. Ex: `communes_2022`
* le nom du champ qui identifie de manière unique la donnée. Ce n'est pas forcément la clé primaire. Ex `code_commune`
* un tableau de champs pour lesquels ne pas vérifier les différences. Ex: `array['region', 'departement']`

La requête à lancer est la suivantes
```sql
SELECT "uid", "status", "table_a_values", "table_b_values"
FROM compare_tables(
'referentiels', 'commune_2021',
'referentiels', 'commune_2022',
'code_commune',
array['region', 'departement']
)
ORDER BY status, uid
;
```

Exemple de données renvoyées:

| uid | status | table_a_values | table_b_values |
|-------|--------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------|
| 12345 | not in table A | NULL | "annee_ref"=>"2022", "nom_commune"=>"Nouvelle commune", "population"=>"5723" |
| 97612 | not in table B | "annee_ref"=>"2021", "nom_commune"=>"Ancienne commune", "population"=>"840" | NULL |
| 97602 | table A != table B | "annee_ref"=>"2021", "population"=>"1245" | "annee_ref"=>"2022", "population"=>"1322" |

Dans l'affichage ci-dessus, je n'ai pas affiché le champ de géométrie, mais la fonction teste aussi les différences de géométries.

*Attention, les performances de ce type de requête ne sont pas forcément assurées pour des volumes de données importants.*


## Lister les triggers appliqués sur les tables

On peut utiliser la requête suivante pour lister l'ensemble des triggers activés sur les tables
Expand Down Expand Up @@ -457,7 +350,7 @@ Si on utilise des vues dans QGIS qui créent un identifiant unique via le numér
* que le type de cet identifiant soit entier `integer` et pas entier long `bigint`
* avoir une clause `ORDER BY` pour essayer au maximum que QGIS récupère les objets toujours dans le même ordre.

Quand une requête d'une vue utilise `row_number() OVER()` , depuis des versions récentes de PostgreSQL, cela renvoie un entier long `bigint` ce qui n'est pas conseillé.
Quand une requête d'une vue utilise `row_number() OVER()`, depuis des versions récentes de PostgreSQL, cela renvoie un entier long `bigint` ce qui n'est pas conseillé.

On peut trouver ces vues ou vues matérialisées via cette requête :

Expand Down Expand Up @@ -485,7 +378,7 @@ Pour éviter des soucis de performances sur les gros jeux de données, il faut

En effet, dans QGIS, l'ouverture de ce type de table avec une clé primaire de type `text`, ou même `bigint`, cela entraîne la création et le stockage en mémoire d'une table de correspondance entre chaque objet de la couche et le numéro d'arrivée de la ligne. Sur les tables volumineuses, cela peut être sensible.

Pour trouver toutes les tables, on peut faire cette requête:
Pour trouver toutes les tables, on peut faire cette requête :

```sql
SELECT
Expand All @@ -504,6 +397,16 @@ WHERE indisprimary AND nspname NOT LIKE 'pg_%' AND nspname NOT LIKE 'lizmap_%'
AND format_type(a.atttypid, a.atttypmod) != 'integer';
```

Ce qui donne par exemple :

table_schema | table_name | column_name | column_type
-------------------|----------------------------------|-------------|-------------------
un_schema | une_table_a | id | bigint
un_schema | une_table_b | id | bigint
un_autre_schema | autre_table_c | id | character varying
un_autre_schema | autre_table_d | id | character varying


## Trouver les tables spatiales avec une géométrie non typée

Il est important lorsqu'on crée des champs de type géométrie `geometry` de préciser le type des objets (point, ligne, polygone, etc.) et la projection.
Expand Down Expand Up @@ -537,7 +440,7 @@ ST_Centroid(geom)::geometry(Point, 2154) AS geom
FROM autre_table
```

On peut trouver toutes les tables qui auraient été créée avec des champ de géométrie non typés via la requête suivante :
On peut trouver toutes les tables qui auraient été créées avec des champs de géométrie non typés via la requête suivante :

```sql
SELECT *
Expand All @@ -557,8 +460,117 @@ WHERE ST_NPoints(geom) > 10000
;
```

Les trop gros polygones (zones inondables, zonages issus de regroupement de nombreux objets, etc.) peuvent poser de réels soucis de performance, notamment sur les opération d'intersection avec les objets d'autres couches via `ST_Intersects`.
Les trop gros polygones (zones inondables, zonages issus de regroupement de nombreux objets, etc.) peuvent poser de réels soucis de performance, notamment sur les opérations d'intersection avec les objets d'autres couches via `ST_Intersects`.

On peut corriger cela via la fonction `ST_Subdivide`. Voir [Documentation de ST_Subdivide](https://postgis.net/docs/ST_Subdivide.html)


## Tester les différences entre 2 tables de même structure

Nous souhaitons **comparer deux tables de la base**, par exemple une table de communes en 2021 `communes_2021` et une table de communes en 2022 `communes_2022`.

On peut utiliser une fonction qui utilise les possibilités du format hstore pour comparer les données entre elles.

```sql
-- On ajoute le support du format hstore
CREATE EXTENSION IF NOT EXISTS hstore;

-- On crée la fonction de comparaison
DROP FUNCTION compare_tables(text,text,text,text,text,text[]);
CREATE OR REPLACE FUNCTION compare_tables(
p_schema_name_a text,
p_table_name_a text,
p_schema_name_b text,
p_table_name_b text,
p_common_identifier_field text,
p_excluded_fields text[]

) RETURNS TABLE(
uid text,
status text,
table_a_values hstore,
table_b_values hstore
)
LANGUAGE plpgsql
AS $_$
DECLARE
sqltemplate text;
BEGIN

-- Compare data
sqltemplate = '
SELECT
coalesce(ta."%1$s", tb."%1$s") AS "%1$s",
CASE
WHEN ta."%1$s" IS NULL THEN ''not in table A''
WHEN tb."%1$s" IS NULL THEN ''not in table B''
ELSE ''table A != table B''
END AS status,
CASE
WHEN ta."%1$s" IS NULL THEN NULL
ELSE (hstore(ta.*) - ''%6$s''::text[]) - (hstore(tb) - ''%6$s''::text[])
END AS values_in_table_a,
CASE
WHEN tb."%1$s" IS NULL THEN NULL
ELSE (hstore(tb.*) - ''%6$s''::text[]) - (hstore(ta) - ''%6$s''::text[])
END AS values_in_table_b
FROM "%2$s"."%3$s" AS ta
FULL JOIN "%4$s"."%5$s" AS tb
ON ta."%1$s" = tb."%1$s"
WHERE
(hstore(ta.*) - ''%6$s''::text[]) != (hstore(tb.*) - ''%6$s''::text[])
OR (ta."%1$s" IS NULL)
OR (tb."%1$s" IS NULL)
';

RETURN QUERY
EXECUTE format(sqltemplate,
p_common_identifier_field,
p_schema_name_a,
p_table_name_a,
p_schema_name_b,
p_table_name_b,
p_excluded_fields
);

END;
$_$;
```

Cette fonction attend en paramètres

* le schéma de la **table A**. Ex: `referentiels`
* le nom de la **table A**. Ex: `communes_2021`
* le schéma de la **table B**. Ex: `referentiels`
* le nom de la **table B**. Ex: `communes_2022`
* le nom du champ qui identifie de manière unique la donnée. Ce n'est pas forcément la clé primaire. Ex `code_commune`
* un tableau de champs pour lesquels ne pas vérifier les différences. Ex: `array['region', 'departement']`

La requête à lancer est la suivantes
```sql
SELECT "uid", "status", "table_a_values", "table_b_values"
FROM compare_tables(
'referentiels', 'commune_2021',
'referentiels', 'commune_2022',
'code_commune',
array['region', 'departement']
)
ORDER BY status, uid
;
```

Exemple de données renvoyées:

| uid | status | table_a_values | table_b_values |
|-------|--------------------|-----------------------------------------------------------------------------|------------------------------------------------------------------------------|
| 12345 | not in table A | NULL | "annee_ref"=>"2022", "nom_commune"=>"Nouvelle commune", "population"=>"5723" |
| 97612 | not in table B | "annee_ref"=>"2021", "nom_commune"=>"Ancienne commune", "population"=>"840" | NULL |
| 97602 | table A != table B | "annee_ref"=>"2021", "population"=>"1245" | "annee_ref"=>"2022", "population"=>"1322" |

Dans l'affichage ci-dessus, je n'ai pas affiché le champ de géométrie, mais la fonction teste aussi les différences de géométries.

*Attention, les performances de ce type de requête ne sont pas forcément assurées pour des volumes de données importants.*



Continuer vers [Gestion des droits](./grant.md)

0 comments on commit 5d8ff16

Please sign in to comment.