M04 SQL BDD Papier
M04 SQL BDD Papier
M04 SQL BDD Papier
données
QGIS Perfectionnement
Juin 2022
Juin 2022
Table des
matières
Introduction 5
I - Notions SQL 7
A. Introduction.................................................................................................7
B. La sélection..................................................................................................9
E. Tri et agrégation.........................................................................................14
F. Extensions spatiales.....................................................................................17
G. Présentation de DBManager..........................................................................19
II - Spatialite 33
D. Indexation et optimisation............................................................................41
Objectifs
Découvrir le langage de requête SQL pour répondre à des
besoins d'analyse spatiale dans QGIS.
A. Introduction
1 - http://fr.wikipedia.org/wiki/Syst%C3%A8me_de_gestion_de_base_de_donn%C3%A9es#Typologie
SQL
SQL (Structured Query Language qui signifie langage de requêtes structuré) est un
langage destiné à la manipulation des bases de données au sein d'un SGBD.
SQL est composé de trois sous-ensembles :
Le Langage de Définition de Données (LDD) qui permet de créer et supprimer
des objets dans la base de données et que nous n'aborderons pas
2 - http://geoinfo.metier.i2/presentation-de-la-formation-a349.html
3 - http://www.geoinformations.developpement-durable.gouv.fr/postgis-support-pedagogique-a3347.html
B. La sélection
Syntaxe générale
La requête de sélection est la base de la recherche de données en SQL.
Une requête SQL respecte une syntaxe de type :
SELECT (liste des attributs) FROM (liste des tables) WHERE (Conditions)
La partie SELECT indique le sous-ensemble des attributs (les colonnes) qui doivent
apparaître dans la réponse.
La partie FROM décrit les relations (les tables) qui sont utilisées dans la requête. Les
attributs de la clause SELECT doivent appartenir aux tables listées dans la clause
FROM.
La partie WHERE exprime les conditions, elle est optionnelle.
Nous verrons d'autres options plus tard...
ex 1: SELECT * FROM commune WHERE population > 1000
sélectionne les enregistrements de la table COMMUNE dont la population est
supérieure à 1000 avec tous les attributs (c'est le sens de *) de la table COMMUNE
ex 2 : SELECT nom_comm, insee_comm, population FROM commune
sélectionne tous les enregistrements de la table COMMUNE (cf pas de conditions, c'est
à dire pas de clause WHERE) et renvoi une table avec les attributs NOM_COM,
INSEE_COMM et POPULATION.
4 - https://docs.qgis.org/latest/fr/docs/user_manual/managing_data_source/create_layers.html?highlight=virtual
%20layers#creating-virtual-layers
Fondamental : NULL
Une valeur par défaut peut-être attribuée à une colonne lors de la définition d'une
table. Si aucune valeur par défaut n'est attribuée la valeur par défaut de la colonne
est positionnée à NULL (0 ou espace n'est pas équivalent à NULL).
Il est possible d'utiliser l'opérateur logique IS pour tester si un champ est ou non nul.
Exemple : SELECT * FROM commune WHERE nom_comm IS NULL récupère les
enregistrement qui n'ont pas de nom de commune.
SELECT * FROM commune WHERE nom_comm IS NOT NULL récupère ceux qui ont
effectivement un nom (non positionné à NULL).
5 - http://fr.wikipedia.org/wiki/SQLite#Types_de_donn.C3.A9es
6 - http://docs.postgresql.fr/14/datatype.html
Les fonctions
SQL propose des fonctions dont on trouvera une description par exemple ici7
Examinons en quelques unes...
Fonctions de transtypage:
cast (expr as type) : Est la fonction standard SQL qui permet de convertir un type en
un autre.
Exemple :
Si x_commune est un champ de type INTEGER dans la table commune
SELECT x_commune FROM commune LIMIT 1
renvoi 478935
(noter l'utilisation de la clause LIMIT qui permet d'indiquer le nombre maximum
d'enregistrements en retour. Il est également possible d'utiliser la clause OFFSET
pour décaler le nombre de lignes à obtenir
ex : SELECT * FROM commune LIMIT 10 OFFSET 5 (pour renvoyer les
enregistrements de 6 à 15)
SELECT cast(x_commune as real) FROM commune LIMIT 1 renvoie 478935.0
SELECT cast(x_commune as text) FROM commune LIMIT 1 renvoie '478935' c'est à
dire une chaîne de caractère, puisque entre ''.
PotsgreSQL propose une notation compacte sous la forme expr::type
exemple : SELECT x_commune :: real FROM commune
Une opération de transtypage est parfois nécessaire pour obtenir le résultat souhaité,
en particulier avec SpatiaLite. Prenons l'exemple de calcul d'un indicateur (ratio de
deux entiers) avec SpatiaLite.
Exemple : SELECT (population/superficie) AS densite FROM commune LIMIT
10
renvoie :
7 - http://sqlpro.developpez.com/cours/sqlaz/fonctions/
LIMIT 10
On remarquera à nouveau l'utilisation de LIMIT qui permet d'indiquer le nombre
maximum d'enregistrements retournés... c'est une clause très utile pour la mise au
point de requêtes sur des grosses tables ou pour récupérer juste le premier
enregistrement après un tri.
Le résultat devient bien celui attendu :
renvoie :
fonction round
Remarque
sous PostGIS on écrira SELECT (round (population/superficie) :: numeric, 2)
AS densite FROM commune
le :: étant une forme compacte sous PostGIS pour réaliser le cast. Le format
numérique (numeric) étant obligatoire pour la fonction round sous PostGIS.
E. Tri et agrégation
Tri
Il est possible de classer le résultat d'une requête en ajoutant le mot clef ORDER BY
suivi d'une liste de champs.
Exemple : SELECT * FROM commune ORDER BY nom_comm
pour classer le résultat par nom de commune.
Un tri décroissant peut-être obtenu en ajoutant DESC.
Exemple : SELECT * FROM commune ORDER BY nom_comm DESC
SELECT nom_comm, round(cast(population as float)/superficie,2) AS
densite FROM commune ORDER BY densite
retourne la densité de population par ordre croissant de densité.
8 - http://www.sqlite.org/lang_corefunc.html
9 - https://docs.postgresql.fr/14/functions.html
Remarque
Sous PostGIS on écrira
SELECT nom_comm, round(population/superficie :: numeric,2) AS densite
FROM commune ORDER BY densite)
Agrégations
Une agrégation est une opération qui permet de regrouper les enregistrements de la
table en sortie selon des critères et d'obtenir des informations statistiques sur ces
regroupements. Il faut utiliser l'expression GROUP BY suivi du critère de
regroupement.
Prenons un exemple à partir de la table COMMUNE. Nous souhaitons obtenir la
population totale par département.
SELECT Nom_comm, nom_dept, population FROM commune
nous donne :
F. Extensions spatiales
10 - http://www.opengeospatial.org/standards/sfs
11 - http://www.geoinformations.developpement-durable.gouv.fr/qgis-documents-et-fiches-a1853.html
'WKT', exemple : POLYGON ((30 10, 10 20, 20 40, 40 40, 30 10)) dans une colonne
de table qui est souvent nommée geometry ou the_geom). Le système utilise au
moins deux autres tables internes supplémentaires qu'il maintient à jour :
geometry_columns et spatial_ref_sys (PostGIS 1.5)
Remarque
Vérification de géométrie sous PostGIS
PostGIS ajoute d'autres fonctions de vérification de la géométrie
ST_IsValidReason() : retourne un texte indiquant les raisons d'une éventuelle
invalidité.
ST_IsValidDetail() : retourne en plus un pointeur vers la partie non valide (à partir
de PostGIS 2.0).
ST_MakeValid() : Tente de corriger les géométries invalides (PostGIS 2.0)
12 - http://www.postgis.fr/chrome/site/docs/workshop-foss4g/doc/validity.html
Utilisation St_Centroid
ST_Area() retourne la surface d'un objet
ST_Buffer() retourne un nouvel objet tampon construit autour d'un objet
ST_Length() : retourne la longueur d'un objet de type ligne ou multi-ligne
(attention à ne pas utiliser length() qui retourne la longueur du champ, spatialite
autorise aussi Glength()).
ST_Perimeter() : retourne le périmètre d'un objet polygone ou multi-polygone
G. Présentation de DBManager
Mise en oeuvre
Nous allons mettre en pratique SQL dans le SGBD SpatiaLite.
Il existe de nombreux 'clients' permettant d'écrire et d'exécuter les requêtes SQL.
Depuis QGIS nous allons utiliser le plugin DBManager qui s'interface aussi bien avec
SpatiaLite, PostGIS, ou les virtuals Layer (toute couche ouverte dans QGIS). C'est le
plugin qui est porté par la communauté QGIS.
Avec PostgreSQL, il est possible d'utiliser PgAdmin 13qui est le client le plus populaire
de PostGIS et qui dispose de fonctionnalités intéressantes comme un assistant à
l'écriture de requêtes SQL. Ce client est présenté en détail dans la formation
'PostgreSQL Administrer ses données14'
13 - http://www.pgadmin.org/?lang=fr_FR
14 - http://www.geoinformations.developpement-durable.gouv.fr/postgis-support-pedagogique-a3347.html
Connexion sandbox
En lançant DB Manager vous devez maintenant pouvoir vous connecter à cette base.
Nb : Une base de données de type PostGIS peut être protégée par mot de passe,
dans ce cas il faut le saisir dans la fenêtre qui apparaît pour cela.
DbManager
L'onglet info fournit les informations sur les tables
DBManager Informations
On peut par exemple lire que la table COMMUNE contient 19 enregistrements
(lignes), qu'il y a une colonne de géométrie contenant des objets 'MULTIPOLYGON',
que la projection est Lambert 93 et qu'il n'y a pas d'index spatial (aucun index spatial
défini...nous verrons ce que cela signifie concrètement plus tard).
L'onglet 'table' fournit une vision des données de la table et l'onglet aperçu une
visualisation de la géométrie.
Le bouton 'Fenêtre SQL' ouvre un nouvel onglet dans lequel nous allons pouvoir
exécuter des ordres SQL.
Question 1
Question 2
Question 3
Q3 : sélectionner les communes de la table COMMUNE dont le statut n'est pas chef-
lieu de canton et afficher les colonnes NOM_COMM en lui donnant comme alias NOM
et les colonnes, STATUT, POPULATION et SUPERFICIE
Indice :
traduire le "n 'est pas" par l'utilisation de NOT.
Question 4
Q4 : Établir la liste des noms des tronçons comportant le nom 'ruisseau' dans la
colonne TOPONYME de la table TRONCON_HYDROGRAPHIQUE
Indice :
Utiliser la table troncon_hydrographique. On pourra utiliser LIKE pour indiquer que
le nom de tronçon doit contenir la chaîne 'ruisseau'.
Question 5
exo6 - question 5
on cherche des sommes, moyennes,...par département il faut donc utiliser un
GROUP BY (agrégation) avec comme critère le nom de département
(NOM_DEPT).
Qui dit agrégation implique automatiquement l'utilisation de fonctions
d'agrégation...On utilisera les fonctions d'agrégation donnant la somme, la
moyenne, le maximum et le minimum.
Question 6
Q6 : quels sont les surfaces (en km2) et périmètres (en km), arrondis à deux chiffres
après la virgule, des communes du département de la Sarthe ?
Indice :
trouver la fonction géométrique qui renvoie une aire, et celle qui renvoie un
périmètre. Ces fonctions ne prennent pas de paramètres d'unités, il faut donc faire
la conversion soi-même par une division.
Question 7
Question 8
Q8 : quelle est la longueur de la 'rivière le loir' par type de largeur sur ce jeu de
données ?
Indice :
Une jointure permet de mettre en relation deux (ou plus) tables afin de combiner
leurs colonnes. Il existe plusieurs natures de jointure, mais les cas simples se font en
imposant l'égalité d'une valeur d'une colonne d'une table à une colonne d'une autre
table.
Exemple : Table IRIS (extrait) et table des COMMUNES
Une analyse des tables IRIS_extrait72 et COMMUNE permet de voir que DepCom
représente le N° INSEE de la commune d'appartenance dans la table IRIS_extrait72.
On retrouve ce N°INSEE dans le champ INSEE_COMM de la table COMMUNE. Le lien
peut donc s'établir par égalité des ces colonnes ce que l'on écrira :
IRIS_extrait72.DepCom = COMMUNE.INSEE_COMM
C'est ce que l'on appelle la condition de jointure.
Remarque
Lorsqu'on utilise plusieurs tables, il devient nécessaire s'il y a risque d'ambiguïté de
préciser le nom de la table devant le nom des colonnes sous la forme
NomTable.NomColonne d'où par exemple COMMUNE.INSEE_COMM.
Lorsqu'on réalise une jointure attributaire entre deux tables (deux noms de tables
après le from) il faut retenir qu'en général il faut une condition de jointure qui sera
pour nous une égalité de champ.
La requête pourrait être la suivante (on ne retient que certains champs) :
SELECT DepCom, Nom_Iris, insee_comm, nom_comm FROM iris_extrait72,
commune WHERE DepCom = insee_comm
Ici il n'y a pas d'ambiguïté sur les noms de colonnes et on peut ne pas utiliser la
notation NomTable.NomColonne
Complément
On peut également utiliser une syntaxe normalisée qui est dans ce cas strictement
équivalente :
SELECT <colonnes> FROM <table1> JOIN <table2> ON <condition de jointure>
dans notre exemple cela donne :
SELECT DepCom, Nom_Iris, insee_comm, nom_comm FROM iris_extrait72 JOIN
commune ON iris_extrait72.DepCom = INSEE_COMM
SQL autorise beaucoup de subtilité dans les types de jointures, on pourra par
exemple consulter Le SQL de A à Z sur les jointures15.
15 - http://sqlpro.developpez.com/cours/sqlaz/jointures/
Il n'est pas toujours possible de réaliser une jointure attributaire s'il n'y a pas de
colonne commune entre deux tables. Dans le cas de tables ayant chacune un champ
géométrique il est possible de réaliser des jointures spatiales.
La jointure spatiale utilisera une fonction spatiale (voir ci-dessous) dans la clause
WHERE d'une requête SQL :
exemple :
SELECT * FROM tableA, tableB WHERE ST_Intersects(tableA.geometry,
tableB.geometry)
Prédicats de l'OGC
Ils sont disponibles sous formes de fonctions spatiales qui renvoient VRAI (1) ou
FAUX (0) :
ST_Equals(geometry A, geometry B) retourne vrai si les géométries sont de
même type et ont les mêmes coordonnées.
ST_Intersects(geometry A, geometry B) retourne vrai s'il y a au moins un point
commun.
Question 1
Question 2
résultat à obtenir :
Question 3
Question 4
Objectifs
Découvrir et mettre en pratique le SGBDR SpatiaLite avec
QGIS
Remarque
Le plug-in 'édition hors connexion17' permet de gérer la synchronisation avec une
base SpatiaLite (offline.sqliter) embarquée.
Il est donc possible d'envisager des utilisations avec saisie terrain sous SpatiaLite puis
synchronisation au retour avec une base partagée centrale (sous PostGIS par
exemple).
16 - http://www.opengeospatial.org/standards/sfs
17 - https://docs.qgis.org/latest/fr/docs/user_manual/plugins/core_plugins/plugins_offline_editing.html
18 - https://www.gaia-gis.it/fossil/libspatialite/wiki?name=4.2.0+functions#8
19 - https://fr.wikipedia.org/wiki/Vue_(base_de_donn%C3%A9es)
20 - https://github.com/qgis/QGIS/issues/25922
21 - https://mygeodata.cloud/converter/spatialite-to-geopackage
Advance SQL
Cette boîte de dialogue permet de construire la requête SQL. La démarche est d'abord
de sélectionner la ou les tables sur lesquelles on souhaite travailler (ex :
''AERODROME'', puis les colonnes de ces tables que l'on souhaite en sortie ou mettre
* dans la case Columns pour choisir toutes colonnes.
Ex :
"AERODROME".'NATURE',
"AERODROME".'DESSERTE',
"AERODROME".'TOPONYME'
et éventuellement d'ajouter une condition (clause where) pour laquelle on peut
utiliser les listes déroulantes à droite.
exemple :
NATURE = 'Normal'
Remarque
Il n'y a pas * par défaut dans le champ 'Columns' dans la boîte de dialogue pour
sélectionner tous les champs, mais on peut taper * directement dans le la champ de
saisie des colonnes.
on peut écrire = ou == comme opérateur d'égalité.
on ne dispose pas de = ANY (utiliser IN)
|| → est l'opérateur de concaténation (ne pas utiliser +)
Complément
GLOB : est similaire à l'opérateur LIKE (%= 0 à n caractères, _= 1 caractère) mais
utilise les jokers unix (* = 0 à n caractères, ?= 1 caractère ) et est sensible à la
casse.
BETWEEN n'est pas disponible dans les menus déroulants, mais est utilisable.
MATCH : permet de comparer un ensemble de valeurs de ligne à un ensemble de
lignes retourné par une sous-requête (usage rare). Voir ici22 pour en savoir plus.
REGEXP : permet d'utiliser les expressions régulières ou rationnelles (voir ici23). Le
documentation précise toutefois qu'il faut se définir sa propre fonction regexp() car il
n'y en pas par défaut. L'utilisation de cet opérateur sans définir de fonction génère un
message d'erreur. Très peu utile pour les besoins des services.
22 - http://sqlpro.developpez.com/cours/sqlaz/sousrequetes/#L1.3
23 - http://fr.wikipedia.org/wiki/Expression_rationnelle
24 - http://fr.wikipedia.org/wiki/SQLite#Types_de_donn.C3.A9es
25 - http://www.gaia-gis.it/gaia-sins/spatialite-sql-5.0.1.html
26 - https://www.sqlite.org/lang_corefunc.html
FROM "COMMUNE"
'MULTIPOLYGON', 'XY')
ou SELECT RecoverGeometryColumn('EXEMPLE', 'Geometry', 2154, 'POLYGON',
'XY') si les objets sont des polygones simples (ce qui est le cas pour la couche
"COMMUNE").
La table devient graphique et peut-être chargée sous QGIS pour vérification.
Sous PostGIS (à partir de la version 2.0) il n'est plus nécessaire d'utiliser des
fonctions de mise à jour des tables internes. Le type geometry étant un type à part
entière on peut écrire :
CREATE TABLE EXEMPLE AS
SELECT statut, st_multi(ST_Union(Geom)) :: Geometry(MULTIPOLYGON, 2154)
as geom, sum(superficie) as superficie, sum(population) as population
FROM commune
GROUP BY commune.statut
ORDER BY commune.statut
La conversion en type 'multipolygon' avec le modificateur de type Geometry() met à
jour automatiquement la vue 'geometry_columns'.
(Le résultat de ST_union étant soit un 'polygon', soit un 'multipolygon' on utilise la
fonction st_multi() pour convertir tous les résultats en 'multipolygon').
nb : Pour les lignes il est possible de supprimer les discontinuités et éviter la
constructions de multilignes avec la fonction st_linemerge()
ex : SELECT toponyme, row_number() over() as id,
st_linemerge(st_union(Geom)) as geom from troncon_hydrographique where
toponyme <> '' group by toponyme
DBManager demandant un identifiant de type entier unique pour charger les couches
sous QGIS, il est créé ici avec row_number() over().
Ceci permet de récupérer le numéro de ligne qui est alors utilisé comme identifiant.
Pour ceux qui sont intéressés, cette syntaxe utilise les possibilités avancés de SQL sur
le fenêtrage27.
27 - http://sqlpro.developpez.com/article/olap-clause-window/
Remarque
Les opérateurs spatiaux sont des fonctions.
Si on souhaite utiliser le résultat comme une table spatiale sous QGIS (option
Charger en tant que nouvelle couche), il est nécessaire de choisir une des colonnes
de géométrie en sortie, si on indique *, il y a aura deux colonnes de géométrie dans
la table résultante. Il faudra donc préciser laquelle on considère comme la source de
géométrie (menu déroulant à droite de la case à cocher 'Colonnes de géométrie'.
D. Indexation et optimisation
Dans le cas d'une grosse base de données, les requêtes Sql peuvent être coûteuses
en temps de calcul, a fortiori les requêtes spatiales qui utilisent la géométrie des
objets.
Créer des index (spatiaux ou non) peut permettre d'améliorer les temps de
traitement. Ce n'est cependant pas une recette miracle. Dans SpatiaLite, un index
spatial ne peut accélérer les calculs que dans le cas où le résultat appartient à une
petite portion du jeu de données. Quand les résultats incluent une grande partie du
jeu de données, l'index spatial ne permet pas de gains de performance.
Dans un SGBD élaboré comme PostGIS le planificateur de requête choisit de façon
adaptée d'utiliser ou non les index et l'index spatial (de type Gist que nous verrons
plus tard) est primordial. La seule restriction d'utilisation est celle des tables avec de
très gros objets en petit nombre (ex : tache urbaine départementale répartie en 10
périodes soit 10 enregistrements).
On trouvera quelques explications sur le principe de l'algorithme R-Tree utilisé par
Normalement (exemple sous PostGIS) il est possible d'ajouter une clef primaire en
passant par le menu Table -> Editer une table -> onglet contraintes.
Malheureusement SqLite n'autorise pas (encore?) la création d'une clef primaire après
coup, (pour les anglicistes voir les limites de ALTER TABLE31) il faut re-créer une autre
table. Contacter l'assistance interne si vous êtes confronté à ce type de problèmes.
Index spatial
Si aucun index spatial n'existe DBManager le signale et permet de le créer
directement :
Il est également possible de passer par le menu Table -> Modifier une table -> onglet
index -> Ajouter un index spatial
28 - http://www.gaia-gis.it/gaia-sins/spatialite-cookbook-fr/html/rtree.html
29 - https://doc.postgresql.fr/14/gist.html
30 - https://sites.google.com/site/sgbdspatialite/bon-usage-de-l-index-spatial-r-tree
31 - http://sqlite.org/lang_altertable.html
Index spatial
En appuyant sur 'Utiliser l'index spatial' l'assistant ajoute automatiquement une
syntaxe dans la clause where.
AND "PONCTUEL_HYDROGRAPHIQUE".ROWID IN (
SELECT ROWID FROM SpatialIndex WHERE
f_table_name='PONCTUEL_HYDROGRAPHIQUE' AND
search_frame="TRONCON_HYDROGRAPHIQUE"."geometry")
Sans rentrer trop dans les détails une sous-requête est ici utilisée (ordre SELECT
dans le IN). Cette sous-requête utilise les colonnes f_table_name et search_frame
de la table système SpatialIndex (cette table n'est pas interrogeable).
Requête complète
La différence de temps de traitement n'est pas significative dans notre cas (gain en
millisecondes), Mais dans d'autres cas cette petite gymnastique qui après quelques
essais n'est pas si difficile à mettre en œuvre peut faire gagner beaucoup de temps.
Quelques exemples de gains sont données ici32
Complément
Les possibilités de manipulation spatiale sont très grandes... voici quelques références
supplémentaires pour Spatialite :
Quelques exercices et astuces classiques33
le livre de cuisine !34
Ne pas hésitez à consulter à partir de cette page 35le 'Spatial SQL functions reference
guide' qui est la liste de référence des fonctions disponibles dans la dernière version
de spatialite (attention ce n'est pas forcement celle de votre version de QGIS). Pour
aller plus loin, on pourra en particulier regarder avec intérêt les fonctions 'GEOS
Advanced', ainsi que les fonctions 'LWGEOM'
(Sur SQL d'une façon générale on pourra consulter un cours en ligne36 ou ce site37 de
référence en français
32 - https://www.gaia-gis.it/fossil/libspatialite/wiki?name=speed-optimization
33 - https://sites.google.com/site/sgbdspatialite/exercices-astuces
34 - http://www.gaia-gis.it/gaia-sins/spatialite-cookbook-fr/index.html
35 - https://www.gaia-gis.it/fossil/libspatialite/index
36 - http://www.1keydata.com/fr/sql/
37 - http://sqlpro.developpez.com/
le bouton importer permet de choisir les couches ouvertes dans QGIS qui serviront
dans la requête SQL. Il est également possible d'ajouter n'importe quelle autre
ressource avec le bouton Ajouter.
38 - https://docs.qgis.org/3.22/fr/docs/user_manual/managing_data_source/create_layers.html#creating-virtual-layers
39 - https://docs.qgis.org/3.22/fr/docs/user_manual/managing_data_source/create_layers.html#special-comments
qui l'utilise pour rendre disponibles toutes les couches ouvertes dans QGIS dans le
fournisseur 'Virtual Layers'.
Question 1
Question 2
Question 3
Question 4
40 - http://sqlpro.developpez.com/cours/sqlaz/sousrequetes/
Le résultat est :
exo8 Q4 résultat
Indices :
Il est conseillé de décomposer un problème complexe en problèmes plus simples
pour arriver à la solution...
On pourra dans un premier temps construire une table qui donne les distances de
tous les établissements industriels de la couche BATI_INDUSTRIEL pour chaque
établissement hospitalier. Il faut pour cela utiliser les deux tables PAI_SANTE et
BATI_INDUSTRIEL. On notera qu'on ne peut donner une condition de jointure, ni
attributaire (pas de champ commun), ni géographique (les objets ne se
superposent pas). Dans ce cas on peut construire le produit des deux tables
(produit cartésien) sans condition.
SELECT * FROM "Pai_SANTE","BATI_INDUSTRIEL"
Il reste à ajouter la colonne donnant les distances.
ATTENTION : Faire un produit cartésien sur deux tables sans condition de
jointure doit être réservé à des tables de petite dimension.
nb : Pour éviter de faire le produit cartésien complet, on pourrait penser à utiliser
sous PostGIS la fonction ST_DWithin() avec un rayon de recherche maximum,
fonction qui est disponible que dans spatialite sous le nom de PtDistWithin().
La table précédent peut nous donner accès pour chaque PAI_SANTE à la distance
minimum de l'établissement le plus proche avec un GROUP BY
SELECT PAI_SANTE.ID, min(st_distance(PAI_SANTE.Geometry,
BATI_INDUSTRIEL.Geometry)) AS distance_min from
PAI_SANTE,BATI_INDUSTRIEL GROUP BY PAI_SANTE.ID
On pourrait penser à demander dans le tableau BATI_INDUSTRIEL.ID... mais le
résultat serait faux, car il ne faut pas oublier lorsqu'on utilise un GROUP BY que
chaque colonne en sortie (dans la clause SELECT) doit être, soit le critère de
rupture (celui du GROUP BY), soit être le résultat d'une fonction d'agrégation...
(sous PostGIS vous aurez d'ailleurs un message du type
ERREUR : la colonne "bati_industriel.id" doit apparaître dans la
clause GROUP BY ou être utilisée dans une fonction d'agrégation
Spatialite est plus tolérant, mais il vaut mieux prendre les bonnes habitudes !
Nous voila donc avec le tableau suivant :
résultat sous-selection
solution
SELECT *
FROM "ZONE_VEGETATION", "COMMUNE"
WHERE "ZONE_VEGETATION".'NATURE' = 'Forêt fermée de conifères' and
"COMMUNE".'NOM' = 'La Flèche' and st_intersects("ZONE_VEGETATION".'Geometry',
"COMMUNE".'Geometry'
SELECT
round(sum(st_area(st_intersection("ZONE_VEGETATION".'Geometry',"COMMUNE".'Ge
ometry'))) / 10000) as surface_ha
FROM "ZONE_VEGETATION", "COMMUNE"
WHERE st_intersects("ZONE_VEGETATION".'Geometry',"COMMUNE".'Geometry') and
"ZONE_VEGETATION".'NATURE' = 'Forêt fermée de feuillus' and "COMMUNE".'NOM' =
'La Flèche'
La solution est :
SELECT PAI_SANTE.ID, BATI_INDUSTRIEL.ID, st_distance(PAI_SANTE.Geometry,
BATI_INDUSTRIEL.Geometry) AS distance FROM PAI_SANTE,BATI_INDUSTRIEL
WHERE distance IN (SELECT min(st_distance(PAI_SANTE.Geometry,
BATI_INDUSTRIEL.Geometry)) AS distance_min FROM
PAI_SANTE,BATI_INDUSTRIEL GROUP BY PAI_SANTE.ID)
exo8 Q4 résultat
Sous PostGIS le WHERE distance IN... ne marche pas. Il faut re-écrire
explicitement :
st_distance(PAI_SANTE.Geometry, BATI_INDUSTRIEL.Geometry) IN...
soit
SELECT PAI_SANTE.ID, BATI_INDUSTRIEL.ID, st_distance(PAI_SANTE.Geometry,
BATI_INDUSTRIEL.Geometry) AS distance FROM PAI_SANTE,BATI_INDUSTRIEL
WHERE st_distance(PAI_SANTE.Geometry, BATI_INDUSTRIEL.Geometry) IN
(SELECT min(st_distance(PAI_SANTE.Geometry, BATI_INDUSTRIEL.Geometry))
AS distance_min FROM PAI_SANTE,BATI_INDUSTRIEL GROUP BY PAI_SANTE.PKUID)
Complément
Le but de l'exercice est de montrer l'intérêt et la syntaxe d'une requête complexe.
Cependant pour répondre à la question posée nous aurions pu utiliser l'outil Vecteur -
> Outils d'analyse -> Matrice des distances.
Cet outil ne fonctionnant que sur des couches de points, il faut au préalable créer la
couche BATI_INDUSTRIEL_CENTROID avec la fonction Vecteur -> Outil de géométrie
-> Centroïdes de polygones.
On utilise ensuite l'outil 'matrice de distances' en choisissant 'Utiliser uniquement les
points cibles les plus proches' avec k=1, Ceci génère un fichier csv qui donne le
résultat cherché. Les distances sont un peu différentes qu'avec la fonction st_distance
qui utilise le contour des polygones au lieu du centroïde.
On pourrait également utiliser le plugin NNjoin 41 qui permet de calculer pour chaque
objet de la couche (input layer), l'objet le plus proche de la couche 'Join vector Layer',
ainsi que sa distance.
41 - http://arken.umb.no/~havatv/gis/qgisplugins/NNJoin/