\documentclass[12pt,,a4paperpaper,{article}
\usepackage{lmodern}
\usepackage{amssymb,amsmath}
\usepackage{ifxetex,ifluatex}
\usepackage{fixltx2e} % provides \textsubscript
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[T1]{fontenc}
  \usepackage[utf8]{inputenc}
  \usepackage{eurosym}
\else % if luatex or xelatex
  \ifxetex
    \usepackage{mathspec}
  \else
    \usepackage{fontspec}
  \fi
  \defaultfontfeatures{Ligatures=TeX,Scale=MatchLowercase}
  \newcommand{\euro}{€}
\fi
% use upquote if available, for straight quotes in verbatim environments
\IfFileExists{upquote.sty}{\usepackage{upquote}}{}
% use microtype if available
\IfFileExists{microtype.sty}{%
\usepackage{microtype}
\UseMicrotypeSet[protrusion]{basicmath} % disable protrusion for tt fonts
}{}
\usepackage{hyperref}
\PassOptionsToPackage{usenames,dvipsnames}{color} % color is loaded by hyperref
\hypersetup{unicode=true,
            pdftitle={SQL conception et mise en œuvre},
            pdfauthor={Dalibo},
            pdfkeywords={sql; acid; contraintes; select; join; aggregat},
            colorlinks=true,
            linkcolor=Maroon,
            citecolor=Blue,
            urlcolor=Blue,
            breaklinks=true}
\urlstyle{same}  % don't use monospace font for urls
\ifnum 0\ifxetex 1\fi\ifluatex 1\fi=0 % if pdftex
  \usepackage[shorthands=off,main=]{babel}
\else
  \usepackage{polyglossia}
  \setmainlanguage[]{}
\fi
\usepackage{color}
\usepackage{fancyvrb}
\newcommand{\VerbBar}{|}
\newcommand{\VERB}{\Verb[commandchars=\\\{\}]}
\DefineVerbatimEnvironment{Highlighting}{Verbatim}{commandchars=\\\{\}}
% Add ',fontsize=\small' for more characters per line
\newenvironment{Shaded}{}{}
\newcommand{\KeywordTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}}
\newcommand{\DataTypeTok}[1]{\textcolor[rgb]{0.56,0.13,0.00}{{#1}}}
\newcommand{\DecValTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\BaseNTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\FloatTok}[1]{\textcolor[rgb]{0.25,0.63,0.44}{{#1}}}
\newcommand{\ConstantTok}[1]{\textcolor[rgb]{0.53,0.00,0.00}{{#1}}}
\newcommand{\CharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\SpecialCharTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\StringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\VerbatimStringTok}[1]{\textcolor[rgb]{0.25,0.44,0.63}{{#1}}}
\newcommand{\SpecialStringTok}[1]{\textcolor[rgb]{0.73,0.40,0.53}{{#1}}}
\newcommand{\ImportTok}[1]{{#1}}
\newcommand{\CommentTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textit{{#1}}}}
\newcommand{\DocumentationTok}[1]{\textcolor[rgb]{0.73,0.13,0.13}{\textit{{#1}}}}
\newcommand{\AnnotationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}}
\newcommand{\CommentVarTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}}
\newcommand{\OtherTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{{#1}}}
\newcommand{\FunctionTok}[1]{\textcolor[rgb]{0.02,0.16,0.49}{{#1}}}
\newcommand{\VariableTok}[1]{\textcolor[rgb]{0.10,0.09,0.49}{{#1}}}
\newcommand{\ControlFlowTok}[1]{\textcolor[rgb]{0.00,0.44,0.13}{\textbf{{#1}}}}
\newcommand{\OperatorTok}[1]{\textcolor[rgb]{0.40,0.40,0.40}{{#1}}}
\newcommand{\BuiltInTok}[1]{{#1}}
\newcommand{\ExtensionTok}[1]{{#1}}
\newcommand{\PreprocessorTok}[1]{\textcolor[rgb]{0.74,0.48,0.00}{{#1}}}
\newcommand{\AttributeTok}[1]{\textcolor[rgb]{0.49,0.56,0.16}{{#1}}}
\newcommand{\RegionMarkerTok}[1]{{#1}}
\newcommand{\InformationTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}}
\newcommand{\WarningTok}[1]{\textcolor[rgb]{0.38,0.63,0.69}{\textbf{\textit{{#1}}}}}
\newcommand{\AlertTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
\newcommand{\ErrorTok}[1]{\textcolor[rgb]{1.00,0.00,0.00}{\textbf{{#1}}}}
\newcommand{\NormalTok}[1]{{#1}}
\usepackage{graphicx,grffile}
\makeatletter
\def\maxwidth{\ifdim\Gin@nat@width>\linewidth\linewidth\else\Gin@nat@width\fi}
\def\maxheight{\ifdim\Gin@nat@height>\textheight\textheight\else\Gin@nat@height\fi}
\makeatother
% Scale images if necessary, so that they will not overflow the page
% margins by default, and it is still possible to overwrite the defaults
% using explicit options in \includegraphics[width, height, ...]{}
\setkeys{Gin}{width=\maxwidth,height=\maxheight,keepaspectratio}
\IfFileExists{parskip.sty}{%
\usepackage{parskip}
}{% else
\setlength{\parindent}{0pt}
\setlength{\parskip}{6pt plus 2pt minus 1pt}
}
\setlength{\emergencystretch}{3em}  % prevent overfull lines
\providecommand{\tightlist}{%
  \setlength{\itemsep}{0pt}\setlength{\parskip}{0pt}}
\setcounter{secnumdepth}{5}
% Redefines (sub)paragraphs to behave more like sections
\ifx\paragraph\undefined\else
\let\oldparagraph\paragraph
\renewcommand{\paragraph}[1]{\oldparagraph{#1}\mbox{}}
\fi
\ifx\subparagraph\undefined\else
\let\oldsubparagraph\subparagraph
\renewcommand{\subparagraph}[1]{\oldsubparagraph{#1}\mbox{}}
\fi

\title{SQL conception et mise en œuvre}
\author{Dalibo}
\date{Version 28/11/2016}

\begin{document}
\maketitle

{
\hypersetup{linkcolor=black}
\setcounter{tocdepth}{3}
\tableofcontents
}
\section{Licence Creative Commons
CC-BY-NC-SA}\label{licence-creative-commons-cc-by-nc-sa}

Vous êtes libres de redistribuer et/ou modifier cette création selon les
conditions suivantes :

\begin{itemize}
\item
  Paternité
\item
  Pas d'utilisation commerciale
\item
  Partage des conditions initiales à l'identique
\end{itemize}

Cette formation (diapositives, manuels et travaux pratiques) est sous
licence \textbf{CC-BY-NC-SA}.

Vous êtes libres de redistribuer et/ou modifier cette création selon les
conditions suivantes :

\begin{itemize}
\item
  Paternité
\item
  Pas d'utilisation commerciale
\item
  Partage des conditions initiales à l'identique
\end{itemize}

Vous devez citer le nom de l'auteur original de la manière indiquée par
l'auteur de l'œuvre ou le titulaire des droits qui vous confère cette
autorisation (mais pas d'une manière qui suggérerait qu'ils vous
soutiennent ou approuvent votre utilisation de l'œuvre).

Vous n'avez pas le droit d'utiliser cette création à des fins
commerciales.

Si vous modifiez, transformez ou adaptez cette création, vous n'avez le
droit de distribuer la création qui en résulte que sous un contrat
identique à celui-ci.

À chaque réutilisation ou distribution de cette création, vous devez
faire apparaître clairement au public les conditions contractuelles de
sa mise à disposition. La meilleure manière de les indiquer est un lien
vers cette page web.

Chacune de ces conditions peut être levée si vous obtenez l'autorisation
du titulaire des droits sur cette œuvre.

Rien dans ce contrat ne diminue ou ne restreint le droit moral de
l'auteur ou des auteurs.

Le texte complet de la licence est disponible à cette adresse:
\url{http://creativecommons.org/licenses/by-nc-sa/2.0/fr/legalcode}

\section{Introduction et premiers
SELECT}\label{introduction-et-premiers-select}

\subsection{Préambule}\label{pruxe9ambule}

\begin{itemize}
\tightlist
\item
  Qu'est-ce que le standard SQL ?
\item
  Comment lire des données
\item
  Quel type de données est disponible ?
\end{itemize}

Ce module a pour but de présenter le standard SQL. Un module ne permet
pas de tout voir, aussi ce module se concentrera sur la lecture de
données déjà présentes en base. Cela permet d'aborder aussi la question
des types de données disponibles.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Menu}\label{menu}

\begin{itemize}
\tightlist
\item
  Principes d'une base de données
\item
  Premières requêtes
\item
  Connaître les types de données
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Objectifs}\label{objectifs}

\begin{itemize}
\tightlist
\item
  Comprendre les principes
\item
  Écrire quelques requêtes en lecture
\item
  Connaître les différents types de données

  \begin{itemize}
  \tightlist
  \item
    et quelques fonctions très utiles
  \end{itemize}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Principes d'une base de
données}\label{principes-dune-base-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Base de données

  \begin{itemize}
  \tightlist
  \item
    ensemble organisé d'informations
  \end{itemize}
\item
  Système de Gestion de Bases de Données

  \begin{itemize}
  \tightlist
  \item
    acronyme SGBD (DBMS en anglais)
  \item
    programme assurant la gestion et l'accès à une base de données
  \item
    assure la cohérence des données
  \end{itemize}
\end{itemize}

Si des données sont récoltées, organisées et stockées afin de répondre à
un besoin spécifique, alors on parle de base de données. Une base de
données peut utiliser différents supports: papier, fichiers
informatiques, etc.

Le Système de Gestion de Bases de Données (SGBD), appelé //Database
Management System// (DBMS) en anglais, assure la gestion d'une base de
données informatisée. Il permet l'accès aux données et assure également
la cohérence des données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Type de bases de
données}\label{type-de-bases-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Modèle hiérarchique
\item
  Modèle réseau
\item
  Modèle relationnel
\item
  Modèle objet
\item
  Modèle relationnel-objet
\item
  NoSQL
\end{itemize}

Au fil des années ont été développés plusieurs modèles de données, que
nous allons décrire.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Type de bases de données
(1)}\label{type-de-bases-de-donnuxe9es-1}

\begin{itemize}
\tightlist
\item
  Modèle hiérarchique

  \begin{itemize}
  \tightlist
  \item
    structure arborescente
  \item
    redondance des données
  \end{itemize}
\item
  Modèle réseau

  \begin{itemize}
  \tightlist
  \item
    structure arborescente, mais permettant des associations
  \item
    ex: Bull IDS2 sur GCOS
  \end{itemize}
\end{itemize}

Les modèles hiérarchiques et réseaux ont été les premiers modèles de
données utilisées dans les années 60 sur les mainframes IBM ou Bull. Ils
ont été rapidement supplantés par le modèle relationnel car les requêtes
étaient dépendantes du modèle de données. Il était nécessaire de
connaître les liens entre les différents nœuds de l'arborescence pour
concevoir les requêtes. Les programmes sont donc complètement dépendants
de la structure de la base de données.

Des recherches souhaitent néanmoins arriver à rendre indépendant la vue
logique de l'implémentation physique de la base de données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Type de bases de données
(2)}\label{type-de-bases-de-donnuxe9es-2}

\begin{itemize}
\tightlist
\item
  Modèle relationnel

  \begin{itemize}
  \tightlist
  \item
    basé sur la théorie des ensembles et la logique des prédicats
  \item
    standardisé par la norme SQL
  \end{itemize}
\item
  Modèle objet

  \begin{itemize}
  \tightlist
  \item
    structure objet
  \item
    pas de standard
  \end{itemize}
\item
  Modèle relationnel-objet

  \begin{itemize}
  \tightlist
  \item
    le standard SQL ajoute des concepts objets
  \end{itemize}
\end{itemize}

Le modèle relationnel est issu des travaux du Docteur Edgar F. Codd
qu'il a menés dans les laboratoires d'IBM à la fin des années 60. Ses
travaux avaient pour but de rendre indépendant le stockage physique de
la vue logique de la base de données. Et, mathématicien de formation, il
s'est appuyé sur la théorie des ensembles et la logique des prédicats
pour établir les fondements des bases de données relationnelles. Pour
manipuler les données de façon ensembliste, le Dr Codd a mis au point le
point langage SQL. Ce langage est à l'origine du standard SQL qui a
émergé dans les années 80 et qui a rendu le modèle relationnel très
populaire.

Le modèle objet est, quant à lui, issu de la mouvance autour des
langages objets. Du fait de l'absence d'un standard avéré, le modèle
objet n'a jamais été populaire et est toujours resté dans l'ombre du
modèle relationnel.

Le modèle relationnel a néanmoins était étendu par la norme SQL:1999
pour intégrer des fonctionnalités objets. On parle alors de modèle
relationnel-objet. PostgreSQL en est un exemple, c'est un SGBDRO
(Système de Gestion de Bases de Données Relationnel-Objet).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Type de bases de données
(3)}\label{type-de-bases-de-donnuxe9es-3}

\begin{itemize}
\tightlist
\item
  NoSQL: « //Not only SQL// »

  \begin{itemize}
  \tightlist
  \item
    pas de norme de langage de requête
  \item
    clé-valeur (Redis, Riak)
  \item
    graphe (Neo4J)
  \item
    document (MongoDB, CouchDB)
  \item
    orienté colonne (HBase)
  \end{itemize}
\item
  Rapprochement relationnel/NoSQL

  \begin{itemize}
  \tightlist
  \item
    PostgreSQL permet de stocker des documents (JSON, XML)
  \end{itemize}
\end{itemize}

Les bases NoSQL désigne une famille de bases de données qui répondent à
d'autres besoins et contraintes que les bases relationnelles. Les bases
NoSQL sont souvent des bases « sans-schéma », la base ne vérifiant plus
l'intégrité des données selon des contraintes définies dans le modèle de
données. Chaque base de ce segment dispose d'un langage de requête
spécifique, qui n'est pas normé. Une tentative de standardisation,
débutée en 2011, n'a d'ailleurs abouti sur aucun résultat.

Ce type de base offre souvent la possibilité d'offrir du //sharding//
simple à mettre en œuvre. Le sharding consiste à répartir les données
physiquement sur plusieurs serveurs. Certaines technologies semblent
mieux marcher que d'autres de ce point de vue là. En contre-partie, la
durabilité des données n'est pas assurée, au contraire d'une base
relationnelle qui assure la durabilité dès la réponse à un
\texttt{COMMIT}.

Exemple de requête SQL :

\begin{Shaded}
\begin{Highlighting}[]
 
\KeywordTok{SELECT} \NormalTok{person, }\FunctionTok{SUM}\NormalTok{(score), }\FunctionTok{AVG}\NormalTok{(score), }\FunctionTok{MIN}\NormalTok{(score), }\FunctionTok{MAX}\NormalTok{(score), }\FunctionTok{COUNT}\NormalTok{(*) }
\KeywordTok{FROM} \NormalTok{demo }
\KeywordTok{WHERE} \NormalTok{score > }\DecValTok{0} \KeywordTok{AND} \NormalTok{person }\KeywordTok{IN}\NormalTok{(}\StringTok{'bob'}\NormalTok{,}\StringTok{'jake'}\NormalTok{) }
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{person;}
\end{Highlighting}
\end{Shaded}

La même requête, pour MongoDB :

\begin{verbatim}
db.demo.group({
    "key": {
        "person": true
    },
    "initial": {
        "sumscore": 0,
        "sumforaverageaveragescore": 0,
        "countforaverageaveragescore": 0,
        "countstar": 0
    },
    "reduce": function(obj, prev) {
        prev.sumscore = prev.sumscore + obj.score - 0;
        prev.sumforaverageaveragescore += obj.score;
        prev.countforaverageaveragescore++;
        prev.minimumvaluescore = isNaN(prev.minimumvaluescore) ? obj.score : Math.min(prev.minimumvaluescore, obj.score);
        prev.maximumvaluescore = isNaN(prev.maximumvaluescore) ? obj.score : Math.max(prev.maximumvaluescore, obj.score);
        if (true != null) if (true instanceof Array) prev.countstar += true.length;
        else prev.countstar++;
    },
    "finalize": function(prev) {
        prev.averagescore = prev.sumforaverageaveragescore / prev.countforaverageaveragescore;
        delete prev.sumforaverageaveragescore;
        delete prev.countforaverageaveragescore;
    },
    "cond": {
        "score": {
            "$gt": 0
        },
        "person": {
            "$in": ["bob", "jake"]
        }
    }
});
\end{verbatim}

Un des avantages de ces technologies, c'est qu'un modèle clé-valeur
permet facilement d'utiliser des algorithmes de type MapReduce: diviser
le problème en sous-problèmes traités parallèlement par différents nœuds
(phase Map), puis synthétisés de façon centralisée (phase Reduce).

Les bases de données relationnelles ne sont pas incompatibles avec Map
Reduce en soit. Simplement, le langage SQL étant déclaratif, il est
conceptuellement opposé à la description fine des traitements qu'on doit
réaliser avec MapReduce. C'est (encore une fois) le travail de
l'optimiseur d'être capable d'effectuer ce genre d'opérations. On peut
penser au mode ``parallèle'' qu'on trouve dans certains SGBD comme
Oracle (et que PostgreSQL n'a pas encore), ou à des solutions comme
Postgres-XC. On peut aussi utiliser des technologies comme PlProxy, mais
on perd l'avantage du côté déclaratif de SQL, puisqu'on utilise alors
uniquement des procédures stockées.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Modèle relationnel}\label{moduxe8le-relationnel}

\begin{itemize}
\tightlist
\item
  Indépendance entre la vue logique et la vue physique

  \begin{itemize}
  \tightlist
  \item
    le SGBD gère lui-même le stockage physique
  \end{itemize}
\item
  Table ou \texttt{relation}
\item
  Un ensemble de tables représente la vue logique
\end{itemize}

Le modèle relationnel garantie l'indépendance entre la vue logique et la
vue physique. L'utilisateur ne se préoccupe que des objets logiques
(pour lire ou écrire des enregistrements), et le SGBD traduit la demande
exprimée avec des objets logiques en actions à réaliser sur des objets
physiques.

Les objets logiques sont appelés des relations. Ce sont généralement les
tables, mais il existe d'autres objets qui sont aussi des relations (les
vues par exemple, mais aussi les index et les séquences).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Caractéristiques du modèle
relationnel}\label{caractuxe9ristiques-du-moduxe8le-relationnel}

\begin{itemize}
\tightlist
\item
  Théorie des ensembles
\item
  Logique des prédicats
\item
  Logique 3 états
\end{itemize}

Le modèle relationnel se base sur la théorie des ensembles. Chaque
relation contient un ensemble de données et ces différents ensembles
peuvent se joindre suivant certaines conditions.

La logique des prédicats est un sous-ensemble de la théorie des
ensembles. Elle sert à exprimer des formules logiques qui permettent de
filtrer les ensembles de départ pour créer de nouveaux ensembles
(autrement dit, filtrer les enregistrements d'une relation).

Cependant, tout élément d'un enregistrement n'est pas forcément connu à
un instant t. Les filtres et les jointures doivent donc gérer trois
états lors d'un calcul de prédicat : vrai, faux ou inconnu.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{ACID}\label{acid}

\begin{itemize}
\tightlist
\item
  \textbf{Atomicité} (Atomic)
\item
  \textbf{Cohérence} (Consistent)
\item
  \textbf{Isolation} (Isolated)
\item
  \textbf{Durabilité} (Durable)
\end{itemize}

Les propriétés ACID (acronyme de ``Atomic Consistent Isolated Durable'')
sont le fondement même de toute base de donnée. Il s'agit de quatre
règles fondamentales que toute transaction doit respecter :

\begin{itemize}
\tightlist
\item
  A : Une transaction est entière : « tout ou rien ».
\item
  C : Une transaction amène la base d'un état stable à un autre.
\item
  I : Les transactions n'agissent pas les unes sur les autres.
\item
  D : Une transaction validée provoque des changements permanents.
\end{itemize}

Les propriétés ACID sont quatre propriétés essentielles d'un
sous-système de traitement de transactions d'un système de gestion de
base de données. On considère parfois que seuls les SGBD qui respectent
ces quatre propriétés sont dignes d'être considérées comme des bases de
données //relationnelles//. Les SGBD de la famille des NoSQL (MongoDB,
Cassandra, BigTable, \ldots{}) sont en effet des bases de données, mais
ne respectent pas la Cohérence. Elles sont cohérentes à terme, ou en
anglais ``eventually consistent'', mais la cohérence en fin de
transaction n'est pas garantie.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Langage SQL}\label{langage-sql}

\begin{itemize}
\tightlist
\item
  Norme ISO 9075

  \begin{itemize}
  \tightlist
  \item
    dernière version stable : 2011
  \end{itemize}
\item
  Langage déclaratif

  \begin{itemize}
  \tightlist
  \item
    on décrit le résultat et pas la façon de l'obtenir
  \item
    comme Prolog
  \end{itemize}
\item
  Traitement ensembliste

  \begin{itemize}
  \tightlist
  \item
    par opposition au traitement procédural
  \item
    ``on effectue des opérations sur des relations pour obtenir des
    relations''
  \end{itemize}
\end{itemize}

Le langage SQL a été normalisé par l'ANSI en 1986 et est devenu une
norme ISO internationale en 1987. Elle a subi plusieurs évolutions dans
le but d'ajouter des fonctionnalités correspondants aux attentes de
l'industrie logicielle. Parmi ces améliorations, notons l'intégration de
quelques fonctionnalités objets pour le modèle relationnel-objet.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{SQL est un langage}\label{sql-est-un-langage}

\begin{itemize}
\tightlist
\item
  Langage

  \begin{itemize}
  \tightlist
  \item
    règles d'écriture
  \item
    règles de formatage
  \item
    commentaires
  \end{itemize}
\item
  Améliore la lisibilité d'une requête
\end{itemize}

Il n'y a pas de règles établies concernant l'écriture de requêtes SQL.
Il faut néanmoins avoir à l'esprit qu'il s'agit d'un langage à part
entière et, au même titre que ce qu'un développeur fait avec n'importe
quel code source, il convient de l'écrire de façon lisible.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Recommandations d'écriture et de
formatage}\label{recommandations-duxe9criture-et-de-formatage}

\begin{itemize}
\tightlist
\item
  Écriture

  \begin{itemize}
  \tightlist
  \item
    mots clés SQL en MAJUSCULES
  \item
    identifiants de colonnes/tables en minuscule
  \end{itemize}
\item
  Formatage

  \begin{itemize}
  \tightlist
  \item
    dissocier les éléments d'une requête
  \item
    un prédicat par ligne
  \item
    indentation
  \end{itemize}
\end{itemize}

Quelle est la requête la plus lisible ?

celle-ci ?

\begin{verbatim}
select  groupeid,datecreationitem from itemagenda where typeitemagenda = 5  and groupeid in(12225,12376) and datecreationitem > now() order by groupeid,datecreationitem ;
\end{verbatim}

ou celle-ci ?

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{groupeid, datecreationitem}
  \KeywordTok{FROM} \NormalTok{itemagenda}
 \KeywordTok{WHERE} \NormalTok{typeitemagenda = }\DecValTok{5}
   \KeywordTok{AND} \NormalTok{groupeid }\KeywordTok{IN} \NormalTok{(}\DecValTok{12225}\NormalTok{,}\DecValTok{12376}\NormalTok{)}
   \KeywordTok{AND} \NormalTok{datecreationitem > now()}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{groupeid, datecreationitem;}
\end{Highlighting}
\end{Shaded}

Cet exemple est tiré du forum postgresql.fr
(http://forum.postgresql.fr/viewtopic.php?id=2610).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Commentaires}\label{commentaires}

\begin{itemize}
\tightlist
\item
  Commentaire sur le reste de la ligne

  \begin{itemize}
  \tightlist
  \item
    \texttt{-\/-\ commentaire}
  \end{itemize}
\item
  Commentaire dans un bloc

  \begin{itemize}
  \tightlist
  \item
    \texttt{/*\ bloc\ */}
  \end{itemize}
\end{itemize}

Une requête SQL peut être commentée au même titre qu'un programme
standard.

Le marqueur \texttt{-\/-} permet de signifier à l'analyseur syntaxique
que le reste de la ligne est commenté, il n'en tiendra donc pas compte
dans son analyse de la requête.

Un commentaire peut aussi se présenter sous la forme d'un bloc de
commentaire, le bloc pouvant occuper plusieurs lignes :

\texttt{/*\ Ceci\ est\ un\ commentaire\ \ \ \ sur\ plusieurs\ \ \ \ lignes\ */}

Aucun des éléments compris entre le marqueur de début de bloc
\texttt{/*} et le marqueur de fin de bloc \texttt{*/} ne sera pris en
compte. Certains SGBDR propriétaires utilisent ces commentaires pour y
placer des informations (appelées \texttt{hints} sur Oracle) qui
permettent d'influencer le comportement de l'optimiseur, mais PostgreSQL
ne possède pas ce genre de mécanisme.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Les 4 types d'ordres SQL}\label{les-4-types-dordres-sql}

\begin{itemize}
\tightlist
\item
  DDL

  \begin{itemize}
  \tightlist
  \item
    Data Definition Language
  \item
    définit les structures de données
  \end{itemize}
\item
  DML

  \begin{itemize}
  \tightlist
  \item
    Data Manipulation Language
  \item
    manipule les données
  \end{itemize}
\item
  DCL

  \begin{itemize}
  \tightlist
  \item
    Data Control Language
  \item
    contrôle l'accès aux données
  \end{itemize}
\item
  TCL

  \begin{itemize}
  \tightlist
  \item
    Transaction Control Language
  \item
    contrôle les transactions
  \end{itemize}
\end{itemize}

Le langage SQL est divisé en quatre sous-ensembles qui ont chacun un but
différent.

Les ordres DDL (pour \texttt{Data\ Definition\ Language}) permettent de
définir les structures de données. On retrouve les ordres suivants : *
\texttt{CREATE}, crée un objet * \texttt{ALTER}, modifie la définition
d'un objet * \texttt{DROP}, supprime un objet * \texttt{TRUNCATE}, vide
un objet * \texttt{COMMENT}, ajoute un commentaire sur un objet

Les ordres DML (pour \texttt{Data\ Manipulation\ Language}) permettent
l'accès et la modification des données. On retrouve les ordres suivants
: * \texttt{SELECT}, lit les données d'une ou plusieurs tables *
\texttt{INSERT}, ajoute des données dans une table * \texttt{UPDATE},
modifie les données d'une table * \texttt{DELETE}, supprime les données
d'une table

Les ordres DCL (pour \texttt{Data\ Control\ Language}) permettent de
contrôler l'accès aux données. Ils permettent plus précisément de donner
ou retirer des droits à des utilisateurs ou des groupes sur les objets
de la base de données : * \texttt{GRANT}, donne un droit d'accès à un
rôle sur un objet * \texttt{REVOKE}, retire un droit d'accès d'un rôle
sur un objet

Enfin, les ordres TCL (pour \texttt{Transaction\ Control\ Language})
permettent de contrôler les transactions : * \texttt{BEGIN}, ouvre une
transaction * \texttt{COMMIT}, valide les traitements d'une transaction
* \texttt{ROLLBACK}, annule les traitements d'une transaction *
\texttt{SAVEPOINT}, crée un point de reprise dans une transaction *
\texttt{SET\ TRANSACTION}, modifie les propriétés d'une transaction en
cours

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Lecture de données}\label{lecture-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Ordre \texttt{SELECT}

  \begin{itemize}
  \tightlist
  \item
    lecture d'une ou plusieurs tables
  \item
    ou appel de fonctions
  \end{itemize}
\end{itemize}

La lecture des données se fait via l'ordre \texttt{SELECT}. Il permet de
récupérer des données d'une ou plusieurs tables (il faudra dans ce cas
joindre les tables). Il permet aussi de faire appel à des fonctions
stockées en base.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Syntaxe de SELECT}\label{syntaxe-de-select}

\texttt{sql\ \ SELECT\ expressions\_colonnes\ \ {[}\ FROM\ elements\_from\ {]}\ \ {[}\ WHERE\ predicats\ {]}\ \ {[}\ ORDER\ BY\ expressions\_orderby\ {]}\ \ {[}\ LIMIT\ limite\ {]}\ \ {[}\ OFFSET\ offset\ {]};}

L'ordre \texttt{SELECT} est composé de différents éléments dont la
plupart sont optionnels. L'exemple de syntaxe donné ici n'est pas
complet.

La syntaxe complète de l'ordre \texttt{SELECT} est disponible dans le
manuel de PostgreSQL : http://docs.postgresql.fr/current/sql-select.html
.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Liste de sélection}\label{liste-de-suxe9lection}

\begin{itemize}
\tightlist
\item
  Description du résultat de la requête

  \begin{itemize}
  \tightlist
  \item
    colonnes retournées
  \item
    renommage
  \item
    dédoublonnage
  \end{itemize}
\end{itemize}

La liste de sélection décrit le format de la table virtuelle qui est
retournée par l'ordre \texttt{SELECT}. Les types de données des colonnes
retournées seront conformes au type des éléments donnés dans la liste de
sélection.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Colonnes retournées}\label{colonnes-retournuxe9es}

\begin{itemize}
\tightlist
\item
  Liste des colonnes retournées

  \begin{itemize}
  \tightlist
  \item
    expression
  \item
    séparées par une virgule
  \end{itemize}
\item
  Expression

  \begin{itemize}
  \tightlist
  \item
    constante
  \item
    référence de colonne: \texttt{table.colonne}
  \item
    opération sur des colonnes et/ou des constantes
  \end{itemize}
\end{itemize}

La liste de sélection décrit le format de la table virtuelle qui est
retournée par l'ordre \texttt{SELECT}. Cette liste est composée
d'expressions séparées par une virgule.

Chaque expression peut être une simple constante, peut faire référence à
des colonnes d'une table lue par la requête, et peut être un appel à une
fonction.

Une expression peut être plus complexe. Par exemple, elle peut combiner
plusieurs constantes et/ou colonnes à l'aide d'opérations. Parmi les
opérations les plus classiques, les opérateurs arithmétiques classiques
sont utilisables pour les données numériques. L'opérateur de
concaténation permet de concaténer des chaînes de caractères.

L'expression d'une colonne peut être une constante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1}\NormalTok{;}
 \NormalTok{?}\KeywordTok{column}\NormalTok{? }
\CommentTok{----------}
        \DecValTok{1}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Elle peut aussi être une référence à une colonne d'une table :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{appellation.libelle}
  \KeywordTok{FROM} \NormalTok{appellation;}
\end{Highlighting}
\end{Shaded}

Comme il n'y a pas d'ambiguïté avec la colonne \texttt{libelle}, la
référence de la colonne \texttt{appellation.libelle} peut être
simplifiée en \texttt{libelle} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{libelle}
  \KeywordTok{FROM} \NormalTok{appellation;}
\end{Highlighting}
\end{Shaded}

Le SGBD saura déduire la table et la colonne mises en œuvre dans cette
requête. Il faudra néanmoins utiliser la forme complète
\texttt{table.colonne} si la requête met en œuvre des tables qui
possèdent des colonnes qui portent des noms identiques.

Une requête peut sélectionner plusieurs colonnes. Dans ce cas, les
expressions de colonnes sont définies sous la forme d'une liste dont
chaque élément est séparé par une virgule :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \KeywordTok{id}\NormalTok{, libelle, region_id}
  \KeywordTok{FROM} \NormalTok{appellation;}
\end{Highlighting}
\end{Shaded}

Le joker \texttt{*} permet de sélectionner l'ensemble des colonnes d'une
table, elles apparaitront dans leur ordre physique (attention si l'ordre
change !) :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{appellation;}
\end{Highlighting}
\end{Shaded}

Si une requête met en œuvre plusieurs tables, on peut choisir de
retourner toutes les colonnes d'une seule table :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{appellation.*}
  \KeywordTok{FROM} \NormalTok{appellation;}
\end{Highlighting}
\end{Shaded}

Enfin, on peut récupérer un tuple entier de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{appellation}
  \KeywordTok{FROM} \NormalTok{appellation;}
\end{Highlighting}
\end{Shaded}

Une expression de colonne peut également être une opération, par exemple
une addition :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \NormalTok{+ }\DecValTok{1}\NormalTok{;}
 \NormalTok{?}\KeywordTok{column}\NormalTok{? }
\CommentTok{----------}
        \DecValTok{2}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Ou une soustraction :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{annee, nombre - }\DecValTok{10}
  \KeywordTok{FROM} \NormalTok{stock;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Alias de colonne}\label{alias-de-colonne}

\begin{itemize}
\tightlist
\item
  Renommage

  \begin{itemize}
  \tightlist
  \item
    ou alias
  \item
    \texttt{AS} :\texttt{expression\ AS\ alias}
  \item
    le résultat portera le nom de l'alias
  \end{itemize}
\end{itemize}

Afin de pouvoir nommer de manière adéquate les colonnes du résultat
d'une requête \texttt{SELECT}, le mot clé \texttt{AS} permet de définir
un alias de colonne. Cet alias sera utilisé dans le résultat pour nommer
la colonne en sortie :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \NormalTok{+ }\DecValTok{1} \KeywordTok{AS} \NormalTok{somme;}
 \NormalTok{somme }
\CommentTok{-------}
     \DecValTok{2}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Cet alias n'est pas utilisable dans le reste de la requête (par exemple
dans la clause \texttt{WHERE}).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Dédoublonnage des
résultats}\label{duxe9doublonnage-des-ruxe9sultats}

\begin{itemize}
\tightlist
\item
  Dédoublonnage des résultats avant de les retourner

  \begin{itemize}
  \tightlist
  \item
    \texttt{DISTINCT}
  \item
    à ne pas utiliser systématiquement
  \end{itemize}
\item
  \texttt{SELECT\ DISTINCT\ expressions\_colonnes...}
\end{itemize}

Par défaut, \texttt{SELECT} retourne tous les résultats d'une requête.
Parfois, des doublons peuvent se présenter dans le résultat. La clause
\texttt{DISTINCT} permet de les éviter en réalisant un dédoublonnage des
données avant de retourner le résultat de la requête.

Il faut néanmoins faire attention à l'utilisation systématique de la
clause \texttt{DISTINCT}. En effet, elle entraîne un tri systématique
des données juste avant de retourner les résultats de la requête, ce qui
va consommer de la ressource mémoire, voire de la ressource disque si le
volume de données à trier est important. De plus, cela va augmenter le
temps de réponse de la requête du fait de cette opération
supplémentaire.

En règle générale, la clause \texttt{DISTINCT} devient inutile
lorsqu'elle doit trier un ensemble qui contient des colonnes qui sont
déjà uniques. Si une requête récupère une clé primaire, les données sont
uniques par définition. Le \texttt{SELECT\ DISTINCT} sera alors
transformé en simple \texttt{SELECT}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Dérivation}\label{duxe9rivation}

\begin{itemize}
\tightlist
\item
  SQL permet de dériver les valeurs des colonnes

  \begin{itemize}
  \tightlist
  \item
    opérations arithmétiques: \texttt{+}, \texttt{-}, \texttt{/},
    \texttt{*}
  \item
    concaténation de chaînes: \texttt{\textbar{}\textbar{}}
  \item
    appel de fonction
  \end{itemize}
\end{itemize}

Les constantes et valeurs des colonnes peuvent être dérivées selon le
type des données manipulées.

Les données numériques peuvent être dérivées à l'aide des opérateurs
arithmétiques standards : \texttt{+}, \texttt{-}, \texttt{/},
\texttt{*}. Elles peuvent faire l'objet d'autres calculs à l'aide de
fonctions internes et de fonctions définies par l'utilisateur.

La requête suivante permet de calculer le volume total en litres de vin
disponible dans le stock du caviste :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{SUM}\NormalTok{(c.contenance * s.nombre) }\KeywordTok{AS} \NormalTok{volume_total}
  \KeywordTok{FROM} \NormalTok{stock s}
  \KeywordTok{JOIN} \NormalTok{contenant c}
    \KeywordTok{ON} \NormalTok{(contenant_id=c.id);}
\end{Highlighting}
\end{Shaded}

Les données de type chaînes de caractères peuvent être concaténées à
l'aide de l'opérateur dédié \texttt{\textbar{}\textbar{}}. Cet opérateur
permet de concaténer deux chaînes de caractères mais également des
données numériques avec une chaîne de caractères.

Dans la requête suivante, l'opérateur de concaténation est utilisé pour
ajouter l'unité. Le résultat est ainsi implicitement converti en chaîne
de caractères.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{SUM}\NormalTok{(c.contenance * s.nombre) || }\StringTok{' litres'} \KeywordTok{AS} \NormalTok{volume_total}
\KeywordTok{FROM} \NormalTok{stock }\KeywordTok{AS} \NormalTok{s}
\KeywordTok{JOIN} \NormalTok{contenant }\KeywordTok{AS} \NormalTok{c}
\KeywordTok{ON} \NormalTok{(contenant_id=c.id);}
\end{Highlighting}
\end{Shaded}

De manière générale, il n'est pas recommandé de réaliser les opérations
de formatage des données dans la base de données. La base de données ne
doit servir qu'à récupérer les résultats, le formatage étant assuré par
l'application.

Différentes fonctions sont également applicables aux chaînes de
caractères, de même qu'aux autres types de données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Fonctions utiles}\label{fonctions-utiles}

\begin{itemize}
\tightlist
\item
  Fonctions sur données temporelles :

  \begin{itemize}
  \tightlist
  \item
    date et heure courante: \texttt{now()}
  \item
    âge: \texttt{age(timestamp)}
  \item
    extraire une partie d'une date:
    \texttt{extract(\ \textquotesingle{}year\textquotesingle{}\ FROM\ timestamp)}
  \item
    ou
    \texttt{date\_part(\textquotesingle{}Y\textquotesingle{},timestamp)}
  \end{itemize}
\item
  Fonctions sur données caractères :

  \begin{itemize}
  \tightlist
  \item
    longueur d'une chaîne de caractère: \texttt{char\_length(chaine)}
  \end{itemize}
\item
  Compter les lignes: \texttt{count(*)}
\end{itemize}

Parmi les fonctions les plus couramment utilisés, la fonction
\texttt{now()} permet d'obtenir la date et l'heure courante. Elle ne
prend aucun argument. Elle est souvent utilisée, notamment pour affecter
automatiquement la valeur de l'heure courante à une colonne.

La fonction \texttt{age(timestamp)} permet de connaître l'âge d'une date
par rapport à la date courante.

La fonction \texttt{char\_length(varchar)} permet de connaître la
longueur d'une chaîne de caractère.

Enfin, la fonction \texttt{count(*)} permet de compter le nombre de
lignes. Il s'agit d'une fonction d'agrégat, il n'est donc pas possible
d'afficher les valeurs d'autres colonnes sans faire appel aux capacités
de regroupement des lignes de SQL.

\textbf{Exemples}

Affichage de l'heure courante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{now();}
             \NormalTok{now              }
\CommentTok{------------------------------}
 \DecValTok{2013-06-07} \DecValTok{13}\CharTok{:01:26}\FloatTok{.43797}\DecValTok{+02}
\end{Highlighting}
\end{Shaded}

Affichage de l'âge du 1er janvier 2000 :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{age(}\DataTypeTok{date} \StringTok{'2000-01-01'}\NormalTok{);}
          \NormalTok{age           }
\CommentTok{------------------------}
 \DecValTok{13} \NormalTok{years }\DecValTok{5} \NormalTok{mons }\DecValTok{6} \NormalTok{days}
\end{Highlighting}
\end{Shaded}

Affichage de la longueur de la chaîne ``Dalibo'' :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{char_length(}\StringTok{'Dalibo'}\NormalTok{);}
 \NormalTok{char_length }
\CommentTok{-------------}
           \DecValTok{6}
\end{Highlighting}
\end{Shaded}

Affichage du nombre de lignes de la table \texttt{vin} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \NormalTok{vin;}
 \FunctionTok{count} 
\CommentTok{-------}
  \DecValTok{6067}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause FROM}\label{clause-from}

\begin{itemize}
\tightlist
\item
  \texttt{FROM\ expression\_table\ {[},\ expression\_table\ ...{]}}
\item
  Description des tables mises en œuvre dans la requête

  \begin{itemize}
  \tightlist
  \item
    une seule table
  \item
    plusieurs tables jointes
  \item
    sous-requête
  \end{itemize}
\end{itemize}

La clause \texttt{FROM} permet de lister les tables qui sont mises en
œuvres dans la requêtes \texttt{SELECT}. Il peut s'agir d'une table
physique, d'une vue ou d'une sous-requête. Le résultat de leur lecture
sera une table du point de vue de la requête qui la met en œuvre.

Plusieurs tables peuvent être mises en œuvre, généralement dans le cadre
d'une jointure.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Alias de table}\label{alias-de-table}

\begin{itemize}
\tightlist
\item
  mot-clé \texttt{AS}

  \begin{itemize}
  \tightlist
  \item
    optionnel :\texttt{reference\_table\ alias}
  \item
    la table sera ensuite référencée par l'alias
  \end{itemize}
\item
  \texttt{reference\_table\ {[}AS{]}\ alias}
\item
  \texttt{reference\_table\ AS\ alias\ (alias\_colonne1,\ ...)}
\end{itemize}

De la même façon qu'on peut créer des alias de colonnes, on peut créer
des alias de tables. La table sera ensuite référencée uniquement par cet
alias dans la requête. Elle ne pourra plus être référencée par son nom
réel. L'utilisation du nom réel provoquera d'ailleurs une erreur.

Le mot clé \texttt{AS} permet de définir un alias de table. Le nom réel
de la table se trouve à gauche, l'alias se trouve à droite. L'exemple
suivant définie un alias \texttt{reg} sur la table \texttt{region} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \KeywordTok{id}\NormalTok{, libelle}
  \KeywordTok{FROM} \NormalTok{region }\KeywordTok{AS} \NormalTok{reg;}
\end{Highlighting}
\end{Shaded}

Le mot clé \texttt{AS} est optionnel :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \KeywordTok{id}\NormalTok{, libelle}
  \KeywordTok{FROM} \NormalTok{region reg;}
\end{Highlighting}
\end{Shaded}

La requête suivante montre l'utilisation d'un alias pour les deux tables
mises en œuvre dans la requête. La table \texttt{stock} a pour alias
\texttt{s} et la table \texttt{contenant} a pour alias \texttt{c}. Les
deux tables possèdent toutes les deux une colonnes \texttt{id}, ce qui
peut poser une ambiguïté dans la clause de jointure
(\texttt{ON\ (contenant\_id=c.id)}). La condition de jointure portant
sur la colonne \texttt{contenant\_id} de la table \texttt{stock}, son
nom est unique et ne porte pas à ambiguïté. La condition de jointure
porte également sur la colonne \texttt{id} de table \texttt{contenant},
il faut préciser le nom complet de la colonne en utilisant le préfixe
\texttt{c} pour la nommer : \texttt{c.id}.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{SUM}\NormalTok{(c.contenance * s.nombre) }\KeywordTok{AS} \NormalTok{volume_total}
  \KeywordTok{FROM} \NormalTok{stock s}
  \KeywordTok{JOIN} \NormalTok{contenant c}
    \KeywordTok{ON} \NormalTok{(contenant_id=c.id);}
\end{Highlighting}
\end{Shaded}

Enfin, la forme
\texttt{reference\_table\ AS\ alias\ (alias\_colonne1,\ ...)} permet de
définir un alias de table et définir par la même occasion des alias de
colonnes. Cette forme est peu recommandé car les alias de colonnes
dépendent de l'ordre physique de ces colonnes. Cet ordre peut changer
dans le temps et donc amener à des erreurs :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{id_region, nom_region}
  \KeywordTok{FROM} \NormalTok{region }\KeywordTok{AS} \NormalTok{reg (id_region, nom_region);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Nommage des objets}\label{nommage-des-objets}

\begin{itemize}
\tightlist
\item
  Noms d'objets convertis en minuscules

  \begin{itemize}
  \tightlist
  \item
    \texttt{Nom\_Objet} devient \texttt{nom\_objet}
  \item
    certains nécessitent l'emploi de majuscules
  \end{itemize}
\item
  Le guillemet double **``** conserve la casse

  \begin{itemize}
  \tightlist
  \item
    \texttt{"Nom\_Objet"}
  \end{itemize}
\end{itemize}

Avec PostgreSQL, les noms des objets sont automatiquement convertis en
minuscule, sauf s'ils sont englobés entre des guillemets doubles. Si
jamais ils sont créés avec une casse mixte en utilisant les guillemets
doubles, chaque appel à cet objet devra utiliser la bonne casse et les
guillemets doubles. Il est donc conseillé d'utiliser une notation des
objets ne comprenant que des caractères minuscules.

Il est aussi préférable de ne pas utiliser d'accents ou de caractères
exotiques dans les noms des objets.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause WHERE}\label{clause-where}

\begin{itemize}
\tightlist
\item
  Permet d'exprimer des conditions de filtrage

  \begin{itemize}
  \tightlist
  \item
    prédicats
  \end{itemize}
\item
  Un prédicat est une opération logique

  \begin{itemize}
  \tightlist
  \item
    renvoie vrai ou faux
  \end{itemize}
\item
  La ligne est présente dans le résultat

  \begin{itemize}
  \tightlist
  \item
    si l'expression logique des prédicats est vraie
  \end{itemize}
\end{itemize}

La clause \texttt{WHERE} permet de définir des conditions de filtrage
des données. Ces conditions de filtrage sont appelées des prédicats.

Après le traitement de la clause \texttt{FROM}, chaque ligne de la table
virtuelle dérivée est vérifiée avec la condition de recherche. Si le
résultat de la vérification est positif (\texttt{true}), la ligne est
conservée dans la table de sortie, sinon (c'est-à-dire si le résultat
est faux ou nul) la ligne est ignorée.

La condition de recherche référence typiquement au moins une colonne de
la table générée dans la clause \texttt{FROM} ; ceci n'est pas requis
mais, dans le cas contraire, la clause \texttt{WHERE} n'aurait aucune
utilité.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Expression et opérateurs de
prédicats}\label{expression-et-opuxe9rateurs-de-pruxe9dicats}

\begin{itemize}
\tightlist
\item
  \texttt{expression\ operateur\_comparaison\ expression}
\item
  Comparaison

  \begin{itemize}
  \tightlist
  \item
    \texttt{=}, \texttt{\textless{}}, \texttt{\textgreater{}},
    \texttt{=\textless{}}, \texttt{\textgreater{}=},
    \texttt{\textless{}\textgreater{}}
  \end{itemize}
\item
  Négation

  \begin{itemize}
  \tightlist
  \item
    \texttt{NOT}
  \end{itemize}
\end{itemize}

Un prédicat est composé d'une expression qui est soumise à un opérateur
de prédicat pour être éventuellement comparé à une autre expression.
L'opérateur de prédicat retourne alors \texttt{true} si la condition est
vérifiée ou \texttt{false} si elle ne l'est pas ou \texttt{NULL} si son
résultat ne peut être calculé.

Les opérateurs de comparaison sont les opérateurs de prédicats les plus
souvent utilisés. L'opérateur d'égalité \texttt{=} peut être utilisé
pour vérifier l'égalité de l'ensemble des types de données supportés par
PostgreSQL. Il faudra faire attention à ce que les données comparées
soient de même type.

L'opérateur \texttt{NOT} est une négation. Si un prédicat est vrai,
l'opérateur \texttt{NOT} retournera faux. À l'inverse, si un prédicat
est faux, l'opérateur \texttt{NOT} retournera vrai. La clause
\texttt{NOT} se place devant l'expression entière.

\textbf{Exemples}

Sélection de la région dont l'identifiant est égal à 3 (en ensuite
différent de 3) :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
 \KeywordTok{WHERE} \KeywordTok{id} \NormalTok{= }\DecValTok{3}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
 \KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{id} \NormalTok{= }\DecValTok{3}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Combiner des prédicats}\label{combiner-des-pruxe9dicats}

\begin{itemize}
\tightlist
\item
  OU logique

  \begin{itemize}
  \tightlist
  \item
    \texttt{predicat\ OR\ predicat}
  \end{itemize}
\item
  ET logique *\texttt{predicat\ AND\ predicat}
\end{itemize}

Les opérateurs logiques \texttt{OR} et \texttt{AND} permettent de
combiner plusieurs prédicats dans la clause \texttt{WHERE}.

L'opérateur \texttt{OR} est un OU logique. Il retourne vrai si au moins
un des deux prédicats combinés est vrai. L'opérateur \texttt{AND} est un
ET logique. Il retourne vrai si et seulement si les deux prédicats
combinés sont vrais.

Au même titre qu'une multiplication ou une division sont prioritaires
sur une addition ou une soustraction dans un calcul, l'évaluation de
l'opérateur \texttt{AND} est prioritaire sur celle de l'opérateur
\texttt{OR}. Et, tout comme dans un calcul, il est possible de protéger
les opérations prioritaires en les encadrant de parenthèses.

\textbf{Exemples}

Dans le stock, affiche les vins dont le nombre de bouteilles est
inférieur à 2 ou supérieur à 16 :

\texttt{sql\ SELECT\ *\ \ \ FROM\ stock\ \ WHERE\ nombre\ \textless{}\ 2\ \ \ \ \ OR\ nombre\ \textgreater{}\ 16;}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Correspondance de motif}\label{correspondance-de-motif}

\begin{itemize}
\tightlist
\item
  Comparaison de motif

  \begin{itemize}
  \tightlist
  \item
    \texttt{chaine\ LIKE\ motif\ ESCAPE\ \textquotesingle{}c\textquotesingle{}}
  \item
    \texttt{\%}: toute chaine de 0 à plusieurs caractères
  \item
    \texttt{\_}: un seul caractère
  \end{itemize}
\item
  Expression régulière POSIX

  \begin{itemize}
  \tightlist
  \item
    \texttt{chaine\ \textasciitilde{}\ motif}
  \end{itemize}
\end{itemize}

L'opérateur \texttt{LIKE} permet de réaliser une recherche simple sur
motif. La chaine exprimant le motif de recherche peut utiliser deux
caractères joker: \texttt{\_} et \texttt{\%}. Le caractère \texttt{\_}
prend la place d'un caractère inconnu, qui doit toujours être présent.
Le caractère \texttt{\%} est un joker qui permet d'exprimer que
PostgreSQL doit trouver entre 0 et plusieurs caractères.

Exploiter la clause \texttt{LIKE} avec un motif sans joker ne présente
pas d'intérêt. Il est préférable dans ce cas d'utiliser l'opérateur
d'égalité.

Le mot clé \texttt{ESCAPE\ \textquotesingle{}c\textquotesingle{}} permet
de définir un caractère d'échappement pour protéger les caractères
\texttt{\_} et \texttt{\%} qui font légitimement partie de la chaîne de
caractère du motif évalué. Lorsque PostgreSQL rencontre le caractère
d'échappement indiqué, les caractères \texttt{\_} et \texttt{\%} seront
évalués comme étant les caractères \texttt{\_} et \texttt{\%} et non
comme des jokers.

L'opérateur \texttt{LIKE} dispose d'une déclinaison qui n'est pas
sensible à la casse. Il s'agit de l'opérateur \texttt{ILIKE}.

\textbf{Exemples}

Création d'un jeu d'essai :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{motif (chaine }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{30}\NormalTok{));}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{motif (chaine) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Durand'}\NormalTok{), (}\StringTok{'Dupont'}\NormalTok{), (}\StringTok{'Dupond'}\NormalTok{), (}\StringTok{'Dupon'}\NormalTok{), (}\StringTok{'Dupuis'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Toutes les chaînes commençant par la suite de caractères \texttt{Dur} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{motif }\KeywordTok{WHERE} \NormalTok{chaine }\KeywordTok{LIKE} \StringTok{'Dur%'}\NormalTok{;}
 \NormalTok{chaine }
\CommentTok{--------}
 \NormalTok{Durand}
\end{Highlighting}
\end{Shaded}

Toutes les chaînes terminant par \texttt{d} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{motif }\KeywordTok{WHERE} \NormalTok{chaine }\KeywordTok{LIKE} \StringTok{'%d'}\NormalTok{;}
 \NormalTok{chaine }
\CommentTok{--------}
 \NormalTok{Durand}
 \NormalTok{Dupond}
\end{Highlighting}
\end{Shaded}

Toutes les chaînes qui commencent par \texttt{Dupon} suivi d'un
caractère inconnu. La chaîne \texttt{Dupon} devrait être ignorée :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{motif }\KeywordTok{WHERE} \NormalTok{chaine }\KeywordTok{LIKE} \StringTok{'Dupon_'}\NormalTok{;}
   \NormalTok{chaine   }
\CommentTok{------------}
 \NormalTok{Dupont}
 \NormalTok{Dupond}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Listes et intervalles}\label{listes-et-intervalles}

\begin{itemize}
\tightlist
\item
  Liste de valeurs

  \begin{itemize}
  \tightlist
  \item
    \texttt{expression\ IN\ (valeur1\ {[},\ ...{]})}
  \end{itemize}
\item
  Chevauchement d'intervalle de valeurs

  \begin{itemize}
  \tightlist
  \item
    \texttt{expression\ BETWEEN\ expression\ AND\ expression}
  \end{itemize}
\item
  Chevauchement d'intervalle de dates

  \begin{itemize}
  \tightlist
  \item
    \texttt{(date1,\ date2)\ OVERLAPS\ (date3,\ date4)}
  \end{itemize}
\end{itemize}

La clause \texttt{IN} permet de vérifier que l'expression de gauche est
égale à une valeur présente dans l'expression de droite, qui est une
liste d'expressions. La négation peut être utilisée en utilisant la
construction \texttt{NOT\ IN}.

L'opérateur \texttt{BETWEEN} permet de vérifier que la valeur d'une
expression est comprise entre deux bornes. Par exemple, l'expression
\texttt{valeur\ BETWEEN\ 1\ AND\ 10} revient à exprimer la condition
suivante :
\texttt{valeur\ \textgreater{}=\ 1\ AND\ valeur\ \textless{}nowiki\textgreater{}\textless{}=\textless{}/nowiki\textgreater{}\ 10}.
La négation peut être utilisée en utilisant la construction
\texttt{NOT\ BETWEEN}.

\textbf{Exemples}

Recherche les chaînes qui sont présentes dans la liste \texttt{IN} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{motif }\KeywordTok{WHERE} \NormalTok{chaine }\KeywordTok{IN} \NormalTok{(}\StringTok{'Dupont'}\NormalTok{, }\StringTok{'Dupond'}\NormalTok{, }\StringTok{'Ducobu'}\NormalTok{);}
 \NormalTok{chaine }
\CommentTok{--------}
 \NormalTok{Dupont}
 \NormalTok{Dupond}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Tris}\label{tris}

\begin{itemize}
\tightlist
\item
  SQL ne garantie pas l'ordre des résultats

  \begin{itemize}
  \tightlist
  \item
    tri explicite requis
  \end{itemize}
\item
  Tris des lignes selon des expressions

  \begin{itemize}
  \tightlist
  \item
    \texttt{ORDER\ BY\ expression\ \ {[}\ ASC\ \textbar{}\ DESC\ \textbar{}\ USING\ opérateur\ {]}\ \ \ \ \ \ \ \ \ \ \ \ \ \ {[}\ NULLS\ \{\ FIRST\ \textbar{}\ LAST\ \}\ {]}\ {[},\ ...{]}}
  \item
    ordre du tri: \texttt{ASC} ou \texttt{DESC}
  \item
    placement des valeurs \texttt{NULL}: \texttt{NULLS\ FIRST} ou
    \texttt{NULLS\ LAST}
  \item
    ordre de tri des caractères: \texttt{COLLATE\ collation}
  \end{itemize}
\end{itemize}

La clause \texttt{ORDER\ BY} permet de trier les lignes du résultat
d'une requête selon une ou plusieurs expressions combinées.

L'expression la plus simple est le nom d'une colonne. Dans ce cas, les
lignes seront triées selon les valeurs de la colonne indiquée, et par
défaut dans l'ordre ascendant, c'est-à-dire de la valeur la plus petite
à la plus grande pour une donnée numérique ou temporelle, et dans
l'ordre alphabétique pour une donnée textuelle.

Les lignes peuvent être triées selon une expression plus complexe, par
exemple en dérivant la valeur d'une colonne.

L'ordre de tri peut être modifié à l'aide de la clause \texttt{DESC} qui
permet un tri dans l'ordre descendant, donc de la valeur la plus grande
à la plus petite (ou alphabétique inverse le cas échéant).

La clause \texttt{NULLS} permet de contrôler l'ordre d'apparition des
valeurs \texttt{NULL}. La clause \texttt{NULLS\ FIRST} permet de faire
apparaître d'abord les valeurs \texttt{NULL} puis les valeurs non
\texttt{NULL} selon l'ordre de tri. La clause \texttt{NULLS\ LAST}
permet de faire apparaître d'abord les valeurs non \texttt{NULL} selon
l'ordre de tri suivies par les valeurs NULL. Si cette clause n'est pas
précisée, alors PostgreSQL utilise implicitement \texttt{NULLS\ LAST}
dans le cas d'un tri ascendant (\texttt{ASC}, par défaut) ou
\texttt{NULLS\ FIRST} dans le cas d'un tri descendant (\texttt{DESC},
par défaut).

\textbf{Exemples}

Tri de la table \texttt{region} selon le nom de la région :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{libelle;}
\end{Highlighting}
\end{Shaded}

Tri de la table \texttt{stock} selon le nombre de bouteille, dans
l'ordre décroissant :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{stock}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{nombre }\KeywordTok{DESC}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Enfin, la clause \texttt{COLLATE} permet d'influencer sur l'ordre de tri
des chaînes de caractères.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Limiter le résultat}\label{limiter-le-ruxe9sultat}

\begin{itemize}
\tightlist
\item
  Obtenir des résultats à partir de la ligne n

  \begin{itemize}
  \tightlist
  \item
    \texttt{OFFSET\ n}
  \end{itemize}
\item
  Limiter le nombre de lignes à n lignes

  \begin{itemize}
  \tightlist
  \item
    \texttt{FETCH\ \{FIRST\ \textbar{}\ NEXT\}\ n\ ROWS\ ONLY}
  \item
    \texttt{LIMIT\ n}
  \end{itemize}
\item
  Opérations combinables

  \begin{itemize}
  \tightlist
  \item
    \texttt{OFFSET} doit apparaitre avant \texttt{FETCH}
  \end{itemize}
\item
  Peu d'intérêt sur des résultats non triés
\end{itemize}

La clause \texttt{OFFSET} permet d'exclure les \texttt{n} premières
lignes du résultat. Toutes les autres lignes sont ramenées.

La clause \texttt{FETCH} permet de limiter le résultat d'une requête. La
requête retournera au maximum \texttt{n} lignes de résultats. Elle en
retournera moins, voire aucune, si la requête ne peut ramener
suffisamment de lignes. La clause \texttt{FIRST} ou \texttt{NEXT} est
obligatoire mais le choix de l'une ou l'autre n'a aucune conséquence sur
le résultat.

La clause \texttt{FETCH} est synonyme de la clause \texttt{LIMIT}. Mais
\texttt{LIMIT} est une clause propre à PostgreSQL et quelques autres
SGBD. Il est recommandé d'utiliser \texttt{FETCH} pour se conformer au
standard.

Ces deux opérations peuvent être combinées. La norme impose de faire
apparaître la clause \texttt{OFFSET} avant la clause \texttt{FETCH}.
PostgreSQL permet néanmoins d'exprimer ces clauses dans un ordre
différent, mais la requête ne pourra pas être portée sur un autre SGBD
sans transformation.

Il faut faire attention au fait que ces fonctions ne permettent pas
d'obtenir des résultats stables si les données ne sont pas triées
explicitement. En effet, le standard SQL ne garantie en aucune façon
l'ordre des résultats à moins d'employer la clause \texttt{ORDER\ BY}.

\textbf{Exemples}

La fonction \texttt{generate\_series} permet de générer une suite de
valeurs numériques. Par exemple, une suite comprise entre 1 et 10 :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{);}
 \NormalTok{generate_series }
\CommentTok{-----------------}
               \DecValTok{1}
\NormalTok{(...)}
              \DecValTok{10}
\NormalTok{(}\DecValTok{10} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La clause \texttt{FETCH} permet donc de limiter le nombre de lignes du
résultats :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{) FETCH }\FunctionTok{FIRST} \DecValTok{5} \KeywordTok{ROWS} \KeywordTok{ONLY}\NormalTok{;}
 \NormalTok{generate_series }
\CommentTok{-----------------}
               \DecValTok{1}
               \DecValTok{2}
               \DecValTok{3}
               \DecValTok{4}
               \DecValTok{5}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La clause \texttt{LIMIT} donne un résultat équivalent :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{) }\KeywordTok{LIMIT} \DecValTok{5}\NormalTok{;}
 \NormalTok{generate_series }
\CommentTok{-----------------}
               \DecValTok{1}
               \DecValTok{2}
               \DecValTok{3}
               \DecValTok{4}
               \DecValTok{5}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La clause \texttt{OFFSET\ 4} permet d'exclure les quatre premières
lignes et de retourner les autres lignes du résultat :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{) OFFSET }\DecValTok{4}\NormalTok{;}
 \NormalTok{generate_series }
\CommentTok{-----------------}
               \DecValTok{5}
               \DecValTok{6}
               \DecValTok{7}
               \DecValTok{8}
               \DecValTok{9}
              \DecValTok{10}
\NormalTok{(}\DecValTok{6} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Les clauses \texttt{LIMIT} et \texttt{OFFSET} peuvent être combinées
pour ramener les deux lignes en excluant les quatre premières :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{) OFFSET }\DecValTok{4} \KeywordTok{LIMIT} \DecValTok{2}\NormalTok{;}
 \NormalTok{generate_series }
\CommentTok{-----------------}
               \DecValTok{5}
               \DecValTok{6}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utiliser plusieurs
tables}\label{utiliser-plusieurs-tables}

\begin{itemize}
\tightlist
\item
  Clause \texttt{FROM}

  \begin{itemize}
  \tightlist
  \item
    liste de tables séparées par \texttt{,}
  \end{itemize}
\item
  Une table est combinée avec une autre

  \begin{itemize}
  \tightlist
  \item
    jointure
  \item
    produit cartésien
  \end{itemize}
\end{itemize}

Il est possible d'utiliser plusieurs tables dans une requête
\texttt{SELECT}. Lorsque c'est le cas, et sauf cas particulier, on fera
correspondre les lignes d'une table avec les lignes d'une autre table
selon certains critères. Cette mise en correspondance s'appelle une
jointure et les critères de correspondances s'appellent une condition de
jointure.

Si aucune condition de jointure n'est donnée, chaque ligne de la
première table est mise en correspondance avec toutes les lignes de la
seconde table. C'est un produit cartésien. En général, un produit
cartésien n'est pas souhaitable et est généralement le résultat d'une
erreur de conception de la requête.

\textbf{Exemples}

Création d'un jeu de données simple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{mere (}\KeywordTok{id} \DataTypeTok{integer} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, val_mere text);}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{fille (id_fille }\DataTypeTok{integer} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, id_mere }\DataTypeTok{integer} \KeywordTok{REFERENCES} \NormalTok{mere(}\KeywordTok{id}\NormalTok{), val_fille text);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{mere (}\KeywordTok{id}\NormalTok{, val_mere) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'mere 1'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{mere (}\KeywordTok{id}\NormalTok{, val_mere) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'mere 2'}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (id_fille, id_mere, val_fille) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{1}\NormalTok{, }\StringTok{'fille 1'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (id_fille, id_mere, val_fille) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{1}\NormalTok{, }\StringTok{'fille 2'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Jointure entre la table \texttt{mere} et la table \texttt{fille}. Les
identifiants \texttt{id\_mere} de la table \texttt{fille} doivent
correspondre avec les identifiants \texttt{id} de la table \texttt{mere}
:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{mere, fille}
 \KeywordTok{WHERE} \NormalTok{mere.id = fille.id_mere;}
 \KeywordTok{id} \NormalTok{| val_mere | id_fille | id_mere | val_fille }
\CommentTok{----+----------+----------+---------+-----------}
  \DecValTok{1} \NormalTok{| mere }\DecValTok{1}   \NormalTok{|        }\DecValTok{1} \NormalTok{|       }\DecValTok{1} \NormalTok{| fille }\DecValTok{1}
  \DecValTok{1} \NormalTok{| mere }\DecValTok{1}   \NormalTok{|        }\DecValTok{2} \NormalTok{|       }\DecValTok{1} \NormalTok{| fille }\DecValTok{2}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Un produit cartésien est créé en omettant la condition de jointure, le
résultat n'a plus de sens :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{mere, fille;}
 \KeywordTok{id} \NormalTok{| val_mere | id_fille | id_mere | val_fille }
\CommentTok{----+----------+----------+---------+-----------}
  \DecValTok{1} \NormalTok{| mere }\DecValTok{1}   \NormalTok{|        }\DecValTok{1} \NormalTok{|       }\DecValTok{1} \NormalTok{| fille }\DecValTok{1}
  \DecValTok{1} \NormalTok{| mere }\DecValTok{1}   \NormalTok{|        }\DecValTok{2} \NormalTok{|       }\DecValTok{1} \NormalTok{| fille }\DecValTok{2}
  \DecValTok{2} \NormalTok{| mere }\DecValTok{2}   \NormalTok{|        }\DecValTok{1} \NormalTok{|       }\DecValTok{1} \NormalTok{| fille }\DecValTok{1}
  \DecValTok{2} \NormalTok{| mere }\DecValTok{2}   \NormalTok{|        }\DecValTok{2} \NormalTok{|       }\DecValTok{1} \NormalTok{| fille }\DecValTok{2}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Types de données}\label{types-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Type de données

  \begin{itemize}
  \tightlist
  \item
    du standard SQL
  \item
    certains spécifiques PostgreSQL
  \end{itemize}
\end{itemize}

PostgreSQL propose l'ensemble des types de données du standard SQL, à
l'exception du type \texttt{BLOB} qui a toutefois un équivalent. Mais
PostgreSQL a été conçu pour être extensible et permet de créer
facilement des types de données spécifiques. C'est pourquoi PostgreSQL
propose un certain nombre de types de données spécifiques qui peuvent
être intéressants.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Qu'est-ce qu'un type de données
?}\label{quest-ce-quun-type-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Le système de typage valide les données
\item
  Un type détermine

  \begin{itemize}
  \tightlist
  \item
    les valeurs possibles
  \item
    comment les données sont stockées
  \item
    les opérations que l'on peut appliquer
  \end{itemize}
\end{itemize}

On utilise des types de données pour représenter une information de
manière pertinente. Les valeurs possibles d'une donnée vont dépendre de
son type. Par exemple, un entier long ne permet par exemple pas de coder
des valeurs décimales. De la même façon, un type entier ne permet pas de
représenter une chaîne de caractère, mais l'inverse est possible.

L'intérêt du typage des données est qu'il permet également à la base de
données de valider les données manipulées. Ainsi, un entier
\texttt{integer} permet de représenter des valeurs comprises entre
-2,147,483,648 et 2,147,483,647. Si l'utilisateur tente d'insérer une
donnée qui dépasse les capacités de ce type de données, une erreur lui
sera retournée. On retrouve ainsi la notion d'intégrité des données.
Comme pour les langages de programmation fortement typés, cela permet de
détecter davantage d'erreurs, plus tôt: à la compilation dans les
langages typés, ou ici des la première exécution d'une requête, plutôt
que plus tard, quand une chaîne de caractère ne pourra pas être
convertie à la volée en entier par exemple.

Le choix d'un type de données va également influencer la façon dont les
données sont représentées. En effet, toute donnée à une représentation
textuelle et une représentation en mémoire et sur disque. Ainsi, un
\texttt{integer} est représenté sous la forme d'une suite de 4 octets,
manipulables directement par le processeur, alors que sa représentation
textuelle est une suite de caractères. Cela a une implication forte sur
les performances de la base de données.

Le type de données choisi permet également de déterminer les opérations
que l'on pourra appliquer. Tous les types de données permettent
d'utiliser des opérateurs qui leur sont propres. Ainsi il est possible
d'additionner des entiers, de concaténer des chaines de caractères, etc.
Si une opération ne peut être réalisée nativement sur le type de
données, il faudra utiliser des conversions coûteuses. Vaut-il mieux
additionner deux entiers issus d'une conversion d'une chaîne de
caractère vers un entier ou additionner directement deux entiers ?
Vaut-il mieux stocker une adresse IP avec un \texttt{varchar} ou avec un
type de données dédié ?

Il est à noter que l'utilisateur peut contrôler lui même certains types
de données paramétrés. Le paramètre représente la longueur ou la
précision du type de données. Ainsi, un type \texttt{varchar(15)}
permettra de représenter des chaînes de caractères de 15 caractères
maximum.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Types de données}\label{types-de-donnuxe9es-1}

\begin{itemize}
\tightlist
\item
  Types standards SQL
\item
  Types dérivés
\item
  Types spécifiques à PostgreSQL\\
\item
  Types utilisateurs
\end{itemize}

Les types de données standards permettent de traiter la plupart des
situations qui peuvent survenir.

Dans certains cas, il peut être nécessaire de faire appel aux types
spécifiques à PostgreSQL, par exemple pour stocker des adresses IP avec
le type spécifique et bénéficier par la même occasion de toutes les
classes d'opérateurs qui permettent de manipuler simplement ce type de
données.

Et si cela ne s'avère pas suffisant, PostgreSQL permet à l'utilisateur
de créer lui-même ses propres types de données, ainsi que les classes
d'opérateurs et fonctions permettant d'indexer ces données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Types standards (1)}\label{types-standards-1}

\begin{itemize}
\tightlist
\item
  Caractère

  \begin{itemize}
  \tightlist
  \item
    char, varchar
  \end{itemize}
\item
  Numérique

  \begin{itemize}
  \tightlist
  \item
    integer, smallint, bigint
  \item
    real, double precision
  \item
    numeric, decimal
  \end{itemize}
\item
  Booléen

  \begin{itemize}
  \tightlist
  \item
    boolean
  \end{itemize}
\end{itemize}

Le standard SQL propose des types standards pour stocker des chaînes de
caractères (de taille fixe ou variable), des données numériques
(entières, à virgule flottante) et des booléens.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Types standards (2)}\label{types-standards-2}

\begin{itemize}
\tightlist
\item
  Temporel

  \begin{itemize}
  \tightlist
  \item
    date, time
  \item
    timestamp
  \item
    interval
  \end{itemize}
\item
  Chaînes de bit

  \begin{itemize}
  \tightlist
  \item
    bit, bit varying
  \end{itemize}
\item
  Formats validés

  \begin{itemize}
  \tightlist
  \item
    XML
  \end{itemize}
\end{itemize}

Le standard SQL propose également des types standards pour stocker des
éléments temporels (date, heure, la combinaison des deux avec ou sans
fuseau horaire, intervalle). Plus rarement utilisés, SQL permet
également de stocker des chaînes de bit et des données validées au
format XML.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Caractères}\label{caractuxe8res}

\begin{itemize}
\tightlist
\item
  \texttt{char(n)}

  \begin{itemize}
  \tightlist
  \item
    longueur fixe
  \item
    de \texttt{n} caractères
  \item
    complété à droite par des espaces si nécessaire
  \end{itemize}
\item
  \texttt{varchar(}n\texttt{)}

  \begin{itemize}
  \tightlist
  \item
    longueur variable
  \item
    maximum \texttt{n} caractères
  \item
    \texttt{n} optionnel
  \end{itemize}
\end{itemize}

Le type \texttt{char(n)} permet de stocker des chaînes de caractères de
taille fixe, donnée par l'argument \texttt{n}. Si la chaîne que l'on
souhaite stocker est plus petite que la taille donnée à la déclaration
de la colonne, elle sera complétée par des espaces à droite. Si la
chaîne que l'on souhaite stocker est trop grande, une erreur sera levée.

Le type \texttt{varchar(n)} permet de stocker des chaînes de caractères
de taille variable. La taille maximale de la chaîne est donnée par
l'argument \texttt{n}. Toute chaîne qui excèdera cette taille ne sera
pas prise en compte et génèrera une erreur. Les chaînes de taille
inférieure à la taille limite seront stockées sans altérations.

La longueur de chaîne est mesurée en nombre de caractères sous
PostgreSQL. Ce n'est pas forcément le cas dans d'autres SGBD.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Représentation données
caractères}\label{repruxe9sentation-donnuxe9es-caractuxe8res}

\begin{itemize}
\tightlist
\item
  Norme SQL

  \begin{itemize}
  \tightlist
  \item
    chaîne encadrée par \texttt{\textquotesingle{}}
  \item
    \texttt{\textquotesingle{}chaîne\ de\ caractères\textquotesingle{}}
  \end{itemize}
\item
  Chaînes avec échappement du style C

  \begin{itemize}
  \tightlist
  \item
    chaîne précédée par \texttt{E} ou \texttt{e}
  \item
    \texttt{E\textquotesingle{}chaîne\ de\ caractères\textquotesingle{}}
  \end{itemize}
\item
  Chaînes avec échappement Unicode

  \begin{itemize}
  \tightlist
  \item
    chaîne précédée par \texttt{U\&}
  \item
    \texttt{U\&\textquotesingle{}chaîne\ de\ caractères\textquotesingle{}}
  \end{itemize}
\end{itemize}

La norme SQL définit que les chaînes de caractères sont représentées
encadrées de guillemets simples (caractère \texttt{\textquotesingle{}}).
Le guillemet double (caractère \texttt{"}) ne peut être utilisé car il
sert à protéger la casse des noms d'objets. PostgreSQL interprétera
alors la chaîne comme un nom d'objet et générera une erreur.

Une représentation correcte d'une chaîne de caractères est donc de la
forme suivante :

\texttt{\textquotesingle{}chaîne\ de\ caractères\textquotesingle{}}

Les caractères \texttt{\textquotesingle{}} doivent être doublés s'ils
apparaissent dans la chaîne :

\texttt{\textquotesingle{}J}ai acheté des croissants'`

Une extension de la norme par PostgreSQL permet d'utiliser les
méta-caractères des langages tels que le C, par exemple
\texttt{\textbackslash{}n} pour un retour de ligne,
\texttt{\textbackslash{}t} pour une tabulation, etc. :

\texttt{E\textquotesingle{}chaîne\ avec\ un\ retour\ \textbackslash{}nde\ ligne\ et\ une\ \textbackslash{}ttabulation\textquotesingle{}}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Numériques}\label{numuxe9riques}

\begin{itemize}
\tightlist
\item
  Entier

  \begin{itemize}
  \tightlist
  \item
    \texttt{smallint}, \texttt{integer}, \texttt{bigint}
  \item
    signés
  \end{itemize}
\item
  Virgule flottante

  \begin{itemize}
  \tightlist
  \item
    \texttt{real}, \texttt{double\ precision}
  \item
    valeurs inexactes
  \end{itemize}
\item
  Précision arbitraire

  \begin{itemize}
  \tightlist
  \item
    \texttt{numeric(precision,echelle)},
    \texttt{decimal(precision,echelle)}
  \item
    valeurs exactes
  \end{itemize}
\end{itemize}

Le standard SQL propose des types spécifiques pour stocker des entiers
signés. Le type \texttt{smallint} permet de stocker des valeurs codées
sur 16 bits, soit des valeurs comprises entre -32768 et +32767. Le type
\texttt{integer} ou \texttt{int}, codé sur quatre octets, permet de
stocker des valeurs comprises entre -2147483648 et +2147483647. Enfin,
le type \texttt{bigint}, codé sur huit octets, permet de stocker des
valeurs comprises entre -9223372036854775808 et 9223372036854775807. Le
standard SQL ne propose pas de stockage d'entiers non signés.

Le standard SQL permet de stocker des valeurs décimales en utilisant les
types à virgules flottantes. Avant de les utiliser, il faut avoir à
l'esprit que ces types de données ne permettent pas de stocker des
valeurs exactes, des différences peuvent donc apparaître entre la donnée
insérée et la donnée restituée. Le type \texttt{real} permet d'exprimer
des valeurs à virgules flottantes sur quatre octets, avec une précision
relative de six décimales. Le type \texttt{double\ precision} permet
d'exprimer des valeurs à virgules flottantes sur huit octets, avec une
précision relative de 15 décimales.

Beaucoup d'applications, notamment les applications financières, ne se
satisfont pas de valeurs inexactes. Pour cela, le standard SQL propose
le type \texttt{numeric}, ou son synonyme \texttt{decimal}, qui permet
de stocker des valeurs exactes, selon la précision arbitraire donnée.
Dans la déclaration \texttt{numeric(//precision//,\ //echelle//)}, la
partie \texttt{//precision//} indique combien de chiffres significatifs
sont stockés, la partie \texttt{//echelle//} exprime le nombre de
chiffres après la virgule. Au niveau du stockage, PostgreSQL ne permet
pas d'insérer des valeurs qui dépassent les capacités du type déclaré.
En revanche, si l'échelle de la valeur à stocker dépasse l'échelle
déclarée de la colonne, alors sa valeur est simplement arrondie.

On peut aussi utiliser numeric sans aucune contrainte de taille, pour
stocker de façon exacte n'importe quel nombre.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Représentation de données
numériques}\label{repruxe9sentation-de-donnuxe9es-numuxe9riques}

\begin{itemize}
\tightlist
\item
  Chiffres décimaux: 0 à 9
\item
  Séparateur décimal: \texttt{.}
\item
  chiffres
\item
  \texttt{chiffres.{[}chiffres{]}{[}e{[}+-{]}chiffres{]}}
\item
  \texttt{{[}chiffres{]}.chiffres{[}e{[}+-{]}chiffres{]}}
\item
  \texttt{chiffres\ e{[}+-{]}chiffres}
\item
  Conversion

  \begin{itemize}
  \tightlist
  \item
    \texttt{TYPE\ \textquotesingle{}chaine\textquotesingle{}}
  \end{itemize}
\end{itemize}

Au moins un chiffre doit être placé avant ou après le point décimal,
s'il est utilisé. Au moins un chiffre doit suivre l'indicateur
d'exponentiel (caractère \texttt{e}), s'il est présent. Il peut ne pas y
avoir d'espaces ou d'autres caractères imbriqués dans la constante.
Notez que tout signe plus ou moins en avant n'est pas forcément
considéré comme faisant part de la constante ; il est un opérateur
appliqué à la constante.

Les exemples suivants montrent différentes représentations valides de
constantes numériques :

\begin{verbatim}
42
3.5
4.
.001
5e2
1.925e-3
\end{verbatim}

Une constante numérique contenant soit un point décimal soit un exposant
est tout d'abord présumée du type integer si sa valeur est contenue dans
le type integer (32 bits). Dans le cas contraire, il est présumé de type
\texttt{bigint} si sa valeur entre dans un type \texttt{bigint} (64
bits). Dans le cas contraire, il est pris pour un type \texttt{numeric}.
Les constantes contenant des points décimaux et/ou des exposants sont
toujours présumées de type \texttt{numeric}.

Le type de données affecté initialement à une constante numérique est
seulement un point de départ pour les algorithmes de résolution de
types. Dans la plupart des cas, la constante sera automatiquement
convertie dans le type le plus approprié suivant le contexte. Si
nécessaire, vous pouvez forcer l'interprétation d'une valeur numérique
sur un type de données spécifiques en la convertissant. Par exemple,
vous pouvez forcer une valeur numérique à être traitée comme un type
\texttt{real} (\texttt{float4}) en écrivant :

\texttt{REAL\ \textquotesingle{}1.23\textquotesingle{}}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Booléens}\label{booluxe9ens}

\begin{itemize}
\tightlist
\item
  \texttt{boolean}
\item
  3 valeurs possibles

  \begin{itemize}
  \tightlist
  \item
    \texttt{TRUE}
  \item
    \texttt{FALSE}
  \item
    \texttt{NULL} (ie valeur absente)
  \end{itemize}
\end{itemize}

Le type \texttt{boolean} permet d'exprimer des valeurs booléennes,
c'est-à-dire une valeur exprimant vrai ou faux. Comme tous les types de
données en SQL, une colonne booléenne peut aussi ne pas avoir de valeur,
auquel cas sa valeur sera \texttt{NULL}.

Un des intérêts des types booléens est de pouvoir écrire:

\begin{verbatim}
SELECT * FROM ma_table WHERE valide;
SELECT * FROM ma_table WHERE not consulte;
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Temporel}\label{temporel}

\begin{itemize}
\tightlist
\item
  Date

  \begin{itemize}
  \tightlist
  \item
    \texttt{date}
  \end{itemize}
\item
  Heure

  \begin{itemize}
  \tightlist
  \item
    \texttt{time}
  \item
    avec ou sans fuseau horaire
  \end{itemize}
\item
  Date et heure

  \begin{itemize}
  \tightlist
  \item
    \texttt{timestamp}
  \item
    avec ou sans fuseau horaire
  \end{itemize}
\item
  Intervalle de temps

  \begin{itemize}
  \tightlist
  \item
    \texttt{interval}
  \end{itemize}
\end{itemize}

Le type \texttt{date} exprime une date. Ce type ne connaît pas la notion
de fuseau horaire.

Le type \texttt{time} exprime une heure. Par défaut, il ne connaît pas
la notion de fuseau horaire. En revanche, lorsque le type est déclaré
comme \texttt{time\ with\ time\ zone}, il prend en compte un fuseau
horaire. Mais cet emploi n'est pas recommandé. En effet, une heure
convertie d'un fuseau horaire vers un autre pose de nombreux problèmes.
En effet, le décalage horaire dépend également de la date : quand il est
6h00, heure d'été, à Paris, il est 21H00 sur la côte Pacifique aux
États-Unis mais encore à la date de la veille.

Le type \texttt{timestamp} permet d'exprimer une date et une heure. Par
défaut, il ne connaît pas la notion de fuseau horaire. Lorsque le type
est déclaré \texttt{timestamp\ with\ time\ zone}, il est adapté aux
conversions d'heure d'un fuseau horaire vers un autre car le changement
de date sera répercuté dans la composante date du type de données. Il
est précis à la microseconde.

Le format de saisie et de restitution des dates et heures dépend du
paramètre \texttt{DateStyle}. La documentation de ce paramètre permet de
connaître les différentes valeurs possibles. Il reste néanmoins
recommandé d'utiliser les fonctions de formatage de date qui permettent
de rendre l'application indépendante de la configuration du SGBD.

La norme ISO (ISO-8601) impose le format de date ``année-mois-jour''. La
norme SQL est plus permissive et permet de restituer une date au format
``jour/mois/année'' si DateStyle est égal à
\texttt{\textquotesingle{}SQL,\ DMY\textquotesingle{}}.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SET} \NormalTok{datestyle = }\StringTok{'ISO, DMY'}\NormalTok{;}

\KeywordTok{SELECT} \FunctionTok{current_timestamp}\NormalTok{;}
              \NormalTok{now              }
\CommentTok{-------------------------------}
 \DecValTok{2013-06-06} \DecValTok{12}\CharTok{:13:24}\FloatTok{.899816}\DecValTok{+02}

\KeywordTok{SET} \NormalTok{datestyle = }\StringTok{'SQL, DMY'}\NormalTok{;}

\KeywordTok{SELECT} \FunctionTok{current_timestamp}\NormalTok{;}
              \NormalTok{now               }
\CommentTok{--------------------------------}
 \DecValTok{06}\NormalTok{/}\DecValTok{06}\NormalTok{/}\DecValTok{2013} \DecValTok{12}\CharTok{:13:52}\FloatTok{.81941} \NormalTok{CEST}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Représentation des données
temporelles}\label{repruxe9sentation-des-donnuxe9es-temporelles}

\begin{itemize}
\tightlist
\item
  Conversion explicite

  \begin{itemize}
  \tightlist
  \item
    \texttt{TYPE\ \textquotesingle{}chaine\textquotesingle{}}
  \end{itemize}
\item
  Format d'un timestamp

  \begin{itemize}
  \tightlist
  \item
    \texttt{\textquotesingle{}YYYY-MM-DD\ HH24:MI:SS.ssssss\textquotesingle{}}
  \item
    \texttt{\textquotesingle{}YYYY-MM-DD\ HH24:MI:SS.ssssss+fuseau\textquotesingle{}}
  \item
    \texttt{\textquotesingle{}YYYY-MM-DD\ HH24:MI:SS.ssssss\textquotesingle{}\ AT\ TIME\ ZONE\ \textquotesingle{}fuseau\textquotesingle{}}
  \end{itemize}
\item
  Format d'un intervalle

  \begin{itemize}
  \tightlist
  \item
    \texttt{INTERVAL\ \textquotesingle{}durée\ interval\textquotesingle{}}
  \end{itemize}
\end{itemize}

Expression d'une date, forcément sans gestion du fuseau horaire :

\texttt{DATE\ \textquotesingle{}2013-06-06\textquotesingle{}}

Expression d'une heure sans fuseau horaire :

\texttt{TIME\ \textquotesingle{}10:20:10\textquotesingle{}}

Ou, en spécifiant explicitement l'absence de fuseau horaire :

\texttt{TIME\ WITHOUT\ TIME\ ZONE\ \textquotesingle{}10:20:10\textquotesingle{}}

Expression d'une heure, avec fuseau horaire invariant. Cette forme est
déconseillée :

\texttt{TIME\ WITH\ TIME\ ZONE\ \textquotesingle{}10:20:10\textquotesingle{}\ AT\ TIME\ ZONE\ \textquotesingle{}CEST\textquotesingle{}}

Expression d'un timestamp sans fuseau horaire :

\texttt{TIMESTAMP\ \textquotesingle{}2013-06-06\ 10:20:10\textquotesingle{}}

Ou, en spécifiant explicitement l'absence de fuseau horaire :

\texttt{TIMESTAMP\ WITHOUT\ TIME\ ZONE\ \textquotesingle{}2013-06-06\ 10:20:10\textquotesingle{}}

Expression d'un timestamp avec fuseau horaire, avec microseconde :

\texttt{TIMESTAMP\ WITH\ TIME\ ZONE\ \textquotesingle{}2013-06-06\ 10:20:10.123321\textquotesingle{}\ AT\ TIME\ ZONE\ \textquotesingle{}Europe/Paris\textquotesingle{}}

Expression d'un intervalle d'une journée :

\texttt{INTERVAL\ \textquotesingle{}1\ day\textquotesingle{}}

Il est possible de cumuler plusieurs expressions :

\texttt{INTERVAL\ \textquotesingle{}1\ year\ 1\ day\textquotesingle{}}

Les valeurs possibles sont : * \texttt{YEAR} pour une année ; *
\texttt{MONTH} pour un mois ; * \texttt{DAY} pour une journée ; *
\texttt{HOUR} pour une heure ; * \texttt{MINUTE} pour une minute ; *
\texttt{SECOND} pour une seconde.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Gestion des fuseaux
horaires}\label{gestion-des-fuseaux-horaires}

\begin{itemize}
\tightlist
\item
  Paramètre \texttt{timezone}
\item
  Session: \texttt{SET\ TIME\ ZONE}
\item
  Expression d'un fuseau horaire

  \begin{itemize}
  \tightlist
  \item
    nom complet:
    \texttt{\textquotesingle{}Europe/Paris\textquotesingle{}}
  \item
    nom abbrégé: \texttt{\textquotesingle{}CEST\textquotesingle{}}
  \item
    décalage: \texttt{\textquotesingle{}+02\textquotesingle{}}
  \end{itemize}
\end{itemize}

Le paramètre \texttt{timezone} du \texttt{postgresql.conf} permet de
positionner le fuseau horaire de l'instance PostgreSQL. Elle est
initialisée par défaut en fonction de l'environnement du système
d'exploitation.

Le fuseau horaire de l'instance peut également être défini au cours de
la session à l'aide de la commande \texttt{SET\ TIME\ ZONE}.

La France utilise deux fuseaux horaires normalisés. Le premier,
\texttt{CET}, correspond à \texttt{Central\ European\ Time} ou autrement
dit à l'heure d'hiver en Europe centrale. Le second, \texttt{CEST},
correspond à Central European Summer Time, c'est à dire l'heure d'été en
Europe centrale.

La liste des fuseaux horaires supportés est disponible dans la table
système \texttt{pg\_timezone\_names} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pg_timezone_names ;}
               \NormalTok{name               | abbrev | utc_offset | is_dst }
\CommentTok{----------------------------------+--------+------------+--------}
 \NormalTok{GB                               | BST    | }\DecValTok{01}\CharTok{:00:00}   \NormalTok{| t}
 \NormalTok{ROK                              | KST    | }\DecValTok{09}\CharTok{:00:00}   \NormalTok{| f}
 \NormalTok{Greenwich                        | GMT    | }\DecValTok{00}\CharTok{:00:00}   \NormalTok{| f}
 \NormalTok{(...)}
\end{Highlighting}
\end{Shaded}

Il est possible de positionner le fuseau horaire au niveau de la session
avec l'ordre \texttt{SET\ TIME\ ZONE} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SET} \DataTypeTok{TIME} \DataTypeTok{ZONE} \OtherTok{"Europe/Paris"}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{now();}
              \NormalTok{now              }
\CommentTok{-------------------------------}
 \DecValTok{2013-06-06} \DecValTok{10}\CharTok{:19:56}\FloatTok{.640162}\DecValTok{+02}

\KeywordTok{SET} \DataTypeTok{TIME} \DataTypeTok{ZONE} \OtherTok{"Europe/Kiev"}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{now();}
              \NormalTok{now              }
\CommentTok{-------------------------------}
 \DecValTok{2013-06-06} \DecValTok{11}\CharTok{:20:17}\FloatTok{.199983}\DecValTok{+03}
\end{Highlighting}
\end{Shaded}

Conversion implicite d'une donnée de type \texttt{timestamp} dans le
fuseau horaire courant :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SET} \DataTypeTok{TIME} \DataTypeTok{ZONE} \OtherTok{"Europe/Kiev"}\NormalTok{;}

\KeywordTok{SELECT} \DataTypeTok{TIMESTAMP} \KeywordTok{WITH} \DataTypeTok{TIME} \DataTypeTok{ZONE} \StringTok{'2013-06-06 10:20:10 CEST'}\NormalTok{;}
      \NormalTok{timestamptz       }
\CommentTok{------------------------}
 \DecValTok{2013-06-06} \DecValTok{11}\CharTok{:20:10}\DecValTok{+03}
\end{Highlighting}
\end{Shaded}

Conversion explicite d'une donnée de type \texttt{timestamp} dans un
autre fuseau horaire :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \StringTok{'2013-06-06 06:00:00'} \KeywordTok{AT} \DataTypeTok{TIME} \DataTypeTok{ZONE} \StringTok{'US/Pacific'}\NormalTok{;}
      \NormalTok{timezone       }
\CommentTok{---------------------}
 \DecValTok{05}\NormalTok{/}\DecValTok{06}\NormalTok{/}\DecValTok{2013} \DecValTok{21}\CharTok{:00:00}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Chaînes de bits}\label{chauxeenes-de-bits}

\begin{itemize}
\tightlist
\item
  Chaînes de bits

  \begin{itemize}
  \tightlist
  \item
    \texttt{bit(n)}, \texttt{bit\ varying(n)}
  \end{itemize}
\end{itemize}

Les types \texttt{bit} et \texttt{bit\ varying} permettent de stocker
des masques de bits. Le type \texttt{bit(n)} est à longueur fixe alors
que le type \texttt{bit\ varying(n)} est à longueur variable mais avec
un maximum de \texttt{n} bits.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Représentation des chaînes de
bits}\label{repruxe9sentation-des-chauxeenes-de-bits}

\begin{itemize}
\tightlist
\item
  Représentation binaire

  \begin{itemize}
  \tightlist
  \item
    Chaîne de caractères précédée de la lettre \texttt{B}
  \item
    \texttt{B\textquotesingle{}01010101\textquotesingle{}}
  \end{itemize}
\item
  Représentation hexadécimale

  \begin{itemize}
  \tightlist
  \item
    Chaîne de caractères précédée de la lettre \texttt{X}
  \item
    \texttt{X\textquotesingle{}55\textquotesingle{}}
  \end{itemize}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{XML}\label{xml}

\begin{itemize}
\tightlist
\item
  Type validé

  \begin{itemize}
  \tightlist
  \item
    XML
  \end{itemize}
\item
  Chaîne de caractères

  \begin{itemize}
  \tightlist
  \item
    validation du document XML
  \end{itemize}
\end{itemize}

Le type \texttt{xml} permet de stocker des documents XML. Par rapport à
une chaîne de caractères simple, le type \texttt{xml} apporte la
vérification de la structure du document XML ainsi que des fonctions de
manipulations spécifiques (voir
http://docs.postgresql.fr/current/functions-xml.html ).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Types dérivés}\label{types-duxe9rivuxe9s}

\begin{itemize}
\tightlist
\item
  Types spécifiques à PostgreSQL
\item
  Sériés

  \begin{itemize}
  \tightlist
  \item
    principe de l'\texttt{autoincrement}
  \item
    \texttt{serial}
  \item
    \texttt{smallserial}
  \item
    \texttt{bigserial}
  \item
    équivalent à un type entier associé à une séquence et avec une
    valeur par défaut
  \end{itemize}
\item
  Caractères

  \begin{itemize}
  \tightlist
  \item
    \texttt{text}
  \end{itemize}
\end{itemize}

Les types \texttt{smallserial}, \texttt{serial} et \texttt{bigserial}
permettent d'obtenir des fonctionnalités similaires aux types
\texttt{autoincrement} rencontrés dans d'autres SGBD. Néanmoins, ces
types restent assez proches de la norme car ils définissent au final une
colonne qui utilise un type et des objets standards. Selon le type
dérivé utilisé, la colonne sera de type \texttt{smallint},
\texttt{integer} ou \texttt{bigint}. Une séquence sera également créée
et la colonne prendra pour valeur par défaut la prochaine valeur de
cette séquence.

Le type \texttt{text} est l'équivalent du type \texttt{varchar} mais
sans limite de taille de la chaîne de caractère.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Types additionnels non
SQL}\label{types-additionnels-non-sql}

\begin{itemize}
\tightlist
\item
  bytea
\item
  array
\item
  enum
\item
  cidr, inet, macaddr
\item
  uuid
\item
  json
\item
  hstore
\item
  range
\end{itemize}

Les types standards ne sont pas toujours suffisants pour représenter
certaines données. À l'instar d'autres SGBDR, PostgreSQL propose des
types de données pour répondre à certains besoins.

On notera le type \texttt{bytea} qui permet de stocker des objets
binaires dans une table. Le type \texttt{array} permet de stocker des
tableaux et \texttt{enum} des énumérations.

Les types \texttt{json} et \texttt{hstore} permettent de stocker des
documents non structurés dans la base de données. Le premier au format
JSON, le second dans un format de type clé/valeur. Le type
\texttt{hstore} est d'ailleurs particulièrement efficace car il dispose
de méthodes d'indexation et de fonctions de manipulations performantes.
Quant au type \texttt{json}, il va normalement être amélioré dans la
version 9.4 de PostgreSQL.

Le type \texttt{range} permet de stocker des intervalles de données. Ces
données sont ensuite manipulables par un jeu d'opérateurs dédiés et par
le biais de méthodes d'indexation permettant d'accélérer les recherches.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Types utilisateurs}\label{types-utilisateurs}

\begin{itemize}
\tightlist
\item
  Types utilisateurs

  \begin{itemize}
  \tightlist
  \item
    composites
  \item
    énumérés (\texttt{enum})
  \item
    intervalles (\texttt{range})
  \item
    scalaires
  \item
    tableau
  \end{itemize}
\item
  \texttt{CREATE\ TYPE}
\end{itemize}

PostgreSQL permet de créer ses propres types de données. Les usages les
plus courants consistent à créer des types composites pour permettre à
des fonctions de retourner des données sous forme tabulaire (retour de
type \texttt{SETOF}).

L'utilisation du type énuméré (\texttt{enum}) nécessite aussi la
création d'un type spécifique. Le type sera alors employé pour déclarer
les objets utilisant une énumération.

Enfin, si l'on souhaite étendre les types intervalles (\texttt{range})
déjà disponibles, il est nécessaire de créer un type spécifique.

La création d'un type scalaire est bien plus marginale. Elle permet en
effet d'étendre les types fournis par PostgreSQL mais nécessite d'avoir
des connaissances fines des mécanismes de PostgreSQL. De plus, dans la
majeure partie des cas, les types standards suffisent en général à
résoudre les problèmes qui peuvent se poser à la conception.

Quant aux types tableaux, ils sont créés implicitement par PostgreSQL
quand un utilisateur crée un type personnalisé.

\textbf{Exemples}

Utilisation d'un type \texttt{enum} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TYPE} \NormalTok{arc_en_ciel }\KeywordTok{AS} \NormalTok{ENUM (}\StringTok{'red'}\NormalTok{, }\StringTok{'orange'}\NormalTok{, }\StringTok{'yellow'}\NormalTok{, }\StringTok{'green'}\NormalTok{, }\StringTok{'blue'}\NormalTok{, }\StringTok{'purple'}\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (}\KeywordTok{id} \DataTypeTok{integer}\NormalTok{, couleur arc_en_ciel);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (}\KeywordTok{id}\NormalTok{, couleur) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'red'}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (}\KeywordTok{id}\NormalTok{, couleur) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'pink'}\NormalTok{);}
\NormalTok{ERROR:  invalid input }\FunctionTok{value} \KeywordTok{for} \NormalTok{enum arc_en_ciel: }\OtherTok{"pink"}
\NormalTok{LINE }\DecValTok{1}\NormalTok{: }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (}\KeywordTok{id}\NormalTok{, couleur) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'pink'}\NormalTok{);}
                                                  \NormalTok{^}
\end{Highlighting}
\end{Shaded}

Création d'un type interval float8\_range :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TYPE} \NormalTok{float8_range }
\KeywordTok{AS} \KeywordTok{RANGE} \NormalTok{(subtype = float8, subtype_diff = float8mi);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Conclusion}\label{conclusion}

\begin{itemize}
\tightlist
\item
  SQL : traitement d'ensembles d'enregistrements
\item
  Pour les lectures : \texttt{SELECT}
\item
  Nom des objets en minuscules
\item
  Des types de données simples et d'autres plus complexes
\end{itemize}

Le standard SQL permet de traiter des ensembles d'enregistrements. Un
enregistrement correspond à une ligne dans une relation. Il est possible
de lire ces relations grâce à l'ordre \texttt{SELECT}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Bibliographie}\label{bibliographie}

\begin{itemize}
\tightlist
\item
  Bases de données - de la modélisation au SQL Broché
\item
  SQL avancé : programmation et techniques avancées
\item
  SQL : Au coeur des performances
\item
  The Manga Guide to Databases
\item
  The Art of SQL
\end{itemize}

\textbf{Bases de données - de la modélisation au SQL}

\begin{itemize}
\tightlist
\item
  Auteur: Laurent Audibert,
\item
  Éditeur: chez Ellipses,
\item
  ISBN: 978-2729851200
\end{itemize}

Ce livre présente les notions essentielles pour modéliser une base de
données et utiliser le langage SQL pour utiliser les bases de données
créées. L'auteur appuie ses exercices sur PostgreSQL.

\textbf{SQL avancé : programmation et techniques avancées}

\begin{itemize}
\tightlist
\item
  Auteur: Joe Celko
\item
  Editeur : Vuibert
\item
  ISBN: 978-2711786503
\end{itemize}

Ce livre est écrit par une personne ayant participé à l'élaboration du
standard SQL. Il a souhaité montré les bonnes pratiques pour utiliser le
SQL pour résoudre un certain nombre de problèmes de tous les jours. Le
livre s'appuie cependant sur la norme SQL-92, voire SQL-89. L'édition
anglaise //SQL for Smarties// est bien plus à jour. Pour les
anglophones, la lecture de l'ensemble des livres de Joe Celko est
particulièrement recommandée.

\textbf{SQL : Au coeur des performances}

\begin{itemize}
\tightlist
\item
  Auteur: Markus Winand
\item
  Éditeur: auto-édité
\item
  ISBN: 978-3950307832
\item
  site Internet: {[}{[}http://use-the-index-luke.com/fr{]}{]}
\end{itemize}

Il s'agit du livre de référence sur les performances en SQL. Il dresse
un inventaire des différents cas d'utilisation des index par la base de
données, ce qui permettra de mieux prévoir l'indexation dès la
conception. Ce livre s'adresse à un public avancé.

\textbf{The Manga Guide to Databases}

\begin{itemize}
\tightlist
\item
  Auteur: Takahashi, Mana, Azuma, Shoko
\item
  Éditeur: No Starch Press
\item
  ASIN: B00BUFN70E
\end{itemize}

\textbf{The Art of SQL}

\begin{itemize}
\tightlist
\item
  Auteur: Stéphane Faroult
\item
  Éditeur: O'Reilly
\item
  ISBN: 978-0-596-00894-9
\item
  ISBN: 978-0-596-15971-9 (e-book)
\end{itemize}

Ce livre s'adresse également à un public avancé. Il présence également
les bonnes pratiques lorsque l'on utilise une base de données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Questions}\label{questions}

N'hésitez pas, c'est le moment !

\subsubsection{Énoncés}\label{uxe9noncuxe9s}

Initialisez la base \texttt{tpc} et connectez-vous à la base
\texttt{tpc} à l'aide de pgAdminIII.

Le schéma suivant montre les différentes tables de la base de TP :

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Afficher l'heure courante, au méridien de Greenwich.
\item
  Afficher la date et l'heure qu'il sera dans 1 mois et 1 jour.
\item
  Ajouter 1 au nombre de type réel `1.42'. Pourquoi ce résultat ? Quel
  type de données permet d'obtenir un résultat correct ?
\item
  Afficher le contenu de la table \texttt{pays} en classant les pays
  dans l'ordre alphabétique.
\item
  Afficher les pays contenant la lettre \texttt{a}, majuscule ou
  minuscule. Plusieurs solutions sont possibles.
\item
  Afficher le nombre lignes de commandes dont la quantité commandée est
  comprise entre 5 et 10 :
\item
  Pour chaque pays, afficher son nom et la région du monde dont il fait
  partie.
\end{enumerate}

\texttt{sql\ \ \ \ \ \ \ \ \ \ \ nom\_pays\ \ \ \ \ \ \ \ \ \ \ \ \textbar{}\ \ \ \ \ \ \ \ nom\_region\ \ \ \ \ \ \ \ \ \ -\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-+-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\ \ ALGÉRIE\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \textbar{}\ Afrique\ \ \ \ \ \ \ \ \ (...)}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\tightlist
\item
  Afficher le nombre total de clients français et allemands.
\end{enumerate}

Sortie attendue :

\begin{Shaded}
\begin{Highlighting}[]
 \FunctionTok{count} 
\CommentTok{-------}
 \DecValTok{12418}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{8}
\tightlist
\item
  Afficher le numéro de commande et le nom du client ayant passé la
  commande. Seul un sous-ensemble des résultats ne sera pas affiché. Les
  20 premières lignes du résultat seront exclues et seules les 20
  suivantes seront affichées. Il faut penser à ce que le résultat de
  cette requête soit stable entre plusieurs exécutions.
\end{enumerate}

Sortie attendue :

\begin{Shaded}
\begin{Highlighting}[]
 \NormalTok{numero_commande |  nom_client  }
\CommentTok{-----------------+--------------}
              \DecValTok{67} \NormalTok{| Client112078}
              \DecValTok{68} \NormalTok{| Client33842}
\NormalTok{(...)}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{9}
\tightlist
\item
  Afficher les noms et codes des pays qui font partie de la région
  ``Europe''.
\end{enumerate}

Sortie attendue :

\begin{Shaded}
\begin{Highlighting}[]
       \NormalTok{nom_pays        | code_pays }
\CommentTok{-----------------------+-----------}
 \NormalTok{ALLEMAGNE             | DE}
\NormalTok{(...)}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{10}
\tightlist
\item
  Pour chaque pays, afficher une chaîne de caractère qui est composée de
  son nom, suivi entre parenthèses de son code puis, séparés par une
  virgule, du nom de la région dont il fait partie.
\end{enumerate}

Sortie attendue :

\begin{Shaded}
\begin{Highlighting}[]
                   \NormalTok{detail_pays                    }
\CommentTok{--------------------------------------------------}
 \NormalTok{ALGÉRIE (DZ), Afrique}
\NormalTok{(...)}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{11}
\tightlist
\item
  Pour les clients ayant passés des commandes durant le mois de janvier
  2011, affichez les identifiants des clients, leur nom, leur numéro de
  téléphone et le nom de leur pays.
\end{enumerate}

Sortie attendue :

\begin{Shaded}
\begin{Highlighting}[]
 \NormalTok{client_id |     nom      |    telephone    |           nom_pays            }
\CommentTok{-----------+--------------+-----------------+-------------------------------}
     \DecValTok{83279} \NormalTok{| Client83279  | }\DecValTok{12-835-574-2048} \NormalTok{| JAPON}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{12}
\tightlist
\item
  Pour les dix premières commandes de l'année 2011, afficher le numéro
  de la commande, la date de la commande ainsi que son âge.
\end{enumerate}

Sortie attendue :

\begin{Shaded}
\begin{Highlighting}[]
 \NormalTok{numero_commande | date_commande |            age            }
\CommentTok{-----------------+---------------+---------------------------}
           \DecValTok{11364} \NormalTok{| }\DecValTok{2011-01-01}    \NormalTok{| }\DecValTok{1392} \NormalTok{days }\DecValTok{15}\CharTok{:25:19}\FloatTok{.012521}
\NormalTok{(...)}
\end{Highlighting}
\end{Shaded}

\subsubsection{Solutions}\label{solutions}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  Afficher l'heure courante, au méridien de Greenwich :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{now() }\KeywordTok{AT} \DataTypeTok{TIME} \DataTypeTok{ZONE} \StringTok{'GMT'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
  Afficher la date et l'heure qu'il sera dans 1 mois et 1 jour
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{now() + }\DataTypeTok{INTERVAL} \StringTok{'1 month 1 day'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
  Ajouter 1 au nombre de type réel `1.42'. Pourquoi ce résultat ?
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DataTypeTok{REAL} \StringTok{'1.42'} \NormalTok{+ }\DecValTok{1} \KeywordTok{AS} \NormalTok{resultat;}
     \NormalTok{resultat     }
\CommentTok{------------------}
 \FloatTok{2.41999995708466}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le type de données \texttt{real} est un type numérique à virgule
flottante, codé sur 4 octets. Il n'offre pas une précision suffisante
pour les calculs précis. Son seul avantage est la vitesse de calcul.
Pour effectuer des calculs précis, il vaut mieux privilégier le type de
données \texttt{numeric}.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
  Afficher le contenu de la table \texttt{pays} en classant les pays
  dans l'ordre alphabétique :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{nom_pays;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
  Afficher les pays contenant la lettre \texttt{a}, majuscule ou
  minuscule :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{WHERE} \FunctionTok{lower}\NormalTok{(nom_pays) }\KeywordTok{LIKE} \StringTok{'%a%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{WHERE} \NormalTok{nom_pays ILIKE }\StringTok{'%a%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{WHERE} \NormalTok{nom_pays }\KeywordTok{LIKE} \StringTok{'%a%'} \KeywordTok{OR} \NormalTok{nom_pays }\KeywordTok{LIKE} \StringTok{'%A%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

En terme de performances, la seconde variante sera plus rapide sur un
volume de données important si l'on dispose du bon index. La taille de
la table \texttt{pays} ne permet pas d'observer de différence
significative sur cette requête.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
  Afficher le nombre lignes de commandes dont la quantité commandée est
  comprise entre 5 et 10 :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
  \KeywordTok{FROM} \NormalTok{lignes_commandes}
 \KeywordTok{WHERE} \NormalTok{quantite }\KeywordTok{BETWEEN} \DecValTok{5} \KeywordTok{AND} \DecValTok{10}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Autre écriture possible :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
  \KeywordTok{FROM} \NormalTok{lignes_commandes}
 \KeywordTok{WHERE} \NormalTok{quantite >= }\DecValTok{5}
   \KeywordTok{AND} \NormalTok{quantite <= }\DecValTok{10}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\tightlist
\item
  Pour chaque pays, afficher son nom et la région du monde dont il fait
  partie :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom_pays, nom_region}
  \KeywordTok{FROM} \NormalTok{pays p, regions r}
 \KeywordTok{WHERE} \NormalTok{p.region_id = r.region_id;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\tightlist
\item
  Afficher le nombre total de clients français et allemands :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
  \KeywordTok{FROM} \NormalTok{clients cl, contacts cn, pays p}
 \KeywordTok{WHERE} \NormalTok{cl.contact_id = cn.contact_id}
   \KeywordTok{AND} \NormalTok{cn.code_pays = p.code_pays}
   \KeywordTok{AND} \NormalTok{p.nom_pays }\KeywordTok{IN} \NormalTok{(}\StringTok{'FRANCE'}\NormalTok{, }\StringTok{'ALLEMAGNE'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

À noter que cette syntaxe est obsolète, il faut utiliser la clause
\texttt{JOIN}, plus lisible et plus complète, qui sera vu plus loin :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
  \KeywordTok{FROM} \NormalTok{clients cl}
  \KeywordTok{JOIN} \NormalTok{contacts cn }\KeywordTok{ON} \NormalTok{(cl.contact_id = cn.contact_id)}
  \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{(cn.code_pays = p.code_pays)}
 \KeywordTok{WHERE} \NormalTok{p.nom_pays }\KeywordTok{IN} \NormalTok{(}\StringTok{'FRANCE'}\NormalTok{, }\StringTok{'ALLEMAGNE'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

En connaissant les codes de ces pays, il est possible d'éviter la
lecture de la table \texttt{pays} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
  \KeywordTok{FROM} \NormalTok{clients cl, contacts cn}
 \KeywordTok{WHERE} \NormalTok{cl.contact_id = cn.contact_id}
   \KeywordTok{AND} \NormalTok{cn.code_pays }\KeywordTok{IN} \NormalTok{(}\StringTok{'FR'}\NormalTok{, }\StringTok{'DE'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

L'équivalent avec la syntaxe \texttt{JOIN} serait :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
  \KeywordTok{FROM} \NormalTok{clients cl}
  \KeywordTok{JOIN} \NormalTok{contacts cn }\KeywordTok{ON} \NormalTok{(cl.contact_id = cn.contact_id)}
 \KeywordTok{WHERE} \NormalTok{cn.code_pays }\KeywordTok{IN} \NormalTok{(}\StringTok{'FR'}\NormalTok{, }\StringTok{'DE'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{8}
\tightlist
\item
  Afficher le numéro de commande et le nom du client ayant passé la
  commande. Seul un sous-ensemble des résultats ne sera pas affiché. Les
  20 premières lignes du résultat seront exclues et seules les 20
  suivantes seront afficher. Il faut penser à ce que le résultat de
  cette requête soit stable entre plusieurs exécutions.
\end{enumerate}

La syntaxe normalisée SQL impose d'écrire la requête de la façon
suivante. La stabilité du résultat de la requête est garantie par un tri
explicite, s'il n'est pas précisé la base de données va retourner les
lignes dans l'ordre physique qui est susceptible de changer :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{numero_commande, nom }\KeywordTok{AS} \NormalTok{nom_client}
  \KeywordTok{FROM} \NormalTok{commandes cm, clients cl, contacts cn}
 \KeywordTok{WHERE} \NormalTok{cm.client_id = cl.client_id}
   \KeywordTok{AND} \NormalTok{cl.contact_id = cn.contact_id}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{numero_commande}
 \NormalTok{FETCH }\FunctionTok{FIRST} \DecValTok{20} \KeywordTok{ROWS} \KeywordTok{ONLY}
 \NormalTok{OFFSET }\DecValTok{20}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Mais PostgreSQL supporte également la clause \texttt{LIMIT} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{numero_commande, nom }\KeywordTok{AS} \NormalTok{nom_client}
  \KeywordTok{FROM} \NormalTok{commandes cm, clients cl, contacts cn}
 \KeywordTok{WHERE} \NormalTok{cm.client_id = cl.client_id}
   \KeywordTok{AND} \NormalTok{cl.contact_id = cn.contact_id}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{numero_commande}
 \KeywordTok{LIMIT} \DecValTok{20}
 \NormalTok{OFFSET }\DecValTok{20}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Et l'équivalent avec la syntaxe \texttt{JOIN} serait :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{numero_commande, nom }\KeywordTok{AS} \NormalTok{nom_client}
  \KeywordTok{FROM} \NormalTok{commandes cm}
  \KeywordTok{JOIN} \NormalTok{clients cl }\KeywordTok{ON} \NormalTok{(cm.client_id = cl.client_id)}
  \KeywordTok{JOIN} \NormalTok{contacts cn }\KeywordTok{ON} \NormalTok{(cl.contact_id = cn.contact_id)}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{numero_commande}
 \KeywordTok{LIMIT} \DecValTok{20}
 \NormalTok{OFFSET }\DecValTok{20}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{9}
\tightlist
\item
  Afficher les noms et codes des pays qui font partie de la région
  ``Europe''.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom_pays, code_pays}
  \KeywordTok{FROM} \NormalTok{regions r, pays p}
 \KeywordTok{WHERE} \NormalTok{r.region_id = p.region_id}
   \KeywordTok{AND} \NormalTok{r.nom_region = }\StringTok{'Europe'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Et l'équivalent avec la syntaxe \texttt{JOIN} serait :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom_pays, code_pays}
  \KeywordTok{FROM} \NormalTok{regions r}
  \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{(r.region_id = p.region_id)}
 \KeywordTok{WHERE} \NormalTok{r.nom_region = }\StringTok{'Europe'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{10}
\tightlist
\item
  Pour chaque pays, afficher une chaîne de caractère qui est composée de
  son nom, suivi entre parenthèses de son code puis, séparés par une
  virgule, du nom de la région dont il fait partie.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom_pays || }\StringTok{' ('} \NormalTok{|| code_pays || }\StringTok{'), '} \NormalTok{|| nom_region}
  \KeywordTok{FROM} \NormalTok{regions r, pays p}
 \KeywordTok{WHERE} \NormalTok{r.region_id = p.region_id;}
\end{Highlighting}
\end{Shaded}

Et l'équivalent avec la syntaxe \texttt{JOIN} serait :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom_pays || }\StringTok{' ('} \NormalTok{|| code_pays || }\StringTok{'), '} \NormalTok{|| nom_region}
  \KeywordTok{FROM} \NormalTok{regions r}
  \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{(r.region_id = p.region_id);}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{11}
\tightlist
\item
  Pour les clients ayant passés des commandes durant le mois de janvier
  2011, affichez les identifiants des clients, leur nom, leur numéro de
  téléphone et le nom de leur pays.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{cl.client_id, nom, telephone, nom_pays}
  \KeywordTok{FROM} \NormalTok{clients cl, commandes cm, contacts cn, pays p}
 \KeywordTok{WHERE} \NormalTok{cl.client_id = cm.client_id}
   \KeywordTok{AND} \NormalTok{cl.contact_id = cn.contact_id}
   \KeywordTok{AND} \NormalTok{cn.code_pays = p.code_pays}
   \KeywordTok{AND} \NormalTok{date_commande }\KeywordTok{BETWEEN} \StringTok{'2011-01-01'} \KeywordTok{AND} \StringTok{'2011-01-31'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Le troisième module de la formation abordera les jointures et leurs
syntaxes. À l'issue de ce prochain module, la requête de cet exercice
pourrait être écrite de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{cl.client_id, nom, telephone, nom_pays}
  \KeywordTok{FROM} \NormalTok{clients cl}
  \KeywordTok{JOIN} \NormalTok{commandes cm}
       \KeywordTok{USING} \NormalTok{(client_id)}
  \KeywordTok{JOIN} \NormalTok{contacts co}
       \KeywordTok{USING} \NormalTok{(contact_id)}
  \KeywordTok{JOIN} \NormalTok{pays p}
       \KeywordTok{USING} \NormalTok{(code_pays)}
 \KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \StringTok{'2011-01-01'} \KeywordTok{AND} \StringTok{'2011-01-31'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{12}
\tightlist
\item
  Pour les dix premières commandes de l'année 2011, afficher le numéro
  de la commande, la date de la commande ainsi que son âge.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{numero_commande, date_commande, now() - date_commande }\KeywordTok{AS} \NormalTok{age}
  \KeywordTok{FROM} \NormalTok{commandes}
 \KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \StringTok{'2011-01-01'} \KeywordTok{AND} \StringTok{'2011-12-31'}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{date_commande}
 \KeywordTok{LIMIT} \DecValTok{10}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\section{Création d'objet et mises à
jour}\label{cruxe9ation-dobjet-et-mises-uxe0-jour}

\subsection{Introduction}\label{introduction}

\begin{itemize}
\tightlist
\item
  DDL, gérer les objets
\item
  DML, écrire des données
\item
  Gérer les transactions
\end{itemize}

Le module précédent nous a permis de voir comment lire des données à
partir de requêtes SQL. Ce module a pour but de présenter la création et
la gestion des objets dans la base de données (par exemple les tables),
ainsi que l'ajout, la suppression et la modification de données.

Une dernière partie sera consacrée aux transactions.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Menu}\label{menu-1}

\begin{itemize}
\tightlist
\item
  DDL (\texttt{Data\ Definition\ Language})
\item
  DML (\texttt{Data\ Manipulation\ Language})
\item
  TCL (\texttt{Transaction\ Control\ Language})
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Objectifs}\label{objectifs-1}

\begin{itemize}
\tightlist
\item
  Savoir créer, modifier et supprimer des objets
\item
  Savoir utiliser les contraintes d'intégrité
\item
  Savoir mettre à jour les données
\item
  Savoir utiliser les transactions
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{DDL}\label{ddl}

\begin{itemize}
\tightlist
\item
  DDL

  \begin{itemize}
  \tightlist
  \item
    \texttt{Data\ Definition\ Language}
  \item
    langage de définition de données
  \end{itemize}
\item
  Permet de créer des objets dans la base de données
\end{itemize}

Les ordres DDL (acronyme de \texttt{Data\ Definition\ Language})
permettent de créer des objets dans la base de données et notamment la
structure de base du standard SQL : les tables.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Objets d'une base de
données}\label{objets-dune-base-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Objets définis par la norme SQL :

  \begin{itemize}
  \tightlist
  \item
    schémas
  \item
    séquences
  \item
    tables
  \item
    contraintes
  \item
    domaines
  \item
    vues
  \item
    fonctions
  \item
    triggers
  \end{itemize}
\end{itemize}

La norme SQL définit un certain nombre d'objets standards qu'il est
possible de créer en utilisant les ordres DDL. D'autres types d'objets
existent bien entendu, comme les domaines. Les ordres DDL permettent
également de créer des index, bien qu'ils ne soient pas définis dans la
norme SQL.

La seule structure de données possible dans une base de données
relationnelle est la table.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Créer des objets}\label{cruxe9er-des-objets}

\begin{itemize}
\tightlist
\item
  Ordre \texttt{CREATE}
\item
  Syntaxe spécifique au type d'objet
\item
  Exemple :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SCHEMA} \NormalTok{s1;}
\end{Highlighting}
\end{Shaded}

La création d'objet passe généralement par l'ordre \texttt{CREATE}. La
syntaxe dépend fortement du type d'objet. Voici trois exemples :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SCHEMA} \NormalTok{s1;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (c1 }\DataTypeTok{integer}\NormalTok{, c2 text);}
\KeywordTok{CREATE} \KeywordTok{SEQUENCE} \NormalTok{s1 }\KeywordTok{INCREMENT} \KeywordTok{BY} \DecValTok{5} \KeywordTok{START} \DecValTok{10}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Pour créer un objet, il faut être propriétaire du schéma ou de la base
auquel appartiendra l'objet ou avoir le droit \texttt{CREATE} sur le
schéma ou la base.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Modifier des objets}\label{modifier-des-objets}

\begin{itemize}
\tightlist
\item
  Ordre \texttt{ALTER}
\item
  Syntaxe spécifique pour modifier la définition d'un objet
\item
  Exemples

  \begin{itemize}
  \tightlist
  \item
    renommage
    \texttt{ALTER\ type\_objet\ ancien\_nom\ RENAME\ TO\ nouveau\_nom}
  \item
    changement de propriétaire
    \texttt{ALTER\ type\_objet\ nom\_objet\ OWNER\ TO\ proprietaire}
  \item
    changement de schéma
    \texttt{ALTER\ type\_objet\ nom\_objet\ SET\ SCHEMA\ nom\_schema}
  \end{itemize}
\end{itemize}

Modifier un objet veut dire modifier ses propriétés. On utilise dans ce
cas l'ordre \texttt{ALTER}. Il faut être propriétaire de l'objet pour
pouvoir le faire.

Deux propriétés sont communes à tous les objets : le nom de l'objet et
son propriétaire. Deux autres sont fréquentes et dépendent du type de
l'objet : le schéma et le tablespace. Les autres propriétés dépendent
directement du type de l'objet.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Supprimer des objets}\label{supprimer-des-objets}

\begin{itemize}
\tightlist
\item
  Ordre \texttt{DROP}
\item
  Exemples :

  \begin{itemize}
  \tightlist
  \item
    supprimer un objet: \texttt{DROP\ type\_objet\ nom\_objet}
  \item
    supprimer un objet et ses dépendances:
    \texttt{DROP\ type\_objet\ nom\_objet\ CASCADE}
  \end{itemize}
\end{itemize}

Seul un propriétaire peut supprimer un objet. Il utilise pour cela
l'ordre \texttt{DROP}. Pour les objets ayant des dépendances, l'option
\texttt{CASCADE} permet de tout supprimer d'un coup. C'est très
pratique, et c'est en même temps très dangereux : il faut donc utiliser
cette option à bon escient.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Schéma}\label{schuxe9ma}

\begin{itemize}
\tightlist
\item
  Identique à un espace de nommage
\item
  Permet d'organiser les tables de façon logique
\item
  Possibilité d'avoir des objets de même nom dans des schémas différents
\item
  Pas d'imbrication (contrairement à des répertoires par exemple)
\item
  Schéma \texttt{public}

  \begin{itemize}
  \tightlist
  \item
    créé par défaut dans une base de données PostgreSQL
  \end{itemize}
\end{itemize}

La notion de schéma dans PostgreSQL est à rapprocher de la notion
d'espace de nommage (ou \texttt{namespace}) de certains langages de
programmation. Le catalogue système qui contient la définition des
schémas dans PostgreSQL s'appelle d'ailleurs \texttt{pg\_namespace}.

Les schémas sont utilisés pour répartir les objets de façon logique,
suivant un schéma interne à l'entreprise. Ils servent aussi à faciliter
la gestion des droits (il suffit de révoquer le droit d'utilisation d'un
schéma à un utilisateur pour que les objets contenus dans ce schéma ne
soient plus accessibles à cet utilisateur).

Un schéma \texttt{public} est créé par défaut dans toute nouvelle base
de données. Tout le monde a le droit d'y créer des objets. Il est
cependant possible de révoquer ce droit ou supprimer ce schéma.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Gestion d'un schéma}\label{gestion-dun-schuxe9ma}

\begin{itemize}
\tightlist
\item
  \texttt{CREATE\ SCHEMA\ nom\_schéma}
\item
  \texttt{ALTER\ SCHEMA\ nom\_schéma}

  \begin{itemize}
  \tightlist
  \item
    renommage
  \item
    changement de propriétaire
  \end{itemize}
\item
  \texttt{DROP\ SCHEMA\ {[}\ IF\ EXISTS\ {]}\ nom\_schéma\ {[}\ CASCADE\ {]}}
\end{itemize}

L'ordre \texttt{CREATE\ SCHEMA} permet de créer un schéma. Il suffit de
lui spécifier le nom du schéma. \texttt{CREATE\ SCHEMA} offre d'autres
possibilités qui sont rarement utilisées.

L'ordre
\texttt{ALTER\ SCHEMA\ nom\_schema\ RENAME\ TO\ nouveau\_nom\_schema}
permet de renommer un schéma. L'ordre
\texttt{ALTER\ SCHEMA\ nom\_schema\ OWNER\ TO\ proprietaire} permet de
donner un nouveau propriétaire au schéma.

Enfin, l'ordre \texttt{DROP\ SCHEMA} permet de supprimer un schéma. La
clause \texttt{IF\ EXISTS} permet d'éviter la levée d'une erreur si le
schéma n'existe pas (très utile dans les scripts SQL). La clause
\texttt{CASCADE} permet de supprimer le schéma ainsi que tous les objets
qui sont positionnés dans le schéma.

\textbf{Exemples}

Création d'un schéma \texttt{reference} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SCHEMA} \NormalTok{reference;}
\end{Highlighting}
\end{Shaded}

Une table peut être créée dans ce schéma :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{reference.communes (}
  \NormalTok{commune      text,}
  \NormalTok{codepostal   }\DataTypeTok{char}\NormalTok{(}\DecValTok{5}\NormalTok{),}
  \NormalTok{departement  text,}
  \NormalTok{codeinsee    }\DataTypeTok{integer}
\NormalTok{);}
\end{Highlighting}
\end{Shaded}

La suppression directe du schéma ne fonctionne pas car il porte encore
la table \texttt{communes} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DROP} \KeywordTok{SCHEMA} \NormalTok{reference;}
\NormalTok{ERROR:  cannot }\KeywordTok{drop} \KeywordTok{schema} \NormalTok{reference because other objects depend }\KeywordTok{on} \NormalTok{it}
\NormalTok{DETAIL:  }\KeywordTok{table} \NormalTok{reference.communes depends }\KeywordTok{on} \KeywordTok{schema} \NormalTok{reference}
\NormalTok{HINT:  }\KeywordTok{Use} \KeywordTok{DROP} \NormalTok{... }\KeywordTok{CASCADE} \KeywordTok{to} \KeywordTok{drop} \KeywordTok{the} \NormalTok{dependent objects too.}
\end{Highlighting}
\end{Shaded}

L'option \texttt{CASCADE} permet de supprimer le schéma et ses objets
dépendants :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DROP} \KeywordTok{SCHEMA} \NormalTok{reference }\KeywordTok{CASCADE}\NormalTok{;}
\NormalTok{NOTICE:  }\KeywordTok{drop} \NormalTok{cascades }\KeywordTok{to} \KeywordTok{table} \NormalTok{reference.communes}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Accès aux objets}\label{accuxe8s-aux-objets}

\begin{itemize}
\tightlist
\item
  Nommage explicite

  \begin{itemize}
  \tightlist
  \item
    \texttt{nom\_schema}.\texttt{nom\_objet}
  \end{itemize}
\item
  Chemin de recherche de schéma

  \begin{itemize}
  \tightlist
  \item
    paramètre \texttt{search\_path}
  \item
    \texttt{SET\ search\_path\ =\ schema1,schema2,public;}
  \item
    par défaut: \texttt{\$user,\ public}
  \end{itemize}
\end{itemize}

Le paramètre \texttt{search\_path} permet de définir un chemin de
recherche pour pouvoir retrouver les tables dont le nom n'est pas
qualifié par le nom de son schéma. PostgreSQL procèdera de la même façon
que le système avec la variable \texttt{\$PATH} : il recherche la table
dans le premier schéma listé. S'il trouve une table portant ce nom dans
le schéma, il préfixe le nom de table avec celui du schéma. S'il ne
trouve pas de table de ce nom dans le schéma, il effectue la même
opération sur le prochain schéma de la liste du \texttt{search\_path}.
S'il n'a trouvé aucune table de ce nom dans les schémas listés par
\texttt{search\_path}, PostgreSQL lève une erreur.

Comme beaucoup d'autres paramètres, le \texttt{search\_path} peut être
positionné à différents endroits. Par défaut, il est assigné à
\texttt{\$user,\ public}, c'est-à-dire que le premier schéma de
recherche portera le nom de l'utilisateur courant, et le second schéma
de recherche est \texttt{public}.

On peut vérifier la variable \texttt{search\_path} à l'aide de la
commande \texttt{SHOW} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{SHOW search_path;}
  \NormalTok{search_path   }
\CommentTok{----------------}
 \OtherTok{"$user"}\NormalTok{,}\KeywordTok{public}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Pour obtenir une configuration particulière, la variable
\texttt{search\_path} peut être positionnée dans le fichier
\texttt{postgresql.conf} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{search_path = }\StringTok{'"$user",public'}
\end{Highlighting}
\end{Shaded}

Cette variable peut aussi être positionnée au niveau d'un utilisateur.
Chaque fois que l'utilisateur se connectera, il prendra le
\texttt{search\_path} de sa configuration spécifique :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{ROLE} \NormalTok{nom_role }\KeywordTok{SET} \NormalTok{search_path = }\OtherTok{"$user"}\NormalTok{, }\KeywordTok{public}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Cela peut aussi se faire au niveau d'une base de données. Chaque fois
qu'un utilisateur se connectera à la base, il prendra le
\texttt{search\_path} de cette base, sauf si l'utilisateur a déjà une
configuration spécifique :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{DATABASE} \NormalTok{nom_base }\KeywordTok{SET} \NormalTok{search_path = }\OtherTok{"$user"}\NormalTok{, }\KeywordTok{public}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

La variable \texttt{search\_path} peut également être positionnée pour
un utilisateur particulier, dans une base particulière :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{ROLE} \NormalTok{nom_role }\KeywordTok{IN} \KeywordTok{DATABASE} \NormalTok{nom_base }\KeywordTok{SET} \NormalTok{search_path = }\OtherTok{"$user"}\NormalTok{, }\KeywordTok{public}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Enfin, la variable \texttt{search\_path} peut être modifiée
dynamiquement dans la session avec la commande \texttt{SET} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SET} \NormalTok{search_path = }\OtherTok{"$user"}\NormalTok{, }\KeywordTok{public}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Avant la version 9.3, les requêtes préparées et les fonctions
conservaient en mémoire le plan d'exécution des requêtes. Ce plan ne
faisait plus référence aux noms des objets mais à leurs identifiants. Du
coup, un \texttt{search\_path} changeant entre deux exécutions d'une
requête préparée ou d'une fonction ne permettait pas de cibler une table
différente. Voici un exemple le montrant :

\begin{verbatim}
-- création des objets
CREATE SCHEMA s1;
CREATE SCHEMA s2;
CREATE TABLE s1.t1 (c1 text);
CREATE TABLE s2.t1 (c1 text);
INSERT INTO s1.t1 VALUES('schéma s1');
INSERT INTO s2.t1 VALUES('schéma s2');

SELECT * FROM s1.t1;
    c1     
-----------
 schéma s1
(1 row)

SELECT * FROM s2.t1;
    c1     
-----------
 schéma s2
(1 row)

-- il y a bien des données différentes dans chaque table

SET search_path TO s1;
PREPARE req AS SELECT * FROM t1;

EXECUTE req;
    c1     
-----------
 schéma s1
(1 row)

SET search_path TO s2;
EXECUTE req;
    c1     
-----------
 schéma s1
(1 row)

-- malgré le changement de search_path, 
-- nous en sommes toujours aux données de l'autre table

b1=# SELECT * FROM t1;
    c1     
-----------
 schéma s2
(1 row)
\end{verbatim}

Dans ce cas, il est préférable de configurer le paramètre
\texttt{search\_path} directement au niveau de la fonction.

À partir de la version 9.3, dès que le \texttt{search\_path} change, les
plans en cache sont supprimés (dans le cas de la fonction) ou recréés
(dans le cas des requêtes préparées).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Séquences}\label{suxe9quences}

\begin{itemize}
\tightlist
\item
  Séquence

  \begin{itemize}
  \tightlist
  \item
    génère une séquence de nombres
  \end{itemize}
\item
  Paramètres

  \begin{itemize}
  \tightlist
  \item
    valeur minimale \texttt{MINVALUE}
  \item
    valeur maximale \texttt{MAXVALUE}
  \item
    valeur de départ \texttt{START}
  \item
    incrément \texttt{INCREMENT}
  \item
    cache \texttt{CACHE}
  \item
    cycle autorisé \texttt{CYCLE}
  \end{itemize}
\end{itemize}

Les séquences sont des objets standards qui permettent de générer des
séquences de valeur. Elles sont utilisées notamment pour générer un
numéro unique pour un identifiant ou, plus rarement, pour disposer d'un
compteur informatif, mis à jour au besoin.

Le cache de la séquence a pour effet de générer un certain nombre de
valeurs en mémoire afin de les mettre à disposition de la session qui a
utilisé la séquence. Même si les valeurs pré-calculées ne sont pas
consommées dans la session, elles seront consommées au niveau de la
séquence. Cela peut avoir pour effet de créer des trous dans les
séquences d'identifiants et de consommer très rapidement les numéros de
séquence possibles. Le cache de séquence n'a pas besoin d'être ajusté
sur des applications réalisant de petites transactions. Il permet en
revanche d'améliorer les performances sur des applications qui utilisent
massivement des numéros de séquences, notamment pour réaliser des
insertions massives.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Création d'une
séquence}\label{cruxe9ation-dune-suxe9quence}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SEQUENCE} \NormalTok{nom [ }\KeywordTok{INCREMENT} \NormalTok{incrément ]}
    \NormalTok{[ }\KeywordTok{MINVALUE} \NormalTok{valeurmin | }\KeywordTok{NO} \KeywordTok{MINVALUE} \NormalTok{]}
    \NormalTok{[ }\KeywordTok{MAXVALUE} \NormalTok{valeurmax | }\KeywordTok{NO} \KeywordTok{MAXVALUE} \NormalTok{]}
    \NormalTok{[ }\KeywordTok{START} \NormalTok{[ }\KeywordTok{WITH} \NormalTok{] début ]}
    \NormalTok{[ }\KeywordTok{CACHE} \KeywordTok{cache} \NormalTok{]}
    \NormalTok{[ [ }\KeywordTok{NO} \NormalTok{] }\KeywordTok{CYCLE} \NormalTok{]}
    \NormalTok{[ OWNED }\KeywordTok{BY} \NormalTok{\{ nom_table.nom_colonne | }\KeywordTok{NONE} \NormalTok{\} ]}
\end{Highlighting}
\end{Shaded}

La syntaxe complète est donné dans le slide.

Le mot clé \texttt{TEMPORARY} ou \texttt{TEMP} permet de définir si la
séquence est temporaire. Si tel est le cas, elle sera détruite à la
déconnexion de l'utilisateur.

Le mot clé \texttt{INCREMENT} définit l'incrément de la séquence,
\texttt{MINVALUE}, la valeur minimale de la séquence et
\texttt{MAXVALUE}, la valeur maximale. \texttt{START} détermine la
valeur de départ initiale de la séquence, c'est-à-dire juste après sa
création. La clause \texttt{CACHE} détermine le cache de séquence.
\texttt{CYCLE} permet d'indiquer au SGBD que la séquence peut reprendre
son compte à \texttt{MINVALUE} lorsqu'elle aura atteint
\texttt{MAXVALUE}. La clause \texttt{NO\ CYCLE} indique que le
rebouclage de la séquence est interdit, PostgreSQL lèvera alors une
erreur lorsque la séquence aura atteint son \texttt{MAXVALUE}. Enfin, la
clause \texttt{OWNED\ BY} détermine l'appartenance d'une séquence à une
colonne d'une table. Ainsi, si la colonne est supprimée, la séquence
sera implicitement supprimée.

Exemple de séquence avec rebouclage :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SEQUENCE} \NormalTok{testseq }\KeywordTok{INCREMENT} \KeywordTok{BY} \DecValTok{1} \KeywordTok{MINVALUE} \DecValTok{3} \KeywordTok{MAXVALUE} \DecValTok{5} \KeywordTok{CYCLE} \KeywordTok{START} \KeywordTok{WITH} \DecValTok{4}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
       \DecValTok{4}
       
\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
       \DecValTok{5}
       
\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
       \DecValTok{3}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Modification d'une
séquence}\label{modification-dune-suxe9quence}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{SEQUENCE} \NormalTok{nom [ }\KeywordTok{INCREMENT} \KeywordTok{increment} \NormalTok{]}
    \NormalTok{[ }\KeywordTok{MINVALUE} \NormalTok{valeurmin | }\KeywordTok{NO} \KeywordTok{MINVALUE} \NormalTok{]}
    \NormalTok{[ }\KeywordTok{MAXVALUE} \NormalTok{valeurmax | }\KeywordTok{NO} \KeywordTok{MAXVALUE} \NormalTok{]}
    \NormalTok{[ }\KeywordTok{START} \NormalTok{[ }\KeywordTok{WITH} \NormalTok{] début ]}
    \NormalTok{[ RESTART [ [ }\KeywordTok{WITH} \NormalTok{] nouveau_début ] ]}
    \NormalTok{[ }\KeywordTok{CACHE} \KeywordTok{cache} \NormalTok{] [ [ }\KeywordTok{NO} \NormalTok{] }\KeywordTok{CYCLE} \NormalTok{]}
    \NormalTok{[ OWNED }\KeywordTok{BY} \NormalTok{\{ nom_table.nom_colonne | }\KeywordTok{NONE} \NormalTok{\} ]}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  Il est aussi possible de modifier

  \begin{itemize}
  \tightlist
  \item
    le propriétaire
  \item
    le schéma
  \end{itemize}
\end{itemize}

Les propriétés de la séquence peuvent être modifiés avec l'ordre
\texttt{ALTER\ SEQUENCE}.

La séquence peut être affectée à un nouveau propriétaire :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{SEQUENCE} \NormalTok{[ }\KeywordTok{IF} \KeywordTok{EXISTS} \NormalTok{] nom OWNER }\KeywordTok{TO} \NormalTok{nouveau_propriétaire}
\end{Highlighting}
\end{Shaded}

Elle peut être renommée :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{SEQUENCE} \NormalTok{[ }\KeywordTok{IF} \KeywordTok{EXISTS} \NormalTok{] nom }\KeywordTok{RENAME} \KeywordTok{TO} \NormalTok{nouveau_nom}
\end{Highlighting}
\end{Shaded}

Enfin, elle peut être positionnée dans un nouveau schéma :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{SEQUENCE} \NormalTok{[ }\KeywordTok{IF} \KeywordTok{EXISTS} \NormalTok{] nom }\KeywordTok{SET} \KeywordTok{SCHEMA} \NormalTok{nouveau_schema}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Suppression d'une
séquence}\label{suppression-dune-suxe9quence}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DROP} \KeywordTok{SEQUENCE} \NormalTok{nom [, ...]}
\end{Highlighting}
\end{Shaded}

Voici la syntaxe complète de \texttt{DROP\ SEQUENCE} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DROP} \KeywordTok{SEQUENCE} \NormalTok{[ }\KeywordTok{IF} \KeywordTok{EXISTS} \NormalTok{] nom [, ...] [ }\KeywordTok{CASCADE} \NormalTok{| }\KeywordTok{RESTRICT} \NormalTok{]}
\end{Highlighting}
\end{Shaded}

Le mot clé \texttt{CASCADE} permet de supprimer la séquence ainsi que
tous les objets dépendants (par exemple la valeur par défaut d'une
colonne).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Séquences, utilisation}\label{suxe9quences-utilisation}

\begin{itemize}
\tightlist
\item
  Obtenir la valeur suivante

  \begin{itemize}
  \tightlist
  \item
    \texttt{nextval(\textquotesingle{}nom\_sequence\textquotesingle{})}
  \end{itemize}
\item
  Obtenir la valeur courante

  \begin{itemize}
  \tightlist
  \item
    \texttt{currval(\textquotesingle{}nom\_sequence\textquotesingle{})}
  \item
    mais \texttt{nextval()} doit être appelé avant dans la même session
  \end{itemize}
\end{itemize}

La fonction \texttt{nextval()} permet d'obtenir le numéro de séquence
suivant. Son comportement n'est pas transactionnel. Une fois qu'un
numéro est consommé, il n'est pas possible de revenir dessus, malgré un
\texttt{ROLLBACK} de la transaction. La séquence est le seul objet a
avoir un comportement de ce type.

La fonction \texttt{currval()} permet d'obtenir le numéro de séquence
courant, mais son usage nécessite d'avoir utilisé \texttt{nextval()}
dans la session.

Il est possible d'interroger une séquence avec une requête
\texttt{SELECT}. Cela permet d'obtenir des informations sur la séquence,
dont la dernière valeur utilisée dans la colonne \texttt{last\_value}.
Cet usage n'est pas recommandé en production et doit plutôt être utilisé
à titre informatif.

\textbf{Exemples}

Utilisation d'une séquence simple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SEQUENCE} \NormalTok{testseq }
    \KeywordTok{INCREMENT} \KeywordTok{BY} \DecValTok{1} 
    \KeywordTok{MINVALUE} \DecValTok{10} 
    \KeywordTok{MAXVALUE} \DecValTok{20} 
    \KeywordTok{START} \KeywordTok{WITH} \DecValTok{15} 
    \KeywordTok{CACHE} \DecValTok{1}\NormalTok{;}


\KeywordTok{SELECT} \NormalTok{currval(}\StringTok{'testseq'}\NormalTok{);}
\NormalTok{ERROR:  currval }\KeywordTok{of} \KeywordTok{sequence} \OtherTok{"testseq"} \KeywordTok{is} \KeywordTok{not} \NormalTok{yet defined }\KeywordTok{in} \NormalTok{this }\KeywordTok{session}


\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{testseq ;}
 \NormalTok{sequence_name | }\FunctionTok{last_value} \NormalTok{| start_value | increment_by | max_value | min_value | cache_value | log_cnt | is_cycled | is_called }
\CommentTok{---------------+------------+-------------+--------------+-----------+-----------+-------------+---------+-----------+-----------}
 \NormalTok{testseq       |         }\DecValTok{15} \NormalTok{|          }\DecValTok{15} \NormalTok{|            }\DecValTok{1} \NormalTok{|        }\DecValTok{20} \NormalTok{|        }\DecValTok{10} \NormalTok{|           }\DecValTok{5} \NormalTok{|       }\DecValTok{0} \NormalTok{| f         | f}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}


\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
      \DecValTok{15}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}


\KeywordTok{SELECT} \NormalTok{currval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{currval }
\CommentTok{---------}
      \DecValTok{15}

\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
      \DecValTok{16}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}

\KeywordTok{ALTER} \KeywordTok{SEQUENCE} \NormalTok{testseq RESTART }\KeywordTok{WITH} \DecValTok{5}\NormalTok{;}
\NormalTok{ERROR:  RESTART }\FunctionTok{value} \NormalTok{(}\DecValTok{5}\NormalTok{) cannot be }\KeywordTok{less} \KeywordTok{than} \KeywordTok{MINVALUE} \NormalTok{(}\DecValTok{10}\NormalTok{)}

\KeywordTok{DROP} \KeywordTok{SEQUENCE} \NormalTok{testseq;}
\end{Highlighting}
\end{Shaded}

Utilisation d'une séquence simple avec cache:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{SEQUENCE} \NormalTok{testseq }\KeywordTok{INCREMENT} \KeywordTok{BY} \DecValTok{1} \KeywordTok{CACHE} \DecValTok{10}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
       \DecValTok{1}
\end{Highlighting}
\end{Shaded}

Déconnexion et reconnexion de l'utilisateur :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nextval(}\StringTok{'testseq'}\NormalTok{);}
 \NormalTok{nextval }
\CommentTok{---------}
      \DecValTok{11}
\end{Highlighting}
\end{Shaded}

Suppression en cascade d'une séquence :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t2 (}\KeywordTok{id} \NormalTok{serial);}

\NormalTok{\textbackslash{}d t2}
                           \KeywordTok{Table} \OtherTok{"s2.t2"}
 \KeywordTok{Column} \NormalTok{|  }\KeywordTok{Type}   \NormalTok{|                    Modifiers                    }
\CommentTok{--------+---------+-------------------------------------------------}
 \KeywordTok{id}     \NormalTok{| }\DataTypeTok{integer} \NormalTok{| }\KeywordTok{not} \KeywordTok{null} \KeywordTok{default} \NormalTok{nextval(}\StringTok{'t2_id_seq'}\NormalTok{:}\CharTok{:regclass}\NormalTok{)}

\KeywordTok{DROP} \KeywordTok{SEQUENCE} \NormalTok{t2_id_seq;}
\NormalTok{ERROR:  cannot }\KeywordTok{drop} \KeywordTok{sequence} \NormalTok{t2_id_seq because other objects depend }\KeywordTok{on} \NormalTok{it}
\NormalTok{DETAIL:  }\KeywordTok{default} \KeywordTok{for} \KeywordTok{table} \NormalTok{t2 }\KeywordTok{column} \KeywordTok{id} \NormalTok{depends }\KeywordTok{on} \KeywordTok{sequence} \NormalTok{t2_id_seq}
\NormalTok{HINT:  }\KeywordTok{Use} \KeywordTok{DROP} \NormalTok{... }\KeywordTok{CASCADE} \KeywordTok{to} \KeywordTok{drop} \KeywordTok{the} \NormalTok{dependent objects too.}

\KeywordTok{DROP} \KeywordTok{SEQUENCE} \NormalTok{t2_id_seq }\KeywordTok{CASCADE}\NormalTok{;}
\NormalTok{NOTICE:  }\KeywordTok{drop} \NormalTok{cascades }\KeywordTok{to} \KeywordTok{default} \KeywordTok{for} \KeywordTok{table} \NormalTok{t2 }\KeywordTok{column} \KeywordTok{id}

\NormalTok{\textbackslash{}d t2}
        \KeywordTok{Table} \OtherTok{"s2.t2"}
 \KeywordTok{Column} \NormalTok{|  }\KeywordTok{Type}   \NormalTok{| Modifiers }
\CommentTok{--------+---------+-----------}
 \KeywordTok{id}     \NormalTok{| }\DataTypeTok{integer} \NormalTok{| }\KeywordTok{not} \KeywordTok{null}
\end{Highlighting}
\end{Shaded}

=== Type SERIAL ===

\begin{itemize}
\tightlist
\item
  Type \texttt{SERIAL}/\texttt{BIGSERIAL}

  \begin{itemize}
  \tightlist
  \item
    séquence générée automatiquement
  \item
    valeur par défaut \texttt{nextval(...)}
  \end{itemize}
\end{itemize}

Certaines bases de données offre des colonnes auto-incrémentées
(\texttt{autoincrement} de MySQL ou \texttt{identity} de SQL Server).
PostgreSQL possède un équivalent, mais qui s'appuie sur les possibilités
du standard SQL : les séquences et la possibilité d'appliquer une valeur
par défaut à une colonne.

Par exemple, si l'on crée la table suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{exemple_serial (}
  \KeywordTok{id} \NormalTok{SERIAL }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{,}
  \NormalTok{valeur }\DataTypeTok{INTEGER} \KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{);}
\end{Highlighting}
\end{Shaded}

On s'aperçoit que cela a créé la table tel que demandé, mais qu'une
séquence a aussi été également créé. Elle porte un nom dérivé de la
table associé à la colonne correspondant au type \texttt{SERIAL},
terminé par \texttt{seq} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{postgres=# \textbackslash{}d}
                 \KeywordTok{List} \KeywordTok{of} \NormalTok{relations}
 \KeywordTok{Schema} \NormalTok{|         Name          |   }\KeywordTok{Type}   \NormalTok{| Owner  }
\CommentTok{--------+-----------------------+----------+--------}
 \KeywordTok{public} \NormalTok{| exemple_serial        | }\KeywordTok{table}    \NormalTok{| thomas}
 \KeywordTok{public} \NormalTok{| exemple_serial_id_seq | }\KeywordTok{sequence} \NormalTok{| thomas}
\end{Highlighting}
\end{Shaded}

En examinant plus précisément la définition de la table, on s'aperçoit
que la colonne \texttt{id} porte une valeur par défaut qui correspond à
l'appel de la fonction \texttt{nextval()} sur la séquence qui a été
créée implicitement :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{postgres=# \textbackslash{}d exemple_serial}
                         \KeywordTok{Table} \OtherTok{"public.exemple_serial"}
 \KeywordTok{Column} \NormalTok{|  }\KeywordTok{Type}   \NormalTok{|                          Modifiers                          }
\CommentTok{--------+---------+-------------------------------------------------------------}
 \KeywordTok{id}     \NormalTok{| }\DataTypeTok{integer} \NormalTok{| }\KeywordTok{not} \KeywordTok{null} \KeywordTok{default} \NormalTok{nextval(}\StringTok{'exemple_serial_id_seq'}\NormalTok{:}\CharTok{:regclass}\NormalTok{)}
 \NormalTok{valeur | }\DataTypeTok{integer} \NormalTok{| }\KeywordTok{not} \KeywordTok{null}
\NormalTok{Indexes:}
    \OtherTok{"exemple_serial_pkey"} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, btree (}\KeywordTok{id}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Domaines}\label{domaines}

\begin{itemize}
\tightlist
\item
  Permet d'associer

  \begin{itemize}
  \tightlist
  \item
    un type standard
  \item
    et une contrainte (optionnelle)
  \end{itemize}
\end{itemize}

Un domaine est utilisé pour définir un type utilisateur qui est en fait
un type utilisateur standard accompagné de la définition de contraintes
particulières.

Les domaines sont utiles pour ramener la définition de contraintes
communes à plusieurs colonnes sur un seul objet. La maintenance en est
ainsi facilitée.

L'ordre \texttt{CREATE\ DOMAIN} permet de créer un domaine,
\texttt{ALTER\ DOMAIN} permet de modifier sa définition, et enfin,
\texttt{DROP\ DOMAIN} permet de supprimer un domaine.

\textbf{Exemples}

Gestion d'un domaine \texttt{salaire} :

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{-- ajoutons le domaine et la table}
\KeywordTok{CREATE} \KeywordTok{DOMAIN} \NormalTok{salaire }\KeywordTok{AS} \DataTypeTok{integer} \KeywordTok{CHECK} \NormalTok{(}\FunctionTok{VALUE} \NormalTok{> }\DecValTok{0}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{employes (}\KeywordTok{id} \NormalTok{serial, nom text, paye salaire);}

\CommentTok{-- insérons des données dans la nouvelle table}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes (nom, paye) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Albert'}\NormalTok{, }\DecValTok{1500}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes (nom, paye) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Alphonse'}\NormalTok{, }\DecValTok{0}\NormalTok{);}
\NormalTok{ERROR:  }\FunctionTok{value} \KeywordTok{for} \KeywordTok{domain} \NormalTok{salaire violates }\KeywordTok{check} \KeywordTok{constraint} \OtherTok{"salaire_check"}
\CommentTok{-- erreur logique vu qu'on ne peut avoir qu'un entier strictement positif}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes (nom, paye) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Alphonse'}\NormalTok{, }\DecValTok{1000}\NormalTok{);}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{1}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes (nom, paye) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Bertrand'}\NormalTok{, }\KeywordTok{NULL}\NormalTok{);}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{1}
\CommentTok{-- tous les employés doivent avoir un salaire}
\CommentTok{-- il faut donc modifier la contrainte, pour s'assurer qu'aucune valeur NULL }
\CommentTok{-- ne soit saisi}
\KeywordTok{ALTER} \KeywordTok{DOMAIN} \NormalTok{salaire }\KeywordTok{SET} \KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{;}
\NormalTok{ERROR:  }\KeywordTok{column} \OtherTok{"paye"} \KeywordTok{of} \KeywordTok{table} \OtherTok{"employes"} \NormalTok{contains }\KeywordTok{null} \KeywordTok{values}
\CommentTok{-- la ligne est déjà présente, il faut la modifier}
\KeywordTok{UPDATE} \NormalTok{employes }\KeywordTok{SET} \NormalTok{paye=}\DecValTok{1500} \KeywordTok{WHERE} \NormalTok{nom=}\StringTok{'Bertrand'}\NormalTok{;}
\CommentTok{-- maintenant, on peut ajouter la contrainte au domaine}
\KeywordTok{ALTER} \KeywordTok{DOMAIN} \NormalTok{salaire }\KeywordTok{SET} \KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{;}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes (nom, paye) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Deplhine'}\NormalTok{, }\KeywordTok{NULL}\NormalTok{);}
\NormalTok{ERROR:  }\KeywordTok{domain} \NormalTok{salaire does }\KeywordTok{not} \NormalTok{allow }\KeywordTok{null} \KeywordTok{values}
\CommentTok{-- la contrainte est bien vérifiée}
\CommentTok{-- supprimons maintenant la contrainte}
\KeywordTok{DROP} \KeywordTok{DOMAIN} \NormalTok{salaire;}
\NormalTok{ERROR:  cannot }\KeywordTok{drop} \KeywordTok{type} \NormalTok{salaire because other objects depend }\KeywordTok{on} \NormalTok{it}
\NormalTok{DETAIL:  }\KeywordTok{table} \NormalTok{employes }\KeywordTok{column} \NormalTok{paye depends }\KeywordTok{on} \KeywordTok{type} \NormalTok{salaire}
\NormalTok{HINT:  }\KeywordTok{Use} \KeywordTok{DROP} \NormalTok{... }\KeywordTok{CASCADE} \KeywordTok{to} \KeywordTok{drop} \KeywordTok{the} \NormalTok{dependent objects too.}
\CommentTok{-- il n'est possible de supprimer le domaine }
\CommentTok{-- car il est référencé dans une table}
\CommentTok{-- il faut donc utiliser l'option CASCADE}
\NormalTok{b1=# }\KeywordTok{DROP} \KeywordTok{DOMAIN} \NormalTok{salaire }\KeywordTok{CASCADE}\NormalTok{;}
\NormalTok{NOTICE:  }\KeywordTok{drop} \NormalTok{cascades }\KeywordTok{to} \KeywordTok{table} \NormalTok{employes }\KeywordTok{column} \NormalTok{paye}
\KeywordTok{DROP} \KeywordTok{DOMAIN}
\CommentTok{-- le domaine a été supprimée ainsi que toutes les colonnes ayant ce type}
\NormalTok{\textbackslash{}d employes}
                           \KeywordTok{Table} \OtherTok{"s2.employes"}
 \KeywordTok{Column} \NormalTok{|  }\KeywordTok{Type}   \NormalTok{|                       Modifiers                       }
\CommentTok{--------+---------+-------------------------------------------------------}
 \KeywordTok{id}     \NormalTok{| }\DataTypeTok{integer} \NormalTok{| }\KeywordTok{not} \KeywordTok{null} \KeywordTok{default} \NormalTok{nextval(}\StringTok{'employes_id_seq'}\NormalTok{:}\CharTok{:regclass}\NormalTok{)}
 \NormalTok{nom    | text    | }
\end{Highlighting}
\end{Shaded}

Création et utilisation d'un domaine \texttt{code\_postal\_us} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{DOMAIN} \NormalTok{code_postal_us }\KeywordTok{AS} \NormalTok{TEXT}
\KeywordTok{CHECK}\NormalTok{(}
   \FunctionTok{VALUE} \NormalTok{~ }\StringTok{'^\textbackslash{}d\{5\}$'}
\KeywordTok{OR} \FunctionTok{VALUE} \NormalTok{~ }\StringTok{'^\textbackslash{}d\{5\}-\textbackslash{}d\{4\}$'}
\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{courrier_us (}
  \NormalTok{id_adresse SERIAL }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{,}
  \NormalTok{rue1 TEXT }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{rue2 TEXT,}
  \NormalTok{rue3 TEXT,}
  \NormalTok{ville TEXT }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{code_postal code_postal_us }\KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Tables}\label{tables}

\begin{itemize}
\tightlist
\item
  Équivalent ensembliste d'une relation
\item
  Composé principalement de

  \begin{itemize}
  \tightlist
  \item
    colonnes ordonnées
  \item
    contraintes
  \end{itemize}
\end{itemize}

La table est l'élément de base d'une base de données. Elle est composée
de colonnes (à sa création) et est remplie avec des enregistrements
(lignes de la table). Sa définition peut aussi faire intervenir des
contraintes, qui sont au niveau table ou colonne.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Création d'une table}\label{cruxe9ation-dune-table}

\begin{itemize}
\tightlist
\item
  Définition de son nom
\item
  Définition de ses colonnes

  \begin{itemize}
  \tightlist
  \item
    nom, type, contraintes éventuelles
  \end{itemize}
\item
  Clauses de stockage
\item
  \texttt{CREATE\ TABLE}
\end{itemize}

Pour créer une table, il faut donner son nom et la liste des colonnes.
Une colonne est définie par son nom et son type, mais aussi des
contraintes optionnelles.

Des options sont possibles pour les tables, comme les clauses de
stockage. Dans ce cas, on sort du contexte logique pour se placer au
niveau physique.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{CREATE TABLE}\label{create-table}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{nom_table (}
  \NormalTok{definition_colonnes}
  \NormalTok{definition_contraintes}
\NormalTok{) clause_stockage;}
\end{Highlighting}
\end{Shaded}

La création d'une table passe par l'ordre \texttt{CREATE\ TABLE}. La
définition des colonnes et des contraintes sont entre parenthèse après
le nom de la table.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Définition des
colonnes}\label{duxe9finition-des-colonnes}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{nom_colonne }\KeywordTok{type} \NormalTok{[ COLLATE collation ] [ contrainte ]}
\NormalTok{[, ...]}
\end{Highlighting}
\end{Shaded}

Les colonnes sont indiquées l'une après l'autre, en les séparant par des
virgules.

Deux informations sont obligatoires pour chaque colonne : le nom et le
type de la colonne. Dans le cas d'une colonne contenant du texte, il est
possible de fournir le collationnement de la colonne. Quelque soit la
colonne, il est ensuite possible d'ajouter des contraintes.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Valeur par défaut}\label{valeur-par-duxe9faut}

\begin{itemize}
\tightlist
\item
  \texttt{DEFAULT}

  \begin{itemize}
  \tightlist
  \item
    affectation implicite
  \end{itemize}
\item
  Utiliser directement par les types sériés
\end{itemize}

La clause \texttt{DEFAULT} permet d'affecter une valeur par défaut
lorsqu'une colonne n'est pas référencée dans l'ordre d'insertion ou si
une mise à jour réinitialise la valeur de la colonne à sa valeur par
défaut.

Les types sériés définissent une valeur par défaut sur les colonnes de
ce type. Cette valeur est le retour de la fonction \texttt{nextval()}
sur la séquence affectée automatiquement à cette colonne.

\textbf{Exemples}

Assignation d'une valeur par défaut :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{valdefaut (}
  \KeywordTok{id} \DataTypeTok{integer}\NormalTok{,}
  \NormalTok{i }\DataTypeTok{integer} \KeywordTok{DEFAULT} \DecValTok{0}\NormalTok{,}
  \NormalTok{j }\DataTypeTok{integer} \KeywordTok{DEFAULT} \DecValTok{0}
\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{valdefaut (}\KeywordTok{id}\NormalTok{, i) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{valdefaut ;}
 \KeywordTok{id} \NormalTok{| i  | j }
\CommentTok{----+----+---}
  \DecValTok{1} \NormalTok{| }\DecValTok{10} \NormalTok{| }\DecValTok{0}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Copie de la définition d'une
table}\label{copie-de-la-duxe9finition-dune-table}

\begin{itemize}
\tightlist
\item
  Création d'une table à partir d'une autre table

  \begin{itemize}
  \tightlist
  \item
    \texttt{CREATE\ TABLE\ ...\ (LIKE\ table\ clause\_inclusion)}
  \end{itemize}
\item
  Avec les valeurs par défaut des colonnes :

  \begin{itemize}
  \tightlist
  \item
    \texttt{INCLUDING\ DEFAULTS}
  \end{itemize}
\item
  Avec ses autres contraintes :

  \begin{itemize}
  \tightlist
  \item
    \texttt{INCLUDING\ CONSTRAINTS}
  \end{itemize}
\item
  Avec ses index :

  \begin{itemize}
  \tightlist
  \item
    \texttt{INCLUDING\ INDEXES}
  \end{itemize}
\end{itemize}

L'ordre \texttt{CREATE\ TABLE} permet également de créer une table à
partir de la définition d'une table déjà existante en utilisant la
clause \texttt{LIKE} en lieu et place de la définition habituelles des
colonnes. Par défaut, seule la définition des colonnes avec leur typage
est repris.

Les clauses \texttt{INCLUDING} permettent de récupérer d'autres éléments
de la définition de la table, comme les valeurs par défaut
(\texttt{INCLUDING\ DEFAULTS}), les contraintes d'intégrité
(\texttt{INCLUDING\ CONSTRAINTS}), les index
(\texttt{INCLUDING\ INDEXES}), les clauses de stockage
(\texttt{INCLUDING\ STORAGE}) ainsi que les commentaires
(\texttt{INCLUDING\ COMMENTS}). Si l'ensemble de ces éléments sont
repris, il est possible de résumer la clause \texttt{INCLUDING} à
\texttt{INCLUDING\ ALL}.

La clause \texttt{CREATE\ TABLE} suivante permet de créer une table
\texttt{archive\_evenements\_2010} à partir de la définition de la table
\texttt{evenements} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{archive_evenements_2010}
 \NormalTok{(}\KeywordTok{LIKE} \NormalTok{evenements }
  \KeywordTok{INCLUDING} \NormalTok{DEFAULTS}
  \KeywordTok{INCLUDING} \KeywordTok{CONSTRAINTS}
  \KeywordTok{INCLUDING} \KeywordTok{INDEXES}
  \KeywordTok{INCLUDING} \KeywordTok{STORAGE}
  \KeywordTok{INCLUDING} \NormalTok{COMMENTS}
\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Elle est équivalente à :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{archive_evenements_2010}
 \NormalTok{(}\KeywordTok{LIKE} \NormalTok{evenements }
  \KeywordTok{INCLUDING} \KeywordTok{ALL}
\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Modification d'une table}\label{modification-dune-table}

\begin{itemize}
\tightlist
\item
  \texttt{ALTER\ TABLE}
\item
  Définition de la table

  \begin{itemize}
  \tightlist
  \item
    renommage de la table
  \item
    ajout/modification/suppression d'une colonne
  \item
    déplacement dans un schéma différent
  \item
    changement du propriétaire
  \end{itemize}
\item
  Définition des colonnes

  \begin{itemize}
  \tightlist
  \item
    renommage d'une colonne
  \item
    changement de type d'une colonne
  \end{itemize}
\item
  Définition des contraintes

  \begin{itemize}
  \tightlist
  \item
    ajout/suppression d'une contrainte
  \end{itemize}
\end{itemize}

Pour modifier la définition d'une table (et non pas son contenu), il
convient d'utiliser l'ordre \texttt{ALTER\ TABLE}. Il permet de traiter
la définition de la table (nom, propriétaire, schéma, liste des
colonnes), la définition des colonnes (ajout, modification de nom et de
type, suppression\ldots{} mais pas de changement au niveau de leur
ordre), et la définition des contraintes (ajout et suppression).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Suppression d'une table}\label{suppression-dune-table}

\begin{itemize}
\tightlist
\item
  Supprimer une table :\texttt{lDROP\ TABLE\ nom\_table;}
\item
  Supprimer une table et tous les objets dépendants
  :\texttt{DROP\ TABLE\ nom\_table\ CASCADE;}
\end{itemize}

L'ordre \texttt{DROP\ TABLE} permet de supprimer une table. L'ordre
\texttt{DROP\ TABLE\ ...\ CASCADE} permet de supprimer une table ainsi
que tous ses objets dépendants. Il peut s'agir de séquences rattachées à
une colonne d'une table, à des colonnes référençant la table à
supprimer, etc.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Contraintes
d'intégrité}\label{contraintes-dintuxe9grituxe9}

\begin{itemize}
\tightlist
\item
  ACID

  \begin{itemize}
  \tightlist
  \item
    \textbf{C}ohérence
  \item
    une transaction amène la base d'un état stable à un autre
  \end{itemize}
\item
  Assurent la cohérence des données

  \begin{itemize}
  \tightlist
  \item
    unicité des enregistrements
  \item
    intégrité référentielle
  \item
    vérification des valeurs
  \item
    règles sémantiques
  \end{itemize}
\end{itemize}

Les données dans les différentes tables ne sont pas indépendantes mais
obéissent à des règles sémantiques mises en place au moment de la
conception du modèle conceptuel des données. Les contraintes d'intégrité
ont pour principal objectif de garantir la cohérence des données entre
elles, et donc de veiller à ce qu'elles respectent ces règles
sémantiques. Si une insertion, une mise à jour ou une suppression viole
ces règles, l'opération est purement et simplement annulée.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clés primaires}\label{cluxe9s-primaires}

\begin{itemize}
\tightlist
\item
  Identifie une ligne de manière unique
\item
  Une seule clé primaire par table
\item
  Une ou plusieurs colonnes
\item
  À choisir parmi les clés candidates

  \begin{itemize}
  \tightlist
  \item
    parfois, utiliser une clé artificielle
  \end{itemize}
\end{itemize}

Une clé primaire permet d'identifier une ligne de façon unique, il n'en
existe qu'une seule par table.

Une clé primaire garantit que toutes les valeurs de la ou des colonnes
qui composent cette clé sont uniques et non nulles. Elle peut être
composée d'une seule colonne ou de plusieurs colonnes, selon le besoin.

La clé primaire est déterminée au moment de la conception du modèle de
données.

Les clés primaires créent implicitement un index qui permet de renforcer
cette contrainte.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Déclaration d'une clé
primaire}\label{duxe9claration-dune-cluxe9-primaire}

\begin{itemize}
\tightlist
\item
  Construction :
\end{itemize}

\texttt{{[}CONSTRAINT\ nom\_contrainte{]}\ PRIMARY\ KEY\ (\ nom\_colonne\ {[},\ ...\ {]}\ )}

\textbf{Exemples}

Définition de la table \texttt{region} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{region}
  \NormalTok{(}
    \KeywordTok{id}       \NormalTok{serial   }\KeywordTok{UNIQUE} \KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
    \NormalTok{libelle  text     }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}

    \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{(}\KeywordTok{id}\NormalTok{)}
  \NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{region }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'un'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{region }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'deux'}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{region }\KeywordTok{VALUES} \NormalTok{(}\KeywordTok{NULL}\NormalTok{, }\StringTok{'trois'}\NormalTok{);}
\NormalTok{ERROR:  }\KeywordTok{null} \FunctionTok{value} \KeywordTok{in} \KeywordTok{column} \OtherTok{"id"} \NormalTok{violates not-null }\KeywordTok{constraint}
\NormalTok{DETAIL:  Failing }\KeywordTok{row} \NormalTok{contains (}\KeywordTok{null}\NormalTok{, trois).}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{region }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'trois'}\NormalTok{);}
\NormalTok{ERROR:  duplicate }\KeywordTok{key} \FunctionTok{value} \NormalTok{violates }\KeywordTok{unique} \KeywordTok{constraint} \OtherTok{"region_pkey"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(}\KeywordTok{id}\NormalTok{)=(}\DecValTok{1}\NormalTok{) already exists.}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{region }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{3}\NormalTok{, }\StringTok{'trois'}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{region;}
 \KeywordTok{id} \NormalTok{| libelle }
\CommentTok{----+---------}
  \DecValTok{1} \NormalTok{| un}
  \DecValTok{2} \NormalTok{| deux}
  \DecValTok{3} \NormalTok{| trois}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Contrainte d'unicité}\label{contrainte-dunicituxe9}

\begin{itemize}
\tightlist
\item
  Garantie l'unicité des valeurs d'une ou plusieurs colonnes
\item
  Permet les valeurs \texttt{NULL}
\item
  Clause \texttt{UNIQUE}
\item
  Contrainte \texttt{UNIQUE} != index \texttt{UNIQUE}
\end{itemize}

Une contrainte d'unicité permet de garantir que les valeurs de la ou des
colonnes sur lesquelles porte la contrainte sont uniques. Elle autorise
néanmoins d'avoir plusieurs valeurs \texttt{NULL} car elles ne sont pas
considérées comme égales mais de valeur inconnue (\texttt{UNKNOWN}).

Une contrainte d'unicité peut être créée simplement en créant un index
\texttt{UNIQUE} approprié. Ceci est fortement déconseillé du fait que la
contrainte ne sera pas référencée comme telle dans le schéma de la base
de données. Il sera donc très facile de ne pas la remarquer au moment
d'une reprise du schéma pour une évolution majeure de l'application. Une
colonne possédant un index \texttt{UNIQUE} peut malgré tout être
référencée par une clé étrangère.

Les contraintes d'unicité créent implicitement un index qui permet de
renforcer cette unicité.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Déclaration d'une contrainte
d'unicité}\label{duxe9claration-dune-contrainte-dunicituxe9}

\begin{itemize}
\tightlist
\item
  Construction:
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{[ }\KeywordTok{CONSTRAINT} \NormalTok{nom_contrainte] \{ }\KeywordTok{UNIQUE} \NormalTok{( nom_colonne [, ... ] )}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Intégrité
référentielle}\label{intuxe9grituxe9-ruxe9fuxe9rentielle}

\begin{itemize}
\tightlist
\item
  \textbf{Contrainte d'intégrité référentielle}

  \begin{itemize}
  \tightlist
  \item
    ou \textbf{Clé étrangère}
  \end{itemize}
\item
  Référence une \textbf{clé primaire} ou un groupe de colonnes
  \texttt{UNIQUE} et \texttt{NOT\ NULL}
\item
  Garantie l'intégrité des données
\item
  \texttt{FOREIGN\ KEY}
\end{itemize}

Une clé étrangère sur une table fait référence à une clé primaire ou une
contrainte d'unicité d'une autre table. La clé étrangère garantit que
les valeurs des colonnes de cette clé existent également dans la table
portant la clé primaire ou la contrainte d'unicité. On parle de
contrainte référentielle d'intégrité : la contrainte interdit les
valeurs qui n'existent pas dans la table référencée.

Ainsi, la base cave définit une table \texttt{region} et une table
\texttt{appellation}. Une appellation d'origine est liée au terroir, et
par extension à son origine géographique. La table \texttt{appellation}
est donc liée par une clé étrangère à la table \texttt{region}: la
colonne \texttt{region\_id} de la table \texttt{appellation} référence
la colonne \texttt{id} de la table \texttt{region}.

Cette contrainte permet d'empêcher les utilisateurs d'entrer dans la
table \texttt{appellation} des identifiants de région
(\texttt{region\_id}) qui n'existent pas dans la table \texttt{region}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Exemple}\label{exemple}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Exemple de clés étrangères}
\end{figure}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Déclaration d'une clé
étrangère}\label{duxe9claration-dune-cluxe9-uxe9tranguxe8re}

\begin{Shaded}
\begin{Highlighting}[]

\KeywordTok{CONSTRAINT} \NormalTok{nom_contrainte ] }\KeywordTok{FOREIGN} \KeywordTok{KEY} \NormalTok{( nom_colonne [, ...] )}
    \KeywordTok{REFERENCES} \NormalTok{table_reference [ (colonne_reference [, ... ] ) ]}
    \NormalTok{[ MATCH }\KeywordTok{FULL} \NormalTok{| MATCH PARTIAL | MATCH SIMPLE ]}
    \NormalTok{[ }\KeywordTok{ON} \KeywordTok{DELETE} \NormalTok{action ] [ }\KeywordTok{ON} \KeywordTok{UPDATE} \NormalTok{action ] \}}
\end{Highlighting}
\end{Shaded}

\textbf{Exemples}

Définition de la table \texttt{stock} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{stock}
  \NormalTok{(}
    \NormalTok{vin_id        }\DataTypeTok{int}    \KeywordTok{not} \KeywordTok{null}\NormalTok{,}
    \NormalTok{contenant_id  }\DataTypeTok{int}    \KeywordTok{not} \KeywordTok{null}\NormalTok{,}
    \NormalTok{annee         int4   }\KeywordTok{not} \KeywordTok{null}\NormalTok{,}
    \NormalTok{nombre        int4   }\KeywordTok{not} \KeywordTok{null}\NormalTok{,}

    \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{(vin_id,contenant_id,annee),}

    \KeywordTok{FOREIGN} \KeywordTok{KEY}\NormalTok{(vin_id) }\KeywordTok{REFERENCES} \NormalTok{vin(}\KeywordTok{id}\NormalTok{) }\KeywordTok{ON} \KeywordTok{DELETE} \KeywordTok{CASCADE}\NormalTok{,}
    \KeywordTok{FOREIGN} \KeywordTok{KEY}\NormalTok{(contenant_id) }\KeywordTok{REFERENCES} \NormalTok{contenant(}\KeywordTok{id}\NormalTok{) }\KeywordTok{ON} \KeywordTok{DELETE} \KeywordTok{CASCADE}
  \NormalTok{);}
\end{Highlighting}
\end{Shaded}

Création d'une table mère et d'une table fille. La table fille possède
une clé étrangère qui référence la table mère :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{mere (}\KeywordTok{id} \DataTypeTok{integer}\NormalTok{, t text);}

\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{fille (}\KeywordTok{id} \DataTypeTok{integer}\NormalTok{, mere_id }\DataTypeTok{integer}\NormalTok{, t text);}

\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{mere }\KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{pk_mere }\KeywordTok{PRIMARY} \KeywordTok{KEY} \NormalTok{(}\KeywordTok{id}\NormalTok{);}

\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{fille }
  \KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{fk_mere_fille}
      \KeywordTok{FOREIGN} \KeywordTok{KEY} \NormalTok{(mere_id)}
      \KeywordTok{REFERENCES} \NormalTok{mere (}\KeywordTok{id}\NormalTok{)}
         \NormalTok{MATCH }\KeywordTok{FULL}
         \KeywordTok{ON} \KeywordTok{UPDATE} \KeywordTok{NO} \NormalTok{ACTION}
         \KeywordTok{ON} \KeywordTok{DELETE} \KeywordTok{CASCADE}\NormalTok{;}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{mere (}\KeywordTok{id}\NormalTok{, t) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'val1'}\NormalTok{), (}\DecValTok{2}\NormalTok{, }\StringTok{'val2'}\NormalTok{);}

\CommentTok{-- l'ajout de données dans la table fille qui font bien référence }
\CommentTok{-- à la table mere fonctionne}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (}\KeywordTok{id}\NormalTok{, mere_id, t) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{1}\NormalTok{, }\StringTok{'val1'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (}\KeywordTok{id}\NormalTok{, mere_id, t) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{2}\NormalTok{, }\StringTok{'val2'}\NormalTok{);}

\CommentTok{-- l'ajout de données dans la table fille qui ne font pas référence }
\CommentTok{-- à la table mere est annulé}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (}\KeywordTok{id}\NormalTok{, mere_id, t) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{3}\NormalTok{, }\DecValTok{3}\NormalTok{, }\StringTok{'val3'}\NormalTok{);}
\NormalTok{ERROR:  }\KeywordTok{insert} \KeywordTok{or} \KeywordTok{update} \KeywordTok{on} \KeywordTok{table} \OtherTok{"fille"} \NormalTok{violates }\KeywordTok{foreign} \KeywordTok{key} \KeywordTok{constraint} \OtherTok{"fk_mere_fille"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(mere_id)=(}\DecValTok{3}\NormalTok{) }\KeywordTok{is} \KeywordTok{not} \KeywordTok{present} \KeywordTok{in} \KeywordTok{table} \OtherTok{"mere"}\NormalTok{.}

\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{fille;}
 \KeywordTok{id} \NormalTok{| mere_id |  t   }
\CommentTok{----+---------+------}
  \DecValTok{1} \NormalTok{|       }\DecValTok{1} \NormalTok{| val1}
  \DecValTok{2} \NormalTok{|       }\DecValTok{2} \NormalTok{| val2}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}

\CommentTok{-- mettre à jour la référence dans la table mere ne fonctionnera pas}
\CommentTok{-- car la contrainte a été définie pour refuser les mises à jour }
\CommentTok{-- (ON UPDATE NO ACTION)}

\NormalTok{b1=# }\KeywordTok{UPDATE} \NormalTok{mere }\KeywordTok{SET} \NormalTok{id=}\DecValTok{3} \KeywordTok{WHERE} \NormalTok{id=}\DecValTok{2}\NormalTok{;}
\NormalTok{ERROR:  }\KeywordTok{update} \KeywordTok{or} \KeywordTok{delete} \KeywordTok{on} \KeywordTok{table} \OtherTok{"mere"} \NormalTok{violates }\KeywordTok{foreign} \KeywordTok{key} \KeywordTok{constraint} \OtherTok{"fk_mere_fille"} \KeywordTok{on} \KeywordTok{table} \OtherTok{"fille"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(}\KeywordTok{id}\NormalTok{)=(}\DecValTok{2}\NormalTok{) }\KeywordTok{is} \NormalTok{still referenced }\KeywordTok{from} \KeywordTok{table} \OtherTok{"fille"}\NormalTok{.}

\CommentTok{-- par contre, la suppression d'une ligne de la table mere référencée dans la table fille}
\CommentTok{-- va propager la suppression jusqu'à la table fille (ON DELETE CASCADE)}

\NormalTok{b1=# }\KeywordTok{DELETE} \KeywordTok{FROM} \NormalTok{mere }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{2}\NormalTok{;}
\KeywordTok{DELETE} \DecValTok{1}

\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{fille;}
 \KeywordTok{id} \NormalTok{| mere_id |  t   }
\CommentTok{----+---------+------}
  \DecValTok{1} \NormalTok{|       }\DecValTok{1} \NormalTok{| val1}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}

\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{mere;}
 \KeywordTok{id} \NormalTok{|  t   }
\CommentTok{----+------}
  \DecValTok{1} \NormalTok{| val1}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Vérification simple ou
complète}\label{vuxe9rification-simple-ou-compluxe8te}

\begin{itemize}
\tightlist
\item
  Vérification complète ou partielle d'une clé étrangère
\item
  \texttt{MATCH}

  \begin{itemize}
  \tightlist
  \item
    \texttt{MATCH\ FULL} (complète)
  \item
    \texttt{MATCH\ SIMPLE} (partielle)
  \end{itemize}
\end{itemize}

La directive \texttt{MATCH} permet d'indiquer si la contrainte doit être
entièrement vérifiée (\texttt{MATCH\ FULL}) ou si la clé étrangère
autorise des valeurs \texttt{NULL} (\texttt{MATCH\ SIMPLE}).
\texttt{MATCH\ SIMPLE} est la valeur par défaut.

Avec \texttt{MATCH\ FULL}, toutes les valeurs des colonnes qui composent
la clé étrangère de la table référençant doivent avoir une
correspondance dans la table référencée.

Avec \texttt{MATCH\ SIMPLE}, les valeurs des colonnes qui composent la
clé étrangère de la table référençant peuvent comporter des valeurs
\texttt{NULL}. Dans le cas des clés étrangères multi-colonnes, toutes
les colonnes peuvent ne pas être renseignées. Dans le cas des clés
étrangères sur une seule colonne, la contrainte autorise les valeurs
\texttt{NULL}.

\textbf{Exemples}

Les exemples reprennent les tables \texttt{mere} et \texttt{fille}
créées plus haut.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{4}\NormalTok{, }\KeywordTok{NULL}\NormalTok{, }\StringTok{'test'}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{fille;}
 \KeywordTok{id} \NormalTok{| mere_id |  t   }
\CommentTok{----+---------+------}
  \DecValTok{1} \NormalTok{|       }\DecValTok{1} \NormalTok{| val1}
  \DecValTok{2} \NormalTok{|       }\DecValTok{2} \NormalTok{| val2}
  \DecValTok{4} \NormalTok{|         | test}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Mise à jour de la clé
primaire}\label{mise-uxe0-jour-de-la-cluxe9-primaire}

\begin{itemize}
\tightlist
\item
  Que faire en cas de mise à jour d'une clé primaire ?

  \begin{itemize}
  \tightlist
  \item
    les clés étrangères seront fausses
  \end{itemize}
\item
  \texttt{ON\ UPDATE}
\item
  \texttt{ON\ DELETE}
\item
  Définition d'une action au niveau de la clé étrangère

  \begin{itemize}
  \tightlist
  \item
    interdiction
  \item
    propagation de la mise à jour
  \item
    NULL
  \item
    valeur par défaut
  \end{itemize}
\end{itemize}

Si des valeurs d'une clé primaire sont mises à jour ou supprimées, cela
peut entrainer des incohérences dans la base de données si des valeurs
de clés étrangères font référence aux valeurs de la clé primaire
touchées par le changement.

Afin de pouvoir gérer cela, la norme SQL prévoit plusieurs comportements
possibles. La clause \texttt{ON\ UPDATE} permet de définir comment le
SGBD va réagir si la clé primaire référencée est mise à jour. La clause
\texttt{ON\ DELETE} fait de même pour les suppressions.

Les actions possibles sont : * \texttt{NO\ ACTION} (ou
\texttt{RESTRICT}), qui produit une erreur si une ligne référence encore
le ou les lignes touchées par le changement ; * \texttt{CASCADE}, pour
laquelle la mise à jour ou la suppression est propagée aux valeurs
référençant le ou les lignes touchées par le changement ; *
\texttt{SET\ NULL}, la valeur de la colonne devient \texttt{NULL} ; *
\texttt{SET\ DEFAULT}, pour lequel la valeur de la colonne prend la
valeur par défaut de la colonne.

Le comportement par défaut est \texttt{NO\ ACTION}, ce qui est
habituellement recommandé pour éviter les suppressions en chaîne mal
maîtrisées.

\textbf{Exemples}

Les exemples reprennent les tables \texttt{mere} et \texttt{fille}
créées plus haut.

Tentative d'insertion d'une ligne dont la valeur de \texttt{mere\_id}
n'existe pas dans la table \texttt{mere} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (}\KeywordTok{id}\NormalTok{, mere_id, t) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{3}\NormalTok{, }\StringTok{'val3'}\NormalTok{);}
\NormalTok{ERROR:  }\KeywordTok{insert} \KeywordTok{or} \KeywordTok{update} \KeywordTok{on} \KeywordTok{table} \OtherTok{"fille"} \NormalTok{violates }\KeywordTok{foreign} \KeywordTok{key} \KeywordTok{constraint} \OtherTok{"fk_mere_fille"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(mere_id)=(}\DecValTok{3}\NormalTok{) }\KeywordTok{is} \KeywordTok{not} \KeywordTok{present} \KeywordTok{in} \KeywordTok{table} \OtherTok{"mere"}\NormalTok{.}
\end{Highlighting}
\end{Shaded}

Mise à jour d'une ligne de la table \texttt{mere} pour modifier son
\texttt{id}. La clé étrangère est déclarée
\texttt{ON\ UPDATE\ NO\ ACTION}, donc la mise à jour devrait être
interdite :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{mere }\KeywordTok{SET} \KeywordTok{id} \NormalTok{= }\DecValTok{3} \KeywordTok{WHERE} \KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{;}
\NormalTok{ERROR:  }\KeywordTok{update} \KeywordTok{or} \KeywordTok{delete} \KeywordTok{on} \KeywordTok{table} \OtherTok{"mere"} \NormalTok{violates }\KeywordTok{foreign} \KeywordTok{key} \KeywordTok{constraint} \OtherTok{"fk_mere_fille"} \KeywordTok{on} \KeywordTok{table} \OtherTok{"fille"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(}\KeywordTok{id}\NormalTok{)=(}\DecValTok{1}\NormalTok{) }\KeywordTok{is} \NormalTok{still referenced }\KeywordTok{from} \KeywordTok{table} \OtherTok{"fille"}\NormalTok{.}
\end{Highlighting}
\end{Shaded}

Suppression d'une ligne de la table \texttt{mere}. La clé étrangère sur
\texttt{fille} est déclarée \texttt{ON\ DELETE\ CASCADE}, la suppression
sera donc propagée aux tables qui référencent la table \texttt{mere} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DELETE} \KeywordTok{FROM} \NormalTok{mere }\KeywordTok{WHERE} \KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{fille ;}
 \KeywordTok{id} \NormalTok{| mere_id |  t   }
\CommentTok{----+---------+------}
  \DecValTok{2} \NormalTok{|       }\DecValTok{2} \NormalTok{| val2}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Vérifications}\label{vuxe9rifications}

\begin{itemize}
\tightlist
\item
  Présence d'une valeur

  \begin{itemize}
  \tightlist
  \item
    \texttt{NOT\ NULL}
  \end{itemize}
\item
  Vérification de la valeur d'une colonne

  \begin{itemize}
  \tightlist
  \item
    \texttt{CHECK}
  \end{itemize}
\end{itemize}

La clause \texttt{NOT\ NULL} permet de s'assurer que la valeur de la
colonne portant cette contrainte est renseignée. Dis autrement, elle
doit obligatoirement être renseignée. Par défaut, la colonne peut avoir
une valeur \texttt{NULL}, donc n'est pas obligatoirement renseignée.

La clause \texttt{CHECK} spécifie une expression de résultat booléen que
les nouvelles lignes ou celles mises à jour doivent satisfaire pour
qu'une opération d'insertion ou de mise à jour réussisse. Les
expressions de résultat \texttt{TRUE} ou \texttt{UNKNOWN} réussissent.
Si une des lignes de l'opération d'insertion ou de mise à jour produit
un résultat \texttt{FALSE}, une exception est levée et la base de
données n'est pas modifiée. Une contrainte de vérification sur une
colonne ne fait référence qu'à la valeur de la colonne tandis qu'une
contrainte sur la table fait référence à plusieurs colonnes.

Actuellement, les expressions \texttt{CHECK} ne peuvent ni contenir des
sous-requêtes ni faire référence à des variables autres que les colonnes
de la ligne courante. C'est techniquement réalisable, mais non supporté.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Vérifications
différés}\label{vuxe9rifications-diffuxe9ruxe9s}

\begin{itemize}
\tightlist
\item
  Vérifications après chaque ordre SQL

  \begin{itemize}
  \tightlist
  \item
    problèmes de cohérence
  \end{itemize}
\item
  Différer les vérifications de contraintes

  \begin{itemize}
  \tightlist
  \item
    clause \texttt{DEFERRABLE}, \texttt{NOT\ DEFERRABLE}
  \item
    \texttt{INITIALLY\ DEFERED}, \texttt{INITIALLY\ IMMEDIATE}
  \end{itemize}
\end{itemize}

Par défaut, toutes les contraintes d'intégrité sont vérifiées lors de
l'exécution de chaque ordre SQL de modification, y compris dans une
transaction. Cela peut poser des problèmes de cohérences de données:
insérer dans une table fille alors qu'on n'a pas encore inséré les
données dans la table mère, la clé étrangère de la table fille va
rejeter l'insertion et annuler la transaction.

Le moment où les contraintes sont vérifiées est modifiable dynamiquement
par l'ordre \texttt{SET\ CONSTRAINTS} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SET} \KeywordTok{CONSTRAINTS} \NormalTok{\{ }\KeywordTok{ALL} \NormalTok{| nom [, ...] \} \{ }\KeywordTok{DEFERRED} \NormalTok{| }\KeywordTok{IMMEDIATE} \NormalTok{\}}
\end{Highlighting}
\end{Shaded}

mais ce n'est utilisable que pour les contraintes déclarées comme
déferrables.

Voici quelques exemples :

\begin{itemize}
\item
  avec la définition précédente des tables \texttt{mere} et
  \texttt{fille}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{BEGIN}\NormalTok{;}
\KeywordTok{UPDATE} \NormalTok{mere }\KeywordTok{SET} \NormalTok{id=}\DecValTok{3} \KeywordTok{where} \NormalTok{id=}\DecValTok{1}\NormalTok{;}
\NormalTok{ERROR:  }\KeywordTok{update} \KeywordTok{or} \KeywordTok{delete} \KeywordTok{on} \KeywordTok{table} \OtherTok{"mere"} \NormalTok{violates }\KeywordTok{foreign} \KeywordTok{key} \KeywordTok{constraint} \OtherTok{"fk_mere_fille"} \KeywordTok{on} \KeywordTok{table} \OtherTok{"fille"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(}\KeywordTok{id}\NormalTok{)=(}\DecValTok{1}\NormalTok{) }\KeywordTok{is} \NormalTok{still referenced }\KeywordTok{from} \KeywordTok{table} \OtherTok{"fille"}\NormalTok{.}
\end{Highlighting}
\end{Shaded}
\item
  cette erreur survient aussi dans le cas où on demande que la
  vérification des contraintes soit différée pour cette transaction:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN}\NormalTok{;}
\KeywordTok{SET} \KeywordTok{CONSTRAINTS} \KeywordTok{ALL} \KeywordTok{DEFERRED}\NormalTok{;}
\KeywordTok{UPDATE} \NormalTok{mere }\KeywordTok{SET} \NormalTok{id=}\DecValTok{3} \KeywordTok{WHERE} \NormalTok{id=}\DecValTok{1}\NormalTok{;}
\NormalTok{ERROR:  }\KeywordTok{update} \KeywordTok{or} \KeywordTok{delete} \KeywordTok{on} \KeywordTok{table} \OtherTok{"mere"} \NormalTok{violates }\KeywordTok{foreign} \KeywordTok{key} \KeywordTok{constraint} \OtherTok{"fk_mere_fille"} \KeywordTok{on} \KeywordTok{table} \OtherTok{"fille"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(}\KeywordTok{id}\NormalTok{)=(}\DecValTok{1}\NormalTok{) }\KeywordTok{is} \NormalTok{still referenced }\KeywordTok{from} \KeywordTok{table} \OtherTok{"fille"}\NormalTok{.}
\end{Highlighting}
\end{Shaded}
\item
  il faut que la contrainte soit déclarée comme étant différable :
  ```sql CREATE TABLE mere (id integer, t text); CREATE TABLE fille (id
  integer, mere\_id integer, t text); ALTER TABLE mere ADD CONSTRAINT
  pk\_mere PRIMARY KEY (id); ALTER TABLE fille ADD CONSTRAINT
  fk\_mere\_fille FOREIGN KEY (mere\_id) REFERENCES mere (id) MATCH FULL
  ON UPDATE NO ACTION ON DELETE CASCADE DEFERRABLE; INSERT INTO mere
  (id, t) VALUES (1, `val1'), (2, `val2'); INSERT INTO fille (id,
  mere\_id, t) VALUES (1, 1, `val1'); INSERT INTO fille (id, mere\_id,
  t) VALUES (2, 2, `val2');
\end{itemize}

BEGIN; SET CONSTRAINTS all deferred; UPDATE mere SET id=3 WHERE id=1;
SELECT * FROM mere; id \textbar{} t\\
----+------ 2 \textbar{} val2 3 \textbar{} val1 (2 rows)

SELECT * FROM fille; id \textbar{} mere\_id \textbar{} t\\
----+---------+------ 1 \textbar{} 1 \textbar{} val1 2 \textbar{} 2
\textbar{} val2 (2 rows)

UPDATE fille SET mere\_id=3 WHERE mere\_id=1; COMMIT; ```

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Vérifications plus
complexes}\label{vuxe9rifications-plus-complexes}

\begin{itemize}
\tightlist
\item
  Un trigger

  \begin{itemize}
  \tightlist
  \item
    si une contrainte porte sur plusieurs tables
  \item
    si sa vérification nécessite une sous-requête
  \end{itemize}
\item
  Préférer les contraintes déclaratives
\end{itemize}

Les contraintes d'intégrités du SGBD ne permettent pas d'exprimer une
contrainte qui porte sur plusieurs tables ou simplement si sa
vérification nécessite une sous-requête. Dans ce cas là, il est
nécessaire d'écrire un trigger spécifique qui sera déclenché après
chaque modification pour valider la contrainte.

Il ne faut toutefois pas systématiser l'utilisation de triggers pour
valider des contraintes d'intégrité. Cela aurait un impact fort sur les
performances et sur la maintenabilité de la base de données. Il vaut
mieux privilégier les contraintes déclaratives et n'envisager l'emploi
de triggers que dans les cas où ils sont réellement nécessaires.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{DML: mise à jour des
données}\label{dml-mise-uxe0-jour-des-donnuxe9es}

\begin{itemize}
\tightlist
\item
  \texttt{SELECT} peut lire les données d'une table ou plusieurs tables

  \begin{itemize}
  \tightlist
  \item
    mais ne peut pas les mettre à jour
  \end{itemize}
\item
  Ajout de données dans une table

  \begin{itemize}
  \tightlist
  \item
    \texttt{INSERT}
  \end{itemize}
\item
  Modification des données d'une table

  \begin{itemize}
  \tightlist
  \item
    \texttt{UPDATE}
  \end{itemize}
\item
  Suppression des données d'une table

  \begin{itemize}
  \tightlist
  \item
    \texttt{DELETE}
  \end{itemize}
\end{itemize}

L'ordre \texttt{SELECT} permet de lire une ou plusieurs tables. Les
mises à jours utilisent des ordres distincts.

L'ordre \texttt{INSERT} permet d'ajouter ou insérer des données dans une
table. L'ordre \texttt{UPDATE} permet de modifier des lignes déjà
existantes. Enfin, l'ordre \texttt{DELETE} permet de supprimer des
lignes. Ces ordres ne peuvent travailler que sur une seule table à la
fois. Si on souhaite par exemple insérer des données dans deux tables,
il est nécessaire de réaliser deux \texttt{INSERT} distincts.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Ajout de données :
INSERT}\label{ajout-de-donnuxe9es-insert}

\begin{itemize}
\tightlist
\item
  Ajoute des lignes à partir des données de la requête
\item
  Ajoute des lignes à partir d'une requête \texttt{SELECT}
\item
  Syntaxe :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{nom_table [ ( nom_colonne [, ...] ) ]}
    \NormalTok{\{ liste_valeurs | requete \}}
\end{Highlighting}
\end{Shaded}

L'ordre \texttt{INSERT} insère de nouvelles lignes dans une table. Il
permet d'insérer une ou plusieurs lignes spécifiées par les expressions
de valeur, ou zéro ou plusieurs lignes provenant d'une requête.

La liste des noms de colonnes est optionnelle. Si elle n'est pas
spécifiée, alors PostgreSQL utilisera implicitement la liste de toutes
les colonnes de la table dans l'ordre de leur déclaration, ou les
\texttt{N} premiers noms de colonnes si seules \texttt{N} valeurs de
colonnes sont fournies dans la clause \texttt{VALUES} ou dans la
requête. L'ordre des noms des colonnes dans la liste n'a pas
d'importance particulière, il suffit de nommer les colonnes mises à
jour.

Chaque colonne absente de la liste, implicite ou explicite, se voit
attribuer sa valeur par défaut, s'il y en a une ou \texttt{NULL} dans le
cas contraire. Les expressions de colonnes qui ne correspondent pas au
type de données déclarées sont transtypées automatiquement, dans la
mesure du possible.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{INSERT avec liste
d'expressions}\label{insert-avec-liste-dexpressions}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{nom_table [ ( nom_colonne [, ...] ) ]}
    \KeywordTok{VALUES} \NormalTok{( \{ expression | }\KeywordTok{DEFAULT} \NormalTok{\} [, ...] ) [, ...]}
\end{Highlighting}
\end{Shaded}

La clause \texttt{VALUES} permet de définir une liste d'expressions qui
va constituer la ligne à insérer dans la base de données. Les éléments
de cette liste d'expression sont séparés par une virgule. Cette liste
d'expression est composée de constantes ou d'appels à des fonctions
retournant une valeur, pour obtenir par exemple la date courante ou la
prochaine valeur d'une séquence. Les valeurs fournies par la clause
\texttt{VALUES} ou par la requête sont associées à la liste explicite ou
implicite des colonnes de gauche à droite.

\textbf{Exemples}

Insertion d'une ligne dans la table \texttt{stock} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{stock (vin_id, contenant_id, annee, nombre)}
 \KeywordTok{VALUES} \NormalTok{(}\DecValTok{12}\NormalTok{, }\DecValTok{1}\NormalTok{, }\DecValTok{1935}\NormalTok{, }\DecValTok{1}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Insertion d'une ligne dans la table \texttt{vin} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{vin (}\KeywordTok{id}\NormalTok{, recoltant_id, appellation_id, type_vin_id)}
 \KeywordTok{VALUES} \NormalTok{(nextval(}\StringTok{'vin_id_seq'}\NormalTok{), }\DecValTok{3}\NormalTok{, }\DecValTok{6}\NormalTok{, }\DecValTok{1}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{INSERT à partir d'un
SELECT}\label{insert-uxe0-partir-dun-select}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{nom_table [ ( nom_colonne [, ...] ) ]}
    \NormalTok{requête}
\end{Highlighting}
\end{Shaded}

L'ordre \texttt{INSERT} peut aussi prendre une requête SQL en entrée.
Dans ce cas, \texttt{INSERT} va insérer autant de lignes dans la table
d'arrivée qu'il y a de lignes retournées par la requête \texttt{SELECT}.
L'ordre des colonnes retournées par \texttt{SELECT} doit correspondre à
l'ordre des colonnes de la liste des colonnes. Leur type de données doit
également correspondre.

\textbf{Exemples}

Insertion dans une table \texttt{stock2} à partir d'une requête SELECT
sur la table \texttt{stock1} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{stock2 (vin_id, contenant_id, annee, nombre)}
\KeywordTok{SELECT} \NormalTok{vin_id, contenant_id, annee, nombre }\KeywordTok{FROM} \NormalTok{stock;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{INSERT et colonnes
implicites}\label{insert-et-colonnes-implicites}

\begin{itemize}
\tightlist
\item
  L'ordre physique peut changer dans le temps

  \begin{itemize}
  \tightlist
  \item
    résultats incohérents
  \item
    requêtes en erreurs
  \end{itemize}
\end{itemize}

Il est préférable de lister explicitement les colonnes touchées par
l'ordre \texttt{INSERT} afin de garder un ordre d'insertion
déterministe. En effet, l'ordre des colonnes peut changer notamment
lorsque certains ETL sont utilisés pour modifier le type d'une colonne
\texttt{varchar(10)} en \texttt{varchar(11)}. Par exemple, pour la
colonne \texttt{username}, l'ETL Kettle génère les ordres suivants :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{utilisateurs }\KeywordTok{ADD} \KeywordTok{COLUMN} \NormalTok{username_KTL }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{11}\NormalTok{);}
\KeywordTok{UPDATE} \NormalTok{utilisateurs }\KeywordTok{SET} \NormalTok{username_KTL=username;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{utilisateurs }\KeywordTok{DROP} \KeywordTok{COLUMN} \NormalTok{username;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{utilisateurs }\KeywordTok{RENAME} \NormalTok{username_KTL }\KeywordTok{TO} \NormalTok{username}
\end{Highlighting}
\end{Shaded}

Il génère des ordres SQL inutiles et consommateurs d'entrées/sorties
disques car il doit générer des ordres SQL compris par tous les SGBD du
marché. Or, tous les SGBD ne permettent pas de changer le type d'une
colonne aussi simplement que dans PostgreSQL.

\textbf{Exemples}

Exemple de modification du schéma pouvant entrainer des problèmes
d'insertion si les colonnes ne sont pas listées explicitement :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{insere (}\KeywordTok{id} \DataTypeTok{integer} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, col1 }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{5}\NormalTok{), col2 }\DataTypeTok{integer}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{insere }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'XX'}\NormalTok{, }\DecValTok{10}\NormalTok{);}

\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{insere }\KeywordTok{ADD} \KeywordTok{COLUMN} \NormalTok{col1_tmp }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{6}\NormalTok{);}
\KeywordTok{UPDATE} \NormalTok{insere }\KeywordTok{SET} \NormalTok{col1_tmp = col1;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{insere }\KeywordTok{DROP} \KeywordTok{COLUMN} \NormalTok{col1;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{insere }\KeywordTok{RENAME} \KeywordTok{COLUMN} \NormalTok{col1_tmp }\KeywordTok{TO} \NormalTok{col1;}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{insere }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'XXX'}\NormalTok{, }\DecValTok{10}\NormalTok{);}
\NormalTok{ERROR:  invalid input syntax }\KeywordTok{for} \NormalTok{integer: }\OtherTok{"XXX"}
\NormalTok{LINE }\DecValTok{1}\NormalTok{: }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{insere }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'XXX'}\NormalTok{, }\DecValTok{10}\NormalTok{);}
                                      \NormalTok{^}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Mise à jour de données :
UPDATE}\label{mise-uxe0-jour-de-donnuxe9es-update}

\begin{itemize}
\tightlist
\item
  Ordre \texttt{UPDATE}
\item
  Met à jour une ou plusieurs colonnes d'une même ligne

  \begin{itemize}
  \tightlist
  \item
    à partir des valeurs de la requête
  \item
    à partir des anciennes valeurs
  \item
    à partir d'une requête SELECT
  \item
    à partir de valeurs d'une autre table
  \end{itemize}
\end{itemize}

L'ordre de mise à jour de lignes s'appelle \texttt{UPDATE}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Construction d'UPDATE}\label{construction-dupdate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{nom_table}
    \KeywordTok{SET}
    \NormalTok{\{}
     \NormalTok{nom_colonne = \{ expression | }\KeywordTok{DEFAULT} \NormalTok{\}}
    \NormalTok{|}
     \NormalTok{( nom_colonne [, ...] ) = ( \{ expression | }\KeywordTok{DEFAULT} \NormalTok{\} [, ...] )}
    \NormalTok{\} [, ...]}
    \NormalTok{[ }\KeywordTok{FROM} \NormalTok{liste_from ]}
    \NormalTok{[ }\KeywordTok{WHERE} \NormalTok{condition | }\KeywordTok{WHERE} \KeywordTok{CURRENT} \KeywordTok{OF} \NormalTok{nom_curseur ]}
\end{Highlighting}
\end{Shaded}

L'ordre \texttt{UPDATE} permet de mettre à jour les lignes d'une table.

L'ordre \texttt{UPDATE} ne met à jour que les lignes qui satisfont les
conditions de la clause \texttt{WHERE}. La clause \texttt{SET} permet de
définir les colonnes à mettre à jour. Le nom des colonne mises à jour
doivent faire partie de la table mise à jour.

Les valeurs mises à jour peuvent faire référence aux valeurs avant mise
à jour de la colonne, dans ce cas on utilise la forme
\texttt{nom\_colonne\ =\ nom\_colonne}. La partie de gauche référence la
colonne à mettre à jour, la partie de droite est une expression qui
permet de déterminer la valeur à appliquer à la colonne. La valeur à
appliquer peut bien entendu être une référence à une ou plusieurs
colonnes et elles peuvent être dérivées par une opération arithmétique.

La clause \texttt{FROM} ne fait pas partie de la norme SQL mais certains
SGBDR la supportent, notamment SQL Server et PostgreSQL. Elle permet de
réaliser facilement la mise à jour d'une table à partir des valeurs
d'une ou plusieurs tables annexes.

La norme SQL permet néanmoins de réaliser des mises à jour en utilisant
une sous-requête, permettant d'éviter l'usage de la clause
\texttt{FROM}.

\textbf{Exemples}

Mise à jour du prix d'un livre particulier :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{livres }\KeywordTok{SET} \NormalTok{prix = }\DecValTok{10} \KeywordTok{WHERE} \NormalTok{isbn = }\StringTok{'978-3-8365-3872-5'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Augmentation de 5\% du prix des livres :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{livres }\KeywordTok{SET} \NormalTok{prix = prix * }\FloatTok{1.05}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Mise à jour d'une table \texttt{employees} à partir des données d'une
table \texttt{bonus\_plan} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{employees e}
   \KeywordTok{SET} \NormalTok{commission_rate = bp.commission_rate}
  \KeywordTok{FROM} \NormalTok{bonus_plan bp}
    \KeywordTok{ON} \NormalTok{(e.bonus_plan = bp.planid)}
\end{Highlighting}
\end{Shaded}

La même requête avec une sous-requête, conforme à la norme SQL :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{employees}
   \KeywordTok{SET} \NormalTok{commission_rate = (}\KeywordTok{SELECT} \NormalTok{commission_rate}
                            \KeywordTok{FROM} \NormalTok{bonus_plan bp}
                           \KeywordTok{WHERE} \NormalTok{bp.planid = employees.bonus_plan);}
\end{Highlighting}
\end{Shaded}

Lorsque plusieurs colonnes doivent être mises à jour à partir d'une
jointure, il est possible d'utiliser ces deux écriture :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{employees e}
   \KeywordTok{SET} \NormalTok{commission_rate = bp.commission_rate,}
       \NormalTok{commission_rate2 = bp.commission_rate2}
  \KeywordTok{FROM} \NormalTok{bonus_plan bp}
    \KeywordTok{ON} \NormalTok{(e.bonus_plan = bp.planid);}
\end{Highlighting}
\end{Shaded}

et

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{employees e}
   \KeywordTok{SET} \NormalTok{(commission_rate, commission_rate2) = }
        \NormalTok{(}\KeywordTok{SELECT} \NormalTok{bp.commission_rate, }
            \NormalTok{bp.commission_rate2 }
        \KeywordTok{FROM} \NormalTok{bonus_plan bp }\KeywordTok{ON} \NormalTok{(e.bonus_plan = bp.planid));}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Suppression de données :
DELETE}\label{suppression-de-donnuxe9es-delete}

\begin{itemize}
\tightlist
\item
  Supprime les lignes répondant au prédicat
\item
  Syntaxe :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
  \KeywordTok{DELETE} \KeywordTok{FROM} \NormalTok{nom_table [ [ }\KeywordTok{AS} \NormalTok{] alias ]}
    \NormalTok{[ }\KeywordTok{WHERE} \NormalTok{condition}
\end{Highlighting}
\end{Shaded}

L'ordre \texttt{DELETE} supprime l'ensemble des lignes qui répondent au
prédicat de la clause \texttt{WHERE}.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DELETE} \KeywordTok{FROM} \NormalTok{nom_table [ [ }\KeywordTok{AS} \NormalTok{] alias ]}
    \NormalTok{[ }\KeywordTok{WHERE} \NormalTok{condition | }\KeywordTok{WHERE} \KeywordTok{CURRENT} \KeywordTok{OF} \NormalTok{nom_curseur ]}
\end{Highlighting}
\end{Shaded}

\textbf{Exemples}

Suppression d'un livre épuisé du catalogue :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DELETE} \KeywordTok{FROM} \NormalTok{livres }\KeywordTok{WHERE} \NormalTok{isbn = }\StringTok{'978-0-8707-0635-6'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause RETURNING}\label{clause-returning}

\begin{itemize}
\tightlist
\item
  Spécifique à PostgreSQL
\item
  Permet de retourner les lignes complètes ou partielles résultants de
  \texttt{INSERT}, \texttt{UPDATE} ou \texttt{DELETE}
\item
  Syntaxe :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{requete_sql }\KeywordTok{RETURNING} \NormalTok{( * | expression )}
\end{Highlighting}
\end{Shaded}

La clause \texttt{RETURNING} est une extension de PostgreSQL. Elle
permet de retourner les lignes insérées, mises à jour ou supprimées par
un ordre DML de modification. Il est également possible de dériver une
valeur retournée.

L'emploi de la clause \texttt{RETURNING} peut nécessiter des droits
complémentaires sur les objets de la base.

\textbf{Exemples}

Mise à jour du nombre de bouteilles en stock :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{annee, nombre }\KeywordTok{FROM} \NormalTok{stock }\KeywordTok{WHERE} \NormalTok{vin_id = }\DecValTok{7} \KeywordTok{AND} \NormalTok{contenant_id = }\DecValTok{1} \KeywordTok{AND} \NormalTok{annee = }\DecValTok{1967}\NormalTok{;}
 \NormalTok{annee | nombre }
\CommentTok{-------+--------}
  \DecValTok{1967} \NormalTok{|     }\DecValTok{17}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}

\KeywordTok{UPDATE} \NormalTok{stock }\KeywordTok{SET} \NormalTok{nombre = nombre - }\DecValTok{1} \KeywordTok{WHERE} \NormalTok{vin_id = }\DecValTok{7} \KeywordTok{AND} \NormalTok{contenant_id = }\DecValTok{1} \KeywordTok{AND} \NormalTok{annee = }\DecValTok{1967} \KeywordTok{RETURNING} \NormalTok{nombre;}
 \NormalTok{nombre }
\CommentTok{--------}
     \DecValTok{16}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Transactions}\label{transactions}

\begin{itemize}
\tightlist
\item
  ACID

  \begin{itemize}
  \tightlist
  \item
    atomicité
  \item
    un traitement se fait en entier ou pas du tout
  \end{itemize}
\item
  TCL pour Transaction Control Language

  \begin{itemize}
  \tightlist
  \item
    valide une transaction
  \item
    annule une transaction
  \item
    points de sauvegarde
  \end{itemize}
\end{itemize}

Les transactions sont une partie essentielle du langage SQL. Elles
permettent de rendre atomique un certain nombre de requêtes. Le résultat
de toutes les requêtes d'une transaction est validée ou pas, mais on ne
peut pas avoir d'état intermédiaire.

Le langage SQL définit qu'une transaction peut être validée ou annulée.
Ce sont respectivement les ordres \texttt{COMMIT} et \texttt{ROLLBACK}.
Il est aussi possible de faire des points de reprise ou de sauvegarde
dans une transaction. Ils se font en utilisant l'ordre
\texttt{SAVEPOINT}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Auto-commit et
transactions}\label{auto-commit-et-transactions}

\begin{itemize}
\tightlist
\item
  Par défaut, PostgreSQL fonctionne en auto-commit

  \begin{itemize}
  \tightlist
  \item
    à moins d'ouvrir explicitement une transaction
  \end{itemize}
\item
  Ouvrir une transaction

  \begin{itemize}
  \tightlist
  \item
    \texttt{BEGIN\ TRANSACTION}
  \end{itemize}
\end{itemize}

PostgreSQL fonctionne en auto-commit. Autrement dit, sans
\texttt{BEGIN}, une requête est considérée comme une transaction
complète et n'a donc pas besoin de \texttt{COMMIT}.

Une transaction est toujours commencer par un \texttt{START} ou un
\texttt{BEGIN}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Validation ou annulation d'une
transaction}\label{validation-ou-annulation-dune-transaction}

\begin{itemize}
\tightlist
\item
  Valider une transaction

  \begin{itemize}
  \tightlist
  \item
    \texttt{COMMIT}
  \end{itemize}
\item
  Annuler une transaction

  \begin{itemize}
  \tightlist
  \item
    \texttt{ROLLBACK}
  \end{itemize}
\item
  Sans validation, une transaction est forcément annulée
\end{itemize}

Une transaction est toujours terminée par une \texttt{COMMIT} ou un
\texttt{END} quand on veut que les modifications soient définitivement
enregistrées, et par un \texttt{ROLLBACK} dans le cas contraire.

La transaction en cours d'une session qui se termine, quelque soit la
raison, sans \texttt{COMMIT} et sans \texttt{ROLLBACK} est considérée
comme annulée.

\textbf{Exemples}

Avant de retirer une bouteille du stock, on vérifie tout d'abord qu'il
reste suffisamment de bouteilles en stock :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN} \KeywordTok{TRANSACTION}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{annee, nombre }\KeywordTok{FROM} \NormalTok{stock }\KeywordTok{WHERE} \NormalTok{vin_id = }\DecValTok{7} \KeywordTok{AND} \NormalTok{contenant_id = }\DecValTok{1} \KeywordTok{AND} \NormalTok{annee = }\DecValTok{1967}\NormalTok{;}
 \NormalTok{annee | nombre }
\CommentTok{-------+--------}
  \DecValTok{1967} \NormalTok{|     }\DecValTok{17}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}

\KeywordTok{UPDATE} \NormalTok{stock }\KeywordTok{SET} \NormalTok{nombre = nombre - }\DecValTok{1} \KeywordTok{WHERE} \NormalTok{vin_id = }\DecValTok{7} \KeywordTok{AND} \NormalTok{contenant_id = }\DecValTok{1} \KeywordTok{AND} \NormalTok{annee = }\DecValTok{1967} \KeywordTok{RETURNING} \NormalTok{nombre;}
 \NormalTok{nombre }
\CommentTok{--------}
     \DecValTok{16}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}

\KeywordTok{COMMIT}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Programmation}\label{programmation}

\begin{itemize}
\tightlist
\item
  Certains langages implémentent des méthodes de gestion des
  transactions

  \begin{itemize}
  \tightlist
  \item
    PHP, Java, etc.
  \end{itemize}
\item
  Utiliser ces méthodes prioritairement
\end{itemize}

La plupart des langages permettent de gérer les transactions à l'aide de
méthodes ou fonctions particulières. Il est recommandé de les utiliser.

En Java, ouvrir une transaction revient à désactiver l'auto-commit :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{String url = }\StringTok{"jdbc:postgresql://localhost/test?user=fred&password=secret&ssl=true"}\NormalTok{;}
\NormalTok{Connection conn = DriverManager.}\FunctionTok{getConnection}\NormalTok{(url);}
\NormalTok{conn.}\FunctionTok{setAutoCommit}\NormalTok{(}\KeywordTok{false}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

La transaction est confirmée (\texttt{COMMIT}) avec la méthode suivante
:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{conn.}\FunctionTok{commit}\NormalTok{();}
\end{Highlighting}
\end{Shaded}

À l'inverse, elle est annulée (\texttt{ROLLBACK}) avec la méthode
suivante :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{conn.}\FunctionTok{rollback}\NormalTok{();}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Points de sauvegarde}\label{points-de-sauvegarde}

\begin{itemize}
\tightlist
\item
  Certains traitements dans une transaction peuvent être annulés

  \begin{itemize}
  \tightlist
  \item
    mais la transaction est atomique
  \end{itemize}
\item
  Définir un point de sauvegarde

  \begin{itemize}
  \tightlist
  \item
    \texttt{SAVEPOINT\ nom\_savepoint}
  \end{itemize}
\item
  Valider le traitement depuis le dernier point de sauvegarde

  \begin{itemize}
  \tightlist
  \item
    \texttt{RELEASE\ SAVEPOINT\ nom\_savepoint}
  \end{itemize}
\item
  Annuler le traitement depuis le dernier point de sauvegarde

  \begin{itemize}
  \tightlist
  \item
    \texttt{ROLLBACK\ TO\ SAVEPOINT\ nom\_savepoint}
  \end{itemize}
\end{itemize}

Les points de sauvegarde, ou \texttt{SAVEPOINT}, permettent d'encadrer
certains traitements pouvant tomber en erreur dans une transaction. Si
le traitement se déroule jusqu'au bout, le point de sauvegarde pourra
être relâché (\texttt{RELEASE\ SAVEPOINT}), confirmant ainsi les
traitements. Si le traitement tombe en erreur, il suffira de revenir au
point de sauvegarde (\texttt{ROLLBACK\ TO\ SAVEPOINT} pour annuler
uniquement cette partie du traitement sans affecter le reste de la
transaction.

Les points de sauvegarde sont des éléments nommés, il convient donc de
leur affecter un nom particulier. Leur nom doit être unique dans la
transaction courante.

Les langages de programmation permettent également de gérer les points
de sauvegarde en utilisant des méthodes dédiées. Par exemple, en Java :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{Savepoint save1 = connection.}\FunctionTok{setSavepoint}\NormalTok{();}
\end{Highlighting}
\end{Shaded}

En cas d'erreurs, la transaction peut être ramener à l'état du point de
sauvegarde avec :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{connection.}\FunctionTok{rollback}\NormalTok{(save1);}
\end{Highlighting}
\end{Shaded}

À l'inverse, un point de sauvegarde est relâché de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{connection.}\FunctionTok{releaseSavepoint}\NormalTok{(save1);}
\end{Highlighting}
\end{Shaded}

\textbf{Exemples}

Transaction avec un point de sauvegarde et la gestion de l'erreur :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN}\NormalTok{;}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{mere (}\KeywordTok{id}\NormalTok{, val_mere) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{10}\NormalTok{, }\StringTok{'essai'}\NormalTok{);}

\KeywordTok{SAVEPOINT} \NormalTok{insert_fille;}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{fille (id_fille, id_mere, val_fille) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{10}\NormalTok{, }\StringTok{'essai 2'}\NormalTok{);}
\NormalTok{ERROR:  duplicate }\KeywordTok{key} \FunctionTok{value} \NormalTok{violates }\KeywordTok{unique} \KeywordTok{constraint} \OtherTok{"fille_pkey"}
\NormalTok{DETAIL:  }\KeywordTok{Key} \NormalTok{(id_fille)=(}\DecValTok{1}\NormalTok{) already exists.}

\KeywordTok{ROLLBACK} \KeywordTok{TO} \KeywordTok{SAVEPOINT} \NormalTok{insert_fille;}

\KeywordTok{COMMIT}\NormalTok{;}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{mere;}
 \KeywordTok{id} \NormalTok{| val_mere }
\CommentTok{----+----------}
  \DecValTok{1} \NormalTok{| mere }\DecValTok{1}
  \DecValTok{2} \NormalTok{| mere }\DecValTok{2}
 \DecValTok{10} \NormalTok{| essai}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Conclusion}\label{conclusion-1}

\begin{itemize}
\tightlist
\item
  SQL : toujours un traitement d'ensembles d'enregistrements

  \begin{itemize}
  \tightlist
  \item
    c'est le côté relationnel
  \end{itemize}
\item
  Pour les définitions d'objets

  \begin{itemize}
  \tightlist
  \item
    \texttt{CREATE}, \texttt{ALTER}, \texttt{DROP}
  \end{itemize}
\item
  Pour les données

  \begin{itemize}
  \tightlist
  \item
    \texttt{INSERT}, \texttt{UPDATE}, \texttt{DELETE}
  \end{itemize}
\end{itemize}

Le standard SQL permet de traiter des ensembles d'enregistrements, que
ce soit en lecture, en insertion, en modification et en suppression. Les
ensembles d'enregistrements sont généralement des tables qui, comme tous
les autres objets, sont créées (\texttt{CREATE}), modifier
(\texttt{ALTER}) et/ou supprimer (\texttt{DROP}).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Questions}\label{questions-1}

N'hésitez pas, c'est le moment !

\subsubsection{Énoncés}\label{uxe9noncuxe9s-1}

Pour cet exercice, les modifications de schéma doivent être effectuées
par un rôle ayant suffisamment de droits pour modifier son schéma. Le
rôle \texttt{tpc\_admin} a les droits suffisants.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Ajouter une colonne \texttt{email} de type \texttt{text} à la table
  \texttt{contacts}. Cette colonne va permettre de stocker l'adresse
  e-mail des clients et des fournisseurs. Ajouter également un
  commentaire décrivant cette colonne dans le catalogue de PostgreSQL
  (utiliser la commande \texttt{COMMENT}).
\item
  Mettre à jour la table des contacts pour indiquer l'adresse e-mail de
  \texttt{Client6657} qui est \texttt{client6657@dalibo.com}.
\item
  Ajouter une contrainte d'intégrité qui valide que la valeur de la
  colonne \texttt{email} créée est bien formée (vérifier que la chaîne
  de caractère contient au moins le caractère \texttt{@}).
\item
  Valider la contrainte dans une transaction de test.
\item
  Déterminer quels sont les contacts qui disposent d'une adresse email
  et affichez leur nom ainsi que le code de leur pays.
\item
  La génération des numéros de commande est actuellement réalisée à
  l'aide de la séquence \texttt{commandes\_commande\_id\_seq}. Cette
  méthode ne permet pas de garantir que tous les numéros de commande se
  suivent. Proposer une solution pour sérialiser la génération des
  numéros de commande. Autrement dit, proposer une méthode pour obtenir
  un numéro de commande sans avoir de ``trou'' dans la séquence en cas
  d'échec d'une transaction.
\item
  Noter le nombre de lignes de la table \texttt{pieces}. Dans une
  transaction, majorer de 5\% le prix des pièces de moins de 1500\euro{}
  et minorer de 5\% le prix des pièces dont le prix actuel est égal ou
  supérieur à 1500\euro{}. Vérifier si le nombre de lignes mises à jour
  au total correspond au nombre total de lignes de la table
  \texttt{pieces}.
\item
  Dans une même transaction, créer un nouveau client en incluant l'ajout
  de l'ensemble des informations requises pour pouvoir le contacter. Un
  nouveau client a un solde égal à 0.
\end{enumerate}

\subsubsection{Solutions}\label{solutions-1}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  Ajouter une colonne \texttt{email} de type \texttt{text} à la table
  \texttt{contacts}. Cette colonne va permettre de stocker l'adresse
  e-mail des clients et des fournisseurs. Ajouter également un
  commentaire décrivant cette colonne dans le catalogue de PostgreSQL
  (utiliser la commande \texttt{COMMENT}).
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{contacts}
   \KeywordTok{ADD} \KeywordTok{COLUMN} \NormalTok{email text}
\NormalTok{;}

\KeywordTok{COMMENT} \KeywordTok{ON} \KeywordTok{COLUMN} \NormalTok{contacts.email }\KeywordTok{IS} 
   \StringTok{'Adresse e-mail du contact'}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
  Mettre à jour la table des contacts pour indiquer l'adresse e-mail de
  //Client6657// qui est \texttt{client6657@dalibo.com}.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{contacts }
   \KeywordTok{SET} \NormalTok{email = }\StringTok{'client6657@dalibo.com'} 
\KeywordTok{WHERE} \NormalTok{nom = }\StringTok{'Client6657'}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Vérifier les résultats :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
\KeywordTok{FROM} \NormalTok{contacts}
\KeywordTok{WHERE} \NormalTok{nom = }\StringTok{'Client6657'}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
  Ajouter une contrainte d'intégrité qui valide que la valeur de la
  colonne \texttt{email} créée est bien formée (vérifier que la chaîne
  de caractère contient au moins le caractère \texttt{@}).
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{contacts}
\KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{chk_contacts_email_valid}
   \KeywordTok{CHECK} \NormalTok{(email }\KeywordTok{LIKE} \StringTok{'%@%'}\NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Cette expression régulière est simplifiée et simpliste pour les besoins
de l'exercice. Des expressions régulières plus complexes permettent de
valider réellement une adresse email.

Voici un exemple un tout petit peu plus évolué en utilisant une
expression rationnelle simple, ici pour vérifier que la chaîne précédent
le caractère \texttt{@} contient au moins un caractère, et que la chaîne
le suivant est une chaîne de caractères contenant un point :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{contacts}
\KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{chk_contacts_email_valid}
   \KeywordTok{CHECK} \NormalTok{(email ~ }\StringTok{'.+@.+\textbackslash{}..+'}\NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
  Valider la contrainte dans une transaction de test.
\end{enumerate}

Démarrer la transaction :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

Tenter de mettre à jour la table \texttt{contacts} avec une adresse mail
ne répondant pas à la contrainte :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{contacts}
   \KeywordTok{SET} \NormalTok{email = }\StringTok{'test'}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

L'ordre \texttt{UPDATE} retourne l'erreur suivante, indiquant que
l'expression régulière est fonctionnelle :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{ERROR:  }\KeywordTok{new} \KeywordTok{row} \KeywordTok{for} \NormalTok{relation }\OtherTok{"contacts"} \NormalTok{violates }\KeywordTok{check} \KeywordTok{constraint} \OtherTok{"chk_contacts_email_valid"}
\NormalTok{DETAIL:  Failing }\KeywordTok{row} \NormalTok{contains (}\DecValTok{300001}\NormalTok{, Client1737, nkD, SA, }\DecValTok{20-999-929-1440}\NormalTok{, test).}
\end{Highlighting}
\end{Shaded}

La transaction est ensuite annulée :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ROLLBACK} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
  Déterminer quels sont les contacts qui disposent d'une adresse e-mail
  et afficher leur nom ainsi que le code de leur pays.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom, code_pays}
\KeywordTok{FROM} \NormalTok{contacts}
\KeywordTok{WHERE} \NormalTok{email }\KeywordTok{IS} \KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
  La génération des numéros de commande est actuellement réalisée à
  l'aide de la séquence \texttt{commandes\_commande\_id\_seq}. Cette
  méthode ne permet pas de garantir que tous les numéros de commande se
  suivent. Proposer une solution pour sérialiser la génération des
  numéros de commande. Autrement dit, proposer une méthode
  transactionnelle pour obtenir un numéro de commande, sans avoir de
  ``//trou//'' dans la séquence en cas d'échec d'une transaction.
\end{enumerate}

La solution la plus simple pour imposer la sérialisation des numéros de
commandes est d'utiliser une table de séquences. Une ligne de cette
table correspondra au compteur des numéros de commande.

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{-- création de la table qui va contenir la séquence :}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{numeros_sequences (}
   \NormalTok{nom text }\KeywordTok{NOT} \KeywordTok{NULL} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{,}
   \KeywordTok{sequence} \DataTypeTok{integer} \KeywordTok{NOT} \KeywordTok{NULL}
\NormalTok{)}
\NormalTok{;}

\CommentTok{-- initialisation de la séquence :}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{numeros_sequences (nom, }\KeywordTok{sequence}\NormalTok{)}
\KeywordTok{SELECT} \StringTok{'sequence_numero_commande'}\NormalTok{, }\FunctionTok{max}\NormalTok{(numero_commande)}
\KeywordTok{FROM} \NormalTok{commandes}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

L'obtention d'un nouveau numéro de commande sera réalisé dans la
transaction de création de la commande de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN} \NormalTok{;}

\KeywordTok{UPDATE} \NormalTok{numeros_sequences}
   \KeywordTok{SET} \KeywordTok{sequence} \NormalTok{= }\KeywordTok{sequence} \NormalTok{+ }\DecValTok{1}
\KeywordTok{WHERE} \NormalTok{nom = }\StringTok{'numero_commande'}
\KeywordTok{RETURNING} \KeywordTok{sequence}
\NormalTok{;}

\CommentTok{/* insertion d'une nouvelle commande en utilisant le numéro de commande }
\CommentTok{   retourné par la commande précédente :}
\CommentTok{   INSERT INTO commandes (numero_commande, ...)}
\CommentTok{   VALUES (<la nouvelle valeur de la séquence>, ...) ;}
\CommentTok{*/}

\KeywordTok{COMMIT} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

L'ordre \texttt{UPDATE} pose un verrou exclusif sur la ligne mise à
jour. Tant que la mise à jour n'aura pas été validée ou annulée par
\texttt{COMMIT} ou \texttt{ROLLBACK}, le verrou posé va bloquer toutes
les autres transactions qui tenteraient de mettre à jour cette ligne. De
cette façon, toutes les transactions seront sérialisées.

Concernant la génération des numéros de séquence, si la transaction est
annulée, alors le compteur \texttt{sequence} retrouvera sa valeur
précédente et la transaction suivante obtiendra le même numéro de
séquence. Si la transaction est validée, alors le compteur
\texttt{sequence} est incrémenté. La transaction suivante verra alors
cette nouvelle valeur et non plus l'ancienne. Cette méthode garantit
qu'il n'y ait pas de rupture de séquence.

Il va de soi que les transactions de création de commandes doivent être
extrêmement courtes. Si une telle transaction est bloquée, toutes les
transactions suivantes seront également bloquées, paralysant ainsi tous
les utilisateurs de l'application.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\tightlist
\item
  Noter le nombre de lignes de la table \texttt{pieces}. Dans une
  transaction, majorer de 5\% le prix des pièces de moins de 1500\euro{}
  et minorer de 5\% le prix des pièces dont le prix actuel est égal ou
  supérieur à 1500\euro{}. Vérifier si le nombre de lignes mises à jour
  au total correspond au nombre total de lignes de la table
  \texttt{pieces}.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN} \NormalTok{;}

\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
\KeywordTok{FROM} \NormalTok{pieces}
\NormalTok{;}

\KeywordTok{UPDATE} \NormalTok{pieces}
   \KeywordTok{SET} \NormalTok{prix = prix * }\FloatTok{1.05}
\KeywordTok{WHERE} \NormalTok{prix < }\DecValTok{1500}
\NormalTok{;}

\KeywordTok{UPDATE} \NormalTok{pieces}
   \KeywordTok{SET} \NormalTok{prix = prix * }\FloatTok{0.95}
\KeywordTok{WHERE} \NormalTok{prix >= }\DecValTok{1500}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Au total, la transaction a mis à jour 214200 (99922+114278) lignes, soit
14200 lignes de trop qui ont été mises à jour.

Annuler la mise à jour :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ROLLBACK} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

Explication : Le premier \texttt{UPDATE} a majoré de 5\% les pièces dont
le prix est inférieur à 1500\euro{}. Or, tous les prix supérieurs à
1428,58\euro{} passent la barre des 1500\euro{} après le premier
\texttt{UPDATE}. Le second \texttt{UPDATE} minore les pièces dont le
prix est égal ou supérieur à 1500\euro{}, ce qui inclue une partie des
prix majorés par le précédent \texttt{UPDATE}. Certaines lignes ont donc
subies deux modifications au lieu d'une. L'instruction \texttt{CASE} du
langage SQL, qui sera abordée dans le prochain module, propose une
solution à ce genre de problématique :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{pieces}
   \KeywordTok{SET} \NormalTok{prix = (}
      \KeywordTok{CASE}
         \KeywordTok{WHEN} \NormalTok{prix < }\DecValTok{1500} \KeywordTok{THEN} \NormalTok{prix * }\FloatTok{1.05}
         \KeywordTok{WHEN} \NormalTok{prix >= }\DecValTok{1500} \KeywordTok{THEN} \NormalTok{prix * }\FloatTok{0.95}
      \KeywordTok{END}
   \NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\tightlist
\item
  Dans une même transaction, créer un nouveau client en incluant l'ajout
  de l'ensemble des informations requises pour pouvoir le contacter. Un
  nouveau client a un solde égal à 0.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{-- démarrer la transaction}
\KeywordTok{BEGIN} \NormalTok{;}

\CommentTok{-- créer le contact et récupérer le contact_id généré}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{contacts (nom, adresse, telephone, code_pays)}
   \KeywordTok{VALUES} \NormalTok{(}\StringTok{'M. Xyz'}\NormalTok{, }\StringTok{'3, Rue du Champignon, 96000 Champiville'}\NormalTok{, }\StringTok{'+33554325432'}\NormalTok{, }\StringTok{'FR'}\NormalTok{)}
\KeywordTok{RETURNING} \NormalTok{contact_id}
\NormalTok{;}

\CommentTok{-- réaliser l'insertion en utilisant le numéro de contact récupéré précédemment}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{clients (solde, segment_marche, contact_id, commentaire)}
   \CommentTok{-- par exemple ici avec le numéro 350002}
   \KeywordTok{VALUES} \NormalTok{(}\DecValTok{0}\NormalTok{, }\StringTok{'AUTOMOBILE'}\NormalTok{, }\DecValTok{350002}\NormalTok{, }\StringTok{'Client très important'}\NormalTok{)}
\NormalTok{;}

\CommentTok{-- valider la transaction}
\KeywordTok{COMMIT} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

\section{Plus loin avec SQL}\label{plus-loin-avec-sql}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Préambule}\label{pruxe9ambule-1}

\begin{itemize}
\tightlist
\item
  Après la définition des objets, leur lecture et leur écriture
\item
  Aller plus loin dans l'écriture de requêtes

  \begin{itemize}
  \tightlist
  \item
    avec les jointures
  \item
    avec les requêtes intégrées
  \end{itemize}
\end{itemize}

Maintenant que nous avons vu comment définir des objets, comment lire
des données provenant de relation et comment écrire des données, nous
allons pousser vers les perfectionnements du langage SQL. Nous allons
notamment aborder la lecture de plusieurs tables en même temps, que ce
soit par des jointures ou par des sous-requêtes.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Menu}\label{menu-2}

\begin{itemize}
\tightlist
\item
  Valeur \texttt{NULL}
\item
  Agrégats, GROUP BY, HAVING
\item
  Sous-requêtes
\item
  Jointures
\item
  Expression conditionnelle \texttt{CASE}
\item
  Opérateurs ensemblistes: UNION, EXCEPT, INTERSECT
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Objectifs}\label{objectifs-2}

\begin{itemize}
\tightlist
\item
  Comprendre l'intérêt du NULL
\item
  Savoir écrire des requêtes complexes
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Valeur NULL}\label{valeur-null}

\begin{itemize}
\tightlist
\item
  Comment représenter une valeur que l'on ne connait pas ?

  \begin{itemize}
  \tightlist
  \item
    Valeur \texttt{NULL}
  \end{itemize}
\item
  Trois sens possibles pour \texttt{NULL} :

  \begin{itemize}
  \tightlist
  \item
    valeur inconnue
  \item
    valeur inapplicable
  \item
    absence de valeur
  \end{itemize}
\item
  Logique 3 états
\end{itemize}

Le standard SQL définit très précisément la valeur que doit avoir une
colonne dont on ne connaît pas la valeur. Il faut utiliser le mot clé
NULL. En fait, ce mot clé est utilisé dans trois cas : pour les valeurs
inconnues, pour les valeurs inapplicables et pour une absence de
valeurs.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Avertissement}\label{avertissement}

\begin{itemize}
\tightlist
\item
  Chris J. Date a écrit :
\end{itemize}

\begin{quote}
La valeur \texttt{NULL} telle qu'elle est implémentée dans SQL peut
poser plus de problèmes qu'elle n'en résout. Son comportement est
parfois étrange et est source de nombreuses erreurs et de confusions.
\end{quote}

\begin{itemize}
\tightlist
\item
  Éviter d'utiliser \texttt{NULL} le plus possible

  \begin{itemize}
  \tightlist
  \item
    utiliser \texttt{NULL} correctement lorsqu'il le faut
  \end{itemize}
\end{itemize}

Il ne faut utiliser \texttt{NULL} que lorsque cela est réellement
nécessaire. La gestion des valeurs NULL est souvent source de confusions
et d'erreurs, ce qui explique qu'il est préférable de l'éviter tant
qu'on n'entre pas dans les trois cas vu ci-dessus (valeur inconnue,
valeur inapplicable, absence de valeur).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Assignation de NULL}\label{assignation-de-null}

\begin{itemize}
\tightlist
\item
  Assignation de \texttt{NULL} pour \texttt{INSERT} et \texttt{UPDATE}
\item
  Explicitement :

  \begin{itemize}
  \tightlist
  \item
    NULL est indiqué explicitement dans les assignations
  \end{itemize}
\item
  Implicitement :

  \begin{itemize}
  \tightlist
  \item
    la colonne n'est pas affectée par \texttt{INSERT}
  \item
    et n'a pas de valeur par défaut
  \end{itemize}
\item
  Empêcher la valeur \texttt{NULL}

  \begin{itemize}
  \tightlist
  \item
    contrainte \texttt{NOT\ NULL}
  \end{itemize}
\end{itemize}

Il est possible de donner le mot-clé NULL pour certaines colonnes dans
les \texttt{INSERT} et les \texttt{UPDATE}. Si jamais une colonne n'est
pas indiqué dans un \texttt{INSERT}, elle aura comme valeur sa valeur
par défaut (très souvent, il s'agit de \texttt{NULL}). Si jamais on veut
toujours avoir une valeur dans une colonne particulière, il faut
utiliser la clause \texttt{NOT\ NULL} lors de l'ajout de la colonne.
C'est le cas pour les clés primaires par exemple.

Voici quelques exemples d'insertion et de mise à jour :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{public.personnes}
\NormalTok{(}
   \KeywordTok{id} \NormalTok{serial, }
   \NormalTok{nom }\DataTypeTok{character} \DataTypeTok{varying}\NormalTok{(}\DecValTok{60}\NormalTok{) }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{, }
   \NormalTok{prenom }\DataTypeTok{character} \DataTypeTok{varying}\NormalTok{(}\DecValTok{60}\NormalTok{), }
   \NormalTok{date_naissance }\DataTypeTok{date}\NormalTok{, }
   \KeywordTok{CONSTRAINT} \NormalTok{pk_personnes }\KeywordTok{PRIMARY} \KeywordTok{KEY} \NormalTok{(}\KeywordTok{id}\NormalTok{)}
\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes(}
            \NormalTok{nom, prenom, date_naissance)}
    \KeywordTok{VALUES} \NormalTok{(}\StringTok{'Lagaffe'}\NormalTok{, }\StringTok{'Gaston'}\NormalTok{, }\DataTypeTok{date} \StringTok{'28/02/1957'}\NormalTok{);}

\CommentTok{-- assignation explicite}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes(}
            \NormalTok{nom, prenom, date_naissance)}
    \KeywordTok{VALUES} \NormalTok{(}\StringTok{'Fantasio'}\NormalTok{, }\KeywordTok{NULL}\NormalTok{, }\DataTypeTok{date} \StringTok{'01/01/1938'}\NormalTok{);}

\CommentTok{-- assignation implicite}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes(}
            \NormalTok{nom, prenom)}
    \KeywordTok{VALUES} \NormalTok{(}\StringTok{'Prunelle'}\NormalTok{, }\StringTok{'Léon'}\NormalTok{);}

\CommentTok{-- observation des résultats}

 \KeywordTok{id} \NormalTok{|   nom    | prenom | date_naissance }
\CommentTok{----+----------+--------+----------------}
  \DecValTok{1} \NormalTok{| Lagaffe  | Gaston | }\DecValTok{1957-02-28}
  \DecValTok{2} \NormalTok{| Fantasio | (}\KeywordTok{null}\NormalTok{) | }\DecValTok{1938-01-01}
  \DecValTok{3} \NormalTok{| Prunelle | Léon   | (}\KeywordTok{null}\NormalTok{)}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

L'affichage \texttt{(null)} dans \texttt{psql} est obtenu avec la
méta-commande \texttt{\textbackslash{}pset\ null\ (null)}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Calculs avec NULL}\label{calculs-avec-null}

\begin{itemize}
\tightlist
\item
  Utilisation dans un calcul

  \begin{itemize}
  \tightlist
  \item
    propagation de \texttt{NULL}
  \end{itemize}
\item
  \texttt{NULL} est inapplicable

  \begin{itemize}
  \tightlist
  \item
    le résultat vaut \texttt{NULL}
  \end{itemize}
\end{itemize}

La valeur \texttt{NULL} est définie comme inapplicable. Ainsi, si elle
présente dans un calcul, elle est propagée sur l'ensemble du calcul : le
résultat vaudra \texttt{NULL}.

\textbf{Exemples de calcul}

Calculs simples :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \NormalTok{+ }\DecValTok{2} \KeywordTok{AS} \NormalTok{resultat;}
 \NormalTok{resultat }
\CommentTok{----------}
        \DecValTok{3}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}

\KeywordTok{SELECT} \DecValTok{1} \NormalTok{+ }\DecValTok{2} \NormalTok{+ }\KeywordTok{NULL} \KeywordTok{AS} \NormalTok{resultat;}
 \NormalTok{resultat }
\CommentTok{----------}
   \NormalTok{(}\KeywordTok{null}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Calcul à partir de l'âge :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom, prenom, }\DecValTok{1} \NormalTok{+ }\FunctionTok{extract}\NormalTok{(}\StringTok{'year'} \KeywordTok{from} \NormalTok{age(date_naissance)) }\KeywordTok{AS} \NormalTok{calcul_age }\KeywordTok{FROM} \NormalTok{personnes;}
   \NormalTok{nom    | prenom | calcul_age }
\CommentTok{----------+--------+------------}
 \NormalTok{Lagaffe  | Gaston |         }\DecValTok{57}
 \NormalTok{Fantasio | (}\KeywordTok{null}\NormalTok{) |         }\DecValTok{76}
 \NormalTok{Prunelle | Léon   |     (}\KeywordTok{null}\NormalTok{)}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Exemple d'utilisation de \texttt{NULL} dans une concaténation :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom || }\StringTok{' '} \NormalTok{|| prenom }\KeywordTok{AS} \NormalTok{nom_complet }\KeywordTok{FROM} \NormalTok{personnes;}
  \NormalTok{nom_complet   }
\CommentTok{----------------}
 \NormalTok{Lagaffe Gaston}
 \NormalTok{(}\KeywordTok{null}\NormalTok{)}
 \NormalTok{Prunelle Léon}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

L'affichage \texttt{(null)} est obtenu avec la méta-commande
\texttt{\textbackslash{}pset\ null\ (null)} du shell psql.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{NULL et les prédicats}\label{null-et-les-pruxe9dicats}

\begin{itemize}
\tightlist
\item
  Dans un prédicat du \texttt{WHERE} :

  \begin{itemize}
  \tightlist
  \item
    opérateur \texttt{IS\ NULL} ou \texttt{IS\ NOT\ NULL}
  \end{itemize}
\item
  \texttt{AND} :

  \begin{itemize}
  \tightlist
  \item
    vaut \texttt{false} si \texttt{NULL\ AND\ false}
  \item
    vaut \texttt{NULL} si \texttt{NULL\ AND\ true} ou
    \texttt{NULL\ AND\ NULL}
  \end{itemize}
\item
  \texttt{OR} :

  \begin{itemize}
  \tightlist
  \item
    vaut \texttt{true} si \texttt{NULL\ OR\ true}
  \item
    vaut \texttt{NULL} si \texttt{NULL\ OR\ false} ou
    \texttt{NULL\ OR\ NULL}
  \end{itemize}
\end{itemize}

Les opérateurs de comparaisons classiques ne sont pas fonctionnels avec
une valeur \texttt{NULL}. Du fait de la logique à trois états de
PostgreSQL, une comparaison avec \texttt{NULL} vaut toujours
\texttt{NULL}, ainsi \texttt{expression\ =\ NULL} vaudra toujours
\texttt{NULL} et de même pour
\texttt{expression\ \textless{}\textgreater{}\ NULL} vaudra toujours
\texttt{NULL}. Cette comparaison ne vaudra jamais ni vrai, ni faux.

De ce fait, il existe les opérateurs de prédicats \texttt{IS\ NULL} et
\texttt{IS\ NOT\ NULL} qui permettent de vérifier qu'une expression est
\texttt{NULL} ou n'est pas \texttt{NULL}.

Pour en savoir plus sur la logique ternaire qui régit les règles de
calcul des prédicats, se conformer à la page Wikipedia sur la logique
ternaire: {[}{[}http://fr.wikipedia.org/wiki/Logique\_ternaire{]}{]}.

\textbf{Exemples}

Comparaison directe avec \texttt{NULL}, qui est invalide :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{personnes }\KeywordTok{WHERE} \NormalTok{date_naissance = }\KeywordTok{NULL}\NormalTok{;}
 \KeywordTok{id} \NormalTok{| nom | prenom | date_naissance }
\CommentTok{----+-----+--------+----------------}
\NormalTok{(}\DecValTok{0} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

L'opérateur \texttt{IS\ NULL} permet de retourner les lignes dont la
date de naissance n'est pas renseignée :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{personnes }\KeywordTok{WHERE} \NormalTok{date_naissance }\KeywordTok{IS} \KeywordTok{NULL}\NormalTok{;}
 \KeywordTok{id} \NormalTok{|   nom    | prenom | date_naissance }
\CommentTok{----+----------+--------+----------------}
  \DecValTok{3} \NormalTok{| Prunelle | Léon   | (}\KeywordTok{null}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{NULL et les agrégats}\label{null-et-les-agruxe9gats}

\begin{itemize}
\tightlist
\item
  Opérateurs d'agrégats

  \begin{itemize}
  \tightlist
  \item
    ignorent \texttt{NULL}
  \item
    sauf \texttt{count(*)}
  \end{itemize}
\end{itemize}

Les fonctions d'agrégats ne tiennent pas compte des valeurs
\texttt{NULL} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{SUM}\NormalTok{(}\FunctionTok{extract}\NormalTok{(}\StringTok{'year'} \KeywordTok{from} \NormalTok{age(date_naissance))) }\KeywordTok{AS} \NormalTok{age_cumule }
\KeywordTok{FROM} \NormalTok{personnes;}
 \NormalTok{age_cumule }
\CommentTok{------------}
        \DecValTok{131}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Sauf \texttt{count(*)} et uniquement \texttt{count(*)}, la fonction
\texttt{count(expression)} tient compte des valeurs \texttt{NULL} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
  \FunctionTok{count}\NormalTok{(*) }\KeywordTok{AS} \NormalTok{compte_lignes, }
  \FunctionTok{count}\NormalTok{(date_naissance) }\KeywordTok{AS} \NormalTok{compte_valeurs}
\KeywordTok{FROM} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{date_naissance}
      \KeywordTok{FROM} \NormalTok{personnes) date_naissance;}
 \NormalTok{compte_lignes | compte_valeurs }
\CommentTok{---------------+----------------}
             \DecValTok{3} \NormalTok{|              }\DecValTok{2}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{COALESCE}\label{coalesce}

\begin{itemize}
\tightlist
\item
  Remplacer \texttt{NULL} par une autre valeur

  \begin{itemize}
  \tightlist
  \item
    \texttt{COALESCE(attribut,\ ...);}
  \end{itemize}
\end{itemize}

Cette fonction permet de tester une colonne et de récupérer sa valeur si
elle n'est pas NULL et une autre valeur dans le cas contraire. Elle peut
avoir plus de deux arguments. Dans ce cas, la première expression de la
liste qui ne vaut pas \texttt{NULL} sera retournée par la fonction.

Voici quelques exemples :

Remplace les prénoms non-renseignés par la valeur \texttt{X} dans le
résultat :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom, }\KeywordTok{COALESCE}\NormalTok{(prenom, }\StringTok{'X'}\NormalTok{) }
\KeywordTok{FROM} \NormalTok{personnes;}
   \NormalTok{nom    | }\KeywordTok{coalesce} 
\CommentTok{----------+----------}
 \NormalTok{Lagaffe  | Gaston}
 \NormalTok{Fantasio | X}
 \NormalTok{Prunelle | Léon}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Cette fonction est efficace également pour la concaténation précédente :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom || }\StringTok{' '} \NormalTok{|| }\KeywordTok{COALESCE}\NormalTok{(prenom, `) }\KeywordTok{AS} \NormalTok{nom_complet }
\KeywordTok{FROM} \NormalTok{personnes;}
  \NormalTok{nom_complet   }
\CommentTok{----------------}
 \NormalTok{Lagaffe Gaston}
 \NormalTok{Fantasio }
 \NormalTok{Prunelle Léon}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Agrégats}\label{agruxe9gats}

\begin{itemize}
\tightlist
\item
  Regroupement de données
\item
  Calculs d'agrégats
\end{itemize}

Comme son nom l'indique, l'agrégation permet de regrouper des données,
qu'elles viennent d'une ou de plusieurs colonnes. Le but est
principalement de réaliser des calculs sur les données des lignes
regroupées.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Regroupement de
données}\label{regroupement-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Regroupement de données :\texttt{GROUP\ BY\ expression\ {[},\ ...{]}}
\item
  Chaque groupe de données est ensuite représenté sur une seule ligne
\item
  Permet d'appliquer des calculs sur les ensembles regroupés

  \begin{itemize}
  \tightlist
  \item
    comptage, somme, moyenne, etc.
  \end{itemize}
\end{itemize}

La clause \texttt{GROUP\ BY} permet de réaliser des regroupements de
données. Les données regroupées sont alors représentées sur une seule
ligne. Le principal intérêt de ces regroupements est de permettre de
réaliser des calculs sur ces données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Calculs d'agrégats}\label{calculs-dagruxe9gats}

\begin{itemize}
\tightlist
\item
  Effectue un calcul sur un ensemble de valeurs

  \begin{itemize}
  \tightlist
  \item
    somme, moyenne, etc.
  \end{itemize}
\item
  Retourne \texttt{NULL} si l'ensemble est vide

  \begin{itemize}
  \tightlist
  \item
    sauf \texttt{count()}
  \end{itemize}
\end{itemize}

Nous allons voir les différentes fonctions d'agrégats disponibles.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Agrégats simples}\label{agruxe9gats-simples}

\begin{itemize}
\tightlist
\item
  Comptage :

  \begin{itemize}
  \tightlist
  \item
    \texttt{count(expression)}
  \item
    compte les lignes: \texttt{count(*)}
  \item
    compte les valeurs renseignées: \texttt{count(colonne)}
  \end{itemize}
\item
  Valeur minimale :\texttt{min(expression)}
\item
  Valeur maximale :\texttt{max(expression)}
\end{itemize}

La fonction \texttt{count()} permet de compter les éléments. La fonction
est appelée de deux façons.

La première forme consiste à utiliser \texttt{count(*)} qui revient à
transmettre la ligne complète à la fonction d'agrégat. Ainsi, toute
ligne transmise à la fonction sera comptée, même si elle n'est composée
que de valeurs \texttt{NULL}. On rencontre parfois une forme du type
\texttt{count(1)}, qui transmet une valeur arbitraire à la fonction, et
qui permettait d'accélérer le temps de traitement sur certains SGBD mais
qui reste sans intérêt avec PostgreSQL.

La seconde forme consiste à utiliser une expression, par exemple le nom
d'une colonne : \texttt{count(nom\_colonne)}. Dans ce cas-là, seules les
valeurs renseignées, donc non NULL, seront prises en compte. Les valeurs
\texttt{NULL} seront exclues du comptage.

La fonction \texttt{min()} permet de déterminer la valeur la plus petite
d'un ensemble de valeurs données. La fonction \texttt{max()} permet à
l'inverse de déterminer la valeur la plus grande d'un ensemble de
valeurs données. Les valeurs \texttt{NULL} sont bien ignorées. Ces deux
fonctions permettent de travailler sur des données numériques, mais
fonctionnent également sur les autres types de données comme les chaînes
de caractères.

La documentation de PostgreSQL permet d'obtenir la liste des fonctions
d'agrégats disponibles:
http://docs.postgresql.fr/9.2/functions-aggregate.html

\textbf{Exemples}

Différences entre count(*) et count(colonne) :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (x }\DataTypeTok{INTEGER}\NormalTok{);}
\CommentTok{-- insertion de cinq lignes dans la table test}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (x) }
\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{), (}\DecValTok{2}\NormalTok{), (}\DecValTok{2}\NormalTok{), (}\KeywordTok{NULL}\NormalTok{), (}\KeywordTok{NULL}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{x, }\FunctionTok{count}\NormalTok{(*) }\KeywordTok{AS} \NormalTok{count_etoile, }\FunctionTok{count}\NormalTok{(x) }\KeywordTok{AS} \NormalTok{count_x }\KeywordTok{FROM} \NormalTok{test }\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{x;}
   \NormalTok{x    | count_etoile | count_x }
\CommentTok{--------+--------------+---------}
 \NormalTok{(}\KeywordTok{null}\NormalTok{) |            }\DecValTok{2} \NormalTok{|       }\DecValTok{0}
      \DecValTok{1} \NormalTok{|            }\DecValTok{1} \NormalTok{|       }\DecValTok{1}
      \DecValTok{2} \NormalTok{|            }\DecValTok{2} \NormalTok{|       }\DecValTok{2}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Déterminer la date de naissance de la personne la plus jeune :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{MAX}\NormalTok{(date_naissance) }\KeywordTok{FROM} \NormalTok{personnes;}
    \FunctionTok{max}     
\CommentTok{------------}
 \DecValTok{1957-02-28}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Calculs d'agrégats}\label{calculs-dagruxe9gats-1}

\begin{itemize}
\tightlist
\item
  Moyenne :\texttt{avg(expression)}
\item
  Somme :\texttt{sum(expression)}
\item
  Écart-type :\texttt{stddev(expression)}
\item
  Variance :\texttt{variance(expression)}
\end{itemize}

La fonction \texttt{avg()} permet d'obtenir la moyenne d'un ensemble de
valeurs données. La fonction \texttt{sum()} permet, quant à elle,
d'obtenir la somme d'un ensemble de valeurs données. Enfin, les
fonctions \texttt{stddev()} et \texttt{variance()} permettent d'obtenir
respectivement l'écart-type et la variance d'un ensemble de valeurs
données.

Ces fonctions retournent \texttt{NULL} si aucune donnée n'est
applicable. Elles ne prennent en compte que des valeurs numériques.

La documentation de PostgreSQL permet d'obtenir la liste des fonctions
d'agrégats disponibles:
http://docs.postgresql.fr/9.2/functions-aggregate.html

\textbf{Exemples}

Quel est le nombre total de bouteilles en stock par millésime ?

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{annee, }\FunctionTok{sum}\NormalTok{(nombre) }
\KeywordTok{FROM} \NormalTok{stock }
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{annee }
\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{annee;}
 \NormalTok{annee |  }\FunctionTok{sum}   
\CommentTok{-------+--------}
  \DecValTok{1950} \NormalTok{| }\DecValTok{210967}
  \DecValTok{1951} \NormalTok{| }\DecValTok{201977}
  \DecValTok{1952} \NormalTok{| }\DecValTok{202183}
\NormalTok{...}
\end{Highlighting}
\end{Shaded}

Calcul de moyenne avec des valeurs NULL :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (a }\DataTypeTok{int}\NormalTok{, b }\DataTypeTok{int}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{10}\NormalTok{,}\DecValTok{10}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{20}\NormalTok{,}\DecValTok{20}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{30}\NormalTok{,}\DecValTok{30}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test }\KeywordTok{VALUES} \NormalTok{(}\KeywordTok{null}\NormalTok{,}\DecValTok{0}\NormalTok{);}

\KeywordTok{SELECT} \FunctionTok{avg}\NormalTok{(a), }\FunctionTok{avg}\NormalTok{(b) }\KeywordTok{FROM} \NormalTok{test;}
         \FunctionTok{avg}         \NormalTok{|         }\FunctionTok{avg}         
\CommentTok{---------------------+---------------------}
 \FloatTok{20.0000000000000000} \NormalTok{| }\FloatTok{15.0000000000000000}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Agrégats sur plusieurs
colonnes}\label{agruxe9gats-sur-plusieurs-colonnes}

\begin{itemize}
\tightlist
\item
  Possible d'avoir plusieurs paramètres sur la même fonction d'agrégat
\item
  Quelques exemples

  \begin{itemize}
  \tightlist
  \item
    pente, regr\_slope(Y,X)
  \item
    intersection avec l'axe des ordonnées, regr\_intercept(Y,X)
  \item
    indice de corrélation, corr (Y,X)
  \end{itemize}
\end{itemize}

Une fonction d'aggrégat peut aussi prendre plusieurs variables.

Par exemple concernant la méthode des «moindres carrés» :

\begin{itemize}
\tightlist
\item
  pente : \texttt{regr\_slope(Y,X)}
\item
  intersection avec l'axe des ordonnées : \texttt{regr\_intercept(Y,X)}
\item
  indice de corrélation : \texttt{corr\ (Y,X)}
\end{itemize}

Voici un exemple avec un nuage de points proches d'une fonction y=2x+5 :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (x }\DataTypeTok{real}\NormalTok{, y }\DataTypeTok{real}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test }
\KeywordTok{VALUES} \NormalTok{(}\DecValTok{0}\NormalTok{,}\FloatTok{5.01}\NormalTok{), (}\DecValTok{1}\NormalTok{,}\FloatTok{6.99}\NormalTok{), (}\DecValTok{2}\NormalTok{,}\FloatTok{9.03}\NormalTok{);}

\KeywordTok{SELECT} \FunctionTok{regr_slope}\NormalTok{(y,x) }\KeywordTok{FROM} \NormalTok{test;}
    \FunctionTok{regr_slope}    
\CommentTok{------------------}
 \FloatTok{2.00999975204468}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}

\KeywordTok{SELECT} \FunctionTok{regr_intercept}\NormalTok{(y,x) }\KeywordTok{FROM} \NormalTok{test;}
  \FunctionTok{regr_intercept}  
\CommentTok{------------------}
 \FloatTok{5.00000015894572}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}

\KeywordTok{SELECT} \FunctionTok{corr}\NormalTok{(y,x) }\KeywordTok{FROM} \NormalTok{test;}
       \FunctionTok{corr}        
\CommentTok{-------------------}
 \FloatTok{0.999962873745297}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause HAVING}\label{clause-having}

\begin{itemize}
\tightlist
\item
  Filtrer sur des regroupements

  \begin{itemize}
  \tightlist
  \item
    \texttt{HAVING}
  \end{itemize}
\item
  \texttt{WHERE} s'applique sur les lignes lues
\item
  \texttt{HAVING} s'applique sur les lignes groupées
\end{itemize}

La clause \texttt{HAVING} permet de filtrer les résultats sur les
regroupements réalisés par la clause \texttt{GROUP\ BY}. Il est possible
d'utiliser une fonction d'agrégat dans la clause \texttt{HAVING}.

Il faudra néanmoins faire attention à ne pas utiliser la clause
\texttt{HAVING} comme clause de filtrage des données lues par la
requête. La clause \texttt{HAVING} ne doit permettre de filtrer que les
données traitées par la requête.

Ainsi, si l'on souhaite le nombre de vins rouge référencés dans le
catalogue. La requête va donc exclure toutes les données de la table vin
qui ne correspondent pas au filtre \texttt{type\_vin\ =\ 3}. Pour
réaliser cela, on utilisera la clause \texttt{WHERE}.

En revanche, si l'on souhaite connaître le nombre de vins par type de
cépage si ce nombre est supérieur à 2030, on utilisera la clause
\texttt{HAVING}.

\textbf{Exemples}

\begin{verbatim}
SELECT 
  type_vin_id, 
  count(*)
FROM vin
GROUP BY type_vin_id
HAVING count(*) > 2030;

 type_vin_id | count 
-------------+-------
           1 |  2031
\end{verbatim}

Si la colonne correspondant à la fonction d'agrégat est renommée avec la
clause \texttt{AS}, il n'est pas possible d'utiliser le nouveau nom au
sein de la clause \texttt{HAVING}. Par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
  \NormalTok{type_vin_id, }
  \FunctionTok{count}\NormalTok{(*) }\KeywordTok{AS} \NormalTok{nombre}
\KeywordTok{FROM} \NormalTok{vin}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{type_vin_id}
\KeywordTok{HAVING} \NormalTok{nombre > }\DecValTok{2030}\NormalTok{;}

\NormalTok{ERROR:  }\KeywordTok{column} \OtherTok{"nombre"} \NormalTok{does }\KeywordTok{not} \NormalTok{exist}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Sous-requêtes}\label{sous-requuxeates}

\begin{itemize}
\tightlist
\item
  Corrélation requête/sous-requête
\item
  Sous-requêtes retournant une seule ligne
\item
  Sous-requêtes retournant une liste de valeur
\item
  Sous-requêtes retournant un ensemble
\item
  Sous-requêtes retournant un ensemble vide ou non-vide
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Corrélation
requête/sous-requête}\label{corruxe9lation-requuxeatesous-requuxeate}

\begin{itemize}
\tightlist
\item
  Fait référence à la requête principale
\item
  Peut utiliser une valeur issue de la requête principale
\end{itemize}

Une sous-requête peut faire référence à des variables de la requête
principale. Ces variables seront ainsi transformées en constante à
chaque évaluation de la sous-requête.

La corrélation requête/sous-requête permet notamment de créer des
clauses de filtrage dans la sous-requête en utilisant des éléments de la
requête principale.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Qu'est-ce qu'une sous-requête
?}\label{quest-ce-quune-sous-requuxeate}

\begin{itemize}
\tightlist
\item
  Une requête imbriquée dans une autre requête
\item
  Le résultat de la requête principale dépend du résultat de la
  sous-requête
\item
  Encadrée par des parenthèses : \texttt{(} et \texttt{)}
\end{itemize}

Une sous-requête consiste à exécuter une requête à l'intérieur d'une
autre requête. La requête principale peut être une requête de sélection
(\texttt{SELECT}) ou une requête de modification (\texttt{INSERT},
\texttt{UPDATE}, \texttt{DELETE}). La sous-requête est obligatoirement
un \texttt{SELECT}.

Le résultat de la requête principale dépend du résultat de la
sous-requête.

La requête suivante effectue la sélection des colonnes d'une autre
requête, qui est une sous-requête. La sous-requête effectue une lecture
de la table \texttt{appellation}. Son résultat est transformé en un
ensemble qui est nommé \texttt{requete\_appellation} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }
\KeywordTok{FROM} \NormalTok{(}
    \KeywordTok{SELECT} \NormalTok{libelle, region_id }
    \KeywordTok{FROM} \NormalTok{appellation}
     \NormalTok{) requete_appellation;}

                  \NormalTok{libelle                  | region_id }
\CommentTok{-------------------------------------------+-----------}
 \NormalTok{Ajaccio                                   |         }\DecValTok{1}
 \NormalTok{Aloxe-Corton                              |         }\DecValTok{2}
\NormalTok{...}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utiliser une seule ligne}\label{utiliser-une-seule-ligne}

\begin{itemize}
\tightlist
\item
  La sous-requête ne retourne qu'une seule ligne

  \begin{itemize}
  \tightlist
  \item
    sinon une erreur est levée
  \end{itemize}
\item
  Positionnée

  \begin{itemize}
  \tightlist
  \item
    au niveau de la liste des expressions retournées par \texttt{SELECT}
  \item
    au niveau de la clause \texttt{WHERE}
  \item
    au niveau d'une clause \texttt{HAVING}
  \end{itemize}
\end{itemize}

La sous-requête peut être positionnée au niveau de la liste des
expressions retournées par \texttt{SELECT}. La sous-requête est alors
généralement un calcul d'agrégat qui ne donne en résultat qu'une seule
colonne sur une seule ligne. Ce type de sous-requête est peu performant.
Elle est en effet appelée pour chaque ligne retournée par la requête
principale.

La requête suivante permet d'obtenir le cumul du nombre de bouteilles
année par année.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{annee,}
       \FunctionTok{sum}\NormalTok{(nombre) }\KeywordTok{AS} \NormalTok{stock,}
       \NormalTok{(}\KeywordTok{SELECT} \FunctionTok{sum}\NormalTok{(nombre)}
          \KeywordTok{FROM} \NormalTok{stock s}
         \KeywordTok{WHERE} \NormalTok{s.annee <= stock.annee) }\KeywordTok{AS} \NormalTok{stock_cumule}
  \KeywordTok{FROM} \NormalTok{stock}
 \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{annee}
 \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{annee;}

 \NormalTok{annee | stock  | stock_cumule }
\CommentTok{-------+--------+--------------}
  \DecValTok{1950} \NormalTok{| }\DecValTok{210967} \NormalTok{|       }\DecValTok{210967}
  \DecValTok{1951} \NormalTok{| }\DecValTok{201977} \NormalTok{|       }\DecValTok{412944}
  \DecValTok{1952} \NormalTok{| }\DecValTok{202183} \NormalTok{|       }\DecValTok{615127}
  \DecValTok{1953} \NormalTok{| }\DecValTok{202489} \NormalTok{|       }\DecValTok{817616}
  \DecValTok{1954} \NormalTok{| }\DecValTok{202041} \NormalTok{|      }\DecValTok{1019657}
\NormalTok{...}
\end{Highlighting}
\end{Shaded}

Une telle sous-requête peut également être positionnée au niveau de la
clause \texttt{WHERE} ou de la clause \texttt{HAVING}.

Par exemple, pour retourner la liste des vins rouge :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{vin}
 \KeywordTok{WHERE} \NormalTok{type_vin_id = (}\KeywordTok{SELECT} \KeywordTok{id}
                        \KeywordTok{FROM} \NormalTok{type_vin}
                       \KeywordTok{WHERE} \NormalTok{libelle = }\StringTok{'rouge'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utiliser une liste de
valeurs}\label{utiliser-une-liste-de-valeurs}

\begin{itemize}
\tightlist
\item
  La sous-requête retourne

  \begin{itemize}
  \tightlist
  \item
    plusieurs lignes
  \item
    sur une seule colonne
  \end{itemize}
\item
  Positionnée

  \begin{itemize}
  \tightlist
  \item
    avec une clause \texttt{IN}
  \item
    avec une clause \texttt{ANY}
  \item
    avec une clause \texttt{ALL}
  \end{itemize}
\end{itemize}

Les sous-requêtes retournant une liste de valeur sont plus fréquemment
utilisées. Ce type de sous-requête permet de filtrer les résultats de la
requête principale à partir des résultats de la sous-requête.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause IN}\label{clause-in}

\begin{itemize}
\tightlist
\item
  \texttt{expression\ IN\ (sous-requete)}
\item
  L'expression de gauche est évaluée et vérifiée avec la liste de
  valeurs de droite
\item
  \texttt{IN} vaut \texttt{true}

  \begin{itemize}
  \tightlist
  \item
    si l'expression de gauche correspond à un élément de la liste de
    droite
  \end{itemize}
\item
  \texttt{IN} vaut \texttt{false}

  \begin{itemize}
  \tightlist
  \item
    si aucune correspondance n'est trouvée et la liste ne contient pas
    \texttt{NULL}
  \end{itemize}
\item
  \texttt{IN} vaut \texttt{NULL}

  \begin{itemize}
  \tightlist
  \item
    si l'expression de gauche vaut \texttt{NULL}
  \item
    si aucune valeur ne correspond et la liste contient \texttt{NULL}
  \end{itemize}
\end{itemize}

La clause \texttt{IN} dans la requête principale permet alors
d'exploiter le résultat de la sous-requête pour sélectionner les lignes
dont une colonne correspond à une valeur retournée par la sous-requête.

L'opérateur \texttt{IN} retourne \texttt{true} si la valeur de
l'expression de gauche est trouvée au moins une fois dans la liste de
droite. La liste de droite peut contenir la valeur \texttt{NULL} dans ce
cas :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \KeywordTok{IN} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{2}\NormalTok{, }\KeywordTok{NULL}\NormalTok{) }\KeywordTok{AS} \KeywordTok{in}\NormalTok{;}

 \KeywordTok{in} 
\CommentTok{----}
 \NormalTok{t}
\end{Highlighting}
\end{Shaded}

Si aucune correspondance n'est trouvée entre l'expression de gauche et
la liste de droite, alors \texttt{IN} vaut \texttt{false} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \KeywordTok{IN} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{4}\NormalTok{) }\KeywordTok{AS} \KeywordTok{in}\NormalTok{;}
 \KeywordTok{in} 
\CommentTok{-----}
 \NormalTok{f}
\end{Highlighting}
\end{Shaded}

Mais \texttt{IN} vaut \texttt{NULL} si aucune correspondance n'est
trouvée et que la liste de droite contient au moins une valeur
\texttt{NULL} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \KeywordTok{IN} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{4}\NormalTok{, }\KeywordTok{NULL}\NormalTok{) }\KeywordTok{AS} \KeywordTok{in}\NormalTok{;}
   \KeywordTok{in}   
\CommentTok{--------}
 \NormalTok{(}\KeywordTok{null}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\texttt{IN} vaut également \texttt{NULL} si l'expression de gauche vaut
\texttt{NULL} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \KeywordTok{NULL} \KeywordTok{IN} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{4}\NormalTok{) }\KeywordTok{AS} \KeywordTok{in}\NormalTok{;}
   \KeywordTok{in}   
\CommentTok{--------}
 \NormalTok{(}\KeywordTok{null}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Exemples}

La requête suivante permet de sélectionner les bouteilles du stock de la
cave dont la contenance est comprise entre 0,3 litre et 1 litre. Pour
répondre à la question, la sous-requête retourne les identifiants de
contenant qui correspondent à la condition. La requête principale ne
retient alors que les lignes dont la colonne contenant\_id correspond à
une valeur d'identifiant retournée par la sous-requête.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{stock}
 \KeywordTok{WHERE} \NormalTok{contenant_id }\KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \KeywordTok{id}
                          \KeywordTok{FROM} \NormalTok{contenant}
                         \KeywordTok{WHERE} \NormalTok{contenance}
                       \KeywordTok{BETWEEN} \FloatTok{0.3} \KeywordTok{AND} \FloatTok{1.0}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause NOT IN}\label{clause-not-in}

\begin{itemize}
\tightlist
\item
  \texttt{expression\ NOT\ IN\ (sous-requete)}
\item
  L'expression de droite est évaluée et vérifiée avec la liste de
  valeurs de gauche
\item
  \texttt{NOT\ IN} vaut \texttt{true}

  \begin{itemize}
  \tightlist
  \item
    si aucune correspondance n'est trouvée et la liste ne contient pas
    \texttt{NULL}
  \end{itemize}
\item
  \texttt{NOT\ IN} vaut \texttt{false}

  \begin{itemize}
  \tightlist
  \item
    si l'expression de gauche correspond à un élément de la liste de
    droite
  \end{itemize}
\item
  \texttt{NOT\ IN} vaut \texttt{NULL}

  \begin{itemize}
  \tightlist
  \item
    si l'expression de gauche vaut \texttt{NULL}
  \item
    si aucune valeur ne correspond et la liste contient \texttt{NULL}
  \end{itemize}
\end{itemize}

À l'inverse, la clause \texttt{NOT\ IN} permet dans la requête
principale de sélectionner les lignes dont la colonne impliquée dans la
condition ne correspond pas aux valeurs retournées par la sous-requête.

La requête suivante permet de sélectionner les bouteilles du stock dont
la contenance n'est pas inférieure à 2 litres.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{stock}
 \KeywordTok{WHERE} \NormalTok{contenant_id }\KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \KeywordTok{id}
                              \KeywordTok{FROM} \NormalTok{contenant}
                             \KeywordTok{WHERE} \NormalTok{contenance < }\FloatTok{2.0}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Il est à noter que les requêtes impliquant les clauses \texttt{IN} ou
\texttt{NOT\ IN} peuvent généralement être réécrites sous la forme d'une
jointure.

De plus, les optimiseurs SQL parviennent difficilement à optimiser une
requête impliquant \texttt{NOT\ IN}. Il est préférable d'essayer de
réécrire ces requêtes en utilisant une jointure.

Avec \texttt{NOT\ IN}, la gestion des valeurs \texttt{NULL} est à
l'inverse de celle de la clause \texttt{IN} :

Si une correspondance est trouvée, \texttt{NOT\ IN} vaut \texttt{false}
:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}\DecValTok{1}\NormalTok{, }\DecValTok{2}\NormalTok{, }\KeywordTok{NULL}\NormalTok{) }\KeywordTok{AS} \NormalTok{notin;}

 \NormalTok{notin }
\CommentTok{-------}
 \NormalTok{f}
\end{Highlighting}
\end{Shaded}

Si aucune correspondance n'est trouvée, \texttt{NOT\ IN} vaut
\texttt{true} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{4}\NormalTok{) }\KeywordTok{AS} \NormalTok{notin;}
 \NormalTok{notin }
\CommentTok{-------}
 \NormalTok{t}
\end{Highlighting}
\end{Shaded}

Si aucune correspondance n'est trouvée mais que la liste de valeurs de
droite contient au moins un \texttt{NULL}, \texttt{NOT\ IN} vaut
\texttt{NULL} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \DecValTok{1} \KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{4}\NormalTok{, }\KeywordTok{NULL}\NormalTok{) }\KeywordTok{AS} \NormalTok{notin;}
 \NormalTok{notin  }
\CommentTok{--------}
 \NormalTok{(}\KeywordTok{null}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Si l'expression de gauche vaut \texttt{NULL}, alors \texttt{NOT\ IN}
vaut \texttt{NULL} également :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \KeywordTok{NULL} \KeywordTok{IN} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{4}\NormalTok{) }\KeywordTok{AS} \NormalTok{notin;}
 \NormalTok{notin  }
\CommentTok{--------}
 \NormalTok{(}\KeywordTok{null}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Les sous-requêtes retournant des valeurs \texttt{NULL} posent souvent
des problèmes avec \texttt{NOT\ IN}. Il est préférable d'utiliser
\texttt{EXISTS} ou \texttt{NOT\ EXISTS} pour ne pas avoir à se soucier
des valeurs \texttt{NULL}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause ANY}\label{clause-any}

\texttt{expression\ operateur\ ANY\ (sous-requete)}

\begin{itemize}
\tightlist
\item
  L'expression de gauche est comparée au résultat de la sous-requête
  avec l'opérateur donné
\item
  La ligne de gauche est retournée

  \begin{itemize}
  \tightlist
  \item
    si le résultat d'au moins une comparaison est vraie
  \end{itemize}
\item
  La ligne de gauche n'est pas retournée

  \begin{itemize}
  \tightlist
  \item
    si aucun résultat de la comparaison n'est vrai
  \item
    si l'expression de gauche vaut \texttt{NULL}
  \item
    si la sous-requête ramène un ensemble vide
  \end{itemize}
\end{itemize}

La clause \texttt{ANY}, ou son synonyme \texttt{SOME}, permet de
comparer l'expression de gauche à chaque ligne du résultat de la
sous-requête en utilisant l'opérateur indiqué. Ainsi, la requête de
l'exemple avec la clause \texttt{IN} aurait pu être écrite avec
\texttt{=\ ANY} de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{stock}
 \KeywordTok{WHERE} \NormalTok{contenant_id = }\KeywordTok{ANY} \NormalTok{(}\KeywordTok{SELECT} \KeywordTok{id}
                          \KeywordTok{FROM} \NormalTok{contenant}
                         \KeywordTok{WHERE} \NormalTok{contenance}
                       \KeywordTok{BETWEEN} \FloatTok{0.3} \KeywordTok{AND} \FloatTok{1.0}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause ALL}\label{clause-all}

\texttt{expression\ operateur\ ALL\ (sous-requete)}

\begin{itemize}
\tightlist
\item
  L'expression de gauche est comparée à tous les résultats de la
  sous-requête avec l'opérateur donné
\item
  La ligne de gauche est retournée

  \begin{itemize}
  \tightlist
  \item
    si tous les résultats des comparaisons sont vrais
  \item
    si la sous-requête retourne un ensemble vide
  \end{itemize}
\item
  La ligne de gauche n'est pas retournée

  \begin{itemize}
  \tightlist
  \item
    si au moins une comparaison est fausse
  \item
    si au moins une comparaison est \texttt{NULL}
  \end{itemize}
\end{itemize}

La clause \texttt{ALL} permet de comparer l'expression de gauche à
chaque ligne du résultat de la sous-requête en utilisant l'opérateur de
comparaison indiqué.

La ligne de la table de gauche sera retournée si toutes les comparaisons
sont vraies ou si la sous-requête retourne un ensemble vide. En
revanche, la ligne de la table de gauche sera exclue si au moins une
comparaison est fausse ou si au moins une comparaison est \texttt{NULL}.

La requête d'exemple de la clause \texttt{NOT\ IN} aurait pu être écrite
avec \texttt{\textless{}\textgreater{}\ ALL} de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{stock}
 \KeywordTok{WHERE} \NormalTok{contenant_id <> }\KeywordTok{ALL} \NormalTok{(}\KeywordTok{SELECT} \KeywordTok{id}
                              \KeywordTok{FROM} \NormalTok{contenant}
                             \KeywordTok{WHERE} \NormalTok{contenance < }\FloatTok{2.0}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utiliser un ensemble}\label{utiliser-un-ensemble}

\begin{itemize}
\tightlist
\item
  La sous-requête retourne

  \begin{itemize}
  \tightlist
  \item
    plusieurs lignes
  \item
    sur plusieurs colonnes
  \end{itemize}
\item
  Positionnée au niveau de la clause \texttt{FROM}
\item
  Nommée avec un alias de table
\end{itemize}

La sous-requête peut être utilisée dans la clause \texttt{FROM} afin
d'être utilisée comme une table dans la requête principale. La
sous-requête devra obligatoirement être nommée avec un alias de table.
Lorsqu'elles sont issues d'un calcul, les colonnes résultantes doivent
également être nommées avec un alias de colonne afin d'éviter toute
confusion ou comportement incohérent.

La requête suivante permet de déterminer le nombre moyen de bouteilles
par années :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{AVG}\NormalTok{(nombre_total_annee) }\KeywordTok{AS} \NormalTok{moyenne}
  \KeywordTok{FROM} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{annee, }\FunctionTok{sum}\NormalTok{(nombre) }\KeywordTok{AS} \NormalTok{nombre_total_annee}
          \KeywordTok{FROM} \NormalTok{stock}
         \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{annee) stock_total_par_annee;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Clause EXISTS}\label{clause-exists}

\texttt{EXISTS\ (sous-requete)}

\begin{itemize}
\tightlist
\item
  Intéressant avec une corrélation
\item
  La clause \texttt{EXISTS} vérifie la présence ou l'absence de
  résultats

  \begin{itemize}
  \tightlist
  \item
    vrai si l'ensemble est non vide
  \item
    faux si l'ensemble est vide
  \end{itemize}
\end{itemize}

\texttt{EXISTS} présente peu d'intérêt sans corrélation entre la
sous-requête et la requête principale.

Le prédicat \texttt{EXISTS} est en général plus performant que
\texttt{IN}. Lorsqu'une requête utilisant \texttt{IN} ne peut pas être
réécrite sous la forme d'une jointure, il est recommandé d'utiliser
\texttt{EXISTS} en lieu et place de \texttt{IN}. Et à l'inverse, une
clause \texttt{NOT\ IN} sera réécrite avec \texttt{NOT\ EXISTS}.

La requête suivante permet d'identifier les vins pour lesquels il y a au
moins une bouteille en stock :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{vin}
 \KeywordTok{WHERE} \KeywordTok{EXISTS} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{*}
                 \KeywordTok{FROM} \NormalTok{stock}
                \KeywordTok{WHERE} \NormalTok{vin_id = vin.id);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Jointures}\label{jointures}

\begin{itemize}
\tightlist
\item
  Produit cartésien
\item
  Jointure interne
\item
  Jointures externes
\item
  Jointure ou sous-requête ?
\end{itemize}

Les jointures permettent d'écrire des requêtes qui impliquent plusieurs
tables. Elles permettent de combiner les colonnes de plusieurs tables
selon des critères particuliers, appelés conditions de jointures.

Les jointures permettent de tirer parti du modèle de données dans lequel
les tables sont associées à l'aide de clés étrangères.

Bien qu'il soit possible de décrire une jointure interne sous la forme
d'une requête \texttt{SELECT} portant sur deux tables dont la condition
de jointure est décrite dans la clause \texttt{WHERE}, cette forme
d'écriture n'est pas recommandée. En effet, les conditions de jointures
se trouveront mélangées avec les clauses de filtrage, rendant ainsi la
compréhension et la maintenance difficiles. Il arrive aussi que, noyé
dans les autres conditions de filtrage, l'utilisateur oublie la
configuration de jointure, ce qui aboutie à un produit cartésien,
n'ayant rien à voir avec le résultat attendu, sans même parler de la
lenteur de la requête.

Il est recommandé d'utiliser la syntaxe SQL:92 et d'exprimer les
jointures à l'aide de la clause \texttt{JOIN}. D'ailleurs, cette syntaxe
est la seule qui soit utilisable pour exprimer simplement et
efficacement une jointure externe. Cette syntaxe facilite la
compréhension de la requête mais facilite également le travail de
l'optimiseur SQL qui peut déduire beaucoup plus rapidement les jointures
qu'en analysant la clause \texttt{WHERE} pour déterminer les conditions
de jointure et les tables auxquelles elles s'appliquent le cas échéant.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Produit cartésien}\label{produit-cartuxe9sien}

\begin{itemize}
\tightlist
\item
  Clause \texttt{CROSS\ JOIN}
\item
  Réalise toutes les combinaisons entre les lignes d'une table et les
  lignes d'une autre
\item
  À éviter dans la mesure du possible

  \begin{itemize}
  \tightlist
  \item
    peu de cas d'utilisation
  \item
    peu performant
  \end{itemize}
\end{itemize}

Le produit cartésien peut être exprimé avec la clause de jointure
\texttt{CROSS\ JOIN} :

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{-- préparation du jeu de données }
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (i1 }\DataTypeTok{integer}\NormalTok{, v1 }\DataTypeTok{integer}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t2 (i2 }\DataTypeTok{integer}\NormalTok{, v2 }\DataTypeTok{integer}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 (i1, v1) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{0}\NormalTok{, }\DecValTok{0}\NormalTok{), (}\DecValTok{1}\NormalTok{, }\DecValTok{1}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t2 (i1, v1) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\DecValTok{2}\NormalTok{), (}\DecValTok{3}\NormalTok{, }\DecValTok{3}\NormalTok{);}

\CommentTok{-- requête CROSS JOIN}
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{CROSS} \KeywordTok{JOIN} \NormalTok{t2;}
 \NormalTok{i1 | v1 | i2 | v2 }
\CommentTok{----+----+----+----}
  \DecValTok{0} \NormalTok{|  }\DecValTok{0} \NormalTok{|  }\DecValTok{2} \NormalTok{|  }\DecValTok{2}
  \DecValTok{0} \NormalTok{|  }\DecValTok{0} \NormalTok{|  }\DecValTok{3} \NormalTok{|  }\DecValTok{3}
  \DecValTok{1} \NormalTok{|  }\DecValTok{1} \NormalTok{|  }\DecValTok{2} \NormalTok{|  }\DecValTok{2}
  \DecValTok{1} \NormalTok{|  }\DecValTok{1} \NormalTok{|  }\DecValTok{3} \NormalTok{|  }\DecValTok{3}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Ou plus simplement, en listant les deux tables dans la clause
\texttt{FROM} sans indiquer de condition de jointure :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1, t2;}
 \NormalTok{i1 | v1 | i2 | v2 }
\CommentTok{----+----+----+----}
  \DecValTok{0} \NormalTok{|  }\DecValTok{0} \NormalTok{|  }\DecValTok{2} \NormalTok{|  }\DecValTok{2}
  \DecValTok{0} \NormalTok{|  }\DecValTok{0} \NormalTok{|  }\DecValTok{3} \NormalTok{|  }\DecValTok{3}
  \DecValTok{1} \NormalTok{|  }\DecValTok{1} \NormalTok{|  }\DecValTok{2} \NormalTok{|  }\DecValTok{2}
  \DecValTok{1} \NormalTok{|  }\DecValTok{1} \NormalTok{|  }\DecValTok{3} \NormalTok{|  }\DecValTok{3}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Voici un autre exemple utilisant aussi un \texttt{NOT\ EXISTS} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{sondes (id_sonde }\DataTypeTok{int}\NormalTok{, nom_sonde text);}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{releves_horaires (}
    \NormalTok{id_sonde }\DataTypeTok{int}\NormalTok{, }
    \NormalTok{heure_releve timestamptz }
        \KeywordTok{check} \NormalTok{(date_trunc(}\StringTok{'hour'}\NormalTok{,heure_releve)=heure_releve), }
    \NormalTok{valeur }\DataTypeTok{numeric}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{sondes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{,}\StringTok{'sonde 1'}\NormalTok{),}
                          \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'sonde 2'}\NormalTok{),}
                          \NormalTok{(}\DecValTok{3}\NormalTok{, }\StringTok{'sonde 3'}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{releves_horaires }\KeywordTok{VALUES}
  \NormalTok{(}\DecValTok{1}\NormalTok{,}\StringTok{'2013-01-01 12:00:00'}\NormalTok{,}\DecValTok{10}\NormalTok{),}
  \NormalTok{(}\DecValTok{1}\NormalTok{,}\StringTok{'2013-01-01 13:00:00'}\NormalTok{,}\DecValTok{11}\NormalTok{),}
  \NormalTok{(}\DecValTok{1}\NormalTok{,}\StringTok{'2013-01-01 14:00:00'}\NormalTok{,}\DecValTok{12}\NormalTok{),}
  \NormalTok{(}\DecValTok{2}\NormalTok{,}\StringTok{'2013-01-01 12:00:00'}\NormalTok{,}\DecValTok{10}\NormalTok{),}
  \NormalTok{(}\DecValTok{2}\NormalTok{,}\StringTok{'2013-01-01 13:00:00'}\NormalTok{,}\DecValTok{12}\NormalTok{),}
  \NormalTok{(}\DecValTok{2}\NormalTok{,}\StringTok{'2013-01-01 14:00:00'}\NormalTok{,}\DecValTok{12}\NormalTok{),}
  \NormalTok{(}\DecValTok{3}\NormalTok{,}\StringTok{'2013-01-01 12:00:00'}\NormalTok{,}\DecValTok{10}\NormalTok{),}
  \NormalTok{(}\DecValTok{3}\NormalTok{,}\StringTok{'2013-01-01 14:00:00'}\NormalTok{,}\DecValTok{10}\NormalTok{);}

\CommentTok{-- quels sont les relevés manquants entre 12h et 14h ?}

\KeywordTok{SELECT} \NormalTok{id_sonde,}
       \NormalTok{heures_releves}
\KeywordTok{FROM} \NormalTok{sondes }
\KeywordTok{CROSS} \KeywordTok{JOIN} \NormalTok{generate_series(}\StringTok{'2013-01-01 12:00:00'}\NormalTok{,}
               \StringTok{'2013-01-01 14:00:00'}\NormalTok{,}
               \DataTypeTok{interval} \StringTok{'1 hour'}\NormalTok{) series(heures_releves)}
\KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{EXISTS}
    \NormalTok{(}\KeywordTok{SELECT} \DecValTok{1}
     \KeywordTok{FROM} \NormalTok{releves_horaires}
     \KeywordTok{WHERE} \NormalTok{releves_horaires.id_sonde=sondes.id_sonde}
       \KeywordTok{AND} \NormalTok{releves_horaires.heure_releve=series.heures_releves);}

 \NormalTok{id_sonde |     heures_releves     }
\CommentTok{----------+------------------------}
        \DecValTok{3} \NormalTok{| }\DecValTok{2013-01-01} \DecValTok{13}\CharTok{:00:00}\DecValTok{+01}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Jointure interne}\label{jointure-interne}

\begin{itemize}
\tightlist
\item
  Clause \texttt{INNER\ JOIN}

  \begin{itemize}
  \tightlist
  \item
    meilleure lisibilité
  \item
    facilite le travail de l'optimiseur
  \end{itemize}
\item
  Joint deux tables entre elles

  \begin{itemize}
  \tightlist
  \item
    Selon une condition de jointure
  \end{itemize}
\end{itemize}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Jointure interne}
\end{figure}

Une jointure interne est considérée comme un produit cartésien
accompagné d'une clause de jointure pour ne conserver que les lignes qui
répondent à la condition de jointure. Les SGBD réalisent néanmoins
l'opération plus simplement.

La condition de jointure est généralement une égalité, ce qui permet
d'associer entre elles les lignes de la table à gauche et de la table à
droite dont les colonnes de condition de jointure sont égales.

La jointure interne est exprimée à travers la clause
\texttt{INNER\ JOIN} ou plus simplement \texttt{JOIN}. En effet, si le
type de jointure n'est pas spécifié, l'optimiseur considère la jointure
comme étant une jointure interne.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Syntaxe d'une jointure
interne}\label{syntaxe-dune-jointure-interne}

\begin{itemize}
\tightlist
\item
  Condition de jointure par prédicats
  :\texttt{table1\ {[}INNER{]}\ JOIN\ table2\ ON\ prédicat\ {[}...{]}}
\item
  Condition de jointure implicite par liste des colonnes impliquées
  :\texttt{table1\ {[}INNER{]}\ JOIN\ table2\ USING\ (colonne\ {[},\ ...{]})}
\item
  Liste des colonnes implicites
  :\texttt{table1\ NATURAL\ {[}INNER{]}\ JOIN\ table2}
\end{itemize}

La clause \texttt{ON} permet d'écrire les conditions de jointures sous
la forme de prédicats tels qu'on les retrouve dans une clause
\texttt{WHERE}.

La clause \texttt{USING} permet de spécifier les colonnes sur lesquelles
porte la jointure. Les tables jointes devront posséder toutes les
colonnes sur lesquelles portent la jointure. La jointure sera réalisée
en vérifiant l'égalité entre chaque colonne portant le même nom.

La clause \texttt{NATURAL} permet de réaliser la jointure entre deux
tables en utilisant les colonnes qui portent le même nom sur les deux
tables comme condition de jointure. La forme \texttt{NATURAL\ JOIN} est
déconseillée car elle entraîne des comportements inattendus.

La requête suivante permet de joindre la table appellation avec la table
\texttt{region} pour déterminer l'origine d'une appellation :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
  \NormalTok{apl.libelle }\KeywordTok{AS} \NormalTok{appellation, }
  \NormalTok{reg.libelle }\KeywordTok{AS} \NormalTok{region}
\KeywordTok{FROM} \NormalTok{appellation apl}
\KeywordTok{JOIN} \NormalTok{region reg}
    \KeywordTok{ON} \NormalTok{(apl.region_id = reg.id);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Jointure externe}\label{jointure-externe}

\begin{itemize}
\tightlist
\item
  Jointure externe à gauche

  \begin{itemize}
  \tightlist
  \item
    ramène le résultat de la jointure interne
  \item
    ramène l'ensemble de la table de gauche qui ne peut être joint avec
    la table de droite
  \item
    les attributs de la table de droite sont alors \texttt{NULL}
  \end{itemize}
\end{itemize}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Jointure externe}
\end{figure}

Il existe deux types de jointure externe : la jointure à gauche et la
jointure à droite. Cela ne concerne que l'ordre de la jointure, le
traitement en lui-même est identique.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Jointure externe - 2}\label{jointure-externe---2}

\begin{itemize}
\tightlist
\item
  Jointure externe à droite

  \begin{itemize}
  \tightlist
  \item
    ramène le résultat de la jointure interne
  \item
    ramène l'ensemble de la table de droite qui ne peut être joint avec
    la table de gauche
  \item
    les attributs de la table de gauche sont alors \texttt{NULL}
  \end{itemize}
\end{itemize}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Jointure externe à droite}
\end{figure}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Jointure externe
complète}\label{jointure-externe-compluxe8te}

\begin{itemize}
\tightlist
\item
  Ramène le résultat de la jointure interne
\item
  Ramène l'ensemble de la table de gauche qui ne peut être joint avec la
  table de droite

  \begin{itemize}
  \tightlist
  \item
    les attributs de la table de droite sont alors \texttt{NULL}
  \end{itemize}
\item
  Ramène l'ensemble de la table de droite qui ne peut être joint avec la
  table de gauche

  \begin{itemize}
  \tightlist
  \item
    les attributs de la table de gauche sont alors \texttt{NULL}
  \end{itemize}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Syntaxe d'une jointure externe à
gauche}\label{syntaxe-dune-jointure-externe-uxe0-gauche}

\begin{itemize}
\tightlist
\item
  Condition de jointure par prédicats
  :\texttt{table1\ LEFT\ {[}OUTER{]}\ JOIN\ table2\ ON\ prédicat\ {[}...{]}}
\item
  Condition de jointure implicite par liste des colonnes impliquées
  :\texttt{table1\ LEFT\ {[}OUTER{]}\ JOIN\ table2\ USING\ (colonne\ {[},\ ...{]})}
\item
  Liste des colonnes implicites
  :\texttt{table1\ NATURAL\ LEFT\ {[}OUTER{]}\ JOIN\ table2}
\end{itemize}

Il existe trois écritures différentes d'une jointure externe à gauche.
La clause \texttt{NATURAL} permet de réaliser la jointure entre deux
tables en utilisant les colonnes qui portent le même nom sur les deux
tables comme condition de jointure.

Les voici en exemple :

\begin{itemize}
\tightlist
\item
  par prédicat :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{article.art_titre, auteur.aut_nom}
  \KeywordTok{FROM} \NormalTok{article}
  \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{auteur}
    \KeywordTok{ON} \NormalTok{(article.aut_id=auteur.aut_id);}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  par liste de colonnes :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{article.art_titre, auteur.aut_nom}
  \KeywordTok{FROM} \NormalTok{article}
  \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{auteur}
    \KeywordTok{USING} \NormalTok{(aut_id);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Syntaxe d'une jointure externe à
droite}\label{syntaxe-dune-jointure-externe-uxe0-droite}

\begin{itemize}
\tightlist
\item
  Condition de jointure par prédicats
  :\texttt{table1\ RIGHT\ {[}OUTER{]}\ JOIN\ table2\ ON\ prédicat\ {[}...{]}}
\item
  Condition de jointure implicite par liste des colonnes impliquées
  :\texttt{table1\ RIGHT\ {[}OUTER{]}\ JOIN\ table2\ USING\ (colonne\ {[},\ ...{]})}
\item
  Liste des colonnes implicites
  :\texttt{table1\ NATURAL\ RIGHT\ {[}OUTER{]}\ JOIN\ table2}
\end{itemize}

Les jointures à droite sont moins fréquentes mais elles restent
utilisées.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Syntaxe d'une jointure externe
complète}\label{syntaxe-dune-jointure-externe-compluxe8te}

\begin{itemize}
\tightlist
\item
  Condition de jointure par prédicats
  :\texttt{table1\ FULL\ OUTER\ JOIN\ table2\ ON\ prédicat\ {[}...{]}}
\item
  Condition de jointure implicite par liste des colonnes impliquées
  :\texttt{table1\ FULL\ OUTER\ JOIN\ table2\ USING\ (colonne\ {[},\ ...{]})}
\item
  Liste des colonnes implicites
  :\texttt{table1\ NATURAL\ FULL\ OUTER\ JOIN\ table2}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Jointure ou sous-requête
?}\label{jointure-ou-sous-requuxeate}

\begin{itemize}
\tightlist
\item
  Jointures

  \begin{itemize}
  \tightlist
  \item
    algorithmes très efficaces
  \item
    ne gèrent pas tous les cas
  \end{itemize}
\item
  Sous-requêtes

  \begin{itemize}
  \tightlist
  \item
    parfois peu performantes
  \item
    répondent à des besoins non couverts par les jointures
  \end{itemize}
\end{itemize}

Les sous-requêtes sont fréquemment utilisées mais elles sont moins
performantes que les jointures. Ces dernières permettent d'utiliser des
optimisations très efficaces.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Expressions CASE}\label{expressions-case}

\begin{itemize}
\tightlist
\item
  Équivalent à l'instruction \texttt{switch} en C ou Java
\item
  Emprunté au langage Ada
\item
  Retourne une valeur en fonction du résultat de tests
\end{itemize}

\texttt{CASE} permet de tester différents cas. Il s'utilise de la façon
suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT}
  \KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{col1=}\DecValTok{10} \KeywordTok{THEN} \StringTok{'dix'}
       \KeywordTok{WHEN} \NormalTok{col1>}\DecValTok{10} \KeywordTok{THEN} \StringTok{'supérieur à 10'}
       \KeywordTok{ELSE} \StringTok{'inférieur à 10'}
  \KeywordTok{END} \KeywordTok{AS} \NormalTok{test}
\KeywordTok{FROM} \NormalTok{t1;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{CASE simple}\label{case-simple}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} \NormalTok{expression}
     \KeywordTok{WHEN} \NormalTok{valeur }\KeywordTok{THEN} \NormalTok{expression}
     \KeywordTok{WHEN} \NormalTok{valeur }\KeywordTok{THEN} \NormalTok{expression}
     \NormalTok{(...)}
     \KeywordTok{ELSE} \NormalTok{expression}
 \KeywordTok{END}
\end{Highlighting}
\end{Shaded}

Il est possible de tester le résultat d'une expression avec
\texttt{CASE}. Dans ce cas, chaque clause \texttt{WHEN} reprendra la
valeur à laquelle on souhaite associé une expression particulière :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} \NormalTok{nom_region}
  \KeywordTok{WHEN} \StringTok{'Afrique'} \KeywordTok{THEN} \DecValTok{1}
  \KeywordTok{WHEN} \StringTok{'Amérique'} \KeywordTok{THEN} \DecValTok{2}
  \KeywordTok{WHEN} \StringTok{'Asie'} \KeywordTok{THEN} \DecValTok{3}
  \KeywordTok{WHEN} \StringTok{'Europe'} \KeywordTok{THEN} \DecValTok{4}
  \KeywordTok{ELSE} \DecValTok{0}
\KeywordTok{END}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{CASE sur expressions}\label{case-sur-expressions}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{expression }\KeywordTok{THEN} \NormalTok{expression}
     \KeywordTok{WHEN} \NormalTok{expression }\KeywordTok{THEN} \NormalTok{expression}
     \NormalTok{(...)}
     \KeywordTok{ELSE} \NormalTok{expression}
 \KeywordTok{END}
\end{Highlighting}
\end{Shaded}

Une expression peut être évaluée pour chaque clause \texttt{WHEN}. Dans
ce cas, l'expression \texttt{CASE} retourne la première expression qui
est vraie. Si une autre peut satisfaire la suivante, elle ne sera pas
évaluée.

Par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{salaire * prime < }\DecValTok{1300} \KeywordTok{THEN} \NormalTok{salaire * prime}
     \KeywordTok{WHEN} \NormalTok{salaire * prime < }\DecValTok{3000} \KeywordTok{THEN} \NormalTok{salaire}
     \KeywordTok{WHEN} \NormalTok{salaire * prime > }\DecValTok{5000} \KeywordTok{THEN} \NormalTok{salaire * prime}
\KeywordTok{END}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Spécificités de CASE}\label{spuxe9cificituxe9s-de-case}

\begin{itemize}
\tightlist
\item
  Comportement procédural

  \begin{itemize}
  \tightlist
  \item
    les expressions sont évaluées dans l'ordre d'apparition
  \end{itemize}
\item
  Transtypage

  \begin{itemize}
  \tightlist
  \item
    le type du retour de l'expression dépend du type de rang le plus
    élevé de toute l'expression
  \end{itemize}
\item
  Imbrication

  \begin{itemize}
  \tightlist
  \item
    des expressions \texttt{CASE} à l'intérieur d'autres expressions
    \texttt{CASE}
  \end{itemize}
\item
  Clause \texttt{ELSE}

  \begin{itemize}
  \tightlist
  \item
    recommandé
  \end{itemize}
\end{itemize}

Il est possible de placer plusieurs clauses \texttt{WHEN}. Elles sont
évaluées dans leur ordre d'apparition.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} \NormalTok{nom_region}
  \KeywordTok{WHEN} \StringTok{'Afrique'} \KeywordTok{THEN} \DecValTok{1}
  \KeywordTok{WHEN} \StringTok{'Amérique'} \KeywordTok{THEN} \DecValTok{2}
  \CommentTok{/* l'expression suivante ne sera jamais évaluée */}
  \KeywordTok{WHEN} \StringTok{'Afrique'} \KeywordTok{THEN} \DecValTok{5}
  \KeywordTok{WHEN} \StringTok{'Asie'} \KeywordTok{THEN} \DecValTok{1}
  \KeywordTok{WHEN} \StringTok{'Europe'} \KeywordTok{THEN} \DecValTok{3}
  \KeywordTok{ELSE} \DecValTok{0}
\KeywordTok{END} 
\end{Highlighting}
\end{Shaded}

Le type de données renvoyé par l'instruction \texttt{CASE} correspond au
type indiqué par l'expression au niveau des \texttt{THEN} et du
\texttt{ELSE}. Ce doit être le même type. Si les types de données ne
correspondent pas, alors PostgreSQL retournera une erreur :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*,}
  \KeywordTok{CASE} \NormalTok{nom_region}
    \KeywordTok{WHEN} \StringTok{'Afrique'} \KeywordTok{THEN} \DecValTok{1}
    \KeywordTok{WHEN} \StringTok{'Amérique'} \KeywordTok{THEN} \DecValTok{2}
    \KeywordTok{WHEN} \StringTok{'Asie'} \KeywordTok{THEN} \DecValTok{1}
    \KeywordTok{WHEN} \StringTok{'Europe'} \KeywordTok{THEN} \DecValTok{3}
    \KeywordTok{ELSE} \StringTok{'inconnu'}
  \KeywordTok{END}
 \KeywordTok{FROM} \NormalTok{regions;}
\NormalTok{ERROR:  invalid input syntax }\KeywordTok{for} \NormalTok{integer: }\OtherTok{"inconnu"}
\NormalTok{LIGNE }\DecValTok{7} \NormalTok{:     }\KeywordTok{ELSE} \StringTok{'inconnu'}
\end{Highlighting}
\end{Shaded}

La clause \texttt{ELSE} n'est pas obligatoire mais fortement recommandé.
En effet, si une expression \texttt{CASE} ne comporte pas de clause
\texttt{ELSE}, alors la base de données ajoutera une clause
\texttt{ELSE\ NULL} à l'expression.

Ainsi l'expression suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} 
  \KeywordTok{WHEN} \NormalTok{salaire < }\DecValTok{1000} \KeywordTok{THEN} \StringTok{'bas'}
  \KeywordTok{WHEN} \NormalTok{salaire > }\DecValTok{3000} \KeywordTok{THEN} \StringTok{'haut'}
\KeywordTok{END}
\end{Highlighting}
\end{Shaded}

Sera implicitement transformée de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CASE} 
  \KeywordTok{WHEN} \NormalTok{salaire < }\DecValTok{1000} \KeywordTok{THEN} \StringTok{'bas'}
  \KeywordTok{WHEN} \NormalTok{salaire > }\DecValTok{3000} \KeywordTok{THEN} \StringTok{'haut'}
  \KeywordTok{ELSE} \KeywordTok{NULL}
\KeywordTok{END}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Opérateurs ensemblistes}\label{opuxe9rateurs-ensemblistes}

\begin{itemize}
\tightlist
\item
  \texttt{UNION}
\item
  \texttt{INTERSECT}
\item
  \texttt{EXCEPT}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Regroupement de deux
ensembles}\label{regroupement-de-deux-ensembles}

\begin{itemize}
\tightlist
\item
  Regroupement avec dédoublonnage
  :\texttt{requete\_select1\ UNION\ requete\_select2}
\item
  Regroupement sans dédoublonnage
  :\texttt{requete\_select1\ UNION\ ALL\ requete\_select2}
\end{itemize}

L'opérateur ensembliste \texttt{UNION} permet de regrouper deux
ensembles dans un même résultat.

Le dédoublonnage peut être particulièrement coûteux car il implique un
tri des données.

\textbf{Exemples}

La requête suivante assemble les résultats de deux requêtes pour
produire le résultat :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{appellation}
 \KeywordTok{WHERE} \NormalTok{region_id = }\DecValTok{1}
\KeywordTok{UNION} \KeywordTok{ALL}
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{appellation}
 \KeywordTok{WHERE} \NormalTok{region_id = }\DecValTok{3}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Intersection de deux
ensembles}\label{intersection-de-deux-ensembles}

\begin{itemize}
\tightlist
\item
  Intersection de deux ensembles avec dédoublonnage
  :\texttt{requete\_select1\ INTERSECT\ requete\_select2}
\item
  Intersection de deux ensembles sans dédoublonnage
  :\texttt{requete\_select1\ INTERSECT\ ALL\ requete\_select2}
\end{itemize}

L'opérateur ensembliste \texttt{INTERSECT} permet d'obtenir
l'intersection du résultat de deux requêtes.

Le dédoublonnage peut être particulièrement coûteux car il implique un
tri des données.

\textbf{Exemples}

L'exemple suivant n'a pas d'autre intérêt que de montrer le résultat de
l'opérateur \texttt{INTERSECT} sur deux ensembles simples :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
\KeywordTok{INTERSECT}
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
 \KeywordTok{WHERE} \KeywordTok{id} \NormalTok{= }\DecValTok{3}\NormalTok{;}

 \KeywordTok{id} \NormalTok{| libelle }
\CommentTok{----+---------}
  \DecValTok{3} \NormalTok{| Alsace}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Différence entre deux
ensembles}\label{diffuxe9rence-entre-deux-ensembles}

\begin{itemize}
\tightlist
\item
  Différence entre deux ensembles avec dédoublonnage
  :\texttt{requete\_select1\ EXCEPT\ requete\_select2}
\item
  Différence entre deux ensembles sans dédoublonnage
  :\texttt{requete\_select1\ EXCEPT\ ALL\ requete\_select2}
\end{itemize}

L'opérateur ensembliste \texttt{EXCEPT} est l'équivalent de l'opérateur
\texttt{MINUS} d'Oracle. Il permet d'obtenir la différence entre deux
ensembles: toutes les lignes présentes dans les deux ensembles sont
exclues du résultat.

Le dédoublonnage peut être particulièrement coûteux car il implique un
tri des données.

\textbf{Exemples}

L'exemple suivant n'a pas d'autre intérêt que de montrer le résultat de
l'opérateur \texttt{EXCEPT} sur deux ensembles simples. La première
requête retourne l'ensemble des lignes de la table \texttt{region} alors
que la seconde requête retourne la ligne qui correspond au prédicat
\texttt{id\ =\ 3}. Cette ligne est ensuite retirée du résultat car elle
est présente dans les deux ensembles de gauche et de droite :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
\KeywordTok{EXCEPT}
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{region}
 \KeywordTok{WHERE} \KeywordTok{id} \NormalTok{= }\DecValTok{3}\NormalTok{;}

 \KeywordTok{id} \NormalTok{|          libelle           }
\CommentTok{----+----------------------------}
 \DecValTok{11} \NormalTok{| Cotes du Rhone}
 \DecValTok{12} \NormalTok{| Provence produit a Cassis.}
 \DecValTok{10} \NormalTok{| Beaujolais}
 \DecValTok{19} \NormalTok{| Savoie}
  \DecValTok{7} \NormalTok{| Languedoc-Roussillon}
  \DecValTok{4} \NormalTok{| Loire}
  \DecValTok{6} \NormalTok{| Provence}
 \DecValTok{16} \NormalTok{| Est}
  \DecValTok{8} \NormalTok{| Bordeaux}
 \DecValTok{14} \NormalTok{| Lyonnais}
 \DecValTok{15} \NormalTok{| Auvergne}
  \DecValTok{2} \NormalTok{| Bourgogne}
 \DecValTok{17} \NormalTok{| Forez}
  \DecValTok{9} \NormalTok{| Vignoble du Sud-Ouest}
 \DecValTok{18} \NormalTok{| Charente}
 \DecValTok{13} \NormalTok{| Champagne}
  \DecValTok{5} \NormalTok{| Jura}
  \DecValTok{1} \NormalTok{| Provence et Corse}
\NormalTok{(}\DecValTok{18} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Conclusion}\label{conclusion-2}

\begin{itemize}
\tightlist
\item
  Possibilité d'écrire des requêtes complexes
\item
  C'est là où PostgreSQL est le plus performant
\end{itemize}

Le standard SQL va bien plus loin que ce que les requêtes simplistes
laissent penser. Utiliser des requêtes complexes permet de décharger
l'application d'un travail conséquent et le développeur de coder quelque
chose qui existe déjà. Cela aide aussi la base de données car il est
plus simple d'optimiser une requête complexe qu'un grand nombre de
requêtes simplistes.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Questions}\label{questions-2}

N'hésitez pas, c'est le moment !

\subsection{Travaux Pratiques}\label{travaux-pratiques}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Énoncés}\label{uxe9noncuxe9s-2}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Schema de la base}
\end{figure}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  Affichez, par pays, le nombre de fournisseurs.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
           nom_pays            | nombre 
-------------------------------+--------
 ARABIE SAOUDITE               |    425
 ARGENTINE                     |    416
 (...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
  Affichez, par continent (\texttt{regions}), le nombre de fournisseurs.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
        nom_region         | nombre 
---------------------------+--------
 Afrique                   |   1906
 Moyen-Orient              |   2113
 Europe                    |   2094
 Asie                      |   2002
 Amérique                  |   1885
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
  Affichez le nombre de commandes triés selon le nombre de lignes de
  commandes.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 num | count 
-----+-------
   1 | 13733
   2 | 27816
   3 | 27750
   4 | 27967
   5 | 27687
   6 | 27876
   7 | 13895
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
  Pour les 30 premières commandes, affichez le prix total de la
  commande, en appliquant la remise accordée sur chaque article
  commandé. La sortie sera triée de la commande la plus chère à la
  commande la moins chère.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 numero_commande | prix_total 
-----------------+------------
               3 |  259600.00
              40 |  258959.00
               6 |  249072.00
              69 |  211330.00
              70 |  202101.00
               4 |  196132.00
 (...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
  Affichez, par année, le total des ventes. La date de commande fait
  foi. La sortie sera triée par année.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 annee |  total_vente  
-------+---------------
  2005 | 3627568010.00
  2006 | 3630975501.00
  2007 | 3627112891.00
(...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
  Pour toutes les commandes, calculez le temps moyen de livraison,
  depuis la date d'expédition. Le temps de livraison moyen sera exprimé
  en jours, arrondi à l'entier supérieur (fonction \texttt{ceil()}).
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 temps_moyen_livraison 
-----------------------
 8 jour(s)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\tightlist
\item
  Pour les 30 premières commandes, calculez le temps moyen de livraison
  de chaque commande, depuis la date de commande. Le temps de livraison
  moyen sera exprimé en jours, arrondi à l'entier supérieur (fonction
  \texttt{ceil()}).
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 temps_moyen_livraison 
-----------------------
 38 jour(s)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\tightlist
\item
  Déterminez le taux de retour des marchandises (\texttt{R} indiquant
  qu'une marchandise est retournée).
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 taux_retour 
-------------
       24.29
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{8}
\tightlist
\item
  Déterminez le mode d'expédition qui est le plus rapide, en moyenne.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 mode_expedition |       delai        
-----------------+--------------------
 AIR             | 7.4711070230494535
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{9}
\tightlist
\item
  Un bug applicatif est soupçonné, déterminez s'il existe des commandes
  dont la date de commande est postérieure à la date de livraison des
  articles.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 count 
-------
     2
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{10}
\item
  Écrivez une requête qui corrige les données erronés en positionnant la
  date de commande à la date de livraison la plus ancienne des
  marchandises. Vérifiez qu'elle soit correcte. Cette requête permet de
  corriger des calculs de statistiques sur les délais de livraison.
\item
  Écrivez une requête qui calcule le délai total maximal de livraison de
  la totalité d'une commande donnée, depuis la date de la commande.
\end{enumerate}

Sortie attendue pour la commande n°1 :

\begin{verbatim}
delai_max 
-----------
       102
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{12}
\tightlist
\item
  Écrivez une requête pour déterminer les 10 commandes dont le délai de
  livraison, entre la date de commande et la date de réception, est le
  plus important, pour l'année 2011 uniquement.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 numero_commande | delai 
-----------------+-------
          413510 |   146
          123587 |   143
          224453 |   143
(...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{13}
\tightlist
\item
  Un autre bug applicatif est détecté. Certaines commandes n'ont pas de
  lignes de commandes. Écrivez une requête pour les retrouver.
\end{enumerate}

\begin{verbatim}
numero_commande | client_id | etat_commande | prix_total | date_commande | priorite_commande |      vendeur      | priorite_expedition |  commentaire  
-----------------+-----------+---------------+------------+---------------+-------------------+-------------------+---------------------+---------------
           91495 |     93528 | P             |            | 2007-07-07    | 4-NOT SPECIFIED   | Vendeur 000006761 |                   0 | xxxxxxxxxxxxx
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{14}
\item
  Écrivez une requête pour supprimer ces commandes. Vérifiez le travail
  avant de valider.
\item
  Écrivez une requête pour déterminer les 20 pièces qui ont eu le plus
  gros volume de commande.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
                    nom                     |  sum   
--------------------------------------------+--------
 lemon black goldenrod seashell plum        | 461.00
 brown lavender dim white indian            | 408.00
 burlywood white chiffon blanched lemon     | 398.00
(...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{16}
\tightlist
\item
  Affichez les fournisseurs des 20 pièces qui ont été le plus commandée
  sur l'année 2011.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
     nom      | piece_id 
--------------+----------
 Supplier4395 |   191875
 Supplier4397 |   191875
 Supplier6916 |   191875
 Supplier9434 |   191875
 Supplier4164 |    11662
 Supplier6665 |    11662
(...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{17}
\tightlist
\item
  Affichez le pays qui a connu, en nombre, le plus de commandes sur
  l'année 2011.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
    nom_pays     | count 
-----------------+-------
 ARABIE SAOUDITE |  1074
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{18}
\tightlist
\item
  Affichez pour les commandes passées en 2011, la liste des continents
  et la marge brute d'exploitation réalisée par continents, triés dans
  l'ordre décroissant.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
        nom_region         |   benefice    
---------------------------+---------------
 Moyen-Orient              | 2008595508.00
(...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{19}
\tightlist
\item
  Affichez le nom, le numéro de téléphone et le pays des fournisseurs
  qui ont un commentaire contenant le mot clé \texttt{Complaints} :
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 nom_fournisseur |    telephone    |           nom_pays            
-----------------+-----------------+-------------------------------
 Supplier3873    | 10-741-199-8614 | IRAN, RÉPUBLIQUE ISLAMIQUE D'
(...)
\end{verbatim}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{20}
\tightlist
\item
  Déterminez le top 10 des fournisseurs ayant eu le plus long délai de
  livraison, entre la date de commande et la date de réception, pour
  l'année 2011 uniquement.
\end{enumerate}

Sortie attendue :

\begin{verbatim}
 fournisseur_id | nom_fournisseur | delai 
----------------+-----------------+-------
           9414 | Supplier9414    |   146
(...)
\end{verbatim}

\subsubsection{Solutions}\label{solutions-2}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  Affichez, par pays, le nombre de fournisseurs.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{p.nom_pays, }\FunctionTok{count}\NormalTok{(*)}
\KeywordTok{FROM} \NormalTok{fournisseurs f}
   \KeywordTok{JOIN} \NormalTok{contacts c }\KeywordTok{ON} \NormalTok{f.contact_id = c.contact_id}
   \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{c.code_pays = p.code_pays}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{p.nom_pays}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
  Affichez, par continent, le nombre de fournisseurs.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{r.nom_region, }\FunctionTok{count}\NormalTok{(*)}
\KeywordTok{FROM} \NormalTok{fournisseurs f}
   \KeywordTok{JOIN} \NormalTok{contacts c }\KeywordTok{ON} \NormalTok{f.contact_id = c.contact_id}
   \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{c.code_pays = p.code_pays}
   \KeywordTok{JOIN} \NormalTok{regions r }\KeywordTok{ON} \NormalTok{p.region_id = r.region_id}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{r.nom_region}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
  Affichez le nombre de commandes triés selon le nombre de lignes de
  commandes contenues dans chaque commande.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT}
   \NormalTok{nombre_lignes_commandes,}
   \FunctionTok{count}\NormalTok{(*) }\KeywordTok{AS} \NormalTok{nombre_total_commandes}
\KeywordTok{FROM} \NormalTok{(}
   \CommentTok{/* cette sous-requête permet de compter le nombre de lignes de commande de}
\CommentTok{      chaque commande, et remonte cette information à la requête principale */}
   \KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(numero_ligne_commande) }\KeywordTok{AS} \NormalTok{nombre_lignes_commandes}
   \KeywordTok{FROM} \NormalTok{lignes_commandes}
   \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{numero_commande}
   \NormalTok{) comm_agg}
\CommentTok{/* la requête principale aggrège et trie les données sur ce nombre de lignes }
\CommentTok{   de commandes pour compter le nombre de commandes distinctes ayant le même}
\CommentTok{   nombre de lignes de commandes */}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{nombre_lignes_commandes}
\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{nombre_lignes_commandes }\KeywordTok{DESC}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\tightlist
\item
  Pour les 30 premières commandes (par rapport à leur date de commande),
  affichez le prix total de la commande, en appliquant la remise
  accordée sur chaque article commandé.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{c.numero_commande, }\FunctionTok{sum}\NormalTok{(quantite * prix_unitaire - remise) prix_total}
\KeywordTok{FROM} \NormalTok{(}
   \KeywordTok{SELECT} \NormalTok{numero_commande, date_commande}
   \KeywordTok{FROM} \NormalTok{commandes}
   \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{date_commande}
   \KeywordTok{LIMIT} \DecValTok{30}
   \NormalTok{) c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{c.numero_commande}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{sum}\NormalTok{(quantite * prix_unitaire - remise) }\KeywordTok{DESC}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
  Affichez, par année, le total des ventes. La date de commande fait
  foi.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT}
   \FunctionTok{extract} \NormalTok{(}\StringTok{'year'} \KeywordTok{FROM} \NormalTok{date_commande), }
   \FunctionTok{sum}\NormalTok{(quantite * prix - remise) }\KeywordTok{AS} \NormalTok{prix_total}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
   \KeywordTok{JOIN} \NormalTok{pieces p }\KeywordTok{ON} \NormalTok{lc.piece_id = p.piece_id}
\KeywordTok{GROUP} \KeywordTok{BY} \FunctionTok{extract} \NormalTok{(}\StringTok{'year'} \KeywordTok{FROM} \NormalTok{date_commande)}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{extract} \NormalTok{(}\StringTok{'year'} \KeywordTok{FROM} \NormalTok{date_commande)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
  Pour toutes les commandes, calculez le temps moyen de livraison,
  depuis la date d'expédition. Le temps de livraison moyen sera exprimé
  en jours, arrondi à l'entier supérieur (fonction \texttt{ceil()}).
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{ceil}\NormalTok{(}\FunctionTok{avg}\NormalTok{(date_reception - date_expedition)):}\CharTok{:text} \NormalTok{|| }\StringTok{' jour(s)'}
\KeywordTok{FROM} \NormalTok{lignes_commandes lc}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\tightlist
\item
  Pour les 30 commandes les plus récentes (par rapport à leur date de
  commande), calculez le temps moyen de livraison de chaque commande,
  depuis la date de commande. Le temps de livraison moyen sera exprimé
  en jours, arrondi à l'entier supérieur (fonction \texttt{ceil()}).
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*), }\FunctionTok{ceil}\NormalTok{(}\FunctionTok{avg}\NormalTok{(date_reception - date_commande)):}\CharTok{:text} \NormalTok{|| }\StringTok{' jour(s)'}
\KeywordTok{FROM} \NormalTok{(}
   \KeywordTok{SELECT} \NormalTok{numero_commande, date_commande}
   \KeywordTok{FROM} \NormalTok{commandes}
   \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{date_commande }\KeywordTok{DESC}
   \KeywordTok{LIMIT} \DecValTok{30}
   \NormalTok{) c}
\KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande ;}
\end{Highlighting}
\end{Shaded}

Note : la colonne \texttt{date\_commande} de la table \texttt{commandes}
n'a pas de contrainte \texttt{NOT\ NULL}, il est donc possible d'avoir
des commandes sans date de commande renseignée. Dans ce cas, ces
commandes vont remonter par défaut en haut de la liste, puisque la
clause \texttt{ORDER\ BY} renvoie les \texttt{NULL} après les valeurs
les plus grandes, et que l'on inverse le tri. Pour éviter que ces
commandes ne faussent les résultats, il faudrait donc les exclure de la
sous-requête, de la façon suivante :

\begin{Shaded}
\begin{Highlighting}[]
   \KeywordTok{SELECT} \NormalTok{numero_commande, date_commande}
   \KeywordTok{FROM} \NormalTok{commandes}
   \KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{IS} \KeywordTok{NOT} \KeywordTok{NULL}
   \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{date_commande }\KeywordTok{DESC}
   \KeywordTok{LIMIT} \DecValTok{30}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\tightlist
\item
  Déterminez le taux de retour des marchandises (\texttt{R} indiquant
  qu'une marchandise est retournée).
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
   \FunctionTok{round}\NormalTok{(}
      \FunctionTok{sum}\NormalTok{(}
         \KeywordTok{CASE} \NormalTok{etat_retour}
            \KeywordTok{WHEN} \StringTok{'R'} \KeywordTok{THEN} \FloatTok{1.0}
            \KeywordTok{ELSE} \FloatTok{0.0}
         \KeywordTok{END}
      \NormalTok{) / }\FunctionTok{count}\NormalTok{(*):}\CharTok{:numeric} \NormalTok{* }\DecValTok{100}\NormalTok{, }
      \DecValTok{2}
   \NormalTok{):}\CharTok{:text} \NormalTok{|| }\StringTok{' %'} \KeywordTok{AS} \NormalTok{taux_retour}
\KeywordTok{FROM} \NormalTok{lignes_commandes}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

À partir de la version 9.4 de PostgreSQL, la clause \texttt{FILTER} des
fonctions d'aggrégation permet d'écrire une telle requête plus
facilement :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
   \FunctionTok{round}\NormalTok{(}
      \FunctionTok{count}\NormalTok{(*) }\KeywordTok{FILTER} \NormalTok{(}\KeywordTok{WHERE} \NormalTok{etat_retour = }\StringTok{'R'}\NormalTok{) / }\FunctionTok{count}\NormalTok{(*):}\CharTok{:numeric} \NormalTok{* }\DecValTok{100}\NormalTok{,}
      \DecValTok{2}
   \NormalTok{):}\CharTok{:text} \NormalTok{|| }\StringTok{' %'} \KeywordTok{AS} \NormalTok{taux_retour}
\KeywordTok{FROM} \NormalTok{lignes_commandes}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{8}
\tightlist
\item
  Déterminez le mode d'expédition qui est le plus rapide, en moyenne.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{mode_expedition, }\FunctionTok{avg}\NormalTok{(date_reception - date_expedition)}
\KeywordTok{FROM} \NormalTok{lignes_commandes lc}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{mode_expedition}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{avg}\NormalTok{(date_reception - date_expedition) }\KeywordTok{ASC}
\KeywordTok{LIMIT} \DecValTok{1}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{9}
\tightlist
\item
  Un bug applicatif est soupçonné, déterminez s'il existe des commandes
  dont la date de commande est postérieure à la date d'expédition des
  articles.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
      \KeywordTok{AND} \NormalTok{c.date_commande > lc.date_expedition}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{10}
\tightlist
\item
  Écrivez une requête qui corrige les données erronés en positionnant la
  date de commande à la date d'expédition la plus ancienne des
  marchandises. Vérifiez qu'elle soit correcte. Cette requête permet de
  corriger des calculs de statistiques sur les délais de livraison.
\end{enumerate}

Afin de se protéger d'une erreur de manipulation, on ouvre une
transaction :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{commandes c_up}
\KeywordTok{SET} \NormalTok{date_commande = (}
   \KeywordTok{SELECT} \FunctionTok{min}\NormalTok{(date_expedition)}
   \KeywordTok{FROM} \NormalTok{commandes c}
      \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{lc.numero_commande = c.numero_commande}
         \KeywordTok{AND} \NormalTok{c.date_commande > lc.date_expedition}
   \KeywordTok{WHERE} \NormalTok{c.numero_commande = c_up.numero_commande}
   \NormalTok{)}
\KeywordTok{WHERE} \KeywordTok{EXISTS} \NormalTok{(}
   \KeywordTok{SELECT} \DecValTok{1}
   \KeywordTok{FROM} \NormalTok{commandes c2}
      \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{lc.numero_commande = c2.numero_commande}
         \KeywordTok{AND} \NormalTok{c2.date_commande > lc.date_expedition}
      \KeywordTok{WHERE} \NormalTok{c_up.numero_commande  = c2.numero_commande}
   \KeywordTok{GROUP} \KeywordTok{BY} \DecValTok{1}
   \NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

La requête réalisée précédemment doit à présent retourner 0 :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
      \KeywordTok{AND} \NormalTok{c.date_commande > lc.date_expedition}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Si c'est le cas, on valide la transaction :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{COMMIT}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Si ce n'est pas le cas, il doit y avoir une erreur dans la transaction,
on l'annule :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ROLLBACK}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{11}
\tightlist
\item
  Écrivez une requête qui calcule le délai total maximal de livraison de
  la totalité d'une commande donnée, depuis la date de la commande.
\end{enumerate}

Par exemple pour la commande dont le numéro de commande est le
\texttt{1} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{max}\NormalTok{(date_reception - date_commande)}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
\KeywordTok{WHERE} \NormalTok{c.numero_commande = }\DecValTok{1}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{12}
\tightlist
\item
  Écrivez une requête pour déterminer les 10 commandes dont le délai de
  livraison, entre la date de commande et la date de réception, est le
  plus important, pour l'année 2011 uniquement.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
   \NormalTok{c.numero_commande,}
   \FunctionTok{max}\NormalTok{(date_reception - date_commande)}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
\KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{) }\KeywordTok{AND} \FunctionTok{to_date}\NormalTok{(}\StringTok{'31/12/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{c.numero_commande}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{max}\NormalTok{(date_reception - date_commande) }\KeywordTok{DESC}
\KeywordTok{LIMIT} \DecValTok{10}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{13}
\tightlist
\item
  Un autre bug applicatif est détecté. Certaines commandes n'ont pas de
  lignes de commandes. Écrivez une requête pour les retrouver.
\end{enumerate}

Pour réaliser cette requête, il faut effectuer une jointure spéciale,
nommée «~Anti-jointure~». Il y a plusieurs façons d'écrire ce type de
jointure. Les différentes méthodes sont données de la moins efficace à
la plus efficace.

La version la moins performante est la suivante, avec \texttt{NOT\ IN} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{c.numero_commande}
\KeywordTok{FROM} \NormalTok{commandes}
\KeywordTok{WHERE} \NormalTok{numero_commande }\KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}
   \KeywordTok{SELECT} \NormalTok{numero_commande}
   \KeywordTok{FROM} \NormalTok{lignes_commandes}
   \NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Il n'y a aucune corrélation entre la requête principale et la
sous-requête. PostgreSQL doit donc vérifier pour chaque ligne de
\texttt{commandes} que \texttt{numero\_commande} n'est pas présent dans
l'ensemble retourné par la sous-requête. Il est préférable d'éviter
cette syntaxe.

Autre écriture possible, avec \texttt{LEFT\ JOIN} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{c.numero_commande}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
\CommentTok{/* c'est le filtre suivant qui permet de ne conserver que les lignes de la }
\CommentTok{   table commandes qui n'ont pas de correspondance avec la table }
\CommentTok{   numero_commandes */}
\KeywordTok{WHERE} \NormalTok{lc.numero_commande }\KeywordTok{IS} \KeywordTok{NULL}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Enfin, l'écriture généralement préférée, tant pour la lisibilité que
pour les performances, avec \texttt{NOT\ EXISTS} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{c.numero_commande}
\KeywordTok{FROM} \NormalTok{commandes c}
\KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{EXISTS} \NormalTok{(}
   \KeywordTok{SELECT} \DecValTok{1}
   \KeywordTok{FROM} \NormalTok{lignes_commandes lc}
   \KeywordTok{WHERE} \NormalTok{lc.numero_commande = c.numero_commande}
   \NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{14}
\tightlist
\item
  Écrivez une requête pour supprimer ces commandes. Vérifiez le travail
  avant de valider.
\end{enumerate}

Afin de se protéger d'une erreur de manipulation, on ouvre une
transaction :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

La requête permettant de supprimer ces commandes est dérivée de la
version \texttt{NOT\ EXISTS} de la requête ayant permis de trouver le
problème :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DELETE}
\KeywordTok{FROM} \NormalTok{commandes c}
\KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{EXISTS} \NormalTok{(}
   \KeywordTok{SELECT} \DecValTok{1}
   \KeywordTok{FROM} \NormalTok{lignes_commandes lc}
   \KeywordTok{WHERE} \NormalTok{lc.numero_commande = c.numero_commande}
   \NormalTok{)}
\CommentTok{-- on peut renvoyer directement les numeros de commande qui ont été supprimés :}
\KeywordTok{RETURNING} \NormalTok{numero_commande}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Pour vérifier que le problème est corrigé :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
\KeywordTok{FROM} \NormalTok{commandes c}
\KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{EXISTS} \NormalTok{(}
   \KeywordTok{SELECT} \DecValTok{1}
   \KeywordTok{FROM} \NormalTok{lignes_commandes lc}
   \KeywordTok{WHERE} \NormalTok{lc.numero_commande = c.numero_commande}
   \NormalTok{)}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Si la requête ci-dessus remonte \texttt{0}, alors la transaction peut
être validée:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{COMMIT}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{15}
\tightlist
\item
  Écrivez une requête pour déterminer les 20 pièces qui ont eu le plus
  gros volume de commande.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{p.nom,}
   \FunctionTok{sum}\NormalTok{(quantite)}
\KeywordTok{FROM} \NormalTok{pieces p}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{p.piece_id = lc.piece_id}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{p.nom}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{sum}\NormalTok{(quantite) }\KeywordTok{DESC}
\KeywordTok{LIMIT} \DecValTok{20}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{16}
\tightlist
\item
  Affichez les fournisseurs des 20 pièces qui ont été le plus commandée
  sur l'année 2011.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{co.nom, max_p.piece_id, total_pieces}
\KeywordTok{FROM} \NormalTok{(}
   \CommentTok{/* cette sous-requête est sensiblement la même que celle de l'exercice }
\CommentTok{      précédent, sauf que l'on remonte cette fois l'id de la piece plutôt }
\CommentTok{      que son nom pour pouvoir faire la jointure avec pieces_fournisseurs, et}
\CommentTok{      que l'on ajoute une jointure avec commandes pour pouvoir filtrer sur }
\CommentTok{      l'année 2011 */}
   \KeywordTok{SELECT} 
      \NormalTok{p.piece_id,}
      \FunctionTok{sum}\NormalTok{(quantite) }\KeywordTok{AS} \NormalTok{total_pieces}
   \KeywordTok{FROM} \NormalTok{pieces p}
      \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{p.piece_id = lc.piece_id}
      \KeywordTok{JOIN} \NormalTok{commandes c }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
   \KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{) }\KeywordTok{AND} \FunctionTok{to_date}\NormalTok{(}\StringTok{'31/12/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
   \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{p.piece_id}
   \KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{sum}\NormalTok{(quantite) }\KeywordTok{DESC}
   \KeywordTok{LIMIT} \DecValTok{20}
   \NormalTok{) max_p}
   \CommentTok{/* il faut passer par la table de liens pieces_fournisseurs pour récupérer }
\CommentTok{      la liste des fournisseurs d'une piece */}
   \KeywordTok{JOIN} \NormalTok{pieces_fournisseurs pf }\KeywordTok{ON} \NormalTok{max_p.piece_id = pf.piece_id}
   \KeywordTok{JOIN} \NormalTok{fournisseurs f }\KeywordTok{ON} \NormalTok{f.fournisseur_id = pf.fournisseur_id}
   \CommentTok{-- la jointure avec la table contact permet d'afficher le nom du fournisseur}
   \KeywordTok{JOIN} \NormalTok{contacts co }\KeywordTok{ON} \NormalTok{f.contact_id = co.contact_id}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{17}
\tightlist
\item
  Affichez le pays qui a connu, en nombre, le plus de commandes sur
  l'année 2011.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom_pays,}
   \FunctionTok{count}\NormalTok{(c.numero_commande)}
\KeywordTok{FROM} \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{clients cl }\KeywordTok{ON} \NormalTok{(c.client_id = cl.client_id)}
   \KeywordTok{JOIN} \NormalTok{contacts co }\KeywordTok{ON} \NormalTok{(cl.contact_id = co.contact_id)}
   \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{(co.code_pays = p.code_pays)}
\KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{) }\KeywordTok{AND} \FunctionTok{to_date}\NormalTok{(}\StringTok{'31/12/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{p.nom_pays}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{count}\NormalTok{(c.numero_commande) }\KeywordTok{DESC}
\KeywordTok{LIMIT} \DecValTok{1}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{18}
\tightlist
\item
  Affichez pour les commandes passées en 2011, la liste des régions et
  la marge brute d'exploitation réalisée par régions, triés dans l'ordre
  décroissant.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
   \NormalTok{nom_region,}
   \FunctionTok{round}\NormalTok{(}\FunctionTok{sum}\NormalTok{(quantite * prix - remise) - }\FunctionTok{sum}\NormalTok{(quantite * cout_piece), }\DecValTok{2}\NormalTok{) }\KeywordTok{AS} \NormalTok{marge_brute}
\KeywordTok{FROM} 
   \NormalTok{commandes c}
   \KeywordTok{JOIN} \NormalTok{lignes_commandes lc }\KeywordTok{ON} \NormalTok{lc.numero_commande = c.numero_commande}
   \CommentTok{/* il faut passer par la table de liens pieces_fournisseurs pour récupérer }
\CommentTok{      la liste des fournisseurs d'une piece - attention, la condition de }
\CommentTok{      jointure entre lignes_commandes et pieces_fournisseurs porte sur deux }
\CommentTok{      colonnes ! */}
   \KeywordTok{JOIN} \NormalTok{pieces_fournisseurs pf }\KeywordTok{ON} \NormalTok{lc.piece_id = pf.piece_id }
      \KeywordTok{AND} \NormalTok{lc.fournisseur_id = pf.fournisseur_id}
   \KeywordTok{JOIN} \NormalTok{pieces p }\KeywordTok{ON} \NormalTok{p.piece_id = pf.piece_id}
   \KeywordTok{JOIN} \NormalTok{fournisseurs f }\KeywordTok{ON} \NormalTok{f.fournisseur_id = pf.fournisseur_id}
   \KeywordTok{JOIN} \NormalTok{clients cl }\KeywordTok{ON} \NormalTok{c.client_id = cl.client_id}
   \KeywordTok{JOIN} \NormalTok{contacts co }\KeywordTok{ON} \NormalTok{cl.contact_id = co.contact_id}
   \KeywordTok{JOIN} \NormalTok{pays pa }\KeywordTok{ON} \NormalTok{co.code_pays = pa.code_pays}
   \KeywordTok{JOIN} \NormalTok{regions r }\KeywordTok{ON} \NormalTok{r.region_id = pa.region_id}
\KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{) }\KeywordTok{AND} \FunctionTok{to_date}\NormalTok{(}\StringTok{'31/12/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{nom_region}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{sum}\NormalTok{(quantite * prix - remise) - }\FunctionTok{sum}\NormalTok{(quantite * cout_piece) }\KeywordTok{DESC}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{19}
\tightlist
\item
  Affichez le nom, le numéro de téléphone et le pays des fournisseurs
  qui ont un commentaire contenant le mot clé \texttt{Complaints} :
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} 
   \NormalTok{nom,}
   \NormalTok{telephone,}
   \NormalTok{nom_pays}
\KeywordTok{FROM} 
   \NormalTok{fournisseurs f}
   \KeywordTok{JOIN} \NormalTok{contacts c }\KeywordTok{ON} \NormalTok{f.contact_id = c.contact_id}
   \KeywordTok{JOIN} \NormalTok{pays p }\KeywordTok{ON} \NormalTok{c.code_pays = p.code_pays}
\KeywordTok{WHERE} \NormalTok{f.commentaire }\KeywordTok{LIKE} \StringTok{'%Complaints%'}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{20}
\tightlist
\item
  Déterminez le top 10 des fournisseurs ayant eu le plus long délai de
  livraison, entre la date de commande et la date de réception, pour
  l'année 2011 uniquement.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT}
   \NormalTok{f.fournisseur_id,}
   \NormalTok{co.nom,}
   \FunctionTok{max}\NormalTok{(date_reception - date_commande)}
\KeywordTok{FROM} 
   \NormalTok{lignes_commandes lc}
   \KeywordTok{JOIN} \NormalTok{commandes c }\KeywordTok{ON} \NormalTok{c.numero_commande = lc.numero_commande}
   \KeywordTok{JOIN} \NormalTok{pieces_fournisseurs pf }\KeywordTok{ON} \NormalTok{lc.piece_id = pf.piece_id }
      \KeywordTok{AND} \NormalTok{lc.fournisseur_id = pf.fournisseur_id}
   \KeywordTok{JOIN} \NormalTok{fournisseurs f }\KeywordTok{ON} \NormalTok{pf.fournisseur_id = f.fournisseur_id}
   \KeywordTok{JOIN} \NormalTok{contacts co }\KeywordTok{ON} \NormalTok{f.contact_id = co.contact_id}
\KeywordTok{WHERE} \NormalTok{date_commande }\KeywordTok{BETWEEN} \FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{) }\KeywordTok{AND} \FunctionTok{to_date}\NormalTok{(}\StringTok{'31/12/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{f.fournisseur_id, co.nom}
\KeywordTok{ORDER} \KeywordTok{BY} \FunctionTok{max}\NormalTok{(date_reception - date_commande) }\KeywordTok{DESC}
\KeywordTok{LIMIT} \DecValTok{10}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\section{Approfondissement SQL}\label{approfondissement-sql}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Préambule}\label{pruxe9ambule-2}

\begin{itemize}
\tightlist
\item
  Aller encore plus loin

  \begin{itemize}
  \tightlist
  \item
    tout en évitant le pire
  \end{itemize}
\item
  Appréhender de nouveaux objets

  \begin{itemize}
  \tightlist
  \item
    fonctions utiles
  \item
    vues
  \end{itemize}
\item
  Utiliser des requêtes préparées
\end{itemize}

Notre tour du SQL va se terminer. Il nous reste encore à voir des objets
spéciaux, souvent utilisés, comme les vues et les procédures stockées.
Nous allons aussi nous intéresser aux requêtes préparées. Nous
terminerons avec une liste de recommendations pour éviter une mauvaise
utilisation du SQL.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Menu}\label{menu-3}

\begin{itemize}
\tightlist
\item
  Fonctions de base
\item
  Vues
\item
  Requêtes préparées
\item
  Index
\item
  Ce qu'il ne faut pas faire
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Objectifs}\label{objectifs-3}

\begin{itemize}
\tightlist
\item
  Utiliser des objets avancées
\item
  Gagner en performance
\item
  Éviter les pièges les plus fréquents
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Fonctions de base}\label{fonctions-de-base}

\begin{itemize}
\tightlist
\item
  Transtypage
\item
  Manipulation de chaines
\item
  Manipulation de types numériques
\item
  Manipulation de dates
\item
  Génération de jeu de données
\end{itemize}

PostgreSQL propose un nombre conséquent de fonctions permettant de
manipuler les différents types de données disponibles. Les étudier de
façon exhaustive n'est pas l'objet de ce module. Néanmoins, le manuel de
PostgreSQL établie une liste complète des fonctions disponibles dans le
SGBD :

http://docs.postgresql.fr/9.2/functions.html

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Transtypage}\label{transtypage}

\begin{itemize}
\tightlist
\item
  Conversion d'un type de données vers un autre type de données
\item
  CAST ( \texttt{expression} AS \texttt{type} )
\item
  \texttt{expression}::\texttt{type}
\end{itemize}

Les opérateurs de transtypages permettent de convertir une donnée d'un
type particulier vers un autre type. La conversion échoue si les types
de données sont incompatibles.

\textbf{Exemples}

Transtypage incorrect d'une chaîne de caractères vers un entier :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \StringTok{'toto'}\NormalTok{:}\CharTok{:integer}\NormalTok{;}
\NormalTok{ERROR:  invalid input syntax }\KeywordTok{for} \NormalTok{integer: }\OtherTok{"toto"}
\NormalTok{LINE }\DecValTok{1}\NormalTok{: }\KeywordTok{SELECT} \StringTok{'toto'}\NormalTok{:}\CharTok{:integer}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Opérations simples sur les
chaînes}\label{opuxe9rations-simples-sur-les-chauxeenes}

\begin{itemize}
\tightlist
\item
  Concaténation :\texttt{chaîne1\ \textbar{}\textbar{}\ chaîne2}
\item
  Longueur de la chaîne :\texttt{char\_length(chaîne)}
\item
  Conversion en minuscules :\texttt{lower(chaîne)}
\item
  Conversion en majuscules :\texttt{upper(chaîne)}
\end{itemize}

L'opérateur de concaténation permet de concaténer deux chaînes de
caractères :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \StringTok{'Bonjour'} \NormalTok{|| }\StringTok{', Monde!'}\NormalTok{;   }
\CommentTok{-----------------}
 \NormalTok{Bonjour, Monde!}
\end{Highlighting}
\end{Shaded}

Il permet aussi de concaténer une chaîne de caractères avec d'autres
type de données :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \StringTok{'Texte '} \NormalTok{|| }\DecValTok{1}\NormalTok{:}\CharTok{:integer}\NormalTok{;}
\CommentTok{---------}
 \NormalTok{Texte }\DecValTok{1}
\end{Highlighting}
\end{Shaded}

La fonction \texttt{char\_length()} permet de connaître la longueur
d'une chaîne de caractères :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{char_length(}\StringTok{'Texte'} \NormalTok{|| }\DecValTok{1}\NormalTok{:}\CharTok{:integer}\NormalTok{);}
\CommentTok{-------------}
           \DecValTok{6}
\end{Highlighting}
\end{Shaded}

Les fonctions \texttt{lower} et \texttt{upper} permettent de convertir
une chaîne respectivement en minuscule et en majuscule :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{lower}\NormalTok{(}\StringTok{'Bonjour, Monde!'}\NormalTok{);}
\CommentTok{-----------------}
 \NormalTok{bonjour, monde!}

\KeywordTok{SELECT} \FunctionTok{upper}\NormalTok{(}\StringTok{'Bonjour, Monde!'}\NormalTok{); }
\CommentTok{-----------------}
 \NormalTok{BONJOUR, MONDE!}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Manipulations de
chaînes}\label{manipulations-de-chauxeenes}

\begin{itemize}
\tightlist
\item
  Extrait une chaîne à partir d'une autre:
  \texttt{substring(chaîne\ {[}from\ int{]}\ {[}for\ int{]})}
\item
  Emplacement d'une sous-chaîne:
  \texttt{position(sous-chaîne\ in\ chaîne)}
\end{itemize}

La fonction \texttt{substring} permet d'extraire une chaîne de caractère
à partir d'une chaîne en entrée. Il faut lui indiquer, en plus de la
chaîne source, la position de départ, et la longeur de la sous-chaîne.
Par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{substring(}\StringTok{'Bonjour, Monde'} \KeywordTok{from} \DecValTok{5} \KeywordTok{for} \DecValTok{4}\NormalTok{);}
 \NormalTok{substring }
\CommentTok{-----------}
 \NormalTok{our,}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Notez que vous pouvez aussi utiliser un appel de fonction plus standard
:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{substring(}\StringTok{'Bonjour, Monde'}\NormalTok{, }\DecValTok{5}\NormalTok{, }\DecValTok{4}\NormalTok{);}
 \NormalTok{substring }
\CommentTok{-----------}
 \NormalTok{our,}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La fonction \texttt{position} indique la position d'une chaîne de
caractère dans la chaîne indiquée. Par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{position (}\StringTok{','} \KeywordTok{in} \StringTok{'Bonjour, Monde'}\NormalTok{);}
 \NormalTok{position }
\CommentTok{----------}
        \DecValTok{8}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La combinaison des deux est intéressante :

\begin{verbatim}
SELECT version();
                                                   version                                                    
--------------------------------------------------------------------------------------------------------------
 PostgreSQL 9.2.4 on x86_64-unknown-linux-gnu, compiled by gcc (GCC) 4.7.2 20121109 (Red Hat 4.7.2-8), 64-bit
(1 row)

SELECT substring(version() from 1 for position(' on' in version()));
     substring     
-------------------
 PostgreSQL 9.2.4
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Manipulation de types
numériques}\label{manipulation-de-types-numuxe9riques}

\begin{itemize}
\tightlist
\item
  Opérations arithmétiques
\item
  Manipulation de types numériques
\item
  Génération de données
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Opérations
arithmétiques}\label{opuxe9rations-arithmuxe9tiques}

\begin{itemize}
\tightlist
\item
  Addition : \texttt{+}
\item
  Soustraction : \texttt{-}
\item
  Multiplication : \texttt{*}
\item
  Division : \texttt{/}
\item
  Reste (modulo) : \texttt{\%}
\end{itemize}

L'ensemble des opérations arithmétiques disponibles sont documentées
dans le manuel :

http://docs.postgresql.fr/9.2/functions-math.html

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Fonctions numériques
courantes}\label{fonctions-numuxe9riques-courantes}

\begin{itemize}
\tightlist
\item
  Arrondi : \texttt{round(numeric)}
\item
  Troncature : \texttt{trunc(numeric\ {[},\ precision{]})}
\item
  Entier le plus petit : \texttt{floor(numeric)}
\item
  Entier le plus grand : \texttt{ceil(numeric)}
\end{itemize}

Ces fonctions sont décrites dans le manuel:

http://docs.postgresql.fr/current/functions-math.html

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Génération de
données}\label{guxe9nuxe9ration-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Générer une suite d'entiers
  :\texttt{generate\_series(borne\_debut,\ borne\_fin,\ intervalle)}
\item
  Générer un nombre aléatoire :\texttt{random()}
\end{itemize}

La fonction \texttt{generate\_series(//n//,\ //m//)} est spécifique à
PostgreSQL et permet de générer une suite d'entiers compris entre une
borne de départ et une borne de fin, en suivant un certain intervalle :

\begin{verbatim}
SELECT generate_series(1, 4);
 generate_series 
-----------------
               1
               2
               3
               4
(4 rows)
\end{verbatim}

La déclinaison \texttt{generate\_series(//n//,\ //m//,\ //interval//)}
permet de spécifier un incrément pour chaque itération :

\begin{verbatim}
SELECT generate_series(1, 10, 4);
 generate_series 
-----------------
               1
               5
               9
(3 rows)
\end{verbatim}

Quant à la fonction \texttt{random()}, elle génère un nombre aléatoire,
de type \texttt{numeric}, compris entre 0 et 1.

\begin{verbatim}
SELECT random();
      random       
-------------------
 0.381810061167926
(1 row)
\end{verbatim}

Pour générer un entier compris entre 0 et 100, il suffit de réaliser la
requête suivante :

\begin{verbatim}
SELECT round(100*random())::integer;
 round 
-------
    74
(1 row)
\end{verbatim}

Il est possible de contrôler la graine du générateur de nombres
aléatoires en positionnant le paramètre de session \texttt{SEED} :

\begin{verbatim}
SET SEED = 0.123;
\end{verbatim}

ou à l'aide de la fonction \texttt{setseed()} :

\begin{verbatim}
SELECT setseed(0.123);
\end{verbatim}

La graine est un flottant compris entre -1 et 1.

Ces fonctions sont décrites dans le manuel de PostgreSQL:

http://docs.postgresql.fr/9.2/functions-math.html.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Manipulation de dates}\label{manipulation-de-dates}

\begin{itemize}
\tightlist
\item
  Obtenir la date et l'heure courante
\item
  Manipuler des dates
\item
  Opérations arithmétiques
\item
  Formatage de données
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Date et heure courante}\label{date-et-heure-courante}

\begin{itemize}
\tightlist
\item
  Retourne la date courante :\texttt{current\_date}
\item
  Retourne l'heure courante :\texttt{current\_time}
\item
  Retourne la date et l'heure courante :\texttt{current\_timestamp}
\end{itemize}

Les fonctions \texttt{current\_date} et \texttt{current\_time}
permettent d'obtenir respectivement la date courante et l'heure
courante. La première fonction retourne le résultat sous la forme d'un
type \texttt{date} et la seconde sous la forme d'un type
\texttt{time\ with\ time\ zone}.

La fonction \texttt{current\_timestamp} et son synonyme \texttt{now()}
permettent d'obtenir la date et l'heure courante, le résultat étant de
type \texttt{timestamp\ with\ time\ zone}.

Les fonctions \texttt{current\_date}, \texttt{current\_time} et
\texttt{current\_timestamp} n'ont pas besoin d'être invoquée avec les
parenthèses ouvrantes et fermantes typiques de l'appel d'une fonction.
En revanche, l'appel de la fonction \texttt{now()} requiert ces
parenthèses.

\begin{verbatim}
SELECT current_date;
    date    
------------
 2013-05-28
(1 row)

SELECT current_time;
       timetz       
--------------------
 12:12:14.635475+02
(1 row)

SELECT current_timestamp;
              now              
-------------------------------
 2013-05-28 12:12:16.859721+02
(1 row)

SELECT now();
              now              
-------------------------------
 2013-05-28 12:12:18.345793+02
(1 row)
\end{verbatim}

Il est possible d'utiliser ces variables comme valeur par défaut d'une
colonne :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (}
  \KeywordTok{id} \NormalTok{SERIAL }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{,}
  \NormalTok{dateheure_creation }\DataTypeTok{TIMESTAMP} \KeywordTok{DEFAULT} \FunctionTok{current_timestamp}\NormalTok{,}
  \NormalTok{valeur }\DataTypeTok{VARCHAR}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (valeur) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Bonjour, monde!'}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{test;}
 \KeywordTok{id} \NormalTok{|     dateheure_creation     |     valeur      }
\CommentTok{----+----------------------------+-----------------}
  \DecValTok{1} \NormalTok{| }\DecValTok{2013-05-28} \DecValTok{12}\CharTok{:14:14}\FloatTok{.618154} \NormalTok{| Bonjour, monde!}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Manipulation des
données}\label{manipulation-des-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Âge

  \begin{itemize}
  \tightlist
  \item
    \texttt{age(timestamp)}
  \item
    \texttt{age(timestamp,\ timestamp)}
  \end{itemize}
\end{itemize}

La fonction \texttt{age(timestamp)} permet de déterminer l'âge de la
date donnée en paramètre par rapport à la date courante. L'âge sera
donné sous la forme d'un type \texttt{interval}.

La forme \texttt{age(timestamp,\ timestamp)} permet d'obtenir l'âge
d'une date par rapport à une autre date, par exemple pour connaître
l'âge de Gaston Lagaffe au 5 janvier 1997 :

\begin{verbatim}
SELECT age(date '1997-01-05', date '1957-02-28');
           age           
-------------------------
 39 years 10 mons 5 days
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Tronquer et extraire}\label{tronquer-et-extraire}

\begin{itemize}
\tightlist
\item
  Troncature d'une date :\texttt{date\_trunc(text,\ timestamp)}

  \begin{itemize}
  \tightlist
  \item
    Exemple
    \texttt{date\_trunc(\textquotesingle{}month\textquotesingle{}\ from\ date\_naissance)}
  \end{itemize}
\item
  Extrait une composante de la date \texttt{extract(text,\ timestamp)}

  \begin{itemize}
  \tightlist
  \item
    Exemple
    \texttt{extract(\textquotesingle{}year\textquotesingle{}\ from\ date\_naissance)}
  \end{itemize}
\end{itemize}

La fonction \texttt{date\_trunc(text,\ timestamp)} permet de tronquer la
date à une précision donnée. La précision est exprimée en anglais, et
autorise les valeurs suivantes : * microseconds * milliseconds * second
* minute * hour * day * week * month * quarter * year * decade * century
* millennium

La fonction \texttt{date\_trunc()} peut agir sur une donnée de type
\texttt{timestamp}, \texttt{date} ou \texttt{interval}. Par exemple,
pour //arrondir// l'âge de Gaston Lagaffe de manière à ne représenter
que le nombre d'année :

\begin{verbatim}
SELECT date_trunc('year', age(date '1997-01-05', date '1957-02-28')) AS age_lagaffe;
 age_lagaffe 
-------------
 39 years
\end{verbatim}

La fonction \texttt{extract(text\ from\ timestamp)} permet d'extraire
uniquement une composante donnée d'une date, par exemple l'année. Elle
retourne un type de données flottant \texttt{double\ precision}.

\begin{verbatim}
SELECT extract('year' from age(date '1997-01-05', date '1957-02-28')) AS age_lagaffe;
 age_lagaffe 
-------------
          39
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Arithmétique sur les
dates}\label{arithmuxe9tique-sur-les-dates}

\begin{itemize}
\tightlist
\item
  Opérations arithmétiques sur \texttt{timestamp}, \texttt{time} ou
  \texttt{date}

  \begin{itemize}
  \tightlist
  \item
    \texttt{date/time\ -\ date/time\ =\ interval}
  \item
    \texttt{date/time\ +\ time\ =\ date/time}
  \item
    \texttt{date/time\ +\ interval\ =\ date/time}
  \end{itemize}
\item
  Opérations arithmétiques sur \texttt{interval}

  \begin{itemize}
  \tightlist
  \item
    \texttt{interval\ *\ numeric\ \ =\ interval}
  \item
    \texttt{interval\ /\ numeric\ \ =\ interval}
  \item
    \texttt{interval\ +\ interval\ =\ interval}
  \end{itemize}
\end{itemize}

La soustraction de deux types de données représentant des dates permet
d'obtenir un intervalle qui représente le délai écoulé entre ces deux
dates :

\begin{verbatim}
SELECT timestamp '2012-01-01 10:23:10' - date '0001-01-01' AS soustraction;
     soustraction     
----------------------
 734502 days 10:23:10
\end{verbatim}

L'addition entre deux types de données est plus restreinte. En effet,
l'expression de gauche est obligatoirement de type \texttt{timestamp} ou
\texttt{date} et l'expression de droite doit être obligatoirement de
type \texttt{time}. Le résultat de l'addition permet d'obtenir une
donnée de type \texttt{timestamp}, avec ou sans information sur le
fuseau horaire selon que cette information soit présente ou non sur
l'expression de gauche.

\begin{verbatim}
SELECT timestamp '2001-01-01 10:34:12' + time '23:56:13' AS addition;
      addition       
---------------------
 2001-01-02 10:30:25

SELECT date '2001-01-01' + time '23:56:13' AS addition;
      addition       
---------------------
 2001-01-01 23:56:13
\end{verbatim}

L'addition d'une donnée datée avec une donnée de type \texttt{interval}
permet d'obtenir un résultat du même type que l'expression de gauche :

\begin{verbatim}
SELECT timestamp with time zone '2001-01-01 10:34:12' + interval '1 day 1 hour' AS addition;
        addition        
------------------------
 2001-01-02 11:34:12+01

SELECT date '2001-01-01'  + interval '1 day 1 hour' AS addition;
      addition       
---------------------
 2001-01-02 01:00:00

SELECT time '10:34:24' + interval '1 day 1 hour' AS addition;
 addition 
----------
 11:34:24
\end{verbatim}

Une donnée de type \texttt{interval} peut subir des opérations
arithmétiques. Le résultat sera de type \texttt{interval} :

\begin{verbatim}
SELECT interval '1 day 1 hour' * 2 AS multiplication;
 multiplication  
-----------------
 2 days 02:00:00

SELECT interval '1 day 1 hour' / 2 AS division;
 division 
----------
 12:30:00

SELECT interval '1 day 1 hour' + interval '2 hour' AS addition;
    addition    
----------------
 1 day 03:00:00

SELECT interval '1 day 1 hour' - interval '2 hour' AS soustraction;
  soustraction   
-----------------
 1 day -01:00:00
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Date vers chaîne}\label{date-vers-chauxeene}

\begin{itemize}
\tightlist
\item
  Conversion d'une date en chaîne de caractères
  \texttt{to\_char(timestamp,\ text)}

  \begin{itemize}
  \tightlist
  \item
    Exemple
    \texttt{to\_char(current\_timestamp,\ \textquotesingle{}DD/MM/YYYY\ HH24:MI:SS\textquotesingle{})}
  \end{itemize}
\end{itemize}

La fonction \texttt{to\_char()} permet de restituer une date selon un
format donné :

\begin{verbatim}
SELECT current_timestamp;
              now              
-------------------------------
 2013-06-06 17:41:56.212549+02

SELECT to_char(current_timestamp, 'DD/MM/YYYY HH24:MI:SS');
       to_char       
---------------------
 06/06/2013 17:42:22
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Chaîne vers date}\label{chauxeene-vers-date}

\begin{itemize}
\tightlist
\item
  Conversion d'une chaîne de caractères en date
  \texttt{to\_date(text,\ text)}

  \begin{itemize}
  \tightlist
  \item
    Exemple
    \texttt{to\_date(\textquotesingle{}05/12/2000\textquotesingle{},\ \textquotesingle{}DD/MM/YYYY\textquotesingle{})}
  \end{itemize}
\item
  Conversion d'une chaîne de caractères en timestamp
  \texttt{to\_timestamp(text,\ text)}

  \begin{itemize}
  \tightlist
  \item
    Exemple
    \texttt{to\_timestamp(\textquotesingle{}05/12/2000\ 12:00:00\textquotesingle{},\ \textquotesingle{}DD/MM/YYYY\ HH24:MI:SS\textquotesingle{})}
  \end{itemize}
\item
  Paramètre \texttt{datestyle}
\end{itemize}

Quant à la fonction \texttt{to\_date()}, elle permet de convertir une
chaîne de caractères dans une donnée de type \texttt{date}. La fonction
\texttt{to\_timestamp()} permet de réaliser la même mais en donnée de
type \texttt{timestamp}.

\begin{verbatim}
SELECT to_timestamp('04/12/2000 12:00:00', 'DD/MM/YYYY HH24:MI:SS');
to_timestamp      
------------------------
 2000-12-04 12:00:00+01
\end{verbatim}

Ces fonctions sont détaillées dans la section concernant les fonctions
de formatage de données du manuel:

http://docs.postgresql.fr/9.2/functions-formatting.html

Le paramètre \texttt{DateStyle} contrôle le format de saisie et de
restitution des dates et heures. La documentation de ce paramètre permet
de connaître les différentes valeurs possibles. Il reste néanmoins
recommandé d'utiliser les fonctions de formatage de date qui permettent
de rendre l'application indépendante de la configuration du SGBD.

La norme ISO impose le format de date ``année/mois/jour''. La norme SQL
est plus permissive et permet de restituer une date au format
``jour/mois/année'' si DateStyle est égal à
\texttt{\textquotesingle{}SQL,\ DMY\textquotesingle{}}.

\begin{verbatim}
SET datestyle = 'ISO, DMY';

SELECT current_timestamp;
              now              
-------------------------------
 2013-06-06 12:13:24.899816+02

SET datestyle = 'SQL, DMY';

SELECT current_timestamp;
              now               
--------------------------------
 06/06/2013 12:13:52.81941 CEST
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Génération de
données}\label{guxe9nuxe9ration-de-donnuxe9es-1}

\begin{itemize}
\tightlist
\item
  Générer une suite de timestamp
  :\texttt{generate\_series(timestamp\_debut,\ timestamp\_fin,\ intervalle)}
\end{itemize}

La fonction
\texttt{generate\_series(//date\_debut//,\ //date\_fin//,\ //interval//)}
permet de générer des séries de dates :

\begin{verbatim}
SELECT generate_series(date '2012-01-01', date '2012-12-31', interval '1 month');
    generate_series     
------------------------
 2012-01-01 00:00:00+01
 2012-02-01 00:00:00+01
 2012-03-01 00:00:00+01
 2012-04-01 00:00:00+02
 2012-05-01 00:00:00+02
 2012-06-01 00:00:00+02
 2012-07-01 00:00:00+02
 2012-08-01 00:00:00+02
 2012-09-01 00:00:00+02
 2012-10-01 00:00:00+02
 2012-11-01 00:00:00+01
 2012-12-01 00:00:00+01
(12 rows)
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Vues}\label{vues}

\begin{itemize}
\tightlist
\item
  Tables virtuelles

  \begin{itemize}
  \tightlist
  \item
    définies par une requête SELECT
  \item
    définition stockée dans le catalogue de la base de données
  \end{itemize}
\item
  Objectifs

  \begin{itemize}
  \tightlist
  \item
    masquer la complexité d'une requête
  \item
    masquer certaines données à l'utilisateur
  \end{itemize}
\end{itemize}

Les vues sont des tables virtuelles qui permettent d'obtenir le résultat
d'une requête \texttt{SELECT}. Sa définition est stockée dans le
catalogue système de la base de données.

De cette façon, il est possible de créer une vue à destination de
certains utilisateurs pour combler différents besoins : * permettre
d'interroger facilement une vue qui exécute une requête complexe, lourde
à écrire et utilisée fréquemment, * masquer certaines lignes ou
certaines colonnes aux utilisateurs, pour amener un niveau de sécurité
complémentaire, * rendre les données plus intelligibles, en nommant
mieux les colonnes d'une vue et/ou en simplifiant la structure de
données.

En plus de cela, les vues permettent d'obtenir facilement des valeurs
dérivées d'autres colonnes. Ces valeurs dérivées pourront alors être
utilisées simplement en appelant la vue plutôt qu'en réécrivant
systématiquement le calcul de dérivation à chaque requête qui le
nécessite.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Création d'une vue}\label{cruxe9ation-dune-vue}

\begin{itemize}
\tightlist
\item
  Une vue porte un nom au même titre qu'une table

  \begin{itemize}
  \tightlist
  \item
    elle sera nommée avec les mêmes règles
  \end{itemize}
\item
  Ordre de création d'une vue
  \texttt{CREATE\ VIEW\ vue\ (colonne\ ...)\ \ \ AS\ SELECT\ ...}
\end{itemize}

Bien qu'une vue n'ait pas de représentation physique directe, elle est
accédée au même titre qu'une table avec \texttt{SELECT} et dans certains
cas avec \texttt{INSERT}, \texttt{UPDATE} et \texttt{DELETE}. La vue
logique ne distingue pas les accès à une vue des accès à une table. De
cette façon, une vue doit utiliser les mêmes conventions de nommage
qu'une table.

Une vue est créée avec l'ordre SQL \texttt{CREATE\ VIEW} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \NormalTok{[ }\KeywordTok{OR} \KeywordTok{REPLACE} \NormalTok{] [ TEMP | }\KeywordTok{TEMPORARY} \NormalTok{] [ RECURSIVE ] }\KeywordTok{VIEW} \NormalTok{nom}
    \NormalTok{[ ( nom_colonne [, ...] ) ]}
    \NormalTok{[ }\KeywordTok{WITH} \NormalTok{( nom_option_vue [= valeur_option_vue] [, ... ] ) ]}
    \KeywordTok{AS} \NormalTok{requete}
\end{Highlighting}
\end{Shaded}

Le mot clé \texttt{CREATE\ VIEW} permet de créer une vue. Si elle existe
déjà, il est possible d'utiliser \texttt{CREATE\ OR\ REPLACE\ VIEW} qui
aura pour effet de créer la vue si elle n'existe pas ou de remplacer la
définition de la vue si elle existe déjà. Attention, les colonnes et les
types de données retournés par la vue ne doivent pas changer.

La clause \texttt{nom} permet de nommer la vue. La clause
\texttt{nom\_colonne,\ ...}' permet lister explicitement les colonnes
retournées par une vue, cette clause est optionnelle mais recommandée
pour mieux documenter la vue.

La clause \texttt{requete} correspond simplement à la requête
\texttt{SELECT} exécutée lorsqu'on accède à la vue.

\textbf{Exemples}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{phone_data (person text, phone text, }\KeywordTok{private} \DataTypeTok{boolean}\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{VIEW} \NormalTok{phone_number (person, phone) }\KeywordTok{AS}
    \KeywordTok{SELECT} \NormalTok{person, }\KeywordTok{CASE} \KeywordTok{WHEN} \KeywordTok{NOT} \KeywordTok{private} \KeywordTok{THEN} \NormalTok{phone }\KeywordTok{END} \KeywordTok{AS} \NormalTok{phone}
    \KeywordTok{FROM} \NormalTok{phone_data;}
    
\KeywordTok{GRANT} \KeywordTok{SELECT} \KeywordTok{ON} \NormalTok{phone_number }\KeywordTok{TO} \NormalTok{secretary;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Lecture d'une vue}\label{lecture-dune-vue}

\begin{itemize}
\tightlist
\item
  Une vue est lue comme une table

  \begin{itemize}
  \tightlist
  \item
    \texttt{SELECT\ *\ FROM\ vue;}
  \end{itemize}
\end{itemize}

Une vue est lue de la même façon qu'une table. On utilisera donc l'ordre
\texttt{SELECT} pour le faire. L'optimiseur de PostgreSQL remplacera
l'appel à la vue par la définition de la vue pendant la phase de
réécriture de la requête. Le plan d'exécution prendra alors compte des
particularités de la vue pour optimiser les accès aux données.

\textbf{Exemples}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{phone_data (person text, phone text, }\KeywordTok{private} \DataTypeTok{boolean}\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{VIEW} \NormalTok{phone_number (person, phone) }\KeywordTok{AS}
    \KeywordTok{SELECT} \NormalTok{person, }\KeywordTok{CASE} \KeywordTok{WHEN} \KeywordTok{NOT} \KeywordTok{private} \KeywordTok{THEN} \NormalTok{phone }\KeywordTok{END} \KeywordTok{AS} \NormalTok{phone}
    \KeywordTok{FROM} \NormalTok{phone_data;}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{phone_data (person, phone, }\KeywordTok{private}\NormalTok{)}
  \KeywordTok{VALUES} \NormalTok{(}\StringTok{'Titi'}\NormalTok{, }\StringTok{'0123456789'}\NormalTok{, }\KeywordTok{true}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{phone_data (person, phone, }\KeywordTok{private}\NormalTok{)}
  \KeywordTok{VALUES} \NormalTok{(}\StringTok{'Rominet'}\NormalTok{, }\StringTok{'0123456788'}\NormalTok{, }\KeywordTok{false}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{person, phone }\KeywordTok{FROM} \NormalTok{phone_number;}
 \NormalTok{person  |   phone    }
\CommentTok{---------+------------}
 \NormalTok{Titi    | }
 \NormalTok{Rominet | }\DecValTok{0123456788}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Sécurisation d'une vue}\label{suxe9curisation-dune-vue}

\begin{itemize}
\tightlist
\item
  Sécuriser une vue

  \begin{itemize}
  \tightlist
  \item
    droits avec \texttt{GRANT} et \texttt{REVOKE}
  \end{itemize}
\item
  Utiliser les vues comme moyen de filtrer les lignes est dangereux

  \begin{itemize}
  \tightlist
  \item
    option \texttt{security\_barrier}
  \end{itemize}
\end{itemize}

Il est possible d'accorder (ou de révoquer) à un utilisateur les mêmes
droits sur une vue que sur une table :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{GRANT} \NormalTok{\{ \{ }\KeywordTok{SELECT} \NormalTok{| }\KeywordTok{INSERT} \NormalTok{| }\KeywordTok{UPDATE} \NormalTok{| }\KeywordTok{DELETE} \NormalTok{| }\KeywordTok{TRUNCATE} \NormalTok{| }\KeywordTok{REFERENCES} \NormalTok{| }\KeywordTok{TRIGGER} \NormalTok{\}}
    \NormalTok{[, ...] | }\KeywordTok{ALL} \NormalTok{[ }\KeywordTok{PRIVILEGES} \NormalTok{] \}}
    \KeywordTok{ON} \NormalTok{\{ [ }\KeywordTok{TABLE} \NormalTok{] nom_table [, ...]}
         \NormalTok{| }\KeywordTok{ALL} \KeywordTok{TABLES} \KeywordTok{IN} \KeywordTok{SCHEMA} \NormalTok{nom_schéma [, ...] \}}
    \KeywordTok{TO} \NormalTok{\{ [ }\KeywordTok{GROUP} \NormalTok{] nom_rôle | }\KeywordTok{PUBLIC} \NormalTok{\} [, ...] [ }\KeywordTok{WITH} \KeywordTok{GRANT} \KeywordTok{OPTION} \NormalTok{]}
\end{Highlighting}
\end{Shaded}

Le droit \texttt{SELECT} autorise un utilisateur à lire une table. Les
droits \texttt{INSERT}, \texttt{UPDATE} et \texttt{DELETE} permettent de
contrôler les accès en mise à jour à une vue.

Les droits \texttt{TRUNCATE} et \texttt{REFERENCES} n'ont pas d'utilité
avec une vue. Ils ne sont tout simplement pas supportés car
\texttt{TRUNCATE} n'agit que sur une table et une clé étrangère ne peut
être liée d'une table qu'à une autre table.

Les vues sont parfois utilisées pour filtrer les lignes pouvant être
lues par l'utilisateur. Cette protection peut être contournée si
l'utilisateur a la possibilité de créer une fonction. À partir de
PostgreSQL 9.2, l'option \texttt{security\_barrier} permet d'éviter ce
problème.

\textbf{Exemples}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{demo_9_2=# }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{elements (}\KeywordTok{id} \NormalTok{serial, contenu text, prive }\DataTypeTok{boolean}\NormalTok{);}
\NormalTok{NOTICE:  }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{will }\KeywordTok{create} \NormalTok{implicit }\KeywordTok{sequence} \OtherTok{"elements_id_seq"} \KeywordTok{for} \NormalTok{serial }\KeywordTok{column} \OtherTok{"elements.id"}
\KeywordTok{CREATE} \KeywordTok{TABLE}
\NormalTok{demo_9_2=# }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{elements (contenu, prive)}
\KeywordTok{VALUES} \NormalTok{(}\StringTok{'a'}\NormalTok{, }\KeywordTok{false}\NormalTok{), (}\StringTok{'b'}\NormalTok{, }\KeywordTok{false}\NormalTok{), (}\StringTok{'c super prive'}\NormalTok{, }\KeywordTok{true}\NormalTok{), (}\StringTok{'d'}\NormalTok{, }\KeywordTok{false}\NormalTok{), (}\StringTok{'e prive aussi'}\NormalTok{, }\KeywordTok{true}\NormalTok{);}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{5}
\NormalTok{demo_9_2=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements;}
 \KeywordTok{id} \NormalTok{|    contenu    | prive }
\CommentTok{----+---------------+-------}
  \DecValTok{1} \NormalTok{| a             | f}
  \DecValTok{2} \NormalTok{| b             | f}
  \DecValTok{3} \NormalTok{| c super prive | t}
  \DecValTok{4} \NormalTok{| d             | f}
  \DecValTok{5} \NormalTok{| e prive aussi | t}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La table \texttt{elements} contient cinq lignes, trois considérés comme
privés. Nous allons donc créer une vue ne permettant de ne voir que les
lignes publiques.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{demo_9_2=# }\KeywordTok{CREATE} \KeywordTok{OR} \KeywordTok{REPLACE} \KeywordTok{VIEW} \NormalTok{elements_public }\KeywordTok{AS} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements }\KeywordTok{WHERE} \KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{current_user=}\StringTok{'guillaume'} \KeywordTok{THEN} \KeywordTok{TRUE} \KeywordTok{ELSE} \KeywordTok{NOT} \NormalTok{prive }\KeywordTok{END}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{VIEW}
\NormalTok{demo_9_2=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements_public;}
 \KeywordTok{id} \NormalTok{|    contenu    | prive }
\CommentTok{----+---------------+-------}
  \DecValTok{1} \NormalTok{| a             | f}
  \DecValTok{2} \NormalTok{| b             | f}
  \DecValTok{3} \NormalTok{| c super prive | t}
  \DecValTok{4} \NormalTok{| d             | f}
  \DecValTok{5} \NormalTok{| e prive aussi | t}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}

\NormalTok{demo_9_2=# }\KeywordTok{CREATE} \FunctionTok{USER} \NormalTok{u1;}
\KeywordTok{CREATE} \KeywordTok{ROLE}
\NormalTok{demo_9_2=# }\KeywordTok{GRANT} \KeywordTok{SELECT} \KeywordTok{ON} \NormalTok{elements_public }\KeywordTok{TO} \NormalTok{u1;}
\KeywordTok{GRANT}
\NormalTok{demo_9_2=# \textbackslash{}c - u1}
\NormalTok{You are now connected }\KeywordTok{to} \KeywordTok{database} \OtherTok{"postgres"} \KeywordTok{as} \FunctionTok{user} \OtherTok{"u1"}\NormalTok{.}
\NormalTok{demo_9_2=> }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements;}
\NormalTok{ERROR:  permission denied }\KeywordTok{for} \NormalTok{relation elements}
\NormalTok{demo_9_2=> }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements_public ;}
 \KeywordTok{id} \NormalTok{| contenu | prive }
\CommentTok{----+---------+-------}
  \DecValTok{1} \NormalTok{| a       | f}
  \DecValTok{2} \NormalTok{| b       | f}
  \DecValTok{4} \NormalTok{| d       | f}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

L'utilisateur \texttt{u1} n'a pas le droit de lire directement la table
\texttt{elements} mais a le droit d'y accéder via la vue
\texttt{elements\_public}, uniquement pour les lignes dont le champ
\texttt{prive} est à \texttt{false}.

Avec une simple fonction, cela peut changer :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{demo_9_2=> }\KeywordTok{CREATE} \KeywordTok{OR} \KeywordTok{REPLACE} \KeywordTok{FUNCTION} \NormalTok{abracadabra(}\DataTypeTok{integer}\NormalTok{, text, }\DataTypeTok{boolean}\NormalTok{) RETURNS bool}
\KeywordTok{AS} \NormalTok{$$}
\KeywordTok{BEGIN}
\NormalTok{RAISE NOTICE }\StringTok{'% - % - %'}\NormalTok{, $ }\DecValTok{1}\NormalTok{, $ }\DecValTok{2}\NormalTok{, $ }\DecValTok{3}\NormalTok{;}
\KeywordTok{RETURN} \KeywordTok{true}\NormalTok{;}
\NormalTok{END$$}
\NormalTok{LANGUAGE plpgsql}
\KeywordTok{COST} \FloatTok{0.0000000000000000000001}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{FUNCTION}
\NormalTok{demo_9_2=> }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements_public }\KeywordTok{WHERE} \NormalTok{abracadabra(}\KeywordTok{id}\NormalTok{, contenu, prive);}
\NormalTok{NOTICE:  }\DecValTok{1} \NormalTok{- a - f}
\NormalTok{NOTICE:  }\DecValTok{2} \NormalTok{- b - f}
\NormalTok{NOTICE:  }\DecValTok{3} \NormalTok{- c super prive - t}
\NormalTok{NOTICE:  }\DecValTok{4} \NormalTok{- d - f}
\NormalTok{NOTICE:  }\DecValTok{5} \NormalTok{- e prive aussi - t}
 \KeywordTok{id} \NormalTok{| contenu | prive }
\CommentTok{----+---------+-------}
  \DecValTok{1} \NormalTok{| a       | f}
  \DecValTok{2} \NormalTok{| b       | f}
  \DecValTok{4} \NormalTok{| d       | f}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Que s'est-il passé ? pour comprendre, il suffit de regarder l'EXPLAIN de
cette requête :

\begin{verbatim}
demo_9_2=> EXPLAIN SELECT * FROM elements_public WHERE abracadabra(id, contenu, prive);
                                                        QUERY PLAN                                                        
-------------------------------------------------------------------------------------
 Seq Scan on elements  (cost=0.00..28.15 rows=202 width=37)
   Filter: (abracadabra(id, contenu, prive) AND
           CASE WHEN ("current_user"() = 'u1'::name) THEN (NOT prive) ELSE true END)
(2 rows)
\end{verbatim}

La fonction \texttt{abracadrabra} a un coût si faible que PostgreSQL
l'exécute avant le filtre de la vue. Du coup, la fonction voit toutes
les lignes de la table.

Seul moyen d'échapper à cette optimisation du planificateur, utiliser
l'option \texttt{security\_barrier} en 9.2 :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{demo_9_2=> \textbackslash{}c - guillaume}
\NormalTok{You are now connected }\KeywordTok{to} \KeywordTok{database} \OtherTok{"postgres"} \KeywordTok{as} \FunctionTok{user} \OtherTok{"guillaume"}\NormalTok{.}
\NormalTok{demo_9_2=# }\KeywordTok{CREATE} \KeywordTok{OR} \KeywordTok{REPLACE} \KeywordTok{VIEW} \NormalTok{elements_public }\KeywordTok{WITH} \NormalTok{(security_barrier) }\KeywordTok{AS} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements }\KeywordTok{WHERE} \KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{current_user=}\StringTok{'guillaume'} \KeywordTok{THEN} \KeywordTok{true} \KeywordTok{ELSE} \KeywordTok{NOT} \NormalTok{prive }\KeywordTok{END}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{VIEW}
\NormalTok{demo_9_2=# \textbackslash{}c - u1}
\NormalTok{You are now connected }\KeywordTok{to} \KeywordTok{database} \OtherTok{"postgres"} \KeywordTok{as} \FunctionTok{user} \OtherTok{"u1"}\NormalTok{.}
\NormalTok{demo_9_2=> }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements_public }\KeywordTok{WHERE} \NormalTok{abracadabra(}\KeywordTok{id}\NormalTok{, contenu, prive);}
\NormalTok{NOTICE:  }\DecValTok{1} \NormalTok{- a - f}
\NormalTok{NOTICE:  }\DecValTok{2} \NormalTok{- b - f}
\NormalTok{NOTICE:  }\DecValTok{4} \NormalTok{- d - f}
 \KeywordTok{id} \NormalTok{| contenu | prive }
\CommentTok{----+---------+-------}
  \DecValTok{1} \NormalTok{| a       | f}
  \DecValTok{2} \NormalTok{| b       | f}
  \DecValTok{4} \NormalTok{| d       | f}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}

\NormalTok{demo_9_2=> }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{elements_public }\KeywordTok{WHERE} \NormalTok{abracadabra(}\KeywordTok{id}\NormalTok{, contenu, prive);}
                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                                         
\CommentTok{-------------------------------------------------------------------------------------------}
 \NormalTok{Subquery }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{elements_public  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{34.20} \NormalTok{rows=}\DecValTok{202} \NormalTok{width=}\DecValTok{37}\NormalTok{)}
   \NormalTok{Filter: abracadabra(elements_public.id, elements_public.contenu, elements_public.prive)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{elements  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{28.15} \NormalTok{rows=}\DecValTok{605} \NormalTok{width=}\DecValTok{37}\NormalTok{)}
         \NormalTok{Filter: }\KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{(}\OtherTok{"current_user"}\NormalTok{() = }\StringTok{'u1'}\NormalTok{:}\CharTok{:name}\NormalTok{) }\KeywordTok{THEN} \NormalTok{(}\KeywordTok{NOT} \NormalTok{prive) }\KeywordTok{ELSE} \KeywordTok{true} \KeywordTok{END}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Voir aussi
http://rhaas.blogspot.com/2012/03/security-barrier-views.html.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Mise à jour des vues}\label{mise-uxe0-jour-des-vues}

\begin{itemize}
\tightlist
\item
  Trigger \texttt{INSTEAD\ OF}
\item
  UPDATABLE VIEW (PostgreSQL 9.3)
\end{itemize}

La mise à jour des vues était impossible auparavant sans programmation
complémentaire.

Depuis PostgreSQL 9.3, le moteur gère lui-même la possibilité de mettre
à jour des vues simples. Les critères permettant de déterminer si une
vue peut être mise à jour ou non sont assez simples à résumer : la vue
doit reprendre la définition de la table avec éventuellement une clause
\texttt{WHERE} pour restreindre les résultats. Attention néanmoins, la
clause \texttt{WITH\ CHECK\ OPTION} n'est pas encore supportée par
PostgreSQL. L'absence de support de cette clause permet à l'utilisateur
d'insérer des données qui ne satisfont pas les critères de filtrage de
la vue. Par exemple, il est possible d'insérer un numéro de téléphone
privé alors que la vue ne permet pas d'afficher les numéros privés.

Pour gérer les cas plus complexes, PostgreSQL permet de créer des
triggers \texttt{INSTEAD\ OF} sur des vues. Cette fonctionnalité est
disponible depuis la version 9.1. Une alternative est d'utiliser le
système de règles (\texttt{RULES}) mais cette pratique est peu
recommandé du fait de la difficulté de débuggage et de la maintenance
que cela entraine.

Un trigger \texttt{INSTEAD\ OF} permet de déclencher une fonction
utilisateur lorsqu'une opération de mise à jour est déclenchée sur une
vue. Le code de la fonction sera exécuté en lieu et place de la mise à
jour.

\textbf{Exemples}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{phone_data (person text, phone text, }\KeywordTok{private} \DataTypeTok{boolean}\NormalTok{);}


\KeywordTok{CREATE} \KeywordTok{VIEW} \NormalTok{maj_phone_number (person, phone, }\KeywordTok{private}\NormalTok{) }\KeywordTok{AS}
    \KeywordTok{SELECT} \NormalTok{person, phone, }\KeywordTok{private}
      \KeywordTok{FROM} \NormalTok{phone_data}
     \KeywordTok{WHERE} \KeywordTok{private} \NormalTok{= }\KeywordTok{false}\NormalTok{;}


\KeywordTok{CREATE} \KeywordTok{VIEW} \NormalTok{phone_number (person, phone) }\KeywordTok{AS}
    \KeywordTok{SELECT} \NormalTok{person, }\KeywordTok{CASE} \KeywordTok{WHEN} \KeywordTok{NOT} \KeywordTok{private} \KeywordTok{THEN} \NormalTok{phone }\KeywordTok{END} \KeywordTok{AS} \NormalTok{phone}
      \KeywordTok{FROM} \NormalTok{phone_data;}


\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{phone_number }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Titi'}\NormalTok{, }\StringTok{'0123456789'}\NormalTok{);}
\NormalTok{ERROR:  cannot }\KeywordTok{insert} \KeywordTok{into} \KeywordTok{view} \OtherTok{"phone_number"}
\NormalTok{DETAIL:  Views that }\KeywordTok{return} \KeywordTok{columns} \NormalTok{that are }\KeywordTok{not} \KeywordTok{columns} \KeywordTok{of} \NormalTok{their base relation are }\KeywordTok{not} \NormalTok{automatically updatable.}
\NormalTok{HINT:  }\KeywordTok{To} \NormalTok{make }\KeywordTok{the} \KeywordTok{view} \NormalTok{insertable, provide an unconditional }\KeywordTok{ON} \KeywordTok{INSERT} \NormalTok{DO }\KeywordTok{INSTEAD} \KeywordTok{rule} \KeywordTok{or} \NormalTok{an }\KeywordTok{INSTEAD} \KeywordTok{OF} \KeywordTok{INSERT} \NormalTok{trigger.}


\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{maj_phone_number }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Titi'}\NormalTok{, }\StringTok{'0123456789'}\NormalTok{, }\KeywordTok{false}\NormalTok{);}


\KeywordTok{CREATE} \KeywordTok{OR} \KeywordTok{REPLACE} \KeywordTok{FUNCTION} \NormalTok{phone_number_insert_row()}
  \NormalTok{RETURNS }\KeywordTok{TRIGGER} 
  \NormalTok{LANGUAGE plpgsql}
\KeywordTok{AS} \NormalTok{$function$}
\KeywordTok{BEGIN}
  \KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{phone_data (person, phone, }\KeywordTok{private}\NormalTok{) }\KeywordTok{VALUES} \NormalTok{(NEW.person, NEW.phone, }\KeywordTok{false}\NormalTok{);}
  \KeywordTok{RETURN} \KeywordTok{NEW}\NormalTok{;}
\KeywordTok{END}\NormalTok{;}
\NormalTok{$function$;}


\KeywordTok{CREATE} \KeywordTok{TRIGGER} \NormalTok{view_insert}
    \KeywordTok{INSTEAD} \KeywordTok{OF} \KeywordTok{INSERT} \KeywordTok{ON} \NormalTok{phone_number}
    \KeywordTok{FOR} \KeywordTok{EACH} \KeywordTok{ROW}
    \KeywordTok{EXECUTE} \KeywordTok{PROCEDURE} \NormalTok{phone_number_insert_row();}


\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{phone_number }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'Rominet'}\NormalTok{, }\StringTok{'0123456788'}\NormalTok{);}


\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{phone_number;}
 \NormalTok{person  |   phone    }
\CommentTok{---------+------------}
 \NormalTok{Titi    | }\DecValTok{0123456789}
 \NormalTok{Rominet | }\DecValTok{0123456789}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Mauvaises utilisations}\label{mauvaises-utilisations}

\begin{itemize}
\tightlist
\item
  Prolifération des vues

  \begin{itemize}
  \tightlist
  \item
    créer une vue doit se justifier
  \item
    ne pas créer une vue par table
  \end{itemize}
\end{itemize}

La création d'une vue doit être pensée préalablement et doit se
justifier du point de vue de l'application ou d'une règle métier. Toute
vue créée doit être documentée, au moins en plaçant un commentaire sur
la vue pour la documenter.

Bien qu'une vue n'ait pas de représentation physique, elle occupe malgré
tout de l'espace disque. En effet, le catalogue système comporte une
entrée pour chaque vue créée, autant d'entrées qu'il y a de colonnes à
la vue, etc. Trop de vues entraine donc malgré tout l'augmentation de la
taille du catalogue système, donc une empreinte mémoire plus importante
car ce catalogue reste en général systématiquement présent en cache.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Requêtes préparées}\label{requuxeates-pruxe9paruxe9es}

\begin{itemize}
\tightlist
\item
  Exécution en deux temps

  \begin{itemize}
  \tightlist
  \item
    préparation du plan d'exécution de la requête
  \item
    exécution de la requête en utilisant le plan préparé
  \end{itemize}
\item
  Objectif :

  \begin{itemize}
  \tightlist
  \item
    éviter simplement les injections SQL
  \item
    améliorer les performances
  \end{itemize}
\end{itemize}

Les //requêtes préparées//, aussi appelées //requêtes paramétrées//,
permettent de séparer la phase de préparation du plan d'exécution de la
phase d'exécution. Le plan d'exécution qui est alors généré est
générique car les paramètres de la requêtes sont inconnus à ce moment
là.

L'exécution est ensuite commandée par l'application, en passant
l'ensemble des valeurs des paramètres de la requête. De plus, ces
paramètres sont passés de façon à éviter les injections SQL.

L'exécution peut être ensuite commandée plusieurs fois, sans avoir à
préparer le plan d'exécution. Cela permet un gain important en terme de
performances car l'étape d'analyse syntaxique et de recherche du plan
d'exécution optimal n'est plus à faire.

L'utilisation de requêtes préparées peut toutefois être
contre-performant si les sessions ne sont pas maintenues et les requêtes
exécutées qu'une seule fois. En effet, l'étape de préparation oblige à
un premier aller-retour entre l'application et la base de données et
l'exécution oblige à un second aller-retour, ajoutant ainsi une
surcharge qui peut devenir significative.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utilisation}\label{utilisation}

\begin{itemize}
\tightlist
\item
  \texttt{PREPARE}, préparation du plan d'exécution d'une requête
\item
  \texttt{EXECUTE}, passage des paramètres de la requête et exécution
  réelle
\item
  L'implémentation dépend beaucoup du langage de programmation utilisé

  \begin{itemize}
  \tightlist
  \item
    le connecteur JDBC supporte les requêtes préparées
  \item
    le connecteur PHP/PDO également
  \end{itemize}
\end{itemize}

L'ordre \texttt{PREPARE} permet de préparer le plan d'exécution d'une
requête. Le plan d'exécution prendra en compte le contexte courant de
l'utilisateur au moment où la requête est préparée, et notamment le
\texttt{search\_path}. Tout changement ultérieur de ces variables ne
sera pas pris en compte à l'exécution.

L'ordre \texttt{EXECUTE} permet de passer les paramètres de la requête
et de l'exécuter.

La plupart des langages de programmation mettent à disposition des
méthodes qui permettent d'employer les mécanismes de préparation de
plans d'exécution directement. Les paramètres des requêtes seront alors
transmis un à un à l'aide d'une méthode particulière.

Voici comment on prépare une requête :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{PREPARE} \NormalTok{req1 (text, text) }\KeywordTok{AS} \KeywordTok{SELECT} \NormalTok{person, phone }\KeywordTok{FROM} \NormalTok{phone_number }\KeywordTok{WHERE} \NormalTok{person = ?;}
\end{Highlighting}
\end{Shaded}

Le test suivant montre le gain en performance qu'on peut attendre d'une
requête préparée :

\begin{itemize}
\item
  préparation de la table:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (c1 }\DataTypeTok{integer} \KeywordTok{primary} \KeywordTok{key}\NormalTok{, c2 text);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 }\KeywordTok{select} \NormalTok{i, md5(}\KeywordTok{random}\NormalTok{():}\CharTok{:text}\NormalTok{) }
\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000000}\NormalTok{) }\KeywordTok{AS} \NormalTok{i;}
\end{Highlighting}
\end{Shaded}
\item
  préparation de deux scripts SQL, une pour les requêtes standards,
  l'autre pour les requêtes préparées :
\end{itemize}

\begin{verbatim}
$ for i in $(seq 1 100000); do echo "SELECT * FROM t1 WHERE c1=$i;"; done > requetes_std.sql
echo "PREPARE req AS SELECT * FROM t1 WHERE c1=\$1;" > requetes_prep.sql 
for i in $(seq 1 100000); do echo "EXECUTE req($i);"; done >> requetes_prep.sql
\end{verbatim}

\begin{itemize}
\tightlist
\item
  exécution du test (deux fois pour s'assurer que les temps d'exécution
  sont réalistes) :
\end{itemize}

\begin{verbatim}
$ time psql -f requetes_std.sql postgres >/dev/null

real  0m12.742s
user  0m2.633s
sys 0m0.771s
$ time psql -f requetes_std.sql postgres >/dev/null

real  0m12.781s
user  0m2.573s
sys 0m0.852s
$ time psql -f requetes_prep.sql postgres >/dev/null

real  0m10.186s
user  0m2.500s
sys 0m0.814s
$ time psql -f requetes_prep.sql postgres >/dev/null

real  0m10.131s
user  0m2.521s
sys 0m0.808s
\end{verbatim}

Le gain est de 16\% dans cet exemple. Il peut être bien plus important.
En lisant 500000 lignes (et non pas 100000), on arrive à 25\% de gain.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Indexation}\label{indexation}

\begin{itemize}
\tightlist
\item
  Comment gagner en performance
\item
  Index

  \begin{itemize}
  \tightlist
  \item
    représentation valeur / pointeur
  \item
    arbre de valeurs
  \end{itemize}
\end{itemize}

Quand on cherche à récupérer une ou plusieurs valeurs sur une très
grosse table, il serait dommage de devoir lire toutes les lignes de la
table. Un peu comme lorsqu'on cherche un nom dans un annuaire, avoir une
table des matières indiquant où à quelle page se trouvent les noms
commençant par telle ou telle lettre permet de trouver plus rapide le
nom recherché.

Un index est donc tout simplement ça : un moyen rapide de trouver
l'information recherché. Il est construit généralement sous la forme
d'un arbre car c'est la forme la plus aboutie pour trouver rapidement
une valeur. Lorsque la valeur est trouvée, le pointeur vers la table est
disponible et permet de ne lire que le ou les bloc(s) concernés de la
table.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Créer un index}\label{cruxe9er-un-index}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \NormalTok{[ }\KeywordTok{UNIQUE} \NormalTok{] }\KeywordTok{INDEX} \NormalTok{[ CONCURRENTLY ] [ name ]}
  \KeywordTok{ON} \NormalTok{table_name}
  \NormalTok{( \{ column_name | ( expression ) \}}
    \NormalTok{[ }\KeywordTok{ASC} \NormalTok{| }\KeywordTok{DESC} \NormalTok{] [ }\KeywordTok{NULLS} \NormalTok{\{ }\FunctionTok{FIRST} \NormalTok{| }\FunctionTok{LAST} \NormalTok{\} ] [, ...] )}
  \NormalTok{[ }\KeywordTok{TABLESPACE} \NormalTok{tablespace_name ]}
  \NormalTok{[ }\KeywordTok{WHERE} \NormalTok{predicate ]}
\end{Highlighting}
\end{Shaded}

Le plus simple moyen de créer un index est d'indiquer la ou les colonnes
à indexer et le nom de la table. Voici un exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{i_c1_t1 }\KeywordTok{on} \NormalTok{t1(c1);}
\end{Highlighting}
\end{Shaded}

Cependant, il existe beaucoup d'autres options. Il est possible
d'indiquer l'ordre des valeurs (ascendant ou descendant), ainsi que
l'emplacement des valeurs NULL.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Lecture avec index}\label{lecture-avec-index}

\begin{itemize}
\tightlist
\item
  L'index permet une recherche plus rapide
\item
  Il n'a besoin de lire que quelques blocs

  \begin{itemize}
  \tightlist
  \item
    et non pas la table entière
  \end{itemize}
\end{itemize}

L'index contient des informations redondantes. Il contient uniquement
les données disponibles dans la tête. Son intérêt n'est donc pas ce
qu'il stocke mais la façon dont il le fait. Le stockage réalisé par
l'index est optimisé pour la recherche.

L'exemple suivant montre la rapidité pour trouver un élément dans une
table en contenant un million, avec un index et sans index :

\begin{verbatim}
CREATE TABLE t1 (c1 integer, c2 text);
INSERT INTO t1 SELECT i, 'Ligne '||i 
FROM generate_series(1, 1000000) AS i;

\timing
Timing is on.

select * from t1 where c1=500000;
   c1   |      c2      
--------+--------------
 500000 | Ligne 500000
(1 row)

Time: 109.569 ms

select * from t1 where c1=500000;
   c1   |      c2      
--------+--------------
 500000 | Ligne 500000
(1 row)

Time: 109.386 ms

CREATE INDEX ON t1(c1);

SELECT * FROM t1 WHERE c1=500000;
   c1   |      c2      
--------+--------------
 500000 | Ligne 500000
(1 row)

Time: 0.675 ms
\end{verbatim}

Donc il faut 100 millisecondes pour trouver une ligne parmi un million
sans index, et 0,7 millisecondes avec un index. Le gain est appréciable.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Insertion avec index}\label{insertion-avec-index}

\begin{itemize}
\tightlist
\item
  Accélère la recherche

  \begin{itemize}
  \tightlist
  \item
    mais ralentit les écritures
  \end{itemize}
\item
  Une écriture dans la table doit mettre à jour l'index
\item
  Faire attention à ne pas créer des index inutiles
\end{itemize}

Voici un exemple montrant la différence de durée d'exécution d'une
insertion d'un million de lignes sur une table sans index, puis avec un
index :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{DROP} \KeywordTok{TABLE} \NormalTok{t1;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (c1 }\DataTypeTok{integer}\NormalTok{, c2 text);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 }\KeywordTok{SELECT} \NormalTok{i, }\StringTok{'Ligne '}\NormalTok{||i }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000000}\NormalTok{) }\KeywordTok{AS} \NormalTok{i;}
\NormalTok{Time: }\FloatTok{2761.236} \NormalTok{ms}

\KeywordTok{DROP} \KeywordTok{TABLE} \NormalTok{t1;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (c1 }\DataTypeTok{integer}\NormalTok{, c2 text);}
\KeywordTok{CREATE} \KeywordTok{INDEX} \KeywordTok{ON} \NormalTok{t1(c1);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 }\KeywordTok{SELECT} \NormalTok{i, }\StringTok{'Ligne '}\NormalTok{||i }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000000}\NormalTok{) }\KeywordTok{AS} \NormalTok{i;}
\NormalTok{Time: }\FloatTok{5401.505} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

On passe ici du simple au double. Il est donc essentiel pour les
performances de faire attention aux index créés. Oui, ils permettent de
gagner en performances lors des lectures mais les écritures sont
forcément ralenties. La création d'un index doit se faire en ayant pris
ceci en considération.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Tri avec un index}\label{tri-avec-un-index}

\begin{itemize}
\tightlist
\item
  Un index ne sert pas qu'à la recherche
\item
  Les données étant triées, il sert aussi à accélérer les tris
\end{itemize}

Voici un exemple montrant la différence de durée d'exécution d'un tri
sur un million de lignes sur une table sans index, puis avec un index :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{\textbackslash{}o /dev/null}
\KeywordTok{DROP} \KeywordTok{TABLE} \NormalTok{t1;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (c1 }\DataTypeTok{integer}\NormalTok{, c2 text);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 }\KeywordTok{SELECT} \NormalTok{i, }\StringTok{'Ligne '}\NormalTok{||i }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000000}\NormalTok{) }\KeywordTok{AS} \NormalTok{i;}
\NormalTok{\textbackslash{}timing}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{c1;}
\NormalTok{Time: }\FloatTok{618.203} \NormalTok{ms}

\KeywordTok{CREATE} \KeywordTok{INDEX} \KeywordTok{ON} \NormalTok{t1(c1);}
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{c1;}
\NormalTok{Time: }\FloatTok{359.369} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

Dans cet exemple, le tri est deux fois plus rapide avec un index. Il est
donc tout à fait concevable d'ajouter un index uniquement pour
satisfaire un tri.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Index partiel}\label{index-partiel}

\begin{itemize}
\tightlist
\item
  N'indexer qu'une partie des données
\item
  Requête typique :
  \texttt{SELECT\ *\ FROM\ taches\ WHERE\ fait\ IS\ false\ ORDER\ BY\ date\_rendu;}
\item
  Index typique
  :\texttt{CREATE\ INDEX\ ON\ taches(date\_rendu)\ WHERE\ fait\ IS\ false;}
\end{itemize}

L'index indiqué ici permet de réaliser le filtre et le tri en un seul
parcours d'index. Il aidera fortement cette requête. En revanche, il y a
peu de chances que cet index soit utile pour d'autres requêtes.

L'autre intérêt de cet index concerne sa volumétrie sur disque. Sur
cette requête typique, il clair que, plus le temps passe, et plus il y
aura de tâches réalisées. Du coup, même si la table grossit rapidement,
l'index devrait rester à la même volumétrie. En tout cas, il grossira
beaucoup moins que la table.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Index fonctionnel}\label{index-fonctionnel}

\begin{itemize}
\tightlist
\item
  Indexer le résultat d'une expression
\item
  Fonctions immuables seulement
\item
  Requête typique
  :\texttt{SELECT\ *\ FROM\ employes\ WHERE\ upper(nom)=\textquotesingle{}DUPONT\textquotesingle{};}
\item
  Index typique :\texttt{CREATE\ INDEX\ ON\ employes(upper(nom));}
\end{itemize}

L'idée de ce type d'index est de satisfaire les requêtes qui font une
recherche sur le résultat d'un calcul ou d'une fonction.

Dans la table suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{employe(}\KeywordTok{id} \NormalTok{serial, nom text, prenom text);}
\end{Highlighting}
\end{Shaded}

si on exécute la requête de recherche sur le nom, comme :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{employes }\KeywordTok{WHERE} \NormalTok{nom ILIKE }\StringTok{'DUPONT'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

ou comme :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{employes }\KeywordTok{WHERE} \FunctionTok{upper}\NormalTok{(nom)=}\StringTok{'DUPONT'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

PostgreSQL doit faire un parcours séquentiel car il ne peut pas utiliser
un index sur nom. Il faut utiliser la deuxième variante de la recherche
et créer l'index, non pas sur la colonne nom, mais sur le résultat de la
fonction \texttt{upper} sur la colonne nom.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Ce qu'il ne faut pas faire}\label{ce-quil-ne-faut-pas-faire}

\begin{itemize}
\tightlist
\item
  Modélisation
\item
  Écriture de requêtes
\item
  Conception de l'application
\end{itemize}

Cette partie présente différents problèmes fréquemment rencontrés et
leurs solutions.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Absence de contraintes}\label{absence-de-contraintes}

\begin{itemize}
\tightlist
\item
  Concerne surtout les clés étrangères
\item
  Parfois (souvent?) ignorées pour diverses raisons :

  \begin{itemize}
  \tightlist
  \item
    performances
  \item
    flexibilité du modèle de données
  \item
    compatibilité avec d'autres SGBD
  \item
    héritage de MySQL/MyISAM
  \item
    commodité de développement
  \end{itemize}
\item
  Conséquences

  \begin{itemize}
  \tightlist
  \item
    problèmes d'intégrité des données
  \item
    procédures de vérification de cohérence des données
  \end{itemize}
\end{itemize}

Les contraintes d'intégrités et notamment les clés étrangères sont
parfois absentes des modèles de données. Les problématiques de
performances et de flexibilité sont souvent mises en avant alors que les
contraintes sont justement une aide pour l'optimisation de requêtes par
le planificateur et empêche des incohérence dans les données.

Bien que cela peut sembler être une bonne idée en début de projet,
l'absence de ces contraintes va poser des problèmes d'intégrité des
données au fur et à mesure de l'évolution des besoins et du code. Les
données seront très certainement manipulées de façon différentes par la
suite, par différentes applications, entraînant ainsi de réels problèmes
d'intégrité des données. Mais surtout, du point de vue applicatif, il
est très difficile, si ce n'est impossible, d'empêcher deux écritures
concurrentes qui mèneraient à une incohérence (cas d'une //Race
Condition//). Imaginez le scénario suivant: - la transaction x1 s'assure
que la donnée est présente dans la table t1 - la transaction x2 supprime
la donnée précédente dans la table t1 - la transaction x1 insère une
ligne dans la table t2 faisant référence à la ligne de t1 qu'elle pense
encore présente

Ce cas est très facilement gérable pour un moteur de base de donnée si
une clé étrangère existe. Redévelopper ces mêmes contrôles dans la
couche applicative sera toujours plus coûteux en terme de performance,
voir impossible à faire dans certains cas sans passer par la base de
donnée elle même (multiple serveurs applicatifs accédant à la même base
de donnée).

Il peut s'en suivre des calculs d'agrégats faux et des problèmes
applicatifs de toute sorte. Souvent, plutôt que de corriger le modèle de
données, des procédures de vérification de la cohérence des données
seront mises en place, entraînant ainsi un travail supplémentaire pour
trouver et corriger les incohérences.

L'absence de clés étrangères et plus généralement de contraintes
d'intégrité entraîne dans le temps une charge de travail supplémentaire.
Il est bien préférable de mieux concevoir le modèle de données dès le
début avec les bonnes contraintes d'intégrité.

Parfois, les clés étrangères sont supprimées simplement parce que des
transactions sont en erreur car des données sont insérées dans une table
fille sans avoir alimenter la table mère. Des identifiants de clés
étrangères de la table fille sont absents de la table mère, entrainant
l'arrêt en erreur de la transaction. Il est possible de contourner cela
en différant la vérification des contraintes d'intégrité à la fin de la
transaction

L'exemple ci-dessous montre l'utilisation de la vérification des
contraintes d'intégrité en fin de transaction.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{mere (}\KeywordTok{id} \DataTypeTok{integer}\NormalTok{, t text);}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{fille (}\KeywordTok{id} \DataTypeTok{integer}\NormalTok{, mere_id }\DataTypeTok{integer}\NormalTok{, t text);}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{mere }\KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{pk_mere }\KeywordTok{PRIMARY} \KeywordTok{KEY} \NormalTok{(}\KeywordTok{id}\NormalTok{);}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{fille }
  \KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{fk_mere_fille}
      \KeywordTok{FOREIGN} \KeywordTok{KEY} \NormalTok{(mere_id)}
      \KeywordTok{REFERENCES} \NormalTok{mere (}\KeywordTok{id}\NormalTok{)}
         \NormalTok{MATCH }\KeywordTok{FULL}
         \KeywordTok{ON} \KeywordTok{UPDATE} \KeywordTok{NO} \NormalTok{ACTION}
         \KeywordTok{ON} \KeywordTok{DELETE} \KeywordTok{CASCADE}
         \KeywordTok{DEFERRABLE}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

La transaction insère d'abord les données dans la table fille, puis
ensuite dans la table mère :

\begin{verbatim}
BEGIN TRANSACTION;
SET CONSTRAINTS all deferred;

INSERT INTO fille (id, mere_id, t) VALUES (1, 1, 'val1');
INSERT INTO fille (id, mere_id, t) VALUES (2, 2, 'val2');

INSERT INTO mere (id, t) VALUES (1, 'val1'), (2, 'val2');

COMMIT;
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Stockage EAV}\label{stockage-eav}

\begin{itemize}
\tightlist
\item
  Entité-Attribut-Valeur
\item
  Pourquoi

  \begin{itemize}
  \tightlist
  \item
    flexibilité du modèle de données
  \item
    adapter sans délai ni surcoût le modèle de données
  \end{itemize}
\item
  Conséquences

  \begin{itemize}
  \tightlist
  \item
    création d'une table : \texttt{identifiant,\ nom\_attribut,\ valeur}
  \item
    requêtes abominables et coûteuses
  \end{itemize}
\item
  Solutions

  \begin{itemize}
  \tightlist
  \item
    revenir sur la conception du modèle de données
  \item
    utiliser un type de données plus adapté (\texttt{hstore},
    \texttt{jsonb})
  \end{itemize}
\end{itemize}

Le modèle relationnel a été critiqué depuis sa création pour son manque
de souplesse pour ajouter de nouveaux attributs ou pour proposer
plusieurs attributs sans pour autant nécessiter de redévelopper
l'application.

La solution souvent retenue est d'utiliser une table //à tout faire//
entité-attribut-valeur qui est associée à une autre table de la base de
données. Techniquement, une telle table comporte trois colonnes. La
première est un identifiant généré permet de référencer la table mère.
Les deux autres colonnes stockent le nom de l'attribut représenté et la
valeur représentée.

Ainsi, pour reprendre l'exemple des informations de contacts pour un
individu, une table \texttt{personnes} permet de stocker un identifiant
de personne. Une table \texttt{personne\_attributs} permet d'associer
des données à un identifiant de personne. Le type de données de la
colonne est souvent prévu largement pour faire tenir tout type
d'informations, mais sous forme textuelle. Les données ne peuvent donc
pas être validées.

\begin{verbatim}
CREATE TABLE personnes (id SERIAL PRIMARY KEY);

CREATE TABLE personne_attributs (
  id_pers INTEGER NOT NULL,
  nom_attr varchar(20) NOT NULL,
  val_attr varchar(100) NOT NULL
);

INSERT INTO personnes (id) VALUES (nextval('personnes_id_seq')) RETURNING id;
 id 
----  
 1

INSERT INTO personne_attributs (id_pers, nom_attr, val_attr)
  VALUES (1, 'nom', 'Prunelle'),
         (1, 'prenom', 'Léon');
(...)
\end{verbatim}

Un tel modèle peut sembler souple mais pose plusieurs problèmes. Le
premier concerne l'intégrité des données. Il n'est pas possible de
garantir la présence d'un attribut comme on le ferait avec une
contrainte \texttt{NOT\ NULL}. Si l'on souhaite stocker des données dans
un autre format qu'une chaîne de caractère, pour bénéficier des
contrôles de la base de données sur ce type, la seule solution est de
créer autant de colonnes d'attributs qu'il n'y a de types de données à
représentés. Ces colonnes ne permettront pas d'utiliser des contraintes
\texttt{CHECK} pour garantir la cohérence des valeurs stockées avec ce
qui est attendu, car les attributs peuvent stocker n'importe quelle
donnée.

Les requêtes SQL qui permettent de récupérer les données requises dans
l'application sont également particulièrement lourdes à écrire et à
maintenir, à moins de récupérer les données attribut par attribut.

Des problèmes de performances vont très rapidement se poser. Cette
représentation des données entraîne souvent l'effondrement des
performances d'une base de données relationnelle. Les requêtes sont
difficilement optimisables et nécessitent de réaliser beaucoup
d'entrées-sorties disque, au contraire d'une table, jointe
éventuellement à d'autres tables, où les accès par index permettent de
miniser les entrées-sorties.

Lorsque de telles solutions sont déployées pour stocker des données
transactionnelles, il vaut mieux revenir à un modèle de données
traditionnel qui permet de typer correctement les données, de mettre en
place les contraintes d'intégrité adéquates et d'écrire des requêtes SQL
efficaces.

Dans d'autres cas, il vaut mieux utiliser un type de données de
PostgreSQL qui est approprié, comme \texttt{hstore} ou \texttt{jsonb}
qui permet de stocker des données sous la forme
clé-\textgreater{}valeur. Le type de données \texttt{hstore} existe
depuis longtemps dans PostgreSQL et a pour principal défaut de ne
pouvoir représenter que des chaînes de caractères. Le type
\texttt{jsonb} est une nouveautés de PostgreSQL 9.4 et permet de
représenter des documents JSON et en les stockant dans un format interne
optimisé et indexable. Ces types de données peuvent être indexés pour
garantir de bons temps de réponses pour les requêtes qui nécessitent des
recherches sur certaines clés ou certaines valeurs.

Voici l'exemple précédent revu avec l'extension \texttt{hstore} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{create} \NormalTok{extension hstore;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{personnes (}\KeywordTok{id} \NormalTok{SERIAL }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, attributs hstore);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes (attributs) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'nom=>Prunelle, prenom=>Léon'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes (attributs) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'prenom=>Gaston,nom=>Lagaffe'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes (attributs) }\KeywordTok{VALUES} \NormalTok{(}\StringTok{'nom=>DeMaesmaker'}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{personnes;}
 \KeywordTok{id} \NormalTok{|              attributs               }
\CommentTok{----+--------------------------------------}
  \DecValTok{1} \NormalTok{| }\OtherTok{"nom"}\NormalTok{=>}\OtherTok{"Prunelle"}\NormalTok{, }\OtherTok{"prenom"}\NormalTok{=>}\OtherTok{"Léon"}
  \DecValTok{2} \NormalTok{| }\OtherTok{"nom"}\NormalTok{=>}\OtherTok{"Lagaffe"}\NormalTok{, }\OtherTok{"prenom"}\NormalTok{=>}\OtherTok{"Gaston"}
  \DecValTok{3} \NormalTok{| }\OtherTok{"nom"}\NormalTok{=>}\OtherTok{"DeMaesmaker"}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}

\KeywordTok{SELECT} \KeywordTok{id}\NormalTok{, attributs->}\StringTok{'prenom'} \KeywordTok{AS} \NormalTok{prenom }\KeywordTok{FROM} \NormalTok{personnes;}
 \KeywordTok{id} \NormalTok{|  prenom }
\CommentTok{----+----------}
  \DecValTok{1} \NormalTok{| Léon}
  \DecValTok{2} \NormalTok{| Gaston}
  \DecValTok{3} \NormalTok{| }
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}

\KeywordTok{SELECT} \KeywordTok{id}\NormalTok{, attributs->}\StringTok{'nom'} \KeywordTok{AS} \NormalTok{nom }\KeywordTok{FROM} \NormalTok{personnes;}
 \KeywordTok{id} \NormalTok{|     nom   }
\CommentTok{----+-------------}
  \DecValTok{1} \NormalTok{| Prunelle}
  \DecValTok{2} \NormalTok{| Lagaffe}
  \DecValTok{3} \NormalTok{| DeMaesmaker}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Attributs multi-colonnes}\label{attributs-multi-colonnes}

\begin{itemize}
\tightlist
\item
  Pourquoi

  \begin{itemize}
  \tightlist
  \item
    stocker plusieurs attributs pour une même ligne
  \item
    exemple : les différents numéros de téléphone d'une personne
  \end{itemize}
\item
  Pratique courante

  \begin{itemize}
  \tightlist
  \item
    ajouter plusieurs colonnes portant le même nom
  \end{itemize}
\item
  Conséquences

  \begin{itemize}
  \tightlist
  \item
    et s'il faut rajouter encore une colonne ?
  \item
    maîtrise de l'unicité des valeurs
  \item
    requêtes complexes à maintenir
  \end{itemize}
\item
  Solutions

  \begin{itemize}
  \tightlist
  \item
    créer une table de dépendance
  \item
    utiliser un type tableau
  \end{itemize}
\end{itemize}

Dans certains cas, le modèle de données doit être étendu pour pouvoir
stocker des données complémentaires. Un exemple typique est une table
qui stocke les informations pour contacter une personne. Une table
\texttt{personnes} ou \texttt{contacts} possède une colonne
\texttt{telephone} qui permet de stocker le numéro de téléphone d'une
personne. Or, une personne peut disposer de plusieurs numéros. Le
premier réflexe est souvent de créer une seconde colonne
\texttt{telephone\_2} pour stocker un numéro de téléphone
complémentaire. S'en suit une colonne \texttt{telephone\_3} voire
\texttt{telephone\_4} en fonction des besoins.

Dans de tels cas, les requêtes deviennent plus complexes à maintenir et
il est difficile de garantir l'unicité des valeurs stockées pour une
personne car l'écriture des contraintes d'intégrité devient de plus en
plus complexe au fur et à mesure que l'on ajoute une colonne pour
stocker un numéro.

La solution la plus pérenne pour gérer ce cas de figure est de créer une
table de dépendance qui est dédiée au stockage des numéros de téléphone.
Ainsi, la table \texttt{personnes} ne portera plus de colonnes
\texttt{telephone}, mais une table \texttt{telephones} portera un
identifiant référençant une personne et un numéro de téléphone. Ainsi,
si une personne dispose de plusieurs numéros de téléphone, la table
\texttt{telephones} comportera autant de lignes qu'il y a de numéros
pour une personne.

Les différents numéros de téléphone seront obtenus par jointure entre la
table \texttt{personnes} et la table \texttt{telephones}. L'application
se chargera de l'affichage.

Ci-dessous, un exemple d'implémentation du problème où une table
\texttt{telephones} dans laquelle plusieurs numéros seront stockés sur
plusieurs lignes plutôt que dans plusieurs colonnes.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{personnes (}
  \NormalTok{per_id SERIAL }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{,}
  \NormalTok{nom }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{50}\NormalTok{) }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{pnom }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{50}\NormalTok{) }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{...}
\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{telephones (}
  \NormalTok{per_id }\DataTypeTok{INTEGER} \KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{numero }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{20}\NormalTok{),}
  \KeywordTok{PRIMARY} \KeywordTok{KEY} \NormalTok{(per_id, numero),}
  \KeywordTok{FOREIGN} \KeywordTok{KEY} \NormalTok{(per_id) }\KeywordTok{REFERENCES} \NormalTok{personnes (per_id)}
\NormalTok{);}
\end{Highlighting}
\end{Shaded}

L'unicité des valeurs sera garantie à l'aide d'une contrainte d'unicité
posée sur l'identifiant \texttt{per\_id} et le numéro de téléphone.

Une autre solution consiste à utiliser un tableau pour représenter cette
information. D'un point de vue conceptuel, le lien entre une personne et
son ou ses numéros de téléphone est plus une «composition» qu'une réelle
«relation»: le numéro de téléphone ne nous intéresse pas en tant que
tel, mais uniquement en tant que détail d'une personne. On n'accèdera
jamais à un numéro de téléphone séparément: la table \texttt{telephones}
donnée plus haut n'a pas de clé «naturelle», un simple rattachement à la
table personnes par l'id de la personne. Sans même parler de
partitionnement, on gagnerait donc en performances en stockant
directement les numéros de téléphone dans la table \texttt{personnes},
ce qui est parfaitement faisable sous PostgreSQL :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{personnes (}
  \NormalTok{per_id SERIAL }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{,}
  \NormalTok{nom }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{50}\NormalTok{) }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{pnom }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{50}\NormalTok{) }\KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{,}
  \NormalTok{numero }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{20}\NormalTok{)[]}
\NormalTok{);}

\CommentTok{-- Ajout d'une personne}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{personnes (nom, pnom, numero)}
     \KeywordTok{VALUES} \NormalTok{(}\StringTok{'Simpson'}\NormalTok{, }\StringTok{'Omer'}\NormalTok{, }\StringTok{'\{0607080910\}'}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{personnes;}
 \NormalTok{per_id |   nom   | pnom |    numero    }
\CommentTok{--------+---------+------+--------------}
      \DecValTok{1} \NormalTok{| Simpson | Omer | \{}\DecValTok{0607080910}\NormalTok{\}}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}

\CommentTok{-- Ajout d'un numéro de téléphone pour une personne donnée :}
\KeywordTok{UPDATE} \NormalTok{personnes}
   \KeywordTok{SET} \NormalTok{numero = numero || }\StringTok{'\{0102030420\}'}
 \KeywordTok{WHERE} \NormalTok{per_id = }\DecValTok{1}\NormalTok{;}

\CommentTok{-- Vérification de l'ajout :}
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{personnes;}
 \NormalTok{per_id |   nom   | pnom |         numero          }
\CommentTok{--------+---------+------+-------------------------}
      \DecValTok{1} \NormalTok{| Simpson | Omer | \{}\DecValTok{0607080910}\NormalTok{,}\DecValTok{0102030420}\NormalTok{\}}

\CommentTok{-- Séparation des éléments du tableau :}
\KeywordTok{SELECT} \NormalTok{per_id, nom, pnom, unnest(numero) }\KeywordTok{AS} \NormalTok{numero}
  \KeywordTok{FROM} \NormalTok{personnes;}
 \NormalTok{per_id |   nom   | pnom |   numero   }
\CommentTok{--------+---------+------+------------}
      \DecValTok{1} \NormalTok{| Simpson | Omer | }\DecValTok{0607080910}
      \DecValTok{1} \NormalTok{| Simpson | Omer | }\DecValTok{0102030420}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Choix des types de
données}\label{choix-des-types-de-donnuxe9es}

\begin{itemize}
\tightlist
\item
  Objectif

  \begin{itemize}
  \tightlist
  \item
    représenter des valeurs décimales
  \end{itemize}
\item
  Pratique courante

  \begin{itemize}
  \tightlist
  \item
    utiliser le type \texttt{float} ou \texttt{double}
  \end{itemize}
\item
  Problèmes :

  \begin{itemize}
  \tightlist
  \item
    les types flottants ne stockent qu'une approximation de la valeur
    représentée
  \item
    les erreurs d'arrondis se multiplient
  \item
    les requêtes produisent des résultats faux
  \end{itemize}
\item
  Solutions

  \begin{itemize}
  \tightlist
  \item
    privilégier le type \texttt{numeric(x,\ y)} pour les calculs précis
    (financiers notamment)
  \end{itemize}
\end{itemize}

Certaines applications scientifiques se contentent de types flottants
standards car ils permettent d'encoder des valeurs plus importantes que
les types entiers standards. Néanmoins, les types flottants sont peu
précis, notamment pour les applications financières où une erreur
d'arrondi n'est pas envisageable.

Exemple montrant une perte de précision dans les calculs :

\begin{verbatim}
test=# CREATE TABLE comptes (compte_id SERIAL PRIMARY KEY, solde FLOAT);
NOTICE:  CREATE TABLE will create implicit sequence "comptes_compte_id_seq" for serial column "comptes.compte_id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "comptes_pkey" for table "comptes"
CREATE TABLE

test=# INSERT INTO comptes (solde) VALUES (100000000.1), (10.1), (10000.2), (100000000000000.1);
INSERT 0 4

test=#SELECT SUM(solde) FROM comptes;
       sum       
-----------------
 100000100010010
(1 row)
\end{verbatim}

L'utilisation du type \texttt{numeric} permet d'éviter la perte de
précision :

\begin{verbatim}
test=# CREATE TABLE comptes (compte_id SERIAL PRIMARY KEY, solde NUMERIC);
NOTICE:  CREATE TABLE will create implicit sequence "comptes_compte_id_seq" for serial column "comptes.compte_id"
NOTICE:  CREATE TABLE / PRIMARY KEY will create implicit index "comptes_pkey" for table "comptes"
CREATE TABLE

test=# INSERT INTO comptes (solde) VALUES (100000000.1), (10.1), (10000.2), (100000000000000.1);
INSERT 0 4

test=# SELECT SUM(solde) FROM comptes;
        sum        
-------------------
 100000100010010.5
(1 row)
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Problèmes courants d'écriture de
requêtes}\label{probluxe8mes-courants-duxe9criture-de-requuxeates}

\begin{itemize}
\tightlist
\item
  Utilisation de \texttt{NULL}
\item
  Ordre implicite des colonnes
\item
  Requêtes spaghetti
\item
  Moteur de recherche avec \texttt{LIKE}
\end{itemize}

Le langage SQL est généralement méconnu, ce qui amène à l'écriture de
requêtes peu performantes, voire peu pérennes.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Ordre implicite des
colonnes}\label{ordre-implicite-des-colonnes}

\begin{itemize}
\tightlist
\item
  Objectif

  \begin{itemize}
  \tightlist
  \item
    s'économiser d'écrire la liste des colonnes dans une requête
  \end{itemize}
\item
  Problèmes

  \begin{itemize}
  \tightlist
  \item
    si l'ordre des colonnes change, les résultats changent
  \item
    résultats faux
  \item
    données corrompues
  \end{itemize}
\item
  Solutions

  \begin{itemize}
  \tightlist
  \item
    nommer les colonnes impliquées
  \end{itemize}
\end{itemize}

Le langage SQL permet de s'appuyer sur l'ordre physique des colonnes
d'une table. Or, faire confiance à la base de données pour conserver cet
ordre physique peut entraîner de graves problèmes applicatifs en cas de
changements. Dans le meilleur des cas, l'application ne fonctionnera
plus, ce qui permet d'éviter les corruptions de données silencieuses, où
une colonne prend des valeurs destinées normalement à être stockées dans
une autre colonne. Si l'application continue de fonctionner, elle va
générer des résultats faux et des incohérences d'affichage.

C'est pourquoi, il est préférable de lister explicitement les colonnes
dans les ordres \texttt{INSERT} et \texttt{SELECT}, afin de garder un
ordre d'insertion déterministe.

Par exemple, l'ordre des colonnes peut changer notamment lorsque
certains ETL sont utilisés pour modifier le type d'une colonne
\texttt{varchar(10)} en \texttt{varchar(11)}. Par exemple, pour la
colonne \texttt{username}, l'ETL Kettle génère les ordres suivants :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{utilisateurs }\KeywordTok{ADD} \KeywordTok{COLUMN} \NormalTok{username_KTL }\DataTypeTok{VARCHAR}\NormalTok{(}\DecValTok{11}\NormalTok{);}
\KeywordTok{UPDATE} \NormalTok{utilisateurs }\KeywordTok{SET} \NormalTok{username_KTL=username;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{utilisateurs }\KeywordTok{DROP} \KeywordTok{COLUMN} \NormalTok{username;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{utilisateurs }\KeywordTok{RENAME} \NormalTok{username_KTL }\KeywordTok{TO} \NormalTok{username}
\end{Highlighting}
\end{Shaded}

Il génère des ordres SQL inutiles et consommateurs d'entrées/sorties
disques car il doit générer des ordres SQL compris par tous les SGBD du
marché. Or, tous les SGBD ne permettent pas de changer le type d'une
colonne aussi simplement que dans PostgreSQL.

\textbf{Exemples}

Exemple de modification du schéma pouvant entrainer des problèmes
d'insertion si les colonnes ne sont pas listées explicitement :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{insere (}\KeywordTok{id} \DataTypeTok{integer} \KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, col1 }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{5}\NormalTok{), col2 }\DataTypeTok{integer}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{insere }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'XX'}\NormalTok{, }\DecValTok{10}\NormalTok{);}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{insere ;}
 \KeywordTok{id} \NormalTok{| col1 | col2 }
\CommentTok{----+------+------}
  \DecValTok{1} \NormalTok{| XX   |   }\DecValTok{10}

\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{insere }\KeywordTok{ADD} \KeywordTok{COLUMN} \NormalTok{col1_tmp }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{6}\NormalTok{);}
\KeywordTok{UPDATE} \NormalTok{insere }\KeywordTok{SET} \NormalTok{col1_tmp = col1;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{insere }\KeywordTok{DROP} \KeywordTok{COLUMN} \NormalTok{col1;}
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{insere }\KeywordTok{RENAME} \KeywordTok{COLUMN} \NormalTok{col1_tmp }\KeywordTok{TO} \NormalTok{col1;}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{insere }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'XXX'}\NormalTok{, }\DecValTok{10}\NormalTok{);}
\NormalTok{ERROR:  invalid input syntax }\KeywordTok{for} \NormalTok{integer: }\OtherTok{"XXX"}
\NormalTok{LINE }\DecValTok{1}\NormalTok{: }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{insere }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'XXX'}\NormalTok{, }\DecValTok{10}\NormalTok{);}
                                      \NormalTok{^}

\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{insere ;}
 \KeywordTok{id} \NormalTok{| col2 | col1 }
\CommentTok{----+------+------}
  \DecValTok{1} \NormalTok{|   }\DecValTok{10} \NormalTok{| XX}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Code spaghetti}\label{code-spaghetti}

Le problème est similaire à tout autre langage :

\begin{itemize}
\tightlist
\item
  Code spaghetti pour le SQL

  \begin{itemize}
  \tightlist
  \item
    Écriture d'une requête à partir d'une autre requête
  \item
    Ou évolution d'une requête au fil du temps avec des ajouts
  \end{itemize}
\item
  Vite ingérable

  \begin{itemize}
  \tightlist
  \item
    Ne pas hésiter à reprendre la requête à zéro, en repensant sa
    sémantique
  \item
    Souvent, un changement de spécification est un changement de sens,
    au niveau relationnel, de la requête
  \item
    Ne pas la patcher !
  \end{itemize}
\end{itemize}

FIXME
\{\{/formations/s4-complements\_sql/../j3-optimisation\_sql//monsterquery\_4.jpg\}\}

Un exemple (sous Oracle):

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT}                \NormalTok{Article.datem                                 }\KeywordTok{AS} \NormalTok{Article_1_9, }
                      \NormalTok{Article.degre_alcool                          }\KeywordTok{AS} \NormalTok{Article_1_10, }
                      \NormalTok{Article.id                                    }\KeywordTok{AS} \NormalTok{Article_1_19, }
                      \NormalTok{Article.iddf_categor                          }\KeywordTok{AS} \NormalTok{Article_1_20, }
                      \NormalTok{Article.iddp_clsvtel                          }\KeywordTok{AS} \NormalTok{Article_1_21, }
                      \NormalTok{Article.iddp_cdelist                          }\KeywordTok{AS} \NormalTok{Article_1_22, }
                      \NormalTok{Article.iddf_cd_prix                          }\KeywordTok{AS} \NormalTok{Article_1_23, }
                      \NormalTok{Article.iddp_agreage                          }\KeywordTok{AS} \NormalTok{Article_1_24, }
                      \NormalTok{Article.iddp_codelec                          }\KeywordTok{AS} \NormalTok{Article_1_25, }
                      \NormalTok{Article.idda_compo                            }\KeywordTok{AS} \NormalTok{Article_1_26, }
                      \NormalTok{Article.iddp_comptex                          }\KeywordTok{AS} \NormalTok{Article_1_27, }
                      \NormalTok{Article.iddp_cmptmat                          }\KeywordTok{AS} \NormalTok{Article_1_28, }
                      \NormalTok{Article.idda_articleparent                    }\KeywordTok{AS} \NormalTok{Article_1_29, }
                      \NormalTok{Article.iddp_danger                           }\KeywordTok{AS} \NormalTok{Article_1_30, }
                      \NormalTok{Article.iddf_fabric                           }\KeywordTok{AS} \NormalTok{Article_1_33, }
                      \NormalTok{Article.iddp_marqcom                          }\KeywordTok{AS} \NormalTok{Article_1_34, }
                      \NormalTok{Article.iddp_nomdoua                          }\KeywordTok{AS} \NormalTok{Article_1_35, }
                      \NormalTok{Article.iddp_pays                             }\KeywordTok{AS} \NormalTok{Article_1_37, }
                      \NormalTok{Article.iddp_recept                           }\KeywordTok{AS} \NormalTok{Article_1_40, }
                      \NormalTok{Article.idda_unalvte                          }\KeywordTok{AS} \NormalTok{Article_1_42, }
                      \NormalTok{Article.iddb_sitecl                          }\KeywordTok{AS} \NormalTok{Article_1_43, }
                      \NormalTok{Article.lib_caisse                            }\KeywordTok{AS} \NormalTok{Article_1_49, }
                      \NormalTok{Article.lib_com                               }\KeywordTok{AS} \NormalTok{Article_1_50, }
                      \NormalTok{Article.maj_en_attente                        }\KeywordTok{AS} \NormalTok{Article_1_61, }
                      \NormalTok{Article.qte_stk                               }\KeywordTok{AS} \NormalTok{Article_1_63, }
                      \NormalTok{Article.ref_tech                              }\KeywordTok{AS} \NormalTok{Article_1_64, }
                      \DecValTok{1}                                             \KeywordTok{AS} \NormalTok{Article_1_70, }
                      \KeywordTok{CASE} 
                        \KeywordTok{WHEN} \NormalTok{(}\KeywordTok{SELECT} \FunctionTok{COUNT}\NormalTok{(MA.id) }
                              \KeywordTok{FROM}   \NormalTok{da_majart MA }
                                     \KeywordTok{join} \NormalTok{da_majmas MM }
                                       \KeywordTok{ON} \NormalTok{MM.id = MA.idda_majmas }
                                     \KeywordTok{join} \NormalTok{gt_tmtprg TMT }
                                       \KeywordTok{ON} \NormalTok{TMT.id = MM.idgt_tmtprg }
                                     \KeywordTok{join} \NormalTok{gt_prog PROG }
                                       \KeywordTok{ON} \NormalTok{PROG.id = TMT.idgt_prog }
                              \KeywordTok{WHERE}  \NormalTok{idda_article = Article.id }
                                 \KeywordTok{AND} \FunctionTok{TO_DATE}\NormalTok{(}\FunctionTok{TO_CHAR}\NormalTok{(PROG.date_lancement, }\StringTok{'DDMMYYYY'}\NormalTok{) }
                                             \NormalTok{|| }\FunctionTok{TO_CHAR}\NormalTok{(PROG.heure_lancement, }\StringTok{' HH24:MI:SS'}\NormalTok{), }\StringTok{'DDMMYYYY HH24:MI:SS'}\NormalTok{) >= }\FunctionTok{SYSDATE}\NormalTok{) >= }\DecValTok{1} \KeywordTok{THEN} \DecValTok{1}
                        \KeywordTok{ELSE} \DecValTok{0} 
                      \KeywordTok{END}                                           \KeywordTok{AS} \NormalTok{Article_1_74, }
                      \NormalTok{Article.iddp_compnat                          }\KeywordTok{AS} \NormalTok{Article_2_0, }
                      \NormalTok{Article.iddp_modven                           }\KeywordTok{AS} \NormalTok{Article_2_1, }
                      \NormalTok{Article.iddp_nature                           }\KeywordTok{AS} \NormalTok{Article_2_2, }
                      \NormalTok{Article.iddp_preclin                          }\KeywordTok{AS} \NormalTok{Article_2_3, }
                      \NormalTok{Article.iddp_raybala                          }\KeywordTok{AS} \NormalTok{Article_2_4, }
                      \NormalTok{Article.iddp_sensgrt                          }\KeywordTok{AS} \NormalTok{Article_2_5, }
                      \NormalTok{Article.iddp_tcdtfl                           }\KeywordTok{AS} \NormalTok{Article_2_6, }
                      \NormalTok{Article.iddp_unite                            }\KeywordTok{AS} \NormalTok{Article_2_8, }
                      \NormalTok{Article.idda_untgrat                          }\KeywordTok{AS} \NormalTok{Article_2_9, }
                      \NormalTok{Article.idda_unpoids                          }\KeywordTok{AS} \NormalTok{Article_2_10, }
                      \NormalTok{Article.iddp_unilogi                          }\KeywordTok{AS} \NormalTok{Article_2_11, }
                      \NormalTok{ArticleComplement.datem                       }\KeywordTok{AS} \NormalTok{ArticleComplement_5_6,}
                      \NormalTok{ArticleComplement.extgar_depl                 }\KeywordTok{AS} \NormalTok{ArticleComplement_5_9,}
                      \NormalTok{ArticleComplement.extgar_mo                   }\KeywordTok{AS} \NormalTok{ArticleComplement_5_10,}
                      \NormalTok{ArticleComplement.extgar_piece                }\KeywordTok{AS} \NormalTok{ArticleComplement_5_11,}
                      \NormalTok{ArticleComplement.id                          }\KeywordTok{AS} \NormalTok{ArticleComplement_5_20,}
                      \NormalTok{ArticleComplement.iddf_collect                }\KeywordTok{AS} \NormalTok{ArticleComplement_5_22,}
                      \NormalTok{ArticleComplement.iddp_gpdtcul                }\KeywordTok{AS} \NormalTok{ArticleComplement_5_23,}
                      \NormalTok{ArticleComplement.iddp_support                }\KeywordTok{AS} \NormalTok{ArticleComplement_5_25,}
                      \NormalTok{ArticleComplement.iddp_typcarb                }\KeywordTok{AS} \NormalTok{ArticleComplement_5_27,}
                      \NormalTok{ArticleComplement.mt_ext_gar                  }\KeywordTok{AS} \NormalTok{ArticleComplement_5_36,}
                      \NormalTok{ArticleComplement.pres_cpt                    }\KeywordTok{AS} \NormalTok{ArticleComplement_5_44,}
                      \NormalTok{GenreProduitCulturel.code                     }\KeywordTok{AS} \NormalTok{GenreProduitCulturel_6_0,}
                      \NormalTok{Collection.libelle                            }\KeywordTok{AS} \NormalTok{Collection_8_1, }
                      \NormalTok{Gtin.date_dern_vte                            }\KeywordTok{AS} \NormalTok{Gtin_10_0, }
                      \NormalTok{Gtin.gtin                                     }\KeywordTok{AS} \NormalTok{Gtin_10_1, }
                      \NormalTok{Gtin.id                                       }\KeywordTok{AS} \NormalTok{Gtin_10_3, }
                      \NormalTok{Fabricant.code                                }\KeywordTok{AS} \NormalTok{Fabricant_14_0, }
                      \NormalTok{Fabricant.nom                                 }\KeywordTok{AS} \NormalTok{Fabricant_14_2, }
                      \NormalTok{ClassificationVenteLocale.niveau1             }\KeywordTok{AS} \NormalTok{ClassificationVenteL_16_2,}
                      \NormalTok{ClassificationVenteLocale.niveau2             }\KeywordTok{AS} \NormalTok{ClassificationVenteL_16_3,}
                      \NormalTok{ClassificationVenteLocale.niveau3             }\KeywordTok{AS} \NormalTok{ClassificationVenteL_16_4,}
                      \NormalTok{ClassificationVenteLocale.niveau4             }\KeywordTok{AS} \NormalTok{ClassificationVenteL_16_5,}
                      \NormalTok{MarqueCommerciale.code                        }\KeywordTok{AS} \NormalTok{MarqueCommerciale_18_0,}
                      \NormalTok{MarqueCommerciale.libellelong                 }\KeywordTok{AS} \NormalTok{MarqueCommerciale_18_4,}
                      \NormalTok{Composition.code                              }\KeywordTok{AS} \NormalTok{Composition_20_0, }
                      \NormalTok{CompositionTextile.code                       }\KeywordTok{AS} \NormalTok{CompositionTextile_21_0,}
                      \NormalTok{AssoArticleInterfaceBalance.datem             }\KeywordTok{AS} \NormalTok{AssoArticleInterface_23_0,}
                      \NormalTok{AssoArticleInterfaceBalance.lib_envoi         }\KeywordTok{AS} \NormalTok{AssoArticleInterface_23_3,}
                      \NormalTok{AssoArticleInterfaceCaisse.datem              }\KeywordTok{AS} \NormalTok{AssoArticleInterface_24_0,}
                      \NormalTok{AssoArticleInterfaceCaisse.lib_envoi          }\KeywordTok{AS} \NormalTok{AssoArticleInterface_24_3,}
                      \KeywordTok{NULL}                                          \KeywordTok{AS} \NormalTok{TypeTraitement_25_0, }
                      \KeywordTok{NULL}                                          \KeywordTok{AS} \NormalTok{TypeTraitement_25_1, }
                      \NormalTok{RayonBalance.code                             }\KeywordTok{AS} \NormalTok{RayonBalance_31_0, }
                      \NormalTok{RayonBalance.max_cde_article                  }\KeywordTok{AS} \NormalTok{RayonBalance_31_5, }
                      \NormalTok{RayonBalance.min_cde_article                  }\KeywordTok{AS} \NormalTok{RayonBalance_31_6, }
                      \NormalTok{TypeTare.code                                 }\KeywordTok{AS} \NormalTok{TypeTare_32_0, }
                      \NormalTok{GrilleDePrix.datem                            }\KeywordTok{AS} \NormalTok{GrilleDePrix_34_1, }
                      \NormalTok{GrilleDePrix.libelle                          }\KeywordTok{AS} \NormalTok{GrilleDePrix_34_3, }
                      \NormalTok{FicheAgreage.code                             }\KeywordTok{AS} \NormalTok{FicheAgreage_38_0, }
                      \NormalTok{Codelec.iddp_periact                          }\KeywordTok{AS} \NormalTok{Codelec_40_1, }
                      \NormalTok{Codelec.libelle                               }\KeywordTok{AS} \NormalTok{Codelec_40_2, }
                      \NormalTok{Codelec.niveau1                               }\KeywordTok{AS} \NormalTok{Codelec_40_3, }
                      \NormalTok{Codelec.niveau2                               }\KeywordTok{AS} \NormalTok{Codelec_40_4, }
                      \NormalTok{Codelec.niveau3                               }\KeywordTok{AS} \NormalTok{Codelec_40_5, }
                      \NormalTok{Codelec.niveau4                               }\KeywordTok{AS} \NormalTok{Codelec_40_6, }
                      \NormalTok{PerimetreActivite.code                        }\KeywordTok{AS} \NormalTok{PerimetreActivite_41_0,}
                      \NormalTok{DonneesPersonnalisablesCodelec.gestionreftech }\KeywordTok{AS} \NormalTok{DonneesPersonnalisab_42_0,}
                      \NormalTok{ClassificationArticleInterne.id               }\KeywordTok{AS} \NormalTok{ClassificationArticl_43_0,}
                      \NormalTok{ClassificationArticleInterne.niveau1          }\KeywordTok{AS} \NormalTok{ClassificationArticl_43_2,}
                      \NormalTok{DossierCommercial.id                          }\KeywordTok{AS} \NormalTok{DossierCommercial_52_0,}
                      \NormalTok{DossierCommercial.codefourndc                 }\KeywordTok{AS} \NormalTok{DossierCommercial_52_1,}
                      \NormalTok{DossierCommercial.anneedc                     }\KeywordTok{AS} \NormalTok{DossierCommercial_52_3,}
                      \NormalTok{DossierCommercial.codeclassdc                 }\KeywordTok{AS} \NormalTok{DossierCommercial_52_4,}
                      \NormalTok{DossierCommercial.numversiondc                }\KeywordTok{AS} \NormalTok{DossierCommercial_52_5,}
                      \NormalTok{DossierCommercial.indice                      }\KeywordTok{AS} \NormalTok{DossierCommercial_52_6,}
                      \NormalTok{DossierCommercial.code_ss_classement          }\KeywordTok{AS} \NormalTok{DossierCommercial_52_7,}
                      \NormalTok{OrigineNegociation.code                       }\KeywordTok{AS} \NormalTok{OrigineNegociation_53_0,}
                      \NormalTok{MotifBlocageInformation.libellelong           }\KeywordTok{AS} \NormalTok{MotifBlocageInformat_54_3,}
                      \NormalTok{ArbreLogistique.id                            }\KeywordTok{AS} \NormalTok{ArbreLogistique_63_1, }
                      \NormalTok{ArbreLogistique.codesap                       }\KeywordTok{AS} \NormalTok{ArbreLogistique_63_5, }
                      \NormalTok{Fournisseur.code                              }\KeywordTok{AS} \NormalTok{Fournisseur_66_0, }
                      \NormalTok{Fournisseur.nom                               }\KeywordTok{AS} \NormalTok{Fournisseur_66_2, }
                      \NormalTok{Filiere.code                                  }\KeywordTok{AS} \NormalTok{Filiere_67_0, }
                      \NormalTok{Filiere.nom                                   }\KeywordTok{AS} \NormalTok{Filiere_67_2, }
                      \NormalTok{ValorisationAchat.val_ach_patc                }\KeywordTok{AS} \NormalTok{Valorisation_74_3, }
                      \NormalTok{LienPrixVente.code                            }\KeywordTok{AS} \NormalTok{LienPrixVente_76_0, }
                      \NormalTok{LienPrixVente.datem                           }\KeywordTok{AS} \NormalTok{LienPrixVente_76_1, }
                      \NormalTok{LienGratuite.code                             }\KeywordTok{AS} \NormalTok{LienGratuite_78_0, }
                      \NormalTok{LienGratuite.datem                            }\KeywordTok{AS} \NormalTok{LienGratuite_78_1, }
                      \NormalTok{LienCoordonnable.code                         }\KeywordTok{AS} \NormalTok{LienCoordonnable_79_0,}
                      \NormalTok{LienCoordonnable.datem                        }\KeywordTok{AS} \NormalTok{LienCoordonnable_79_1,}
                      \NormalTok{LienStatistique.code                          }\KeywordTok{AS} \NormalTok{LienStatistique_81_0, }
                      \NormalTok{LienStatistique.datem                         }\KeywordTok{AS} \NormalTok{LienStatistique_81_1 }
\KeywordTok{FROM}   \NormalTok{da_article Article }
       \KeywordTok{join} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{idarticle, }
                    \NormalTok{poids, }
                    \FunctionTok{ROW_NUMBER}\NormalTok{() }
                      \KeywordTok{over} \NormalTok{( }
                        \KeywordTok{PARTITION} \KeywordTok{BY} \NormalTok{RNA.id }
                        \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{INNERSEARCH.poids) RN, }
                    \NormalTok{titre, }
                    \NormalTok{nom, }
                    \NormalTok{prenom }
             \KeywordTok{FROM}   \NormalTok{da_article RNA }
                    \KeywordTok{join} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{idarticle, }
                                 \NormalTok{pkg_db_indexation.CALCULPOIDSMOTS(chaine, }\StringTok{'foire vins%'}\NormalTok{) }\KeywordTok{AS} \NormalTok{POIDS,}
                                 \FunctionTok{DECODE}\NormalTok{(index_clerecherche, }\StringTok{'Piste.titre'}\NormalTok{, chaine, }
                                                            \NormalTok{`)                            }\KeywordTok{AS} \NormalTok{TITRE,}
                                 \FunctionTok{DECODE}\NormalTok{(index_clerecherche, }\StringTok{'Artiste.nom_prenom'}\NormalTok{, }\FunctionTok{SUBSTR}\NormalTok{(chaine, }\DecValTok{0}\NormalTok{, }\FunctionTok{INSTR}\NormalTok{(chaine, }\StringTok{'_'}\NormalTok{) - }\DecValTok{1}\NormalTok{),}
                                                            \NormalTok{`)                            }\KeywordTok{AS} \NormalTok{NOM,}
                                 \FunctionTok{DECODE}\NormalTok{(index_clerecherche, }\StringTok{'Artiste.nom_prenom'}\NormalTok{, }\FunctionTok{SUBSTR}\NormalTok{(chaine, }\FunctionTok{INSTR}\NormalTok{(chaine, }\StringTok{'_'}\NormalTok{) + }\DecValTok{1}\NormalTok{),}
                                                            \NormalTok{`)                            }\KeywordTok{AS} \NormalTok{PRENOM}
                          \KeywordTok{FROM}   \NormalTok{((}\KeywordTok{SELECT} \NormalTok{index_idenreg }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.cde_art     }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Article.codeArticle'}
                                          \KeywordTok{join} \NormalTok{da_article C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot = }\StringTok{'foire'} 
                                   \KeywordTok{INTERSECT} 
                                   \KeywordTok{SELECT} \NormalTok{index_idenreg }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.cde_art     }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Article.codeArticle'}
                                          \KeywordTok{join} \NormalTok{da_article C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot }\KeywordTok{LIKE} \StringTok{'vins%'} 
                                      \KeywordTok{AND} \DecValTok{1} \NormalTok{= }\DecValTok{1}\NormalTok{) }
                                  \KeywordTok{UNION} \KeywordTok{ALL} 
                                  \NormalTok{(}\KeywordTok{SELECT} \NormalTok{index_idenreg }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.cde_art_bal }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Article.codeArticleBalance'}
                                          \KeywordTok{join} \NormalTok{da_article C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot = }\StringTok{'foire'} 
                                   \KeywordTok{INTERSECT} 
                                   \KeywordTok{SELECT} \NormalTok{index_idenreg }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.cde_art_bal }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Article.codeArticleBalance'}
                                          \KeywordTok{join} \NormalTok{da_article C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot }\KeywordTok{LIKE} \StringTok{'vins%'} 
                                      \KeywordTok{AND} \DecValTok{1} \NormalTok{= }\DecValTok{1}\NormalTok{) }
                                  \KeywordTok{UNION} \KeywordTok{ALL} 
                                  \NormalTok{(}\KeywordTok{SELECT} \NormalTok{index_idenreg }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.lib_com     }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Article.libelleCommercial'}
                                          \KeywordTok{join} \NormalTok{da_article C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot = }\StringTok{'foire'} 
                                   \KeywordTok{INTERSECT} 
                                   \KeywordTok{SELECT} \NormalTok{index_idenreg }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.lib_com     }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Article.libelleCommercial'}
                                          \KeywordTok{join} \NormalTok{da_article C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot }\KeywordTok{LIKE} \StringTok{'vins%'} 
                                      \KeywordTok{AND} \DecValTok{1} \NormalTok{= }\DecValTok{1}\NormalTok{) }
                                  \KeywordTok{UNION} \KeywordTok{ALL} 
                                  \NormalTok{(}\KeywordTok{SELECT} \NormalTok{idda_article }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.gtin       }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Gtin.gtin'} 
                                          \KeywordTok{join} \NormalTok{da_gtin C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot = }\StringTok{'foire'} 
                                   \KeywordTok{INTERSECT} 
                                   \KeywordTok{SELECT} \NormalTok{idda_article }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.gtin       }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'Gtin.gtin'} 
                                          \KeywordTok{join} \NormalTok{da_gtin C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot }\KeywordTok{LIKE} \StringTok{'vins%'} 
                                      \KeywordTok{AND} \DecValTok{1} \NormalTok{= }\DecValTok{1}\NormalTok{) }
                                  \KeywordTok{UNION} \KeywordTok{ALL} 
                                  \NormalTok{(}\KeywordTok{SELECT} \NormalTok{idda_article }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.ref_frn    }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'ArbreLogistique.referenceFournisseur'}
                                          \KeywordTok{join} \NormalTok{da_arblogi C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot = }\StringTok{'foire'} 
                                   \KeywordTok{INTERSECT} 
                                   \KeywordTok{SELECT} \NormalTok{idda_article }\KeywordTok{AS} \NormalTok{IDARTICLE, }
                                          \NormalTok{C.ref_frn    }\KeywordTok{AS} \NormalTok{CHAINE, }
                                          \NormalTok{index_clerecherche }
                                   \KeywordTok{FROM}   \NormalTok{cstd_mots M }
                                          \KeywordTok{join} \NormalTok{cstd_index I }
                                            \KeywordTok{ON} \NormalTok{I.mots_id = M.mots_id }
                                               \KeywordTok{AND} \NormalTok{index_clerecherche = }\StringTok{'ArbreLogistique.referenceFournisseur'}
                                          \KeywordTok{join} \NormalTok{da_arblogi C }
                                            \KeywordTok{ON} \KeywordTok{id} \NormalTok{= index_idenreg }
                                   \KeywordTok{WHERE}  \NormalTok{mots_mot }\KeywordTok{LIKE} \StringTok{'vins%'} 
                                      \KeywordTok{AND} \DecValTok{1} \NormalTok{= }\DecValTok{1}\NormalTok{))) INNERSEARCH }
                      \KeywordTok{ON} \NormalTok{INNERSEARCH.idarticle = RNA.id) SEARCHMC }
         \KeywordTok{ON} \NormalTok{SEARCHMC.idarticle = Article.id }
            \KeywordTok{AND} \DecValTok{1} \NormalTok{= }\DecValTok{1} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_artcmpl ArticleComplement }
              \KeywordTok{ON} \NormalTok{Article.id = ArticleComplement.idda_article }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_gpdtcul GenreProduitCulturel }
              \KeywordTok{ON} \NormalTok{ArticleComplement.iddp_gpdtcul = GenreProduitCulturel.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_collect Collection }
              \KeywordTok{ON} \NormalTok{ArticleComplement.iddf_collect = Collection.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_gtin Gtin }
              \KeywordTok{ON} \NormalTok{Article.id = Gtin.idda_article }
                 \KeywordTok{AND} \NormalTok{Gtin.principal = }\DecValTok{1} 
                 \KeywordTok{AND} \NormalTok{Gtin.db_suplog = }\DecValTok{0} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_fabric Fabricant }
              \KeywordTok{ON} \NormalTok{Article.iddf_fabric = Fabricant.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_clsvtel ClassificationVenteLocale }
              \KeywordTok{ON} \NormalTok{Article.iddp_clsvtel = ClassificationVenteLocale.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_marqcom MarqueCommerciale }
              \KeywordTok{ON} \NormalTok{Article.iddp_marqcom = MarqueCommerciale.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_compo Composition }
              \KeywordTok{ON} \NormalTok{Composition.id = Article.idda_compo }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_comptex CompositionTextile }
              \KeywordTok{ON} \NormalTok{CompositionTextile.id = Article.iddp_comptex }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_arttrai AssoArticleInterfaceBalance }
              \KeywordTok{ON} \NormalTok{AssoArticleInterfaceBalance.idda_article = Article.id }
                 \KeywordTok{AND} \NormalTok{AssoArticleInterfaceBalance.iddp_tinterf = }\DecValTok{1} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_arttrai AssoArticleInterfaceCaisse }
              \KeywordTok{ON} \NormalTok{AssoArticleInterfaceCaisse.idda_article = Article.id }
                 \KeywordTok{AND} \NormalTok{AssoArticleInterfaceCaisse.iddp_tinterf = }\DecValTok{4} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_raybala RayonBalance }
              \KeywordTok{ON} \NormalTok{Article.iddp_raybala = RayonBalance.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_valdico TypeTare }
              \KeywordTok{ON} \NormalTok{TypeTare.id = RayonBalance.iddp_typtare }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_categor Categorie }
              \KeywordTok{ON} \NormalTok{Categorie.id = Article.iddf_categor }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_grille GrilleDePrix }
              \KeywordTok{ON} \NormalTok{GrilleDePrix.id = Categorie.iddf_grille }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_agreage FicheAgreage }
              \KeywordTok{ON} \NormalTok{FicheAgreage.id = Article.iddp_agreage }
       \KeywordTok{join} \NormalTok{dp_codelec Codelec }
         \KeywordTok{ON} \NormalTok{Article.iddp_codelec = Codelec.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_periact PerimetreActivite }
              \KeywordTok{ON} \NormalTok{PerimetreActivite.id = Codelec.iddp_periact }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_perscod DonneesPersonnalisablesCodelec }
              \KeywordTok{ON} \NormalTok{Codelec.id = DonneesPersonnalisablesCodelec.iddp_codelec }
                 \KeywordTok{AND} \NormalTok{DonneesPersonnalisablesCodelec.db_suplog = }\DecValTok{0} 
                 \KeywordTok{AND} \NormalTok{DonneesPersonnalisablesCodelec.iddb_sitecl = }\DecValTok{1012124} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_clsart ClassificationArticleInterne }
              \KeywordTok{ON} \NormalTok{DonneesPersonnalisablesCodelec.iddp_clsart = ClassificationArticleInterne.id}
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_artdeno ArticleDenormalise }
              \KeywordTok{ON} \NormalTok{Article.id = ArticleDenormalise.idda_article }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_clasmnt ClassementFournisseur }
              \KeywordTok{ON} \NormalTok{ArticleDenormalise.iddf_clasmnt = ClassementFournisseur.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{tr_dosclas DossierDeClassement }
              \KeywordTok{ON} \NormalTok{ClassementFournisseur.id = DossierDeClassement.iddf_clasmnt }
                 \KeywordTok{AND} \NormalTok{DossierDeClassement.date_deb <= }\StringTok{'2013-09-27'} 
                 \KeywordTok{AND} \KeywordTok{COALESCE}\NormalTok{(DossierDeClassement.date_fin, }\FunctionTok{TO_DATE}\NormalTok{(}\StringTok{'31129999'}\NormalTok{, }\StringTok{'DDMMYYYY'}\NormalTok{)) >= }\StringTok{'2013-09-27'} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{tr_doscomm DossierCommercial }
              \KeywordTok{ON} \NormalTok{DossierDeClassement.idtr_doscomm = DossierCommercial.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_valdico OrigineNegociation }
              \KeywordTok{ON} \NormalTok{DossierCommercial.iddp_dossref = OrigineNegociation.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{dp_motbloc MotifBlocageInformation }
              \KeywordTok{ON} \NormalTok{MotifBlocageInformation.id = ArticleDenormalise.idda_motinf }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_arblogi ArbreLogistique }
              \KeywordTok{ON} \NormalTok{Article.id = ArbreLogistique.idda_article }
                 \KeywordTok{AND} \NormalTok{ArbreLogistique.princ = }\DecValTok{1} 
                 \KeywordTok{AND} \NormalTok{ArbreLogistique.db_suplog = }\DecValTok{0} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_filiere Filiere }
              \KeywordTok{ON} \NormalTok{ArbreLogistique.iddf_filiere = Filiere.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{df_fourn Fournisseur }
              \KeywordTok{ON} \NormalTok{Filiere.iddf_fourn = Fournisseur.id }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{od_dosal dossierALValo }
              \KeywordTok{ON} \NormalTok{dossierALValo.idda_arblogi = ArbreLogistique.id }
                 \KeywordTok{AND} \NormalTok{dossierALValo.idod_dossier }\KeywordTok{IS} \KeywordTok{NULL} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{tt_val_dal valoDossier }
              \KeywordTok{ON} \NormalTok{valoDossier.idod_dosal = dossierALValo.id }
                 \KeywordTok{AND} \NormalTok{valoDossier.estarecalculer = }\DecValTok{0} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{tt_valo ValorisationAchat }
              \KeywordTok{ON} \NormalTok{ValorisationAchat.idtt_val_dal = valoDossier.id }
                 \KeywordTok{AND} \NormalTok{ValorisationAchat.date_modif_retro }\KeywordTok{IS} \KeywordTok{NULL} 
                 \KeywordTok{AND} \NormalTok{ValorisationAchat.date_debut_achat <= }\StringTok{'2013-09-27'} 
                 \KeywordTok{AND} \KeywordTok{COALESCE}\NormalTok{(ValorisationAchat.date_fin_achat, }\FunctionTok{TO_DATE}\NormalTok{(}\StringTok{'31129999'}\NormalTok{, }\StringTok{'DDMMYYYY'}\NormalTok{)) >= }\StringTok{'2013-09-27'} 
                 \KeywordTok{AND} \NormalTok{ValorisationAchat.val_ach_pab }\KeywordTok{IS} \KeywordTok{NOT} \KeywordTok{NULL} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lienart assoALPXVT }
              \KeywordTok{ON} \NormalTok{assoALPXVT.idda_article = Article.id }
                 \KeywordTok{AND} \NormalTok{assoALPXVT.iddp_typlien = }\DecValTok{14893} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lien LienPrixVente }
              \KeywordTok{ON} \NormalTok{LienPrixVente.id = assoALPXVT.idda_lien }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lienart assoALGRAT }
              \KeywordTok{ON} \NormalTok{assoALGRAT.idda_article = Article.id }
                 \KeywordTok{AND} \NormalTok{assoALGRAT.iddp_typlien = }\DecValTok{14894} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lien LienGratuite }
              \KeywordTok{ON} \NormalTok{LienGratuite.id = assoALGRAT.idda_lien }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lienart assoALCOOR }
              \KeywordTok{ON} \NormalTok{assoALCOOR.idda_article = Article.id }
                 \KeywordTok{AND} \NormalTok{assoALCOOR.iddp_typlien = }\DecValTok{14899} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lien LienCoordonnable }
              \KeywordTok{ON} \NormalTok{LienCoordonnable.id = assoALCOOR.idda_lien }
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lienal assoALSTAT }
              \KeywordTok{ON} \NormalTok{assoALSTAT.idda_arblogi = ArbreLogistique.id }
                 \KeywordTok{AND} \NormalTok{assoALSTAT.iddp_typlien = }\DecValTok{14897} 
       \KeywordTok{left} \KeywordTok{join} \NormalTok{da_lien LienStatistique }
              \KeywordTok{ON} \NormalTok{LienStatistique.id = assoALSTAT.idda_lien }\KeywordTok{WHERE}  
\NormalTok{SEARCHMC.rn = }\DecValTok{1}
   \KeywordTok{AND} \NormalTok{( ValorisationAchat.id }\KeywordTok{IS} \KeywordTok{NULL} 
          \KeywordTok{OR} \NormalTok{ValorisationAchat.date_debut_achat = (}\KeywordTok{SELECT} \FunctionTok{MAX}\NormalTok{(VALMAX.date_debut_achat) }
                                                   \KeywordTok{FROM}   \NormalTok{tt_valo VALMAX }
                                                   \KeywordTok{WHERE}  \NormalTok{VALMAX.idtt_val_dal = ValorisationAchat.idtt_val_dal}
                                                      \KeywordTok{AND} \NormalTok{VALMAX.date_modif_retro }\KeywordTok{IS} \KeywordTok{NULL} 
                                                      \KeywordTok{AND} \NormalTok{VALMAX.val_ach_pab }\KeywordTok{IS} \KeywordTok{NOT} \KeywordTok{NULL} 
                                                      \KeywordTok{AND} \NormalTok{VALMAX.date_debut_achat <= }\StringTok{'2013-09-27'}\NormalTok{) ) }
   \KeywordTok{AND} \NormalTok{( Article.id }\KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{A.id }
                        \KeywordTok{FROM}   \NormalTok{da_article A }
                               \KeywordTok{join} \NormalTok{du_ucutiar AssoUcUtiAr }
                                 \KeywordTok{ON} \NormalTok{AssoUcUtiAr.idda_article = A.id }
                               \KeywordTok{join} \NormalTok{du_asucuti AssoUcUti }
                                 \KeywordTok{ON} \NormalTok{AssoUcUti.id = AssoUcUtiAr.iddu_asucuti }
                        \KeywordTok{WHERE}  \NormalTok{( AssoUcUti.iddu_uti }\KeywordTok{IN} \NormalTok{( }\DecValTok{90000000000022} \NormalTok{) ) }
                           \KeywordTok{AND} \NormalTok{a.iddb_sitecl = }\DecValTok{1012124}\NormalTok{) ) }
   \KeywordTok{AND} \NormalTok{Article.db_suplog = }\DecValTok{0}
\KeywordTok{ORDER}  \KeywordTok{BY} \NormalTok{SEARCHMC.poids }\KeywordTok{ASC}
\end{Highlighting}
\end{Shaded}

Ce code a été généré initialement par Hibernate, puis édité plusieurs
fois à la main.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Recherche textuelle}\label{recherche-textuelle}

\begin{itemize}
\tightlist
\item
  Objectif

  \begin{itemize}
  \tightlist
  \item
    ajouter un moteur de recherche à l'application
  \end{itemize}
\item
  Pratique courante

  \begin{itemize}
  \tightlist
  \item
    utiliser l'opérateur \texttt{LIKE}
  \end{itemize}
\item
  Problèmes

  \begin{itemize}
  \tightlist
  \item
    requiert des index spécialisés
  \item
    recherche uniquement le terme exact
  \end{itemize}
\item
  Solution

  \begin{itemize}
  \tightlist
  \item
    utiliser Full Text Search
  \end{itemize}
\end{itemize}

Les bases de données qui stockent des données textuelles ont souvent
pour but de permettre des recherches sur ces données textuelles.

La première solution envisagée lorsque le besoin se fait sentir est
d'utiliser l'opérateur \texttt{LIKE}. Il permet en effet de réaliser des
recherches de motif sur une colonne stockant des données textuelles.
C'est une solution simple et qui peut s'avérer simpliste dans de
nombreux cas.

Tout d'abord, les recherches de type
\texttt{LIKE\ \textquotesingle{}\%motif\%\textquotesingle{}} ne peuvent
généralement pas tirer partie d'un index btree normal. Cela étant dit,
depuis la version 9.2, le module \texttt{pg\_trgm} permet d'optimiser
ces recherches à l'aide d'un index GiST ou GIN.

Mais cette solution n'offre pas la même souplesse que la recherche plein
texte, en anglais Full Text Search, de PostgreSQL.

\textbf{Exemples}

L'exemple ci-dessous montre l'utilisation du module pg\_trgm pour
accélérer une recherche avec
\texttt{LIKE\ \textless{}nowiki\textgreater{}\textquotesingle{}\%motif\%\textquotesingle{}\textless{}/nowiki\textgreater{}}
:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_appellation_libelle }\KeywordTok{ON} \NormalTok{appellation }\KeywordTok{USING} \NormalTok{btree (libelle varchar_pattern_ops);}

\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'%wur%'}\NormalTok{;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6.99} \NormalTok{rows=}\DecValTok{3} \NormalTok{width=}\DecValTok{24}\NormalTok{)}
   \NormalTok{Filter: (libelle ~~ }\StringTok{'%wur%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}

\KeywordTok{CREATE} \NormalTok{EXTENSION pg_trgm;}
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_appellation_libelle_trgm }\KeywordTok{ON} \NormalTok{appellation }\KeywordTok{USING} \NormalTok{gist (libelle gist_trgm_ops);}

\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'%wur%'}\NormalTok{;}
                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                                        
 
\CommentTok{------------------------------------------------------------------------------------------}
\NormalTok{-}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{4.27}\NormalTok{..}\FloatTok{7.41} \NormalTok{rows=}\DecValTok{3} \NormalTok{width=}\DecValTok{24}\NormalTok{)}
   \NormalTok{Recheck Cond: (libelle ~~ }\StringTok{'%wur%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{idx_appellation_libelle_trgm  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{4.27} \NormalTok{rows=}\DecValTok{3} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (libelle ~~ }\StringTok{'%wur%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Conclusion}\label{conclusion-3}

\begin{itemize}
\tightlist
\item
  Des objets de plus en plus complexes

  \begin{itemize}
  \tightlist
  \item
    mais performants
  \item
    et remplissant un besoin
  \end{itemize}
\item
  Des conseils

  \begin{itemize}
  \tightlist
  \item
    pour améliorer les performances
  \item
    et pour éviter les pièges
  \end{itemize}
\end{itemize}

Nous avons vu dans ce module de nouveaux objets SQL comme les procédures
stockées livrées par défaut avec PostgreSQL ainsi que les vues. Nous
avons vu aussi les requêtes préparées, mécanisme sensé fournir un
surplus de performances si vous avez de nombreuses requêtes à exécuter
qui suivent un même motif.

Nous avons fini avec des conseils, principalement pour éviter les pièges
habituels mais aussi pour améliorer les performances.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Questions}\label{questions-3}

N'hésitez pas, c'est le moment !

\subsection{Travaux Pratiques}\label{travaux-pratiques-1}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Énoncés}\label{uxe9noncuxe9s-3}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Ajouter une adresse mail à chaque contact avec la concaténation du nom
  avec le texte `@dalibo.com'.
\item
  Concaténer nom et adresse mail des contacts français sous la forme
  `nom '.
\item
  Même demande mais avec le nom en majuscule et l'adresse mail en
  minuscule.
\item
  Ajouter la colonne \texttt{prix\_total} de type \texttt{numeric(10,2)}
  à la table \texttt{commandes}. Mettre à jour la colonne prix\_total
  des commandes avec les informations des lignes de la table
  lignes\_commandes.
\item
  Récupérer le montant total des commandes par mois pour l'année 2010.
  Les montants seront arrondis à deux décimales.
\item
  Supprimer les commandes de mai 2010.
\item
  Ré-exécuter la requête trouvée au point 5.
\item
  Qu'observez-vous ?
\item
  Corriger le problème rencontré.
\item
  Créer une vue calculant le prix total de chaque commande.
\item
  Réécrire la requête du point 5 pour utiliser la vue créée au point 10.
\end{enumerate}

\subsubsection{Solutions}\label{solutions-3}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\tightlist
\item
  Ajouter une adresse mail à chaque contact avec la concaténation du nom
  avec le texte `@dalibo.com'.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN} \NormalTok{;}

\KeywordTok{UPDATE} \NormalTok{contacts}
\KeywordTok{SET} \NormalTok{email = nom||}\StringTok{'@dalibo.com'}
\NormalTok{;}

\KeywordTok{COMMIT} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

Note : pour éviter de mettre à jour les contacts ayant déjà une adresse
mail, il suffit d'ajouter une clause WHERE
:\texttt{sql\ UPDATE\ contacts\ SET\ email\ =\ nom\textbar{}\textbar{}\textquotesingle{}@dalibo.com\textquotesingle{}\ WHERE\ email\ IS\ NULL\ ;}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{1}
\tightlist
\item
  Concaténer nom et adresse mail des contacts français sous la forme
  `nom '.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{nom||}\StringTok{' <'}\NormalTok{||email||}\StringTok{'>'}
\KeywordTok{FROM} \NormalTok{contacts}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{2}
\tightlist
\item
  Même demande mais avec le nom en majuscule et l'adresse mail en
  minuscule.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{upper}\NormalTok{(nom)||}\StringTok{' <'}\NormalTok{||lower(email)||}\StringTok{'>'}
\KeywordTok{FROM} \NormalTok{contacts}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{3}
\item
  \begin{enumerate}
  \def\labelenumii{\arabic{enumii}.}
  \setcounter{enumii}{3}
  \tightlist
  \item
    Ajouter la colonne \texttt{prix\_total} de type
    \texttt{numeric(10,2)} à la table \texttt{commandes}. Mettre à jour
    la colonne prix\_total des commandes avec les informations des
    lignes de la table lignes\_commandes.
  \end{enumerate}
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{commandes }\KeywordTok{ADD} \KeywordTok{COLUMN} \NormalTok{prix_total }\DataTypeTok{numeric}\NormalTok{(}\DecValTok{10}\NormalTok{,}\DecValTok{2}\NormalTok{);}
\KeywordTok{BEGIN} \NormalTok{;}

\KeywordTok{UPDATE} \NormalTok{commandes c}
\KeywordTok{SET} \NormalTok{prix_total= (}
   \CommentTok{/* cette sous-requête fait une jointure entre lignes_commandes et la table}
\CommentTok{      commandes à mettre à jour pour calculer le prix par commande */}
   \KeywordTok{SELECT} \FunctionTok{SUM}\NormalTok{(quantite * prix_unitaire - remise)}
   \KeywordTok{FROM} \NormalTok{lignes_commandes lc}
   \KeywordTok{WHERE} \NormalTok{lc.numero_commande=c.numero_commande}
\NormalTok{)}
\CommentTok{-- on peut récupérer le détail de la mise à jour directement dans la requête :}
\KeywordTok{RETURNING} \NormalTok{numero_commande, prix_total}
\NormalTok{;}

\KeywordTok{COMMIT} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

Une autre variante de cette requête serait :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{UPDATE} \NormalTok{commandes c }\KeywordTok{SET} \NormalTok{prix_total=prix_calc}
\KeywordTok{FROM} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{numero_commande, }\FunctionTok{SUM}\NormalTok{(quantite * prix_unitaire - remise) }\KeywordTok{AS} \NormalTok{prix_calc}
        \KeywordTok{FROM} \NormalTok{lignes_commandes}
       \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{numero_commande) }\KeywordTok{as} \NormalTok{prix_detail}
 \KeywordTok{WHERE} \NormalTok{prix_detail.numero_commande = c.numero_commande}
\end{Highlighting}
\end{Shaded}

Bien que cette dernière variante soit moins lisible, elle est bien plus
rapide sur un gros volume de données.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{4}
\tightlist
\item
  Récupérer le montant total des commandes par mois pour l'année 2010.
  Les montants seront arrondis à deux décimales.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{extract}\NormalTok{(}\StringTok{'month'} \KeywordTok{from} \NormalTok{date_commande) }\KeywordTok{AS} \NormalTok{numero_mois, }\FunctionTok{round}\NormalTok{(}\FunctionTok{sum}\NormalTok{(prix_total),}\DecValTok{2}\NormalTok{) }\KeywordTok{AS} \NormalTok{montant_total}
\KeywordTok{FROM} \NormalTok{commandes}
\KeywordTok{WHERE} \NormalTok{date_commande >= }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
   \KeywordTok{AND} \NormalTok{date_commande < }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \DecValTok{1}
\KeywordTok{ORDER} \KeywordTok{BY} \DecValTok{1}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Attention, il n'y a pas de contrainte \texttt{NOT\ NULL} sur le champ
\texttt{date\_commande}, ce qui fait que s'il existe des commandes sans
date de commande, celles-ci seront aggrégées à part des autres, puisque
\texttt{extract()} renverra \texttt{NULL} pour ces lignes.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{5}
\tightlist
\item
  Supprimer les commandes de mai 2010.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{BEGIN}\NormalTok{;}

\CommentTok{/* en raison de la présence de clés étrangères, il faut en premier leur }
\CommentTok{   supprimer les lignes de la table lignes_commandes correspondant aux }
\CommentTok{   commandes à supprimer */}
\KeywordTok{DELETE}
\KeywordTok{FROM} \NormalTok{lignes_commandes}
\KeywordTok{WHERE} \NormalTok{numero_commande }\KeywordTok{IN} \NormalTok{(}
   \KeywordTok{SELECT} \NormalTok{numero_commande}
   \KeywordTok{FROM} \NormalTok{commandes}
   \KeywordTok{WHERE} \NormalTok{date_commande >= }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/05/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
      \KeywordTok{AND} \NormalTok{date_commande < }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/06/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
   \NormalTok{)}
\NormalTok{;}

\CommentTok{-- ensuite seulement on peut supprimer les commandes}
\KeywordTok{DELETE}
\KeywordTok{FROM} \NormalTok{commandes}
\KeywordTok{WHERE} \NormalTok{date_commande >= }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/05/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
   \KeywordTok{AND} \NormalTok{date_commande < }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/06/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\NormalTok{;}

\KeywordTok{COMMIT} \NormalTok{;}
\end{Highlighting}
\end{Shaded}

Le problème de l'approche précédent est que cela oblige à effectuer
l'opération en deux temps. Il est possible de réaliser la totalité des
suppressions dans les deux tables \texttt{lignes\_commandes} et
\texttt{commandes} en une seule requête en utilisant une //CTE// :
\texttt{sql\ WITH\ del\_lc\ AS\ (\ \ \ \ /*\ ici\ on\ déclare\ la\ CTE\ qui\ va\ se\ charger\ de\ supprimer\ les\ lignes\ dans\ la\ \ \ \ \ \ \ \ table\ lignes\_commandes\ et\ retourner\ les\ numeros\ de\ commande\ supprimés\ */\ \ \ \ DELETE\ \ \ \ FROM\ lignes\_commandes\ \ \ \ WHERE\ numero\_commande\ IN\ (\ \ \ \ \ \ \ SELECT\ numero\_commande\ \ \ \ \ \ \ FROM\ commandes\ \ \ \ \ \ \ WHERE\ date\_commande\ \textgreater{}=\ to\_date(\textquotesingle{}01/05/2010\textquotesingle{},\ \textquotesingle{}DD/MM/YYYY\textquotesingle{})\ \ \ \ \ \ \ \ \ \ AND\ date\_commande\ \textless{}\ to\_date(\textquotesingle{}01/06/2010\textquotesingle{},\ \textquotesingle{}DD/MM/YYYY\textquotesingle{})\ \ \ \ \ \ \ )\ \ \ \ RETURNING\ numero\_commande\ )\ /*\ requête\ principale,\ qui\ supprime\ les\ commandes\ dont\ les\ numéros\ \ \ \ \ correspondent\ aux\ numéros\ de\ commandes\ remontés\ par\ la\ CTE\ */\ DELETE\ FROM\ commandes\ c\ WHERE\ EXISTS\ (\ \ \ \ SELECT\ 1\ \ \ \ FROM\ del\_lc\ \ \ \ WHERE\ del\_lc.numero\_commande\ =\ c.numero\_commande\ \ \ \ )\ ;}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{6}
\tightlist
\item
  Ré-exécuter la requête trouvée au point 5.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{extract}\NormalTok{(}\StringTok{'month'} \KeywordTok{from} \NormalTok{date_commande) }\KeywordTok{AS} \NormalTok{numero_mois, }\FunctionTok{round}\NormalTok{(}\FunctionTok{sum}\NormalTok{(prix_total),}\DecValTok{2}\NormalTok{) }\KeywordTok{AS} \NormalTok{montant_total}
\KeywordTok{FROM} \NormalTok{commandes}
\KeywordTok{GROUP} \KeywordTok{BY} \DecValTok{1}
\KeywordTok{ORDER} \KeywordTok{BY} \DecValTok{1}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{7}
\tightlist
\item
  Qu'observez-vous ?
\end{enumerate}

La ligne correspondant au mois de mai a disparu.

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{8}
\tightlist
\item
  Corriger le problème rencontré.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{numero_mois, }\FunctionTok{round}\NormalTok{(}\KeywordTok{coalesce}\NormalTok{(}\FunctionTok{sum}\NormalTok{(prix_total), }\FloatTok{0.0}\NormalTok{),}\DecValTok{2}\NormalTok{) }\KeywordTok{AS} \NormalTok{montant_total}
  \CommentTok{/* la fonction generate_series permet de générer une pseudo-table d'une }
\CommentTok{     colonne contenant les chiffres de 1 à 12 */}
\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{12}\NormalTok{) }\KeywordTok{AS} \NormalTok{numero_mois}
  \CommentTok{/* le LEFT JOIN entre cette pseudo-table et la table commandes permet de }
\CommentTok{     s'assurer que même si aucune commande n'a eu lieu sur un mois, la ligne }
\CommentTok{     correspondante sera tout de même présente */}
\KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{commandes }\KeywordTok{ON} \FunctionTok{extract}\NormalTok{(}\StringTok{'month'} \KeywordTok{from} \NormalTok{date_commande) = numero_mois}
      \KeywordTok{AND} \NormalTok{date_commande >= }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
      \KeywordTok{AND} \NormalTok{date_commande < }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \DecValTok{1}
\KeywordTok{ORDER} \KeywordTok{BY} \DecValTok{1}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Notez l'utilisation de la fonction coalesce() dans le \texttt{SELECT},
afin d'affecter la valeur \texttt{0.0} aux lignes «~ajoutées~» par le
\texttt{LEFT\ JOIN} qui n'ont par défaut aucun valeur (\texttt{NULL}).

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{9}
\tightlist
\item
  Créer une vue calculant le prix total de chaque commande.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{VIEW} \NormalTok{commande_montant }\KeywordTok{AS}
\KeywordTok{SELECT} 
   \NormalTok{numero_commande,}
   \FunctionTok{sum}\NormalTok{(quantite * prix_unitaire - remise) }\KeywordTok{AS} \NormalTok{total_commande}
\KeywordTok{FROM} \NormalTok{lignes_commandes}
\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{numero_commande}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\setcounter{enumi}{10}
\tightlist
\item
  Réécrire la requête du point 5 pour utiliser la vue créée au point 10.
\end{enumerate}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{extract}\NormalTok{(}\StringTok{'month'} \KeywordTok{from} \NormalTok{date_commande) }\KeywordTok{AS} \NormalTok{numero_mois, }\FunctionTok{round}\NormalTok{(}\FunctionTok{sum}\NormalTok{(total_commande),}\DecValTok{2}\NormalTok{) }\KeywordTok{AS} \NormalTok{montant_total}
\KeywordTok{FROM} \NormalTok{commandes c}
\KeywordTok{JOIN} \NormalTok{commande_montant cm }\KeywordTok{ON} \NormalTok{cm.numero_commande = c.numero_commande}
\KeywordTok{WHERE} \NormalTok{date_commande >= }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2010'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
   \KeywordTok{AND} \NormalTok{date_commande < }\FunctionTok{to_date}\NormalTok{(}\StringTok{'01/01/2011'}\NormalTok{, }\StringTok{'DD/MM/YYYY'}\NormalTok{)}
\KeywordTok{GROUP} \KeywordTok{BY} \DecValTok{1}
\KeywordTok{ORDER} \KeywordTok{BY} \DecValTok{1}
\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\section{Comprendre EXPLAIN}\label{comprendre-explain}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{}
\end{figure}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Introduction}\label{introduction-1}

\begin{itemize}
\tightlist
\item
  Le matériel, le système et la configuration sont importants pour les
  performances
\item
  Mais il est aussi essentiel de se préoccuper des requêtes et de leurs
  performances
\end{itemize}

Face à un problème de performances, l'administrateur se retrouve assez
rapidement face à une (ou plusieurs) requête(s). Une requête en soi
représente très peu d'informations. Suivant la requête, des dizaines de
plans peuvent être sélectionnés pour l'exécuter. Il est donc nécessaire
de pouvoir trouver le plan d'exécution et de comprendre ce plan. Cela
permet de mieux appréhender la requête et de mieux comprendre les pistes
envisageables pour la corriger.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Au menu}\label{au-menu}

\begin{itemize}
\tightlist
\item
  Exécution globale d'une requête
\item
  Planificateur : utilité, statistiques et configuration
\item
  EXPLAIN
\item
  Nœuds d'un plan
\item
  Outils
\end{itemize}

Avant de détailler le fonctionnement du planificateur, nous allons
regarder la façon dont une requête s'exécute globalement. Ensuite, nous
aborderons le planificateur : en quoi est-il utile, comment
fonctionne-t-il, et comment le configurer. Nous verrons aussi l'ensemble
des opérations utilisables par le planificateur. Enfin, nous
expliquerons comment utiliser \texttt{EXPLAIN} ainsi que les outils
essentiels pour faciliter la compréhension d'un plan de requête.

Tous les exemples proposés ici viennent d'une version 9.1.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Exécution globale d'une
requête}\label{exuxe9cution-globale-dune-requuxeate}

\begin{itemize}
\tightlist
\item
  L'exécution peut se voir sur deux niveaux

  \begin{itemize}
  \tightlist
  \item
    Niveau système
  \item
    Niveau SGBD
  \end{itemize}
\item
  De toute façon, composée de plusieurs étapes
\end{itemize}

L'exécution d'une requête peut se voir sur deux niveaux :

\begin{itemize}
\tightlist
\item
  ce que le système perçoit ;
\item
  ce que le SGBD fait.
\end{itemize}

Dans les deux cas, cela va nous permettre de trouver les possibilités de
lenteurs dans l'exécution d'une requête par un utilisateur.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Niveau système}\label{niveau-systuxe8me}

\begin{itemize}
\tightlist
\item
  Le client envoie une requête au serveur de bases de données
\item
  Le serveur l'exécute
\item
  Puis il renvoie le résultat au client
\end{itemize}

PostgreSQL est un système client-serveur. L'utilisateur se connecte via
un outil (le client) à une base d'une instance PostgreSQL (le serveur).
L'outil peut envoyer une requête au serveur, celui-ci l'exécute et finit
par renvoyer les données résultant de la requête ou le statut de la
requête.

Généralement, l'envoi de la requête est rapide. Par contre, la
récupération des données peut poser problème si une grosse volumétrie
est demandée sur un réseau à faible débit.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Niveau SGBD}\label{niveau-sgbd}

\begin{figure}[htbp]
\centering
\includegraphics{medias/j2-05-traitement_d-une_requete_sql_v2.jpg}
\caption{Traitement d'une requête}
\end{figure}

Lorsque le serveur récupère la requête, un ensemble de traitements est
réalisé :

\begin{itemize}
\tightlist
\item
  le \texttt{parser} va réaliser une analyse syntaxique de la requête
\item
  le \texttt{rewriter} va réécrire, si nécessaire la requête

  \begin{itemize}
  \tightlist
  \item
    pour cela, il prend en compte les règles et vues
  \item
    si une règle demande de changer la requête, la requête envoyée est
    remplacée par la nouvelle
  \item
    si une vue est utilisée, la requête qu'elle contient est intégrée
    dans la requête envoyée
  \end{itemize}
\item
  le \texttt{planner} va générer l'ensemble des plans d'exécutions

  \begin{itemize}
  \tightlist
  \item
    il calcule le coût de chaque plan
  \item
    puis il choisit le plan le moins coûteux, donc le plus intéressant
  \end{itemize}
\item
  l' \texttt{executer} exécute la requête

  \begin{itemize}
  \tightlist
  \item
    pour cela, il doit commencer par récupérer les verrous nécessaires
    sur les objets ciblés
  \item
    une fois les verrous récupérés, il exécute la requête
  \item
    une fois la requête exécutée, il envoie les résultats à
    l'utilisateur
  \end{itemize}
\end{itemize}

Plusieurs goulets d'étranglements sont visibles ici. Les plus importants
sont :

\begin{itemize}
\tightlist
\item
  la planification (à tel point qu'il est parfois préférable de ne
  générer qu'un sous-ensemble de plans, pour passer plus rapidement à la
  phase d'exécution) ;
\item
  la récupération des verrous (une requête peut attendre plusieurs
  secondes, minutes, voire heures avant de récupérer les verrous et
  exécuter réellement la requête) ;
\item
  l'exécution de la requête ;
\item
  l'envoi des résultats à l'utilisateur.
\end{itemize}

Il est possible de tracer l'exécution des différentes étapes grâce aux
options \texttt{log\_parser\_stats}, \texttt{log\_planner\_stats} et
\texttt{log\_executor\_stats}. Voici un exemple complet :

\begin{itemize}
\tightlist
\item
  Mise en place de la configuration sur la session :
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{log_parser_stats }\KeywordTok{TO} \KeywordTok{on}\NormalTok{;}
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{log_planner_stats }\KeywordTok{TO} \KeywordTok{on}\NormalTok{;}
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{log_executor_stats }\KeywordTok{TO} \KeywordTok{on}\NormalTok{;}
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{client_min_messages }\KeywordTok{TO} \FunctionTok{log}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Exécution de la requête :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{10}\NormalTok{;}
\end{Highlighting}
\end{Shaded}
\item
  Trace du \texttt{parser}

\begin{verbatim}
LOG:  PARSER STATISTICS
DETAIL:  ! system usage stats:
! 0.000051 elapsed 0.000000 user 0.000000 system sec
! [0.017997 user 0.021996 sys total]
! 0/0 [13040/248] filesystem blocks in/out
! 0/0 [40/1589] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [167/6] voluntary/involuntary context switches
LOG:  PARSE ANALYSIS STATISTICS
DETAIL:  ! system usage stats:
! 0.000197 elapsed 0.001000 user 0.000000 system sec
! [0.018997 user 0.021996 sys total]
! 0/0 [13040/248] filesystem blocks in/out
! 0/1 [40/1590] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [167/6] voluntary/involuntary context switches
\end{verbatim}
\item
  Trace du \texttt{rewriter}

\begin{verbatim}
LOG:  REWRITER STATISTICS
DETAIL:  ! system usage stats:
! 0.000007 elapsed 0.000000 user 0.000000 system sec
! [0.018997 user 0.021996 sys total]
! 0/0 [13040/248] filesystem blocks in/out
! 0/0 [40/1590] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [167/6] voluntary/involuntary context switches
\end{verbatim}
\item
  Trace du \texttt{planner}

\begin{verbatim}
LOG:  PLANNER STATISTICS
DETAIL:  ! system usage stats:
! 0.000703 elapsed 0.000000 user 0.000000 system sec
! [0.018997 user 0.021996 sys total]
! 0/0 [13040/248] filesystem blocks in/out
! 0/6 [40/1596] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 0/0 [167/6] voluntary/involuntary context switches
\end{verbatim}
\item
  Trace du \texttt{executer}

\begin{verbatim}
LOG:  EXECUTOR STATISTICS
DETAIL:  ! system usage stats:
! 0.078548 elapsed 0.000000 user 0.000000 system sec
! [0.019996 user 0.021996 sys total]
! 16/0 [13056/248] filesystem blocks in/out
! 0/2 [40/1599] page faults/reclaims, 0 [0] swaps
! 0 [0] signals rcvd, 0/0 [0/0] messages rcvd/sent
! 1/0 [168/6] voluntary/involuntary context switches
\end{verbatim}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Exceptions}\label{exceptions}

\begin{itemize}
\tightlist
\item
  Requêtes DDL
\item
  Instructions TRUNCATE et COPY
\item
  Pas de réécriture, pas de plans d'exécution\ldots{} une exécution
  directe
\end{itemize}

Il existe quelques requêtes qui échappent à la séquence d'opérations
présentées précédemment. Toutes les opérations DDL (modification de la
structure de la base), les instructions \texttt{TRUNCATE} et
\texttt{COPY} (en partie) sont vérifiées syntaxiquement, puis
directement exécutées. Les étapes de réécriture et de planification ne
sont pas réalisées.

Le principal souci pour les performances sur ce type d'instructions est
donc l'obtention des verrous et l'exécution réelle.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Quelques définitions}\label{quelques-duxe9finitions}

\begin{itemize}
\tightlist
\item
  Prédicat

  \begin{itemize}
  \tightlist
  \item
    filtre de la clause \texttt{WHERE}
  \end{itemize}
\item
  Sélectivité

  \begin{itemize}
  \tightlist
  \item
    pourcentage de lignes retournées après application d'un prédicat
  \end{itemize}
\item
  Cardinalité

  \begin{itemize}
  \tightlist
  \item
    nombre de lignes d'une table
  \item
    nombre de lignes retournées après filtrage
  \end{itemize}
\end{itemize}

Un prédicat est une condition de filtrage présente dans la clause
\texttt{WHERE} d'une requête. Par exemple \texttt{colonne\ =\ valeur}.

La sélectivité est liée à l'application d'un prédicat sur une table.
Elle détermine le nombre de lignes remontées par la lecture d'une
relation suite à l'application d'une clause de filtrage, ou prédicat.
Elle peut être vue comme un coefficient de filtrage d'un prédicat. La
sélectivité est exprimée sous la forme d'un pourcentage. Pour une table
de 1000 lignes, si la sélectivité d'un prédicat est de 10\%, la lecture
de la table en appliquant le prédicat devrait retourner 100 lignes.

La cardinalité représente le nombre de lignes d'une relation. En
d'autres termes, la cardinalité représente le nombre de lignes d'une
table ou du résultat d'une fonction. Elle représente aussi le nombre de
ligne retourné par la lecture d'une table après application d'un ou
plusieurs prédicats.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Requête étudiée}\label{requuxeate-uxe9tudiuxe9e}

Cette requête d'exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{matricule, nom, prenom, nom_service, fonction, localisation}
  \KeywordTok{FROM} \NormalTok{employes emp}
  \KeywordTok{JOIN} \NormalTok{services ser }\KeywordTok{ON} \NormalTok{(emp.num_service = ser.num_service)}
 \KeywordTok{WHERE} \NormalTok{ser.localisation = }\StringTok{'Nantes'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Cette requête permet de déterminer quels sont les employés basés à
Nantes.

Le script suivant permet de recréer le jeu d'essai :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{services (}
    \NormalTok{num_service }\DataTypeTok{integer} \KeywordTok{primary} \KeywordTok{key}\NormalTok{,}
    \NormalTok{nom_service }\DataTypeTok{character} \DataTypeTok{varying}\NormalTok{(}\DecValTok{20}\NormalTok{),}
    \NormalTok{localisation }\DataTypeTok{character} \DataTypeTok{varying}\NormalTok{(}\DecValTok{20}\NormalTok{)}
\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{employes (}
    \NormalTok{matricule     }\DataTypeTok{integer} \KeywordTok{primary} \KeywordTok{key}\NormalTok{,}
    \NormalTok{nom           }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{15}\NormalTok{) }\KeywordTok{not} \KeywordTok{null}\NormalTok{,}
    \NormalTok{prenom        }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{15}\NormalTok{) }\KeywordTok{not} \KeywordTok{null}\NormalTok{,}
    \NormalTok{fonction      }\DataTypeTok{varchar}\NormalTok{(}\DecValTok{20}\NormalTok{) }\KeywordTok{not} \KeywordTok{null}\NormalTok{,}
    \NormalTok{manager       }\DataTypeTok{integer}\NormalTok{,}
    \NormalTok{date_embauche }\DataTypeTok{date}\NormalTok{,}
    \NormalTok{num_service   }\DataTypeTok{integer} \KeywordTok{not} \KeywordTok{null} \KeywordTok{references} \NormalTok{services (num_service)}
\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{services }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{, }\StringTok{'Comptabilité'}\NormalTok{, }\StringTok{'Paris'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{services }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{2}\NormalTok{, }\StringTok{'R}\CharTok{&D}\StringTok{'}\NormalTok{, }\StringTok{'Rennes'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{services }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{3}\NormalTok{, }\StringTok{'Commerciaux'}\NormalTok{, }\StringTok{'Limoges'}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{services }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{4}\NormalTok{, }\StringTok{'Consultants'}\NormalTok{, }\StringTok{'Nantes'}\NormalTok{);}

\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{33}\NormalTok{, }\StringTok{'Roy'}\NormalTok{, }\StringTok{'Arthur'}\NormalTok{, }\StringTok{'Consultant'}\NormalTok{, }\DecValTok{105}\NormalTok{, }\StringTok{'2000-06-01'}\NormalTok{, }\DecValTok{4}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{81}\NormalTok{, }\StringTok{'Prunelle'}\NormalTok{, }\StringTok{'Léon'}\NormalTok{, }\StringTok{'Commercial'}\NormalTok{, }\DecValTok{97}\NormalTok{, }\StringTok{'2000-06-01'}\NormalTok{, }\DecValTok{3}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{97}\NormalTok{, }\StringTok{'Lebowski'}\NormalTok{, }\StringTok{'Dude'}\NormalTok{, }\StringTok{'Responsable'}\NormalTok{, }\DecValTok{104}\NormalTok{, }\StringTok{'2003-01-01'}\NormalTok{, }\DecValTok{3}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{104}\NormalTok{, }\StringTok{'Cruchot'}\NormalTok{, }\StringTok{'Ludovic'}\NormalTok{, }\StringTok{'Directeur Général'}\NormalTok{, }\KeywordTok{NULL}\NormalTok{, }\StringTok{'2005-03-06'}\NormalTok{, }\DecValTok{3}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{105}\NormalTok{, }\StringTok{'Vacuum'}\NormalTok{, }\StringTok{'Anne-Lise'}\NormalTok{, }\StringTok{'Responsable'}\NormalTok{, }\DecValTok{104}\NormalTok{, }\StringTok{'2005-03-06'}\NormalTok{, }\DecValTok{4}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{119}\NormalTok{, }\StringTok{'Thierrie'}\NormalTok{, }\StringTok{'Armand'}\NormalTok{, }\StringTok{'Consultant'}\NormalTok{, }\DecValTok{105}\NormalTok{, }\StringTok{'2006-01-01'}\NormalTok{, }\DecValTok{4}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{120}\NormalTok{, }\StringTok{'Tricard'}\NormalTok{, }\StringTok{'Gaston'}\NormalTok{, }\StringTok{'Développeur'}\NormalTok{, }\DecValTok{125}\NormalTok{, }\StringTok{'2006-01-01'}\NormalTok{, }\DecValTok{2}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{125}\NormalTok{, }\StringTok{'Berlicot'}\NormalTok{, }\StringTok{'Jules'}\NormalTok{, }\StringTok{'Responsable'}\NormalTok{, }\DecValTok{104}\NormalTok{, }\StringTok{'2006-03-01'}\NormalTok{, }\DecValTok{2}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{126}\NormalTok{, }\StringTok{'Fougasse'}\NormalTok{, }\StringTok{'Lucien'}\NormalTok{, }\StringTok{'Comptable'}\NormalTok{, }\DecValTok{128}\NormalTok{, }\StringTok{'2006-03-01'}\NormalTok{, }\DecValTok{1}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{128}\NormalTok{, }\StringTok{'Cruchot'}\NormalTok{, }\StringTok{'Josépha'}\NormalTok{, }\StringTok{'Responsable'}\NormalTok{, }\DecValTok{105}\NormalTok{, }\StringTok{'2006-03-01'}\NormalTok{, }\DecValTok{1}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{131}\NormalTok{, }\StringTok{'Lareine-Leroy'}\NormalTok{, }\StringTok{'Émilie'}\NormalTok{, }\StringTok{'Développeur'}\NormalTok{, }\DecValTok{125}\NormalTok{, }\StringTok{'2006-06-01'}\NormalTok{, }\DecValTok{2}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{135}\NormalTok{, }\StringTok{'Brisebard'}\NormalTok{, }\StringTok{'Sylvie'}\NormalTok{, }\StringTok{'Commercial'}\NormalTok{, }\DecValTok{97}\NormalTok{, }\StringTok{'2006-09-01'}\NormalTok{, }\DecValTok{3}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{136}\NormalTok{, }\StringTok{'Barnier'}\NormalTok{, }\StringTok{'Germaine'}\NormalTok{, }\StringTok{'Consultant'}\NormalTok{, }\DecValTok{105}\NormalTok{, }\StringTok{'2006-09-01'}\NormalTok{, }\DecValTok{4}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{employes }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{137}\NormalTok{, }\StringTok{'Pivert'}\NormalTok{, }\StringTok{'Victor'}\NormalTok{, }\StringTok{'Consultant'}\NormalTok{, }\DecValTok{105}\NormalTok{, }\StringTok{'2006-09-01'}\NormalTok{, }\DecValTok{4}\NormalTok{);}

\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{employes }\KeywordTok{ADD} \KeywordTok{CONSTRAINT} \NormalTok{fk_employe_service }\KeywordTok{FOREIGN} \KeywordTok{KEY} \NormalTok{(num_service) }\KeywordTok{REFERENCES} \NormalTok{services (num_service);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Plan de la requête
étudiée}\label{plan-de-la-requuxeate-uxe9tudiuxe9e}

L'objet de ce module est de comprendre son plan d'exécution :

\begin{Shaded}
\begin{Highlighting}[]
 \KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1.06}\NormalTok{..}\FloatTok{2.29} \NormalTok{rows=}\DecValTok{4} \NormalTok{width=}\DecValTok{48}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (emp.num_service = ser.num_service)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes emp  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.14} \NormalTok{rows=}\DecValTok{14} \NormalTok{width=}\DecValTok{35}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1.05}\NormalTok{..}\FloatTok{1.05} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{21}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{services ser  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.05} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{21}\NormalTok{)}
               \NormalTok{Filter: ((localisation):}\CharTok{:text} \NormalTok{= }\StringTok{'Nantes'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La directive \texttt{EXPLAIN} permet de connaître le plan d'exécution
d'une requête. Elle permet de savoir par quelles étapes va passer le
SGBD pour répondre à la requête.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Planificateur}\label{planificateur}

\begin{itemize}
\tightlist
\item
  Chargé de sélectionner le meilleur plan d'exécution
\item
  Énumère tous les plans d'exécution

  \begin{itemize}
  \tightlist
  \item
    Tous ou presque\ldots{}
  \end{itemize}
\item
  Calcule leur coût suivant des statistiques, un peu de configuration et
  beaucoup de règles
\item
  Sélectionne le meilleur (le moins coûteux)
\end{itemize}

Le but du planificateur est assez simple. Pour une requête, il existe de
nombreux plans d'exécution possibles. Il va donc énumérer tous les plans
d'exécution possibles (sauf si cela représente vraiment trop de plans
auquel cas, il ne prendra en compte qu'une partie des plans possibles).
Il calcule ensuite le coût de chaque plan. Pour cela, il dispose
d'informations sur les données (des statistiques), d'une configuration
(réalisée par l'administrateur de bases de données) et d'un ensemble de
règles inscrites en dur. Une fois tous les coûts calculés, il ne lui
reste plus qu'à sélectionner le plan qui a le plus petit coût.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utilité}\label{utilituxe9}

\begin{itemize}
\tightlist
\item
  SQL est un langage déclaratif
\item
  Une requête décrit le résultat à obtenir

  \begin{itemize}
  \tightlist
  \item
    Mais pas la façon de l'obtenir
  \end{itemize}
\item
  C'est au planificateur de déduire le moyen de parvenir au résultat
  demandé
\end{itemize}

Le planificateur est un composant essentiel d'un moteur de bases de
données. Les moteurs utilisent un langage SQL qui permet à l'utilisateur
de décrire le résultat qu'il souhaite obtenir. Par exemple, s'il veut
récupérer des informations sur tous les clients dont le nom commence par
la lettre A en triant les clients par leur département, il pourrait
utiliser une requête du type :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{clients }\KeywordTok{WHERE} \NormalTok{nom }\KeywordTok{LIKE} \StringTok{'A%'} \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{departement;}
\end{Highlighting}
\end{Shaded}

Un moteur de bases de données peut récupérer les données de plusieurs
façons :

\begin{itemize}
\tightlist
\item
  faire un parcours séquentiel de la table \texttt{clients} en filtrant
  les enregistrements d'après leur nom, puis trier les données grâce à
  un algorithme ;
\item
  faire un parcours d'index sur la colonne nom pour trouver plus
  rapidement les enregistrements de la table \texttt{clients}
  satisfaisant le filtre `A\%', puis trier les données grâce à un
  algorithme ;
\item
  faire un parcours d'index sur la colonne département pour récupérer
  les enregistrements déjà triés, et ne retourner que ceux vérifiant nom
  like `A\%'
\end{itemize}

Et ce ne sont que quelques exemples car il serait possible d'avoir un
index utilisable pour le tri et le filtre par exemple.

Donc la requête décrit le résultat à obtenir, et le planificateur va
chercher le meilleur moyen pour parvenir à ce résultat.

Pour ce travail, il dispose d'un certain nombre d'opérateurs. Ces
opérateurs travaillent sur des ensembles de lignes, généralement un ou
deux. Chaque opérateur renvoie un seul ensemble de lignes. Le
planificateur peut combiner ces opérations suivant certaines règles. Un
opérateur peut renvoyer l'ensemble de résultats de deux façons : d'un
coup (par exemple le tri) ou petit à petit (par exemple un parcours
séquentiel). Le premier cas utilise plus de mémoire, et peut nécessiter
d'écrire des données temporaires sur disque. Le deuxième cas aide à
accélérer des opérations comme les curseurs, les sous-requêtes
\texttt{IN} et \texttt{EXISTS}, la clause \texttt{LIMIT}, etc.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Règles}\label{ruxe8gles}

\begin{itemize}
\tightlist
\item
  1ère règle : Récupérer le bon résultat
\item
  2è règle: Le plus rapidement possible

  \begin{itemize}
  \tightlist
  \item
    En minimisant les opérations disques
  \item
    En préférant les lectures séquentielles
  \item
    En minimisant la charge CPU
  \item
    En minimisant l'utilisation de la mémoire
  \end{itemize}
\end{itemize}

Le planificateur suit deux règles :

\begin{itemize}
\tightlist
\item
  il doit récupérer le bon résultat ;
\item
  il doit le récupérer le plus rapidement possible.
\end{itemize}

Cette deuxième règle lui impose de minimiser l'utilisation des
ressources : en tout premier lieu les opérations disques vu qu'elles
sont les plus coûteuses, mais aussi la charge CPU et l'utilisation de la
mémoire. Dans le cas des opérations disques, s'il doit en faire, il doit
absolument privilégier les opérations séquentielles aux opérations
aléatoires (qui demandent un déplacement de la tête de disque, ce qui
est l'opération la plus coûteuse sur les disques magnétiques).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Outils de l'optimiseur}\label{outils-de-loptimiseur}

\begin{itemize}
\tightlist
\item
  L'optimiseur s'appuie sur :

  \begin{itemize}
  \tightlist
  \item
    un mécanisme de calcul de coûts
  \item
    des statistiques sur les données
  \item
    le schéma de la base de données
  \end{itemize}
\end{itemize}

Pour déterminer le chemin d'exécution le moins coûteux, l'optimiseur
devrait connaître précisément les données mises en œuvre dans la
requête, les particularités du matériel et la charge en cours sur ce
matériel. Cela est impossible. Ce problème est contourné en utilisant
deux mécanismes liés l'un à l'autre : * un mécanisme de calcul de coût
de chaque opération, * des statistiques sur les données.

Pour quantifier la charge nécessaire pour répondre à une requête,
PostgreSQL utilise un mécanisme de coût. Il part du principe que chaque
opération a un coût plus ou moins important.

Les statistiques sur les données permettent à l'optimiseur de requêtes
de déterminer assez précisément la répartition des valeurs d'une colonne
d'une table, sous la forme d'histogramme. Il dispose encore d'autres
informations comme la répartition des valeurs les plus fréquentes, le
pourcentage de \texttt{NULL}, le nombre de valeurs distinctes,
etc\ldots{} Toutes ces informations aideront l'optimiseur à déterminer
la sélectivité d'un filtre (prédicat de la clause \texttt{WHERE},
condition de jointure) et donc quelle est la quantité de données
récupérées par la lecture d'une table en utilisant le filtre évalué.

Enfin, l'optimiseur s'appuie sur le schéma de la base de données afin de
déterminer différents paramètres qui entrent dans le calcul du plan
d'exécution : contrainte d'unicité sur une colonne, présence d'une
contrainte \texttt{NOT\ NULL}, etc.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Optimisations}\label{optimisations}

\begin{itemize}
\tightlist
\item
  À partir du modèle de données

  \begin{itemize}
  \tightlist
  \item
    suppression de jointures externes inutiles
  \end{itemize}
\item
  Transformation des sous-requêtes

  \begin{itemize}
  \tightlist
  \item
    certaines sous-requêtes transformées en jointures
  \end{itemize}
\item
  Appliquer les prédicats le plus tôt possible

  \begin{itemize}
  \tightlist
  \item
    réduit le jeu de données manipulé
  \end{itemize}
\item
  Intègre le code des fonctions SQL simples (inline)

  \begin{itemize}
  \tightlist
  \item
    évite un appel de fonction coûteux
  \end{itemize}
\end{itemize}

À partir du modèle de données et de la requête soumise, l'optimiseur de
PostgreSQL va pouvoir déterminer si une jointure externe n'est pas utile
à la production du résultat.

\textbf{Suppression des jointures externes inutiles}

Sous certaines condition, PostgreSQL peut supprimer des jointures
externes, à condition que le résultat ne soit pas modifié :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{e.matricule, e.nom, e.prenom}
  \KeywordTok{FROM} \NormalTok{employes e}
  \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{services s}
    \KeywordTok{ON} \NormalTok{(e.num_service = s.num_service)}
 \KeywordTok{WHERE} \NormalTok{e.num_service = }\DecValTok{4}\NormalTok{;}
                        \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{-----------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes e  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.18} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{23}\NormalTok{)}
   \NormalTok{Filter: (num_service = }\DecValTok{4}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Toutefois, si le prédicat de la requête est modifié pour s'appliquer sur
la table \texttt{services}, la jointure est tout de même réalisée,
puisqu'on réalise un test d'existence sur cette table \texttt{service} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{e.matricule, e.nom, e.prenom}
  \KeywordTok{FROM} \NormalTok{employes e}
  \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{services s}
    \KeywordTok{ON} \NormalTok{(e.num_service = s.num_service)}
 \KeywordTok{WHERE} \NormalTok{s.num_service = }\DecValTok{4}\NormalTok{;}
                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                              
\CommentTok{-------------------------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{0.15}\NormalTok{..}\FloatTok{9.39} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{19}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{services_pkey }\KeywordTok{on} \NormalTok{services s  (cost=}\FloatTok{0.15}\NormalTok{..}\FloatTok{8.17} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (num_service = }\DecValTok{4}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes e  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.18} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{23}\NormalTok{)}
         \NormalTok{Filter: (num_service = }\DecValTok{4}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Transformation des sous-requêtes}

Certaines sous-requêtes sont transformées en jointure :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{employes emp}
  \KeywordTok{JOIN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{services }\KeywordTok{WHERE} \NormalTok{num_service = }\DecValTok{1}\NormalTok{) ser}
    \KeywordTok{ON} \NormalTok{(emp.num_service = ser.num_service);}
                                      \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{--------------------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{0.15}\NormalTok{..}\FloatTok{9.36} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{163}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{services_pkey }\KeywordTok{on} \NormalTok{services  (cost=}\FloatTok{0.15}\NormalTok{..}\FloatTok{8.17} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{120}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (num_service = }\DecValTok{1}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes emp  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.18} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{43}\NormalTok{)}
         \NormalTok{Filter: (num_service = }\DecValTok{1}\NormalTok{)}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

La sous-requête \texttt{ser} a été remonté dans l'arbre de requête pour
être intégré en jointure.

\textbf{Application des prédicats au plus tôt}

Lorsque cela est possible, PostgreSQL essaye d'appliquer les prédicats
au plus tôt :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \FunctionTok{MAX}\NormalTok{(date_embauche)}
  \KeywordTok{FROM} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{employes }\KeywordTok{WHERE} \NormalTok{num_service = }\DecValTok{4}\NormalTok{) e}
 \KeywordTok{WHERE} \NormalTok{e.date_embauche < }\StringTok{'2006-01-01'}\NormalTok{;}
                                  \KeywordTok{QUERY} \KeywordTok{PLAN}                                  
\CommentTok{------------------------------------------------------------------------------}
 \NormalTok{Aggregate  (cost=}\FloatTok{1.21}\NormalTok{..}\FloatTok{1.22} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.21} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{Filter: ((date_embauche < }\StringTok{'2006-01-01'}\NormalTok{:}\CharTok{:date}\NormalTok{) }\KeywordTok{AND} \NormalTok{(num_service = }\DecValTok{4}\NormalTok{))}
\NormalTok{(}\DecValTok{3} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Les deux prédicats \texttt{num\_service\ =\ 4} et
\texttt{\textless{}nowiki\textgreater{}date\_embauche\ \textless{}\ \textquotesingle{}2006-01-01\textquotesingle{}\textless{}/nowiki\textgreater{}}
ont été appliqués en même temps, réduisant ainsi le jeu de données à
considéré dès le départ.

En cas de problème, il est possible d'utiliser une CTE (clause
\texttt{WITH}) pour bloquer cette optimisation :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{WITH} \NormalTok{e }\KeywordTok{AS} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{employes }\KeywordTok{WHERE} \NormalTok{num_service = }\DecValTok{4}\NormalTok{)}
\KeywordTok{SELECT} \FunctionTok{MAX}\NormalTok{(date_embauche)}
 \KeywordTok{FROM} \NormalTok{e}
\KeywordTok{WHERE} \NormalTok{e.date_embauche < }\StringTok{'2006-01-01'}\NormalTok{;}
                           \KeywordTok{QUERY} \KeywordTok{PLAN}                            
\CommentTok{-----------------------------------------------------------------}
 \NormalTok{Aggregate  (cost=}\FloatTok{1.29}\NormalTok{..}\FloatTok{1.30} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{CTE e}
     \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.18} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{43}\NormalTok{)}
           \NormalTok{Filter: (num_service = }\DecValTok{4}\NormalTok{)}
   \NormalTok{->  CTE }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{e  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.11} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{Filter: (date_embauche < }\StringTok{'2006-01-01'}\NormalTok{:}\CharTok{:date}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Function inlining}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{inline (}\KeywordTok{id} \NormalTok{serial, tdate }\DataTypeTok{date}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{inline (tdate)}
 \KeywordTok{SELECT} \NormalTok{generate_series(}\StringTok{'1800-01-01'}\NormalTok{, }\StringTok{'2015-12-01'}\NormalTok{, }\DataTypeTok{interval} \StringTok{'15 days'}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{OR} \KeywordTok{REPLACE} \KeywordTok{FUNCTION} \NormalTok{add_months_sql(mydate }\DataTypeTok{date}\NormalTok{, nbrmonth }\DataTypeTok{integer}\NormalTok{)}
  \NormalTok{RETURNS }\DataTypeTok{date} \KeywordTok{AS}
\NormalTok{$BODY$}
\KeywordTok{SELECT} \NormalTok{( mydate + }\DataTypeTok{interval} \StringTok{'1 month'} \NormalTok{* nbrmonth ):}\CharTok{:date}\NormalTok{;}
\NormalTok{$BODY$}
  \NormalTok{LANGUAGE SQL;}

\KeywordTok{CREATE} \KeywordTok{OR} \KeywordTok{REPLACE} \KeywordTok{FUNCTION} \NormalTok{add_months_plpgsql(mydate }\DataTypeTok{date}\NormalTok{, nbrmonth }\DataTypeTok{integer}\NormalTok{)}
  \NormalTok{RETURNS }\DataTypeTok{date} \KeywordTok{AS}
\NormalTok{$BODY$}
 \KeywordTok{BEGIN} \KeywordTok{RETURN} \NormalTok{( mydate + }\DataTypeTok{interval} \StringTok{'1 month'} \NormalTok{* nbrmonth ); }\KeywordTok{END}\NormalTok{;}
\NormalTok{$BODY$}
  \NormalTok{LANGUAGE plpgsql;}
\end{Highlighting}
\end{Shaded}

Si l'on utilise la fonction écrite en PL/pgsql, on retrouve l'appel de
la fonction dans la clause \texttt{Filter} du plan d'exécution de la
requête :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{mabase=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{, BUFFERS) }
\NormalTok{mabase=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{inline }\KeywordTok{WHERE} \NormalTok{tdate = add_months_plpgsql(now():}\CharTok{:date}\NormalTok{, -}\DecValTok{1}\NormalTok{);}
                                             \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{-----------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{inline  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1430.52} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{42.102}\NormalTok{..}\FloatTok{42.102} \NormalTok{rows=}\DecValTok{0} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (tdate = add_months_plpgsql((now()):}\CharTok{:date}\NormalTok{, (-}\DecValTok{1}\NormalTok{)))}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{5258}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{24}
 \NormalTok{Total runtime: }\FloatTok{42.139} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

PostgreSQL ne sait pas intégrer le code des fonctions PL/pgsql dans ses
plans d'exécution.

En revanche, en utilisant la fonction écrite en langage SQL, la
définition de la fonction a été intégré dans la clause de filtrage de la
requête :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{mabase=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{, BUFFERS) }
\NormalTok{mabase=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{inline }\KeywordTok{WHERE} \NormalTok{tdate = add_months_sql(now():}\CharTok{:date}\NormalTok{, -}\DecValTok{1}\NormalTok{);}
                                            \KeywordTok{QUERY} \KeywordTok{PLAN}                          
\CommentTok{--------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{inline  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{142.31} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{6.647}\NormalTok{..}\FloatTok{6.647} \NormalTok{rows=}\DecValTok{0} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (tdate = (((now()):}\CharTok{:date} \NormalTok{+ }\StringTok{'-1 mons'}\NormalTok{:}\CharTok{:interval}\NormalTok{)):}\CharTok{:date}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{5258}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{24}
 \NormalTok{Total runtime: }\FloatTok{6.699} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Le code de la fonction SQL a été correctement intégré dans le plan
d'exécution. Le temps d'exécution a été divisé par 6 sur le jeu de
donnée réduit, montrant l'impact de l'appel d'une fonction dans une
clause de filtrage.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Décisions}\label{duxe9cisions}

\begin{itemize}
\tightlist
\item
  Stratégie d'accès aux lignes

  \begin{itemize}
  \tightlist
  \item
    Par parcours d'une table, d'un index, de TID, etc
  \end{itemize}
\item
  Stratégie d'utilisation des jointures

  \begin{itemize}
  \tightlist
  \item
    Ordre des jointures
  \item
    Type de jointure (Nested Loop, Merge/Sort Join, Hash Join)
  \item
    Ordre des tables jointes dans une même jointure
  \end{itemize}
\item
  Stratégie d'aggrégation

  \begin{itemize}
  \tightlist
  \item
    Brut, trié, haché
  \end{itemize}
\end{itemize}

Pour exécuter une requête, le planificateur va utiliser des opérations.
Pour lire des lignes, il peut utiliser un parcours de table, un parcours
d'index ou encore d'autres types de parcours. Ce sont généralement les
premières opérations utilisées. Ensuite, d'autres opérations permettent
différentes actions :

\begin{itemize}
\tightlist
\item
  joindre deux ensembles de lignes avec des opérations de jointures
  (trois au total) ;
\item
  agréger un ensemble de lignes avec une opération d'agrégation (trois
  là-aussi) ;
\item
  trier un ensemble de lignes ;
\item
  etc.
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Mécanisme de coûts}\label{muxe9canisme-de-couxfbts}

\begin{itemize}
\tightlist
\item
  Modèle basé sur les coûts

  \begin{itemize}
  \tightlist
  \item
    quantifier la charge pour répondre à une requête
  \end{itemize}
\item
  Chaque opération a un coût :

  \begin{itemize}
  \tightlist
  \item
    lire un bloc selon sa position sur le disque
  \item
    manipuler une ligne issue d'une lecture de table ou d'index
  \item
    appliquer un opérateur
  \end{itemize}
\end{itemize}

L'optimiseur statistique de PostgreSQL utilise un modèle de calcul de
coût. Les coûts calculés sont des indications arbitraires sur la charge
nécessaire pour répondre à une requête. Chaque facteur de coût
représente une unité de travail : lecture d'un bloc, manipulation des
lignes en mémoire, appliquer un opérateur sur des données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Coûts unitaires}\label{couxfbts-unitaires}

\begin{itemize}
\tightlist
\item
  L'optimiseur a besoin de connaître :

  \begin{itemize}
  \tightlist
  \item
    le coût relatif d'un accès séquentiel au disque.
  \item
    le coût relatif d'un accès aléatoire au disque.
  \item
    le coût relatif de la manipulation d'une ligne en mémoire.
  \item
    le coût de traitement d'une donnée issue d'un index.
  \item
    le coût d'application d'un opérateur.
  \item
    le coût de la manipulation d'une ligne en mémoire pour un parcours
    parallèle parallélisé.
  \item
    le coût de mise en place d'un parcours parallélisé.
  \end{itemize}
\end{itemize}

Pour quantifier la charge nécessaire pour répondre à une requête,
PostgreSQL utilise un mécanisme de coût. Il part du principe que chaque
opération a un coût plus ou moins important.

Cinq paramètres permettent d'ajuster les coûts relatifs : *
\texttt{seq\_page\_cost} représente le coût relatif d'un accès
séquentiel au disque. Ce paramètre vaut 1 et ne devrait pas être
modifié. * \texttt{random\_page\_cost} représente le coût relatif d'un
accès aléatoire au disque. Ce paramètre vaut 4 par défaut, cela signifie
que le temps de déplacement de la tête de lecture de façon aléatoire est
estimé quatre fois plus important que le temps d'accès d'un bloc à un
autre. * \texttt{cpu\_tuple\_cost} représente le coût relatif de la
manipulation d'une ligne en mémoire. Ce paramètre vaut par défaut 0,01.
* \texttt{cpu\_index\_tuple\_cost} répercute le coût de traitement d'une
donnée issue d'un index. Ce paramètre vaut par défaut 0,005. *
\texttt{cpu\_operator\_cost} indique le coût d'application d'un
opérateur sur une donnée. Ce paramètre vaut par défaut 0,0025. *
\texttt{parallel\_tuple\_cost} indique le coût de traitement d'une ligne
lors d'un parcours parallélisé. Ce paramètre vaut par défaut 0.1. *
\texttt{parallel\_setup\_cost} indique le coût de mise en place d'un
parcours parallélisé. Ce paramètre vaut par défaut 1000.0.

En général, on ne modifie pas ces paramètres sans justification
sérieuse. On peut être amené à diminuer \texttt{random\_page\_cost} si
le serveur dispose de disques rapides et d'une carte RAID équipée d'un
cache important. Mais en faisant cela, il faut veiller à ne pas
déstabiliser des plans optimaux qui obtiennent des temps de réponse
constant. À trop diminuer \texttt{random\_page\_cost}, on peut obtenir
de meilleurs temps de réponse si les données sont en cache, mais aussi
des temps de réponse dégradés si les données ne sont pas en cache. Il
n'est pas recommandé de modifier les paramètres
\texttt{cpu\_tuple\_cost}, \texttt{cpu\_index\_tuple\_cost} et
\texttt{cpu\_operator\_cost} sans réelle justification.

Pour des besoins particuliers, ces paramètres sont des paramètres de
sessions. Ils peuvent être modifiés dynamiquement avec l'ordre
\texttt{SET} au niveau de l'application en vue d'exécuter des requêtes
bien particulières.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Statistiques}\label{statistiques}

\begin{itemize}
\tightlist
\item
  Toutes les décisions du planificateur se basent sur les statistiques

  \begin{itemize}
  \tightlist
  \item
    Le choix du parcours
  \item
    Comme le choix des jointures
  \end{itemize}
\item
  Statistiques mises à jour avec ANALYZE
\item
  Sans bonnes statistiques, pas de bons plans
\end{itemize}

Le planificateur se base principalement sur les statistiques pour ses
décisions. Le choix du parcours, le choix des jointures, le choix de
l'ordre des jointures, tout cela dépend des statistiques (et un peu de
la configuration). Sans statistiques à jour, le choix du planificateur a
un fort risque d'être mauvais. Il est donc important que les
statistiques soient mises à jour fréquemment. La mise à jour se fait
avec l'instruction \texttt{ANALYZE} qui peut être exécuté manuellement
ou automatiquement (via un cron ou l'autovacuum par exemple).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Utilisation des
statistiques}\label{utilisation-des-statistiques}

\begin{itemize}
\tightlist
\item
  L'optimiseur utilise les statistiques pour déterminer :

  \begin{itemize}
  \tightlist
  \item
    la cardinalité d'un filtre -\textgreater{} quelle stratégie d'accès
  \item
    la cardinalité d'une jointure -\textgreater{} quel algorithme de
    jointure
  \item
    la cardinalité d'un regroupement -\textgreater{} quel algorithme de
    regroupement
  \end{itemize}
\end{itemize}

Les statistiques sur les données permettent à l'optimiseur de requêtes
de déterminer assez précisément la répartition des valeurs d'une colonne
d'une table, sous la forme d'un histogramme de répartition des valeurs.
Il dispose encore d'autres informations comme la répartition des valeurs
les plus fréquentes, le pourcentage de \texttt{NULL}, le nombre de
valeurs distinctes, etc\ldots{} Toutes ces informations aideront
l'optimiseur à déterminer la sélectivité d'un filtre (prédicat de la
clause \texttt{WHERE}, condition de jointure) et donc quelle sera la
quantité de données récupérées par la lecture d'une table en utilisant
le filtre évalué.

Par exemple, pour une table simple, nommée \texttt{test}, de 1 million
de lignes dont 250000 lignes ont des valeurs uniques et les autres
portent la même valeur :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (i }\DataTypeTok{integer} \KeywordTok{not} \KeywordTok{null}\NormalTok{, t text);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test }\KeywordTok{SELECT} \KeywordTok{CASE} \KeywordTok{WHEN} \NormalTok{i > }\DecValTok{250000} \KeywordTok{THEN} \DecValTok{250000} \KeywordTok{ELSE} \NormalTok{i }\KeywordTok{END}\NormalTok{, md5(i:}\CharTok{:text}\NormalTok{) }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000000}\NormalTok{) i;}
\KeywordTok{CREATE} \KeywordTok{INDEX} \KeywordTok{ON} \NormalTok{test (i);}
\end{Highlighting}
\end{Shaded}

Après un chargement massif de données, il est nécessaire de collecter
les statistiques :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ANALYZE} \NormalTok{test;}
\end{Highlighting}
\end{Shaded}

Ensuite, grâce aux statistiques connues par PostgreSQL (voir la vue
\texttt{pg\_stats}), l'optimiseur est capable de déterminer le chemin le
plus intéressant selon les valeurs recherchées.

Ainsi, avec un filtre peu sélectif, \texttt{i\ =\ 250000}, la requête va
ramener les 3/4 de la table. PostgreSQL choisira donc une lecture
séquentielle de la table, ou \texttt{Seq\ Scan} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{base=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{, BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{i = }\DecValTok{250000}\NormalTok{;}
                                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                                                   
\CommentTok{----------------------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{20834.00} \NormalTok{rows=}\DecValTok{752400} \NormalTok{width=}\DecValTok{37}\NormalTok{) (actual time=}\FloatTok{43.489}\NormalTok{..}\FloatTok{196.081} \NormalTok{rows=}\DecValTok{750001} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (i = }\DecValTok{250000}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{249999}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{8334}
 \NormalTok{Total runtime: }\FloatTok{244.605} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

La partie \texttt{cost} montre que l'optimiseur estime que la lecture va
ramener 752400 lignes. En réalité, ce sont 750001 lignes qui sont
ramenées. L'optimiseur se base donc sur une estimation obtenue selon la
répartition des données.

Avec un filtre plus sélectif, la requête ne ramènera qu'une seule ligne.
L'optimiseur préférera donc passer par l'index que l'on a créé :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{base=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{, BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{i = }\DecValTok{250}\NormalTok{;}
                                                     \KeywordTok{QUERY} \KeywordTok{PLAN}                                                     
\CommentTok{--------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{4.62}\NormalTok{..}\FloatTok{100.82} \NormalTok{rows=}\DecValTok{25} \NormalTok{width=}\DecValTok{37}\NormalTok{) (actual time=}\FloatTok{0.051}\NormalTok{..}\FloatTok{0.052} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: (i = }\DecValTok{250}\NormalTok{)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{4}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{test_i_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{4.61} \NormalTok{rows=}\DecValTok{25} \NormalTok{width=}\DecValTok{0}\NormalTok{) (actual time=}\FloatTok{0.038}\NormalTok{..}\FloatTok{0.038} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (i = }\DecValTok{250}\NormalTok{)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{3}
 \NormalTok{Total runtime: }\FloatTok{0.134} \NormalTok{ms}
\NormalTok{(}\DecValTok{7} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Dans ce deuxième essai, l'optimiseur estime ramener 25 lignes. En
réalité, il n'en ramène qu'une seule. L'estimation reste relativement
précise étant donné le volume de données.

Dans le premier cas, l'optimiseur estime qu'il est moins coûteux de
passer par une lecture séquentielle de la table plutôt qu'une lecture
d'index. Dans le second cas, où le filtre est très sélectif, une lecture
par index est plus appropriée.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Statistiques: table et
index}\label{statistiques-table-et-index}

\begin{itemize}
\tightlist
\item
  Taille
\item
  Cardinalité
\item
  Stocké dans \texttt{pg\_class}

  \begin{itemize}
  \tightlist
  \item
    \texttt{relpages} et \texttt{reltuples}
  \end{itemize}
\end{itemize}

L'optimiseur a besoin de deux données statistiques pour une table ou un
index: sa taille physique et le nombre de lignes portées par l'objet.

Ces deux données statistiques sont stockées dans la table
\texttt{pg\_class}. La taille de la table ou de l'index est exprimée en
nombre de blocs de 8 Ko et stockée dans la colonne \texttt{relpages}. La
cardinalité de la table ou de l'index, c'est à dire le nombre de lignes,
est stockée dans la colonne \texttt{reltuples}.

L'optimiseur utilisera ces deux informations pour apprécier la
cardinalité de la table en fonction de sa volumétrie courante en
calculant sa densité estimée puis en utilisant cette densité multipliée
par le nombre de blocs actuel de la table pour estimer le nombre de
lignes réels de la table :

\begin{verbatim}
  density = reltuples / relpages;
  tuples = density * curpages;
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Statistiques: colonnes}\label{statistiques-colonnes}

\begin{itemize}
\tightlist
\item
  Nombre de valeurs distinctes
\item
  Nombre d'éléments qui n'ont pas de valeur (\texttt{NULL})
\item
  Largeur d'une colonne
\item
  Distribution des données

  \begin{itemize}
  \tightlist
  \item
    tableau des valeurs les plus fréquentes
  \item
    histogramme de répartition des valeurs
  \end{itemize}
\end{itemize}

Au niveau d'une colonne, plusieurs données statistiques sont stockées :
* le nombre de valeurs distinctes, * le nombre d'éléments qui n'ont pas
de valeur (\texttt{NULL}), * la largeur moyenne des données portées par
la colonne, * le facteur de corrélation entre l'ordre des données triées
et la répartition physiques des valeurs dans la table, * la distribution
des données.

La distribution des données est représentée sous deux formes qui peuvent
être complémentaires. Tout d'abord, un tableau de répartition permet de
connaître les valeurs les plus fréquemment rencontrées et la fréquence
d'apparition de ces valeurs. Un histogramme de distribution des valeurs
rencontrées permet également de connaître la répartition des valeurs
pour la colonne considérée.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Stockage des
statistiques}\label{stockage-des-statistiques}

\begin{itemize}
\tightlist
\item
  Les informations statistiques vont dans la table
  \texttt{pg\_statistic}

  \begin{itemize}
  \tightlist
  \item
    mais elle est difficile à comprendre
  \item
    mieux vaut utiliser la vue \texttt{pg\_stats}
  \item
    une table vide n'a pas de statistiques
  \end{itemize}
\item
  Taille et cardinalité dans \texttt{pg\_class}

  \begin{itemize}
  \tightlist
  \item
    colonnes \texttt{relpages} et \texttt{reltuples}
  \end{itemize}
\end{itemize}

Le stockage des statistiques se fait dans le catalogue système
\texttt{pg\_statistic} mais les colonnes de cette table sont difficile à
interpréter. Il est préférable de passer par la vue \texttt{pg\_stats}
qui est plus facilement compréhensible par un être humain.

La collecte des statistiques va également mettre à jour la table
\texttt{pg\_class} avec deux informations importantes pour l'optimiseur.
Il s'agit de la taille d'une table, exprimée en nombre de blocs de 8 Ko
et stockée dans la colonne \texttt{relpages}. La cardinalité de la
table, c'est à dire le nombre de lignes de la table, est stockée dans la
colonne \texttt{reltuples}. L'optimiseur utilisera ces deux informations
pour apprécier la cardinalité de la table en fonction de sa volumétrie
courante.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Vue pg\_stats}\label{vue-pgux5fstats}

\begin{itemize}
\tightlist
\item
  Une ligne par colonne de chaque table
\item
  3 colonnes d'identification

  \begin{itemize}
  \tightlist
  \item
    schemaname, tablename, attname
  \end{itemize}
\item
  8 colonnes d'informations statistiques

  \begin{itemize}
  \tightlist
  \item
    inherited, null\_frac, avg\_width, n\_distinct
  \item
    most\_common\_vals, most\_common\_freqs, histogram\_bounds
  \item
    most\_common\_elems, most\_common\_elem\_freqs,
    elem\_count\_histogram
  \item
    correlation
  \end{itemize}
\end{itemize}

La vue \texttt{pg\_stats} a été créée pour faciliter la compréhension
des statistiques récupérées par la commande \texttt{ANALYZE}.

Elle est composée de trois colonnes qui permettent d'identifier la
colonne :

\begin{itemize}
\tightlist
\item
  schemaname : nom du schéma (jointure possible avec pg\_namespace)
\item
  tablename : nom de la table (jointure possible avec pg\_class,
  intéressant pour récupérer reltuples et relpages)
\item
  attname : nom de la colonne (jointure possible avec pg\_attribute,
  intéressant pour récupérer attstatstarget, valeur d'échantillon)
\end{itemize}

Suivent ensuite les colonnes de statistiques.

\textbf{inherited}

Si true, les statistiques incluent les valeurs de cette colonne dans les
tables filles.

Exemple

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \KeywordTok{ONLY} \KeywordTok{parent}\NormalTok{;}
\NormalTok{-[ }\DataTypeTok{RECORD} \DecValTok{1} \NormalTok{]}
\FunctionTok{count} \NormalTok{| }\DecValTok{0}
\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pg_stats }\KeywordTok{WHERE} \NormalTok{tablename=}\StringTok{'parent'}\NormalTok{;}
\NormalTok{-[ }\DataTypeTok{RECORD} \DecValTok{1} \NormalTok{]}\CommentTok{-----+-----------------------------------------------------}
\NormalTok{schemaname        | }\KeywordTok{public}
\NormalTok{tablename         | }\KeywordTok{parent}
\NormalTok{attname           | }\KeywordTok{id}
\NormalTok{inherited         | t}
\NormalTok{null_frac         | }\DecValTok{0}
\NormalTok{avg_width         | }\DecValTok{4}
\NormalTok{n_distinct        | -}\FloatTok{0.285714}
\NormalTok{most_common_vals  | \{}\DecValTok{1}\NormalTok{,}\DecValTok{2}\NormalTok{,}\DecValTok{17}\NormalTok{,}\DecValTok{18}\NormalTok{,}\DecValTok{19}\NormalTok{,}\DecValTok{20}\NormalTok{,}\DecValTok{3}\NormalTok{,}\DecValTok{4}\NormalTok{,}\DecValTok{5}\NormalTok{,}\DecValTok{15}\NormalTok{,}\DecValTok{16}\NormalTok{,}\DecValTok{6}\NormalTok{,}\DecValTok{7}\NormalTok{,}\DecValTok{8}\NormalTok{,}\DecValTok{9}\NormalTok{,}\DecValTok{10}\NormalTok{\}}
\NormalTok{[...]}
\NormalTok{histogram_bounds  | \{}\DecValTok{11}\NormalTok{,}\DecValTok{12}\NormalTok{,}\DecValTok{13}\NormalTok{,}\DecValTok{14}\NormalTok{\}}
\NormalTok{correlation       | }\FloatTok{0.762715}
\end{Highlighting}
\end{Shaded}

\textbf{null\_frac}

Cette statistique correspond au pourcentage de valeurs NULL dans
l'échantillon considéré. Elle est toujours calculée.

\textbf{avg\_width}

Il s'agit de la largeur moyenne en octets des éléments de cette colonne.
Elle est constante pour les colonnes dont le type est à taille fixe
(integer, booléen, char, etc.). Dans le cas du type \texttt{char(n)}, il
s'agit du nombre de caractères saisissables + 1. Il est variable pour
les autres (principalement text, varchar, bytea).

\textbf{n\_distinct}

Si cette colonne contient un nombre positif, il s'agit du nombre de
valeurs distinctes dans l'échantillon. Cela arrive uniquement quand le
nombre de valeurs distinctes possibles semble fixe.

Si cette colonne contient un nombre négatif, il s'agit du nombre de
valeurs distinctes dans l'échantillon divisé par le nombre de lignes.
Cela survient uniquement quand le nombre de valeurs distinctes possibles
semble variable. -1 indique donc que toutes les valeurs sont distinctes,
-0,5 que chaque valeur apparaît deux fois.

Cette colonne peut être \texttt{NULL} si le type de données n'a pas
d'opérateur =.

Il est possible de forcer cette colonne a une valeur constante en
utilisant l'ordre
\texttt{ALTER\ TABLE\ nom\_table\ ALTER\ COLUMN\ nom\_colonne\ SET\ (parametre\ =\ valeur);}
où parametre vaut soit \texttt{n\_distinct} (pour une table standard)
soit \texttt{n\_distinct\_inherited} (pour une table comprenant des
partitions). Pour les grosses tables contenant des valeurs distinctes,
indiquer une grosse valeur ou la valeur -1 permet de favoriser
l'utilisation de parcours d'index à la place de parcours de bitmap.
C'est aussi utile pour des tables où les données ne sont pas réparties
de façon homogènes, et où la collecte de cette statistique est alors
faussée.

\textbf{most\_common\_vals}

Cette colonne contient une liste triée des valeurs les plus communes.
Elle peut être \texttt{NULL} si les valeurs semblent toujours aussi
communes ou si le type de données n'a pas d'opérateur =.

\textbf{most\_common\_freqs}

Cette colonne contient une liste triée des fréquences pour les valeurs
les plus communes. Cette fréquence est en fait le nombre d'occurrences
de la valeur divisé par le nombre de lignes. Elle est \texttt{NULL} si
\texttt{most\_common\_vals} est \texttt{NULL}.

\textbf{histogram\_bounds}

PostgreSQL prend l'échantillon récupéré par \texttt{ANALYZE}. Il trie
ces valeurs. Ces données triées sont partagées en x tranches, appelées
classes, égales, où x dépend de la valeur du paramètre
\texttt{default\_statistics\_target} ou de la configuration spécifique
de la colonne. Il construit ensuite un tableau dont chaque valeur
correspond à la valeur de début d'une tranche.

\textbf{most\_common\_elems, most\_common\_elem\_freqs,
elem\_count\_histogram}

Ces trois colonnes sont équivalents aux trois précédentes, mais
uniquement pour les données de type tableau.

\textbf{correlation}

Cette colonne est la corrélation statistique entre l'ordre physique et
l'ordre logique des valeurs de la colonne. Si sa valeur est proche de -1
ou 1, un parcours d'index est privilégié. Si elle est proche de 0, un
parcours séquentiel est mieux considéré.

Cette colonne peut être \texttt{NULL} si le type de données n'a pas
d'opérateur \textless{}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{ANALYZE}\label{analyze}

\begin{itemize}
\tightlist
\item
  Ordre SQL de calcul de statistiques

  \begin{itemize}
  \tightlist
  \item
    \texttt{ANALYZE\ {[}\ VERBOSE\ {]}\ {[}\ table\ {[}\ (\ colonne\ {[},\ ...{]}\ )\ {]}\ {]}}
  \end{itemize}
\item
  Sans argument : base entière
\item
  Avec argument : la table complète ou certaines colonnes seulement
\item
  Prend un échantillon de chaque table
\item
  Et calcule des statistiques sur cet échantillon
\item
  Si table vide, conservation des anciennes statistiques
\end{itemize}

\texttt{ANALYZE} est l'ordre SQL permettant de mettre à jour les
statistiques sur les données. Sans argument, l'analyse se fait sur la
base complète. Si un argument est donné, il doit correspondre au nom de
la table à analyser. Il est même possible d'indiquer les colonnes à
traiter.

En fait, cette instruction va exécuter un calcul d'un certain nombre de
statistiques. Elle ne va pas lire la table entière, mais seulement un
échantillon. Sur cet échantillon, chaque colonne sera traitée pour
récupérer quelques informations comme le pourcentage de valeurs NULL,
les valeurs les plus fréquentes et leur fréquence, sans parler d'un
histogramme des valeurs. Toutes ces informations sont stockées dans un
catalogue système nommé \texttt{pg\_statistics}.

Dans le cas d'une table vide, les anciennes statistiques sont
conservées. S'il s'agit d'une nouvelle table, les statistiques sont
initialement vides. La table n'est jamais considérée vide par
l'optimiseur, qui utilise alors des valeurs par défaut.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Fréquence d'analyse}\label{fruxe9quence-danalyse}

\begin{itemize}
\tightlist
\item
  Dépend principalement de la fréquence des requêtes DML
\item
  Cron

  \begin{itemize}
  \tightlist
  \item
    Avec psql
  \item
    Avec vacuumdb (option --analyze-only en 9.0)
  \end{itemize}
\item
  Autovacuum fait du ANALYZE

  \begin{itemize}
  \tightlist
  \item
    Pas sur les tables temporaires
  \item
    Pas assez rapidement dans certains cas
  \end{itemize}
\end{itemize}

Les statistiques doivent être mises à jour fréquemment. La fréquence
exacte dépend surtout de la fréquence des requêtes
d'insertion/modification/suppression des lignes des tables. Néanmoins,
un \texttt{ANALYZE} tous les jours semble un minimum, sauf cas
spécifique.

L'exécution périodique peut se faire avec cron (ou les tâches planifiées
sous Windows). Il n'existe pas d'outils PostgreSQL pour lancer un seul
\texttt{ANALYZE}. L'outil \texttt{vaccumdb} se voit doté d'une option
\texttt{-\/-analyze-only} pour combler ce manque. Avant, il était
nécessaire de passer par \texttt{psql} et son option \texttt{-c} qui
permet de préciser la requête à exécuter. En voici un exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{psql} \NormalTok{-c }\StringTok{"ANALYZE"} \NormalTok{b1}
\end{Highlighting}
\end{Shaded}

Cet exemple exécute la commande \texttt{ANALYZE} sur la base b1 locale.

Le démon \texttt{autovacuum} fait aussi des \texttt{ANALYZE}. La
fréquence dépend de sa configuration. Cependant, il faut connaître deux
particularités de cet outil :

\begin{itemize}
\tightlist
\item
  Ce démon a sa propre connexion à la base. Il ne peut donc pas voir les
  tables temporaires appartenant aux autres sessions. Il ne sera donc
  pas capable de mettre à jour leur statistiques.
\item
  Après une insertion ou une mise à jour massive, autovacuum ne va pas
  forcément lancer un \texttt{ANALYZE} immédiat. En effet,
  \texttt{autovacuum} ne cherche les tables à traiter que toutes les
  minutes (par défaut). Si, après la mise à jour massive, une requête
  est immédiatement exécutée, il y a de fortes chances qu'elle s'exécute
  avec des statistiques obsolètes. Il est préférable dans ce cas de
  lancer un \texttt{ANALYZE} manuel sur la ou les tables ayant subi
  l'insertion ou la mise à jour massive.
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Échantillon
statistique}\label{uxe9chantillon-statistique}

\begin{itemize}
\tightlist
\item
  Se configure dans postgresql.conf

  \begin{itemize}
  \tightlist
  \item
    \texttt{default\_statistics\_target\ =\ 100}
  \end{itemize}
\item
  Configurable par colonne

  \begin{itemize}
  \tightlist
  \item
    \texttt{ALTER\ TABLE\ nom\ ALTER\ {[}\ COLUMN\ {]}\ colonne\ SET\ STATISTICS\ valeur;}
  \end{itemize}
\item
  Par défaut, récupère 30000 lignes au hasard

  \begin{itemize}
  \tightlist
  \item
    \texttt{300\ *\ default\_statistics\_target}
  \end{itemize}
\item
  Va conserver les 100 valeurs les plus fréquentes avec leur fréquence
\end{itemize}

Par défaut, un \texttt{ANALYZE} récupère 30000 lignes d'une table. Les
statistiques générées à partir de cet échantillon sont bonnes si la
table ne contient pas des millions de lignes. Si c'est le cas, il faudra
augmenter la taille de l'échantillon. Pour cela, il faut augmenter la
valeur du paramètre \texttt{default\_statistics\_target}. Ce dernier
vaut 100 par défaut. La taille de l'échantillon est de
\texttt{300\ x\ default\_statistics\_target}. Augmenter ce paramètre va
avoir plusieurs répercussions. Les statistiques seront plus précises
grâce à un échantillon plus important. Mais du coup, les statistiques
seront plus longues à calculer, prendront plus de place sur le disque,
et demanderont plus de travail au planificateur pour générer le plan
optimal. Augmenter cette valeur n'a donc pas que des avantages.

Du coup, les développeurs de PostgreSQL ont fait en sorte qu'il soit
possible de le configurer colonne par colonne avec l'instruction
suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{nom_table }\KeywordTok{ALTER} \NormalTok{[ }\KeywordTok{COLUMN} \NormalTok{] nom_colonne }\KeywordTok{SET} \KeywordTok{STATISTICS} \NormalTok{valeur;}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Qu'est-ce qu'un plan d'exécution
?}\label{quest-ce-quun-plan-dexuxe9cution}

\begin{itemize}
\tightlist
\item
  Plan d'exécution

  \begin{itemize}
  \tightlist
  \item
    représente les différentes opérations pour répondre à la requête
  \item
    sous forme arborescente
  \item
    composé des nœuds d'exécution
  \item
    plusieurs opérations simples mises bout à bout
  \end{itemize}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Nœud d'exécution}\label{nux153ud-dexuxe9cution}

\begin{itemize}
\tightlist
\item
  Nœud

  \begin{itemize}
  \tightlist
  \item
    opération simple: lectures, jointures, tris, etc.
  \item
    unité de traitement
  \item
    produit et consomme des données
  \end{itemize}
\item
  Enchaînement des opérations

  \begin{itemize}
  \tightlist
  \item
    chaque nœud produit les données consommées par le nœud parent
  \item
    nœud final retourne les données à l'utilisateur
  \end{itemize}
\end{itemize}

Les nœuds correspondent à des unités de traitement qui réalisent des
opérations simples sur un ou deux ensemble de données : lecture d'une
table, jointures entre deux tables, tri d'un ensemble, etc. Si le plan
d'exécution était une recette, chaque nœud est une étape de la recette.

Les nœuds peuvent produire et consommer des données.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Lecture d'un plan}\label{lecture-dun-plan}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Lecture d'un plan}
\end{figure}

Un plan d'exécution est lu en partant du nœud se trouvant le plus à
droite et en remontant jusqu'au nœud final. Quand le plan contient
plusieurs nœuds, le premier nœud exécuté est celui qui se trouve le plus
à droite. Celui qui est le plus à gauche (la première ligne) est le
dernier nœud exécuté. Tous les nœuds sont exécutés simultanément, et
traitent les données dès qu'elles sont transmises par le nœud parent (le
ou les nœud juste en dessous, à droite).

Chaque nœud montre les coûts estimés dans le premier groupe de
parenthèses : * \texttt{cost} est un couple de deux coûts * la première
valeur correspond au coût pour récupérer la première ligne (souvent nul
dans le cas d'un parcours séquentiel) ; * la deuxième valeur correspond
au coût pour récupérer toutes les lignes (cette valeur dépend
essentiellement de la taille de la table lue, mais aussi de l'opération
de filtre ici présente) ; * \texttt{rows} correspond au nombre de lignes
que le planificateur pense récupérer à la sortie de ce nœud ; *
\texttt{width} est la largeur en octets de la ligne.

Cet exemple simple permet de voir le travail de l'optimiseur :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{=> }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{matricule, nom, prenom, nom_service, fonction, localisation}
  \KeywordTok{FROM} \NormalTok{employes emp}
  \KeywordTok{JOIN} \NormalTok{services ser }\KeywordTok{ON} \NormalTok{(emp.num_service = ser.num_service)}
 \KeywordTok{WHERE} \NormalTok{ser.localisation = }\StringTok{'Nantes'}\NormalTok{;}

                               \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{-------------------------------------------------------------------------}

 \KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1.06}\NormalTok{..}\FloatTok{2.29} \NormalTok{rows=}\DecValTok{4} \NormalTok{width=}\DecValTok{48}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (emp.num_service = ser.num_service)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{employes emp  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.14} \NormalTok{rows=}\DecValTok{14} \NormalTok{width=}\DecValTok{35}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1.05}\NormalTok{..}\FloatTok{1.05} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{21}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{services ser  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.05} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{21}\NormalTok{)}
               \NormalTok{Filter: ((localisation):}\CharTok{:text} \NormalTok{= }\StringTok{'Nantes'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Ce plan débute par la lecture de la table \texttt{services}.
L'optimiseur estime que cette lecture ramènera une seule ligne
(\texttt{rows=1}), que cette ligne occupera 21 octets en mémoire
(\texttt{width=21}). Il s'agit de la sélectivité du filtre
\texttt{\textless{}nowiki\textgreater{}WHERE\ localisation\ =\ \textquotesingle{}Nantes\textquotesingle{}\textless{}/nowiki\textgreater{}}.
Le coût de départ de cette lecture est de 0 (\texttt{cost=0.00}). Le
coût total de cette lecture est de \texttt{1.05}, qui correspond à la
lecture séquentielle d'un seul bloc (donc \texttt{seq\_page\_cost}) et à
la manipulation des 4 lignes de la tables \texttt{services} (donc 4 *
\texttt{cpu\_tuple\_cost} + 4 * \texttt{cpu\_operator\_cost}). Le
résultat de cette lecture est ensuite haché par le nœud \texttt{Hash},
qui précède la jointure de type \texttt{Hash\ Join}.

La jointure peut maintenant commencer, avec le nœud \texttt{Hash\ Join}.
Il est particulier car il prend 2 entrées : la donnée hachée
initialement, et les données issues de la lecture d'une seconde table
(peu importe le type d'accès). Le nœud a un coût de démarrage de
\texttt{1.06}, soit le coût du hachage additionné au coût de
manipulation du tuple de départ. Il s'agit du coût de production du
premier tuple de résultat. Le coût total de production du résultat est
de \texttt{2.29}. La jointure par hachage démarre réellement lorsque la
lecture de la table \texttt{employes} commence. Cette lecture remontera
14 lignes, sans application de filtre. La totalité de la table est donc
remontée et elle est très petite donc tient sur un seul bloc de 8 Ko. Le
coût d'accès total est donc facilement déduit à partir de cette
information. À partir des sélectivités précédentes, l'optimiseur estime
que la jointure ramènera 4 lignes au total.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Options de l'EXPLAIN}\label{options-de-lexplain}

\begin{itemize}
\tightlist
\item
  Des options supplémentaires

  \begin{itemize}
  \tightlist
  \item
    ANALYZE
  \item
    BUFFERS
  \item
    COSTS
  \item
    TIMING
  \item
    VERBOSE
  \item
    FORMAT
  \end{itemize}
\item
  Donnant des informations supplémentaires très utiles
\end{itemize}

Au fil des versions, \texttt{EXPLAIN} a gagné en options. L'une d'entre
elles permet de sélectionner le format en sortie. Toutes les autres
permettent d'obtenir des informations supplémentaires.

\textbf{Option ANALYZE}

Le but de cette option est d'obtenir les informations sur l'exécution
réelle de la requête.

\textbf{Attention} : Avec cette option, la requête est réellement
exécutée. Attention aux INSERT/UPDATE/DELETE. Pensez à les englober dans
une transaction que vous annulerez après coup.

Voici un exemple utilisant cette option :

\begin{Shaded}
\begin{Highlighting}[]

\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
                \NormalTok{(actual time=}\FloatTok{0.015}\NormalTok{..}\FloatTok{0.504} \NormalTok{rows=}\DecValTok{999} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (c1 < }\DecValTok{1000}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{0.766} \NormalTok{ms}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Quatre nouvelles informations apparaissent, toutes liées à l'exécution
réelle de la requête :

\begin{itemize}
\tightlist
\item
  \texttt{actual\ time}

  \begin{itemize}
  \tightlist
  \item
    la première valeur correspond à la durée en milliseconde pour
    récupérer la première ligne ;
  \item
    la deuxième valeur est la durée en milliseconde pour récupérer
    toutes les lignes ;
  \end{itemize}
\item
  \texttt{rows} est le nombre de lignes réellement récupérées ;
\item
  \texttt{loops} est le nombre d'exécution de ce nœud.
\end{itemize}

Multiplier la durée par le nombre de boucles pour obtenir la durée
réelle d'exécution du nœud.

L'intérêt de cette option est donc de trouver l'opération qui prend du
temps dans l'exécution de la requête, mais aussi de voir les différences
entre les estimations et la réalité (notamment au niveau du nombre de
lignes).

\textbf{Option BUFFERS}

Cette option apparaît en version 9.1. Elle n'est utilisable qu'avec
l'option \texttt{ANALYZE}. Elle est désactivée par défaut.

Elle indique le nombre de blocs impactés par chaque nœud du plan
d'exécution, en lecture comme en écriture.

Voici un exemple de son utilisation :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
                                \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{) }
                 \NormalTok{(actual time=}\FloatTok{0.015}\NormalTok{..}\FloatTok{0.493} \NormalTok{rows=}\DecValTok{999} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (c1 < }\DecValTok{1000}\NormalTok{)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{5}
 \NormalTok{Total runtime: }\FloatTok{0.821} \NormalTok{ms}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La nouvelle ligne est la ligne \texttt{Buffers}. Elle peut contenir un
grand nombre d'informations :

\^{} Informations \^{} Type d'objet concerné \^{} Explications \^{}
\textbar{} Shared hit \textbar{} Table ou index standard \textbar{}
Lecture d'un bloc dans le cache \textbar{} \textbar{} Shared read
\textbar{} Table ou index standard \textbar{} Lecture d'un bloc hors du
cache \textbar{} \textbar{} Shared written \textbar{} Table ou index
standard \textbar{} Écriture d'un bloc \textbar{} \textbar{} Local hit
\textbar{} Table ou index temporaire \textbar{} Lecture d'un bloc dans
le cache \textbar{} \textbar{} Local read \textbar{} Table ou index
temporaire \textbar{} Lecture d'un bloc hors du cache \textbar{}
\textbar{} Local written \textbar{} Table ou index temporaire \textbar{}
Écriture d'un bloc \textbar{} \textbar{} Temp read \textbar{} Tris et
hachages \textbar{} Lecture d'un bloc \textbar{} \textbar{} Temp written
\textbar{} Tris et hachages \textbar{} Écriture d'un bloc \textbar{}

\textbf{Option COSTS}

L'option \texttt{COSTS} apparaît avec la version 9.0. Une fois activée,
elle indique les estimations du planificateur.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(COSTS }\KeywordTok{OFF}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
           \KeywordTok{QUERY} \KeywordTok{PLAN}           
\CommentTok{--------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1}
   \NormalTok{Filter: (c1 < }\DecValTok{1000}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}

\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(COSTS }\KeywordTok{ON}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
                          \KeywordTok{QUERY} \KeywordTok{PLAN}                          
\CommentTok{--------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{Filter: (c1 < }\DecValTok{1000}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Option TIMING}

Cette option n'est disponible que depuis la version 9.2. Elle n'est
utilisable qu'avec l'option \texttt{ANALYZE}.

Elle ajoute les informations sur les durées en milliseconde. Elle est
activée par défaut. Sa désactivation peut être utile sur certains
systèmes où le chronométrage prend beaucoup de temps et allonge
inutilement la durée d'exécution de la requête.

Voici un exemple de son utilisation :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,TIMING }\KeywordTok{ON}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
                                 \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
                 \NormalTok{(actual time=}\FloatTok{0.017}\NormalTok{..}\FloatTok{0.520} \NormalTok{rows=}\DecValTok{999} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (c1 < }\DecValTok{1000}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{1}
 \NormalTok{Total runtime: }\FloatTok{0.783} \NormalTok{ms}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}

\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,TIMING }\KeywordTok{OFF}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
                                 \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual rows=}\DecValTok{999} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (c1 < }\DecValTok{1000}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{1}
 \NormalTok{Total runtime: }\FloatTok{0.418} \NormalTok{ms}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Option VERBOSE}

L'option \texttt{VERBOSE} permet d'afficher des informations
supplémentaires comme la liste des colonnes en sortie, le nom de la
table qualifié du schéma, le nom de la fonction qualifié du schéma, le
nom du trigger, etc. Elle est désactivée par défaut.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(VERBOSE) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 <}\DecValTok{1000}\NormalTok{;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{public.t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{Output: c1, c2}
   \NormalTok{Filter: (t1.c1 < }\DecValTok{1000}\NormalTok{)}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

On voit dans cet exemple que le nom du schéma est ajouté au nom de la
table. La nouvelle section \texttt{Output} indique la liste des colonnes
de l'ensemble de données en sortie du nœud.

\textbf{Option FORMAT}

L'option \texttt{FORMAT} apparaît en version 9.0. Elle permet de
préciser le format du texte en sortie. Par défaut, il s'agit du texte
habituel, mais il est possible de choisir un format balisé parmi XML,
JSON et YAML. Voici ce que donne la commande \texttt{EXPLAIN} avec le
format XML :

\begin{verbatim}
b1=# EXPLAIN (FORMAT XML) SELECT * FROM t1 WHERE c1 <1000;
                        QUERY PLAN                        
----------------------------------------------------------
 <explain xmlns="http://www.postgresql.org/2009/explain">+
   <Query>                                               +
     <Plan>                                              +
       <Node-Type>Seq Scan</Node-Type>                   +
       <Relation-Name>t1</Relation-Name>                 +
       <Alias>t1</Alias>                                 +
       <Startup-Cost>0.00</Startup-Cost>                 +
       <Total-Cost>17.50</Total-Cost>                    +
       <Plan-Rows>1000</Plan-Rows>                       +
       <Plan-Width>8</Plan-Width>                        +
       <Filter>(c1 &lt; 1000)</Filter>                   +
     </Plan>                                             +
   </Query>                                              +
 </explain>
(1 row)
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Détecter les
problèmes}\label{duxe9tecter-les-probluxe8mes}

\begin{itemize}
\tightlist
\item
  Différence importante entre l'estimation du nombre de lignes et la
  réalité
\item
  Boucles

  \begin{itemize}
  \tightlist
  \item
    appels très nombreux dans une boucle (nested loop)
  \item
    opération lente sur lesquels PostgreSQL boucle
  \end{itemize}
\item
  Temps d'exécution conséquent sur une opération
\item
  Opérations utilisant beaucoup de blocs (option BUFFERS)
\end{itemize}

Lorsqu'une requête s'exécute lentement, cela peut être un problème dans
le plan. La sortie de \texttt{EXPLAIN} peut apporter quelques
informations qu'il faut savoir décoder. Une différence importante entre
le nombre de lignes estimé et le nombre de lignes réel laisse un doute
sur les statistiques présentes. Soit elles n'ont pas été ré-actualisées
récemment, soit l'échantillon n'est pas suffisamment important pour que
les statistiques donnent une vue proche du réel du contenu de la table.

L'option \texttt{BUFFERS} d'\texttt{EXPLAIN} permet également de mettre
en valeur les opérations d'entrées/sorties lourdes. Cette option affiche
notamment le nombre de blocs lus en/hors cache de PostgreSQL, sachant
qu'un bloc fait généralement 8 Ko, il est aisé de déterminer le volume
de données manipulé par une requête.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Statistiques et coûts}\label{statistiques-et-couxfbts}

\begin{itemize}
\tightlist
\item
  Détermine à partir des statistiques

  \begin{itemize}
  \tightlist
  \item
    cardinalité des prédicats
  \item
    cardinalité des jointures
  \end{itemize}
\item
  Coût d'accès déterminé selon

  \begin{itemize}
  \tightlist
  \item
    des cardinalités
  \item
    volumétrie des tables
  \end{itemize}
\end{itemize}

Afin de comparer les différents plans d'exécution possibles pour une
requête et choisir le meilleur, l'optimiseur a besoin d'estimer un coût
pour chaque nœud du plan.

L'estimation la plus cruciale est celle liée aux nœuds de parcours de
données, car c'est d'eux que découlera la suite du plan. Pour estimer le
coût de ces nœuds, l'optimiseur s'appuie sur les informations
statistiques collectées, ainsi que sur la valeur de paramètres de
configuration.

Les deux notions principales de ce calcul sont la cardinalité (nombre de
lignes estimées en sortie d'un nœud) et la sélectivité (fraction des
lignes conservées après l'application d'un filtre).

Voici ci-dessous un exemple de calcul de cardinalité et de détermination
du coût associé.

\textbf{Calcul de cardinalité}

Pour chaque prédicat et chaque jointure, PostgreSQL va calculer sa
sélectivité et sa cardinalité. Pour un prédicat, cela permet de
déterminer le nombre de lignes retournées par le prédicat par rapport au
nombre total de lignes de la table. Pour une jointure, cela permet de
déterminer le nombre de lignes retournées par la jointure entre deux
tables.

L'optimiseur dispose de plusieurs façons de calculer la cardinalité d'un
filtre ou d'une jointure selon que la valeur recherchée est une valeur
unique, que la valeur se trouve dans le tableau des valeurs les plus
fréquentes ou dans l'histogramme. L'exemple ci-dessous montre comment
calculer la cardinalité d'un filtre simple sur une table \texttt{pays}
de 25 lignes. La valeur recherchée se trouve dans le tableau des valeurs
les plus fréquentes, la cardinalité peut être calculée directement. Si
ce n'était pas le cas, il aurait fallu passer par l'histogramme des
valeurs pour calculer d'abord la sélectivité du filtre pour en déduire
ensuite la cardinalité.

Dans l'exemple qui suit, une table \texttt{pays} contient 25 entrées

La requête suivante permet de récupérer la fréquence d'apparition de la
valeur recherchée dans le prédicat \texttt{WHERE\ region\_id\ =\ 1} :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{tablename, attname, }\FunctionTok{value}\NormalTok{, freq}
  \KeywordTok{FROM} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{tablename, attname, mcv.value, mcv.freq }\KeywordTok{FROM} \NormalTok{pg_stats,}
                 \NormalTok{LATERAL }\KeywordTok{ROWS} \KeywordTok{FROM} \NormalTok{(unnest(most_common_vals:}\CharTok{:text}\NormalTok{:}\CharTok{:int}\NormalTok{[]),}
                                    \NormalTok{unnest(most_common_freqs)) }\KeywordTok{AS} \NormalTok{mcv(}\FunctionTok{value}\NormalTok{, freq)}
         \KeywordTok{WHERE} \NormalTok{tablename = }\StringTok{'pays'}
           \KeywordTok{AND} \NormalTok{attname = }\StringTok{'region_id'}\NormalTok{) get_mcv}
 \KeywordTok{WHERE} \FunctionTok{value} \NormalTok{= }\DecValTok{1}\NormalTok{;}
 \NormalTok{tablename |  attname  | }\FunctionTok{value} \NormalTok{| freq }
\CommentTok{-----------+-----------+-------+------}
 \NormalTok{pays      | region_id |     }\DecValTok{1} \NormalTok{|  }\FloatTok{0.2}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

L'optimiseur calcule la cardinalité du prédicat
\texttt{WHERE\ region\_id\ =\ 1} en multipliant cette fréquence de la
valeur recherchée avec le nombre total de ligne de la table :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FloatTok{0.2} \NormalTok{* reltuples }\KeywordTok{AS} \NormalTok{cardinalite_predicat}
  \KeywordTok{FROM} \NormalTok{pg_class}
 \KeywordTok{WHERE} \NormalTok{relname = }\StringTok{'pays'}\NormalTok{;}
 \NormalTok{cardinalite_predicat }
\CommentTok{----------------------}
                    \DecValTok{5}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

On peut vérifier que le calcul est bon en obtenant le plan d'exécution
de la requête impliquant la lecture de \texttt{pays} sur laquelle on
applique le prédicat évoqué plus haut :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{WHERE} \NormalTok{region_id = }\DecValTok{1}\NormalTok{;}
                     \KeywordTok{QUERY} \KeywordTok{PLAN}                      
\CommentTok{-----------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pays  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.31} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{49}\NormalTok{)}
   \NormalTok{Filter: (region_id = }\DecValTok{1}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Calcul de coût}

Une table \texttt{pays} peuplée de 25 lignes va permettre de montrer le
calcul des coûts réalisés par l'optimiseur. L'exemple présenté
ci-dessous est simplifié. En réalité, les calculs sont plus complexes
car ils tiennent également compte de la volumétrie réelle de la table.

Le coût de la lecture séquentielle de la table \texttt{pays} est calculé
à partir de deux composantes. Toute d'abord, le nombre de pages (ou
blocs) de la table permet de déduire le nombre de bloc à accéder pour
lire la table intégralement. Le paramètre \texttt{seq\_page\_cost} sera
appliqué ensuite pour indiquer le coût de l'opération :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{relname, relpages * current_setting(}\StringTok{'seq_page_cost'}\NormalTok{):}\CharTok{:float} \KeywordTok{AS} \NormalTok{cout_acces}
  \KeywordTok{FROM} \NormalTok{pg_class}
 \KeywordTok{WHERE} \NormalTok{relname = }\StringTok{'pays'}\NormalTok{;}
 \NormalTok{relname | cout_acces }
\CommentTok{---------+------------}
 \NormalTok{pays    |          }\DecValTok{1}
\end{Highlighting}
\end{Shaded}

Cependant, le coût d'accès seul ne représente pas le coût de la lecture
des données. Une fois que le bloc est monté en mémoire, PostgreSQL doit
décoder chaque ligne individuellement. L'optimiseur utilise
\texttt{cpu\_tuple\_cost} pour estimer le coût de manipulation des
lignes :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{relname,}
       \NormalTok{relpages * current_setting(}\StringTok{'seq_page_cost'}\NormalTok{):}\CharTok{:float}
       \NormalTok{+ reltuples * current_setting(}\StringTok{'cpu_tuple_cost'}\NormalTok{):}\CharTok{:float} \KeywordTok{AS} \NormalTok{cout}
  \KeywordTok{FROM} \NormalTok{pg_class}
 \KeywordTok{WHERE} \NormalTok{relname = }\StringTok{'pays'}\NormalTok{;}
 \NormalTok{relname | cout }
\CommentTok{---------+------}
 \NormalTok{pays    | }\FloatTok{1.25}
\end{Highlighting}
\end{Shaded}

On peut vérifier que le calcul est bon :

\begin{verbatim}
EXPLAIN SELECT * FROM pays;
                      QUERY PLAN                      
------------------------------------------------------
 Seq Scan on pays  (cost=0.00..1.25 rows=25 width=53)
(1 ligne)
\end{verbatim}

Si l'on applique un filtre à la requête, les traitements seront plus
lourds. Par exemple, en ajoutant le prédicat
\texttt{WHERE\ pays\ =\ \textquotesingle{}FR\textquotesingle{}}.

Il faut non seulement extraire les lignes les unes après les autres,
mais il faut également appliquer l'opérateur de comparaison utilisé.
L'optimiseur utilise le paramètre \texttt{cpu\_operator\_cost} pour
déterminer le coût d'application d'un filtre :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{relname,}
       \NormalTok{relpages * current_setting(}\StringTok{'seq_page_cost'}\NormalTok{):}\CharTok{:float}
       \NormalTok{+ reltuples * current_setting(}\StringTok{'cpu_tuple_cost'}\NormalTok{):}\CharTok{:float}
       \NormalTok{+ reltuples * current_setting(}\StringTok{'cpu_operator_cost'}\NormalTok{):}\CharTok{:float} \KeywordTok{AS} \KeywordTok{cost}
  \KeywordTok{FROM} \NormalTok{pg_class}
 \KeywordTok{WHERE} \NormalTok{relname = }\StringTok{'pays'}\NormalTok{;}
 \NormalTok{relname |  }\KeywordTok{cost}  
\CommentTok{---------+--------}
 \NormalTok{pays    | }\FloatTok{1.3125}
\end{Highlighting}
\end{Shaded}

En récupérant le plan d'exécution de la requête à laquelle est appliqué
le filtre
\texttt{WHERE\ pays\ =\ \textquotesingle{}FR\textquotesingle{}}, on
s'aperçoit que le calcul est juste, à l'arrondi près :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{WHERE} \NormalTok{code_pays = }\StringTok{'FR'}\NormalTok{;}
                     \KeywordTok{QUERY} \KeywordTok{PLAN}                      
\CommentTok{-----------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pays  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.31} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{53}\NormalTok{)}
   \NormalTok{Filter: (code_pays = }\StringTok{'FR'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Pour aller plus loin dans le calcul de sélectivité, de cardinalité et de
coût, la documentation de PostgreSQL montre un exemple complet de calcul
de sélectivité et indique les références des fichiers sources dans
lesquels fouiller pour en savoir plus :

\href{http://docs.postgresql.fr/current/planner-stats-details.html}{Comment
le planificateur utilise les statistiques}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Nœuds d'exécution les plus
courants}\label{nux153uds-dexuxe9cution-les-plus-courants}

\begin{itemize}
\tightlist
\item
  Un plan est composé de nœuds

  \begin{itemize}
  \tightlist
  \item
    certains produisent des données
  \item
    d'autres consomment des données et les retournent
  \item
    le nœud final retourne les données à l'utilisateur
  \item
    chaque nœud consomme au fur et à mesure les données produites par
    les nœuds parents
  \end{itemize}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Noeuds de type parcours}\label{noeuds-de-type-parcours}

\begin{itemize}
\tightlist
\item
  Seq Scan
\item
  Parallel Seq Scan
\item
  Function Scan
\item
  et des parcours d'index
\end{itemize}

Les parcours sont les seules opérations qui lisent les données des
tables (normales, temporaires ou non journalisées). Elles ne prennent
donc rien en entrée et fournissent un ensemble de données en sortie. Cet
ensemble peut être trié ou non, filtré ou non.

Il existe plusieurs types de parcours possibles. Parmi les plus
fréquents, on retrouve : * le parcours de table ; * le parcours de
fonction ; * les parcours d'index.

Depuis la version 9.6, les parcours de table sont parallélisables.

Les parcours d'index sont documentés par la suite.

L'opération \texttt{Seq\ Scan} correspond à une lecture séquentielle
d'une table, aussi appelée \texttt{Full\ Table\ Scan} sur d'autres SGBD.
Il consiste à lire l'intégralité de la table, du premier bloc au dernier
bloc. Une clause de filtrage peut être appliquée.

On retrouve ce noeud lorsque la requête nécessite de lire l'intégralité
de la table :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{region;}
                       \KeywordTok{QUERY} \KeywordTok{PLAN}                       
\CommentTok{--------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{region  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.19} \NormalTok{rows=}\DecValTok{19} \NormalTok{width=}\DecValTok{15}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Ce noeud peut également filtrer directement les données, la présence de
la clause \texttt{Filter} montre le filtre appliqué à la lecture des
données :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{region }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{5}\NormalTok{;}
                      \KeywordTok{QUERY} \KeywordTok{PLAN}                       
\CommentTok{-------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{region  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.24} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{15}\NormalTok{)}
   \NormalTok{Filter: (}\KeywordTok{id} \NormalTok{= }\DecValTok{5}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le coût d'accès pour ce type de noeud sera dépendant du nombre de blocs
à parcourir et du paramètre \texttt{seq\_page\_cost}.

Il est possible d'avoir un parcours parallélisé d'une table sous
certaines conditions (la première étant qu'il faut avoir au minimum une
version 9.6). Pour que ce type de parcours soit valable, il faut que
l'optimiseur soit persuadé que le problème sera le temps CPU et non la
bande passante disque. Autrement dit, dans la majorité des cas, il faut
un filtre pour que la parallélisation se déclenche et il faut que la
table soit suffisamment volumineuse.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{postgres=# }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t20 }\KeywordTok{AS} \KeywordTok{SELECT} \KeywordTok{id} \KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000000}\NormalTok{) g(}\KeywordTok{id}\NormalTok{);}
\NormalTok{postgres=# }\KeywordTok{SET} \NormalTok{max_parallel_workers_per_gather }\KeywordTok{TO} \DecValTok{6}\NormalTok{;}
\NormalTok{postgres=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t20 }\KeywordTok{WHERE} \NormalTok{id<}\DecValTok{10000}\NormalTok{;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Gather  (cost=}\FloatTok{1000.00}\NormalTok{..}\FloatTok{11676.13} \NormalTok{rows=}\DecValTok{10428} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{Workers Planned: }\DecValTok{2}
   \NormalTok{->  }\KeywordTok{Parallel} \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t20  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{9633.33} \NormalTok{rows=}\DecValTok{4345} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{Filter: (}\KeywordTok{id} \NormalTok{< }\DecValTok{10000}\NormalTok{)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Ici, deux processus supplémentaires seront exécutés pour réaliser la
requête. Dans le cas de ce type de parcours, chaque processus traite
toutes les lignes d'un bloc. Enfin quand un processus a terminé de
traiter son bloc, il regarde quel est le prochain bloc à traiter et le
traite.

On retrouve le noeud \texttt{Function\ Scan} lorsqu'une requête utilise
directement le résultat d'une fonction. C'est un noeud que l'on
rencontre lorsqu'on utilise les fonctions d'informations systèmes de
PostgreSQL :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{postgres=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{from} \NormalTok{pg_get_keywords();}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{-----------------------------------------------------------------------}
 \KeywordTok{Function} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_get_keywords  (cost=}\FloatTok{0.03}\NormalTok{..}\FloatTok{4.03} \NormalTok{rows=}\DecValTok{400} \NormalTok{width=}\DecValTok{65}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}
\end{Highlighting}
\end{Shaded}

En dehors des différents parcours d'index, on retrouve également
d'autres types de parcours mais PostgreSQL les utilisent rarement. Ils
sont néanmoins détaillés en annexe.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours d'index}\label{parcours-dindex}

\begin{itemize}
\tightlist
\item
  Index Scan
\item
  Index Only Scan
\item
  Bitmap Index Scan
\end{itemize}

PostgreSQL dispose de trois moyens d'accéder aux données à travers les
index.

Le noeud \texttt{Index\ Scan} est le premier qui a été disponible. Il
consiste à parcourir les blocs d'index jusqu'à trouver les pointeurs
vers les blocs contenant les données. PostgreSQL lit ensuite les données
de la table qui sont pointées par l'index.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{clients }\KeywordTok{WHERE} \NormalTok{client_id = }\DecValTok{10000}\NormalTok{;}
                                 \KeywordTok{QUERY} \KeywordTok{PLAN}                                  
\CommentTok{-----------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{clients_pkey }\KeywordTok{on} \NormalTok{clients  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{8.44} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{52}\NormalTok{)}
   \KeywordTok{Index} \NormalTok{Cond: (client_id = }\DecValTok{10000}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Ce type de noeud ne permet pas d'extraire directement les données à
retourner depuis l'index, sans passer par la lecture des blocs
correspondants de la table. Le noeud \texttt{Index\ Only\ Scan} permet
cette optimisation, à condition que les colonnes retournées fassent
partie de l'index :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{client_id }\KeywordTok{FROM} \NormalTok{clients }\KeywordTok{WHERE} \NormalTok{client_id = }\DecValTok{10000}\NormalTok{;}
                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                                    
\CommentTok{---------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{clients_pkey }\KeywordTok{on} \NormalTok{clients  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{8.44} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \KeywordTok{Index} \NormalTok{Cond: (client_id = }\DecValTok{10000}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Enfin, on retrouve le dernier parcours sur des opérations de type
//range scan//, c'est à dire où PostgreSQL doit retourner une plage de
valeurs. On le retrouve également lorsque PostgreSQL doit combiner le
résultat de la lecture de plusieurs index.

Contrairement à d'autres SGBD, un index bitmap n'a aucune existence sur
disque. Il est créé en mémoire lorsque son utilisation a un intérêt. Le
but est de diminuer les déplacements de la tête de lecture en découplant
le parcours de l'index du parcours de la table : * Lecture en un bloc de
l'index ; * Lecture en un bloc de la partie intéressante de la table
(dans l'ordre physique de la table, pas dans l'ordre logique de
l'index).

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{clients }\KeywordTok{WHERE} \NormalTok{client_id }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{12000}\NormalTok{;}
                                  \KeywordTok{QUERY} \KeywordTok{PLAN}                                   
\CommentTok{-------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{clients  (cost=}\FloatTok{44.99}\NormalTok{..}\FloatTok{1201.32} \NormalTok{rows=}\DecValTok{2007} \NormalTok{width=}\DecValTok{52}\NormalTok{)}
   \NormalTok{Recheck Cond: ((client_id >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(client_id <= }\DecValTok{12000}\NormalTok{))}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{clients_pkey  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{44.49} \NormalTok{rows=}\DecValTok{2007} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: ((client_id >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(client_id <= }\DecValTok{12000}\NormalTok{))}
\NormalTok{(}\DecValTok{4} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

On retrouve aussi des Bitmap Index Scan lorsqu'il s'agit de combiner le
résultat de la lecture de plusieurs index :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{clients }
\NormalTok{tpc=# }\KeywordTok{WHERE} \NormalTok{client_id }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{12000} 
\NormalTok{tpv=# }\KeywordTok{AND} \NormalTok{segment_marche = }\StringTok{'AUTOMOBILE'}\NormalTok{;}
                                                 \KeywordTok{QUERY} \KeywordTok{PLAN}                                                  
\CommentTok{-------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{clients  (cost=}\FloatTok{478.25}\NormalTok{..}\FloatTok{1079.74} \NormalTok{rows=}\DecValTok{251} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{Recheck Cond: ((client_id >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(client_id <= }\DecValTok{12000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(segment_marche = }\StringTok{'AUTOMOBILE'}\NormalTok{:}\CharTok{:bpchar}\NormalTok{))}
   \NormalTok{->  BitmapAnd  (cost=}\FloatTok{478.25}\NormalTok{..}\FloatTok{478.25} \NormalTok{rows=}\DecValTok{251} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{clients_pkey  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{44.49} \NormalTok{rows=}\DecValTok{2007} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
               \KeywordTok{Index} \NormalTok{Cond: ((client_id >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(client_id <= }\DecValTok{12000}\NormalTok{))}
         \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{idx_clients_segmarche  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{433.38} \NormalTok{rows=}\DecValTok{18795} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
               \KeywordTok{Index} \NormalTok{Cond: (segment_marche = }\StringTok{'AUTOMOBILE'}\NormalTok{:}\CharTok{:bpchar}\NormalTok{)}
\NormalTok{(}\DecValTok{7} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Noeuds de jointure}\label{noeuds-de-jointure}

\begin{itemize}
\tightlist
\item
  PostgreSQL implémente les 3 algorithmes de jointures habituels :

  \begin{itemize}
  \tightlist
  \item
    Nested Loop (boucle imbriquée)
  \item
    Hash Join (hachage de la table interne)
  \item
    Merge Join (tri-fusion)
  \end{itemize}
\item
  Et pour \texttt{EXISTS}, \texttt{IN} et certaines jointures externes :

  \begin{itemize}
  \tightlist
  \item
    Semi Join et Anti Join
  \end{itemize}
\end{itemize}

Le choix du type de jointure dépend non seulement des données mises en
oeuvres, mais elle dépend également beaucoup du paramétrage de
PostgreSQL, notamment des paramètres \texttt{work\_mem},
\texttt{seq\_page\_cost} et \texttt{random\_page\_cost}.

La \texttt{Nested\ Loop} se retrouve principalement quand on joint de
petits ensembles de données :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }
\NormalTok{sql1=# }\KeywordTok{FROM} \NormalTok{commandes }
\NormalTok{sql1=# }\KeywordTok{JOIN} \NormalTok{lignes_commandes }\KeywordTok{USING} \NormalTok{(numero_commande) }
\NormalTok{sql&=# }\KeywordTok{WHERE} \NormalTok{numero_commande < }\DecValTok{1000}\NormalTok{;}
                                              \KeywordTok{QUERY} \KeywordTok{PLAN}                                              
\CommentTok{------------------------------------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{0.84}\NormalTok{..}\FloatTok{4161.14} \NormalTok{rows=}\DecValTok{1121} \NormalTok{width=}\DecValTok{154}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{orders_pkey }\KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{29.64} \NormalTok{rows=}\DecValTok{280} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (numero_commande < }\DecValTok{1000}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{14.71} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{82}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (numero_commande = commandes.numero_commande)}
\end{Highlighting}
\end{Shaded}

Le \texttt{Hash\ Join} se retrouve également lorsque l'ensemble de la
table interne est très petit. L'optimiseur réalise alors un hachage des
valeurs de la colonne de jointure sur la table externe et réalise
ensuite une lecture de la table externe et compare les hachages de la
clé de jointure avec le/les hachage(s) obtenus à la lecture de la table
interne.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }
\NormalTok{sql1=# }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{JOIN} \NormalTok{lignes_commandes }\KeywordTok{USING} \NormalTok{(numero_commande);}
                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                                   
\CommentTok{--------------------------------------------------------------------------------}
 \KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{10690.31}\NormalTok{..}\FloatTok{59899.18} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{154}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (lignes_commandes.numero_commande = commandes.numero_commande)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{16325.39} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{82}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{6489.25}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La jointure par tri-fusion, ou \texttt{Merge\ Join} prend deux ensembles
de données triés en entrée et restitue l'ensemble de données après
jointure. Cette jointure est assez lourde à initialiser si PostgreSQL ne
peut pas utiliser d'index, mais elle a l'avantage de retourner les
données triées directement :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }
\NormalTok{sql1=# }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{JOIN} \NormalTok{lignes_commandes }\KeywordTok{USING} \NormalTok{(numero_commande) }
\NormalTok{sql1=# }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{numero_commande }\KeywordTok{DESC}\NormalTok{;}
                                                      \KeywordTok{QUERY} \KeywordTok{PLAN}                                                       
\CommentTok{-----------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Merge} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1.40}\NormalTok{..}\FloatTok{64405.98} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{154}\NormalTok{)}
   \KeywordTok{Merge} \NormalTok{Cond: (commandes.numero_commande = lignes_commandes.numero_commande)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \NormalTok{Backward }\KeywordTok{using} \NormalTok{orders_pkey }\KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{12898.63} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \NormalTok{Backward }\KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{42747.64} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{82}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Il s'agit d'un algorithme de jointure particulièrement efficace pour
traiter les volumes de données importants.

Les clauses \texttt{EXISTS} et \texttt{NOT\ EXISTS} mettent également en
oeuvre des algorithmes dérivés de semi et anti jointures. Par exemple
avec la clause \texttt{EXISTS} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN}
 \KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*)}
   \KeywordTok{FROM} \NormalTok{commandes c}
  \KeywordTok{WHERE} \KeywordTok{EXISTS} \NormalTok{(}\KeywordTok{SELECT} \DecValTok{1}
                  \KeywordTok{FROM} \NormalTok{lignes_commandes l}
                 \KeywordTok{WHERE} \NormalTok{c.date_commande > l.date_expedition}
                   \KeywordTok{AND} \NormalTok{c.numero_commande = l.numero_commande);}
                                          \KeywordTok{QUERY} \KeywordTok{PLAN}                                          
\CommentTok{----------------------------------------------------------------------------------------------}
 \NormalTok{Aggregate  (cost=}\FloatTok{42439.18}\NormalTok{..}\FloatTok{42439.19} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash} \NormalTok{Semi }\KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{27927.38}\NormalTok{..}\FloatTok{42321.76} \NormalTok{rows=}\DecValTok{46967} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Hash} \NormalTok{Cond: (c.numero_commande = l.numero_commande)}
         \KeywordTok{Join} \NormalTok{Filter: (c.date_commande > l.date_expedition)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes c  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{12}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{16325.39}\NormalTok{..}\FloatTok{16325.39} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{12}\NormalTok{)}
               \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{lignes_commandes l  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{16325.39} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{12}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

On obtient un plan sensiblement identique, avec \texttt{NOT\ EXISTS}. Le
noeud \texttt{Hash\ Semi\ Join} est remplacé par
\texttt{Hash\ Anti\ Join} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN}
\KeywordTok{SELECT} \NormalTok{* }
  \KeywordTok{FROM} \NormalTok{commandes}
 \KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{EXISTS} \NormalTok{(}\KeywordTok{SELECT} \DecValTok{1}
                     \KeywordTok{FROM} \NormalTok{lignes_commandes l}
                    \KeywordTok{WHERE} \NormalTok{l.numero_commande = commandes.numero_commande);}
                                      \KeywordTok{QUERY} \KeywordTok{PLAN}                                       
\CommentTok{---------------------------------------------------------------------------------------}
 \KeywordTok{Hash} \NormalTok{Anti }\KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{27276.38}\NormalTok{..}\FloatTok{47110.99} \NormalTok{rows=}\DecValTok{25824} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (commandes.numero_commande = l.numero_commande)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{16325.39}\NormalTok{..}\FloatTok{16325.39} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{lignes_commandes l  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{16325.39} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Noeuds de tris et de
regroupements}\label{noeuds-de-tris-et-de-regroupements}

\begin{itemize}
\tightlist
\item
  Un seul noeud de tri:

  \begin{itemize}
  \tightlist
  \item
    Sort
  \end{itemize}
\item
  Regroupement/Agrégation:

  \begin{itemize}
  \tightlist
  \item
    Aggregate
  \item
    HashAggregate
  \item
    GroupAggregate
  \item
    Partial Aggregate/Finalize Aggregate
  \end{itemize}
\end{itemize}

Pour réaliser un tri, PostgreSQL ne dispose que d'un seul noeud pour
réaliser cela : \texttt{Sort}. Son efficacité va dépendre du paramètre
\texttt{work\_mem} qui va définir la quantité de mémoire que PostgreSQL
pourra utiliser pour un tri.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{lignes_commandes }\KeywordTok{WHERE} \NormalTok{numero_commande = }\DecValTok{1000} \KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{quantite;}
                                                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                                                                   
\CommentTok{------------------------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{15.57}\NormalTok{..}\FloatTok{15.58} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{82}\NormalTok{) (actual time=}\FloatTok{0.096}\NormalTok{..}\FloatTok{0.097} \NormalTok{rows=}\DecValTok{4} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Sort} \NormalTok{Key: quantite}
   \KeywordTok{Sort} \NormalTok{Method: quicksort  Memory: 25kB}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{15.51} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{82}\NormalTok{) (actual time=}\FloatTok{0.017}\NormalTok{..}\FloatTok{0.021} \NormalTok{rows=}\DecValTok{4} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (numero_commande = }\DecValTok{1000}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Si le tri ne tient pas en mémoire, l'algorithme de tri gère
automatiquement le débordement sur disque :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{prix_total ;}
                                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                                                         
\CommentTok{---------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{28359.74}\NormalTok{..}\FloatTok{28776.55} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{) (actual time=}\FloatTok{993.441}\NormalTok{..}\FloatTok{1157.935} \NormalTok{rows=}\DecValTok{166725} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Sort} \NormalTok{Key: prix_total}
   \KeywordTok{Sort} \NormalTok{Method: external }\KeywordTok{merge}  \NormalTok{Disk: 15608kB}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{) (actual time=}\FloatTok{173.615}\NormalTok{..}\FloatTok{236.712} \NormalTok{rows=}\DecValTok{166725} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Cependant, si un index existe, PostgreSQL peut également utiliser un
index pour récupéré les données triées directement :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{date_commande;}
                                              \KeywordTok{QUERY} \KeywordTok{PLAN}                                               
\CommentTok{-------------------------------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{idx_commandes_date_commande }\KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{23628.15} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Dans n'importe quel ordre de tri :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{date_commande }\KeywordTok{DESC}\NormalTok{;}
                                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                                                   
\CommentTok{----------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Scan} \NormalTok{Backward }\KeywordTok{using} \NormalTok{idx_commandes_date_commande }\KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{23628.15} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le choix du type d'opération de regroupement dépend non seulement des
données mises en oeuvres, mais elle dépend également beaucoup du
paramétrage de PostgreSQL, notamment du paramètre \texttt{work\_mem}.

Concernant les opérations d'agrégations, on retrouve un noeud de type
\texttt{Aggregate} lorsque la requête réalise une opération d'agrégation
simple, sans regroupement :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \NormalTok{commandes;}
                                                 \KeywordTok{QUERY} \KeywordTok{PLAN}                                                 
\CommentTok{------------------------------------------------------------------------------------------------------------}
 \NormalTok{Aggregate  (cost=}\FloatTok{4758.11}\NormalTok{..}\FloatTok{4758.12} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{commandes_client_id_idx }\KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{4341.30} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Si l'optimiseur estime que l'opération d'agrégation tient en mémoire
(paramètre \texttt{work\_mem}), il va utiliser un noeud de type
HashAggregate :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{code_pays, }\FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \NormalTok{contacts }\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{code_pays;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}                              
\CommentTok{----------------------------------------------------------------------}
 \NormalTok{HashAggregate  (cost=}\FloatTok{3982.02}\NormalTok{..}\FloatTok{3982.27} \NormalTok{rows=}\DecValTok{25} \NormalTok{width=}\DecValTok{3}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{contacts  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{3182.01} \NormalTok{rows=}\DecValTok{160001} \NormalTok{width=}\DecValTok{3}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

L'inconvénient de ce noeud est que sa consommation mémoire n'est pas
limitée par \texttt{work\_mem}, il continuera malgré tout à allouer de
la mémoire. Dans certains cas, heureusement très rares, l'optimiseur
peut se tromper suffisamment pour qu'un noeud \texttt{HashAggregate}
consomme plusieurs giga-octets de mémoire et ne sature la mémoire du
serveur.

Lorsque l'optimiseur estime que le volume de données à traité ne tient
pas dans \texttt{work\_mem}, il utilise plutôt l'algorithme
\texttt{GroupAggregate} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{explain} \KeywordTok{select} \NormalTok{numero_commande, }\FunctionTok{count}\NormalTok{(*) }\KeywordTok{from} \NormalTok{lignes_commandes }\KeywordTok{group} \KeywordTok{by} \NormalTok{numero_commande;}
                                                    \KeywordTok{QUERY} \KeywordTok{PLAN}                                                    
\CommentTok{------------------------------------------------------------------------------------------------------------------}
 \NormalTok{GroupAggregate  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{47493.84} \NormalTok{rows=}\DecValTok{140901} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{42747.64} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le calcul d'un agrégat peut être parallélisé à partir de la version 9.6.
Dans ce cas, deux noeuds sont utilisés : un pour le calcul partiel de
chaque processus (Partial Aggregate), et un pour le calcul final
(Finalize Aggregate). Voici un exemple de plan :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*), }\FunctionTok{min}\NormalTok{(C1), }\FunctionTok{max}\NormalTok{(C1) }\KeywordTok{FROM} \NormalTok{t1;}

                                         \KeywordTok{QUERY} \KeywordTok{PLAN}                                          
\CommentTok{---------------------------------------------------------------------------------------------}
 \NormalTok{Finalize Aggregate (actual time=}\FloatTok{1766.820}\NormalTok{..}\FloatTok{1766.820} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  Gather (actual time=}\FloatTok{1766.767}\NormalTok{..}\FloatTok{1766.799} \NormalTok{rows=}\DecValTok{3} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{Workers Planned: }\DecValTok{2}
         \NormalTok{Workers Launched: }\DecValTok{2}
         \NormalTok{->  Partial Aggregate (actual time=}\FloatTok{1765.236}\NormalTok{..}\FloatTok{1765.236} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{3}\NormalTok{)}
               \NormalTok{->  }\KeywordTok{Parallel} \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1 (actual time=}\FloatTok{0.021}\NormalTok{..}\FloatTok{862.430} \NormalTok{rows=}\DecValTok{6666667} \NormalTok{loops=}\DecValTok{3}\NormalTok{)}
 \NormalTok{Planning time: }\FloatTok{0.072} \NormalTok{ms}
 \NormalTok{Execution time: }\FloatTok{1769.164} \NormalTok{ms}
\NormalTok{(}\DecValTok{8} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Les autres noeuds}\label{les-autres-noeuds}

\begin{itemize}
\tightlist
\item
  Limit
\item
  Unique
\item
  Append (UNION ALL), Except, Intersect
\item
  Gather
\item
  InitPlan, Subplan, etc.
\end{itemize}

On rencontre le noeud \texttt{Limit} lorsqu'on limite le résultat avec
l'ordre \texttt{LIMIT} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{LIMIT} \DecValTok{1}\NormalTok{;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Limit}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.04} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

À noter, que le noeud \texttt{Sort} utilisera une méthode de tri appelée
\texttt{top-N\ heapsort} qui permet d'optimiser le tri pour retourner
les //n// premières lignes :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{prix_total }\KeywordTok{LIMIT} \DecValTok{5}\NormalTok{;}
                                                          \KeywordTok{QUERY} \KeywordTok{PLAN}                                                          
\CommentTok{------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Limit}  \NormalTok{(cost=}\FloatTok{9258.49}\NormalTok{..}\FloatTok{9258.50} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{80}\NormalTok{) (actual time=}\FloatTok{86.332}\NormalTok{..}\FloatTok{86.333} \NormalTok{rows=}\DecValTok{5} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{9258.49}\NormalTok{..}\FloatTok{9675.30} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{) (actual time=}\FloatTok{86.330}\NormalTok{..}\FloatTok{86.331} \NormalTok{rows=}\DecValTok{5} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: prix_total}
         \KeywordTok{Sort} \NormalTok{Method: top-N heapsort  Memory: 25kB}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6489.25} \NormalTok{rows=}\DecValTok{166725} \NormalTok{width=}\DecValTok{80}\NormalTok{) (actual time=}\FloatTok{3.683}\NormalTok{..}\FloatTok{22.687} \NormalTok{rows=}\DecValTok{166725} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

On retrouve le noeud \texttt{Unique} lorsque l'on utilise
\texttt{DISTINCT} pour dédoublonner le résultat d'une requête :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \KeywordTok{DISTINCT} \NormalTok{numero_commande }\KeywordTok{FROM} \NormalTok{lignes_commandes;}
                                                    \KeywordTok{QUERY} \KeywordTok{PLAN}                                                    
\CommentTok{------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Unique}  \NormalTok{(cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{44416.23} \NormalTok{rows=}\DecValTok{140901} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{42747.64} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

À noter qu'il est souvent plus efficace d'utiliser \texttt{GROUP\ BY}
pour dédoublonner les résultats d'une requête :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{) }\KeywordTok{SELECT} \KeywordTok{DISTINCT} \NormalTok{numero_commande }\KeywordTok{FROM} \NormalTok{lignes_commandes }\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{numero_commande;}
                                                                               \KeywordTok{QUERY} \KeywordTok{PLAN}                                                              
                  
\CommentTok{-------------------------------------------------------------------------------------------------------------------------------------------------------}
\CommentTok{------------------}
 \KeywordTok{Unique}  \NormalTok{(cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{44768.49} \NormalTok{rows=}\DecValTok{140901} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.047}\NormalTok{..}\FloatTok{357.745} \NormalTok{rows=}\DecValTok{166724} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Group}  \NormalTok{(cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{44416.23} \NormalTok{rows=}\DecValTok{140901} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.045}\NormalTok{..}\FloatTok{306.550} \NormalTok{rows=}\DecValTok{166724} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{42747.64} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.040}\NormalTok{..}\FloatTok{197.817} \NormalTok{rows=}\DecValTok{667439} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{667439}
 \NormalTok{Total runtime: }\FloatTok{365.315} \NormalTok{ms}

\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{) }\KeywordTok{SELECT} \NormalTok{numero_commande }\KeywordTok{FROM} \NormalTok{lignes_commandes }\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{numero_commande;}
                                                                            \KeywordTok{QUERY} \KeywordTok{PLAN}                                                                 
            
\CommentTok{-------------------------------------------------------------------------------------------------------------------------------------------------------}
\CommentTok{------------}
 \KeywordTok{Group}  \NormalTok{(cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{44416.23} \NormalTok{rows=}\DecValTok{140901} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.053}\NormalTok{..}\FloatTok{302.875} \NormalTok{rows=}\DecValTok{166724} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes  (cost=}\FloatTok{0.42}\NormalTok{..}\FloatTok{42747.64} \NormalTok{rows=}\DecValTok{667439} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.046}\NormalTok{..}\FloatTok{194.495} \NormalTok{rows=}\DecValTok{667439} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{667439}
 \NormalTok{Total runtime: }\FloatTok{310.506} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

Le gain est infime, 50 millisecondes environ sur cette requête, mais
laisse présager des gains sur une volumétrie plus importante.

Les noeuds \texttt{Append}, \texttt{Except} et \texttt{Intersect} se
rencontrent lorsqu'on utilise les opérateurs ensemblistes
\texttt{UNION}, \texttt{EXCEPT} et \texttt{INTERSECT}. Par exemple, avec
\texttt{UNION\ ALL} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN}
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays}
 \KeywordTok{WHERE} \NormalTok{region_id = }\DecValTok{1}
 \KeywordTok{UNION} \KeywordTok{ALL}
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays}
 \KeywordTok{WHERE} \NormalTok{region_id = }\DecValTok{2}\NormalTok{;}
                            \KeywordTok{QUERY} \KeywordTok{PLAN}                            
\CommentTok{------------------------------------------------------------------}
 \NormalTok{Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2.73} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{53}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pays  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.31} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{53}\NormalTok{)}
         \NormalTok{Filter: (region_id = }\DecValTok{1}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pays pays_1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.31} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{53}\NormalTok{)}
         \NormalTok{Filter: (region_id = }\DecValTok{2}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le noeud Gather a été introduit en version 9.6 et est utilisé comme
noeud de rassemblement des données pour les plans parallélisés.

Le noeud InitPlan apparaît lorsque PostgreSQL a besoin d'exécuter une
première sous-requête pour ensuite exécuter le reste de la requête. Il
est assez rare :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{*, (}\KeywordTok{SELECT} \NormalTok{nom_region }\KeywordTok{FROM} \NormalTok{regions }\KeywordTok{WHERE} \NormalTok{region_id=}\DecValTok{1}\NormalTok{) }\KeywordTok{FROM} \NormalTok{pays }\KeywordTok{WHERE} \NormalTok{region_id = }\DecValTok{1}\NormalTok{;}
                           \KeywordTok{QUERY} \KeywordTok{PLAN}                           
\CommentTok{----------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pays  (cost=}\FloatTok{1.06}\NormalTok{..}\FloatTok{2.38} \NormalTok{rows=}\DecValTok{5} \NormalTok{width=}\DecValTok{53}\NormalTok{)}
   \NormalTok{Filter: (region_id = }\DecValTok{1}\NormalTok{)}
   \NormalTok{InitPlan }\DecValTok{1} \NormalTok{(returns $}\DecValTok{0}\NormalTok{)}
     \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{regions  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{26}\NormalTok{)}
           \NormalTok{Filter: (region_id = }\DecValTok{1}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le noeud SubPlan est utilisé lorsque PostgreSQL a besoin d'exécuter une
sous-requête pour filtrer les données :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN}
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{pays}
 \KeywordTok{WHERE} \NormalTok{region_id }\KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{region_id }\KeywordTok{FROM} \NormalTok{regions}
                          \KeywordTok{WHERE} \NormalTok{nom_region = }\StringTok{'Europe'}\NormalTok{);}
                          \KeywordTok{QUERY} \KeywordTok{PLAN}                           
\CommentTok{---------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pays  (cost=}\FloatTok{1.06}\NormalTok{..}\FloatTok{2.38} \NormalTok{rows=}\DecValTok{12} \NormalTok{width=}\DecValTok{53}\NormalTok{)}
   \NormalTok{Filter: (}\KeywordTok{NOT} \NormalTok{(hashed SubPlan }\DecValTok{1}\NormalTok{))}
   \NormalTok{SubPlan }\DecValTok{1}
     \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{regions  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
           \NormalTok{Filter: (nom_region = }\StringTok{'Europe'}\NormalTok{:}\CharTok{:bpchar}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

D'autres types de noeud peuvent également être trouvés dans les plans
d'exécution. L'annexe décrit tous ces noeuds en détail.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Problèmes les plus
courants}\label{probluxe8mes-les-plus-courants}

\begin{itemize}
\tightlist
\item
  L'optimiseur se trompe parfois

  \begin{itemize}
  \tightlist
  \item
    mauvaises statistiques
  \item
    écriture particulière de la requête
  \item
    problèmes connus de l'optimiseur
  \end{itemize}
\end{itemize}

L'optimiseur de PostgreSQL est sans doute la partie la plus complexe de
PostgreSQL. Il se trompe rarement, mais certains facteurs peuvent
entraîner des temps d'exécution très lent voire catastrophiques de
certaines requêtes.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Colonnes corrélées}\label{colonnes-corruxe9luxe9es}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1=}\DecValTok{1} \KeywordTok{AND} \NormalTok{c2=}\DecValTok{1}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  \texttt{c1=1} est vrai pour 20\% des lignes
\item
  \texttt{c2=1} est vrai pour 10\% des lignes
\item
  Le planificateur va penser que le résultat complet ne récupérera que
  20\% * 10\% (soit 2\%) des lignes

  \begin{itemize}
  \tightlist
  \item
    En réalité, ça peut aller de 0 à 10\% des lignes
  \end{itemize}
\end{itemize}

PostgreSQL conserve des statistiques par colonne simple. Dans l'exemple
ci-dessus, le planificateur sait que l'estimation pour \texttt{c1=1} est
de 20\% et que l'estimation pour \texttt{c2=1} est de 10\%. Par contre,
il n'a aucune idée de l'estimation pour \texttt{c1=1\ AND\ c2=1}. En
réalité, l'estimation pour cette formule va de 0 à 10\% mais le
planificateur doit statuer sur une seule valeur. Ce sera le résultat de
la multiplication des deux estimations, soit 2\% (20\% * 10\%).

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Mauvaise écriture de
prédicats}\label{mauvaise-uxe9criture-de-pruxe9dicats}

\begin{itemize}
\item
  Exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }
  \KeywordTok{FROM} \NormalTok{commandes }
  \KeywordTok{WHERE} \FunctionTok{extract}\NormalTok{(}\StringTok{'year'} \KeywordTok{from} \NormalTok{date_commande) = }\DecValTok{2014}\NormalTok{;}
\end{Highlighting}
\end{Shaded}
\item
  L'optimiseur n'a pas de statistiques sur le résultat de la fonction
  \texttt{extract}

  \begin{itemize}
  \tightlist
  \item
    il estime la sélectivité du prédicat à 0.5\%.
  \end{itemize}
\end{itemize}

Dans un prédicat, lorsque les valeurs des colonnes sont transformées par
un calcul ou par une fonction, l'optimiseur n'a aucun moyen pour
connaître la sélectivité d'un prédicat. Il utilise donc une estimation
codée en dur dans le code de l'optimiseur : 0,5\% du nombre de lignes de
la table.

Dans la requête suivante, l'optimiseur estime que la requête va ramener
834 lignes :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{sql1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }
    \KeywordTok{FROM} \NormalTok{commandes }
    \KeywordTok{WHERE} \FunctionTok{extract}\NormalTok{(}\StringTok{'year'} \KeywordTok{from} \NormalTok{date_commande) = }\DecValTok{2014}\NormalTok{;}
                                                 \KeywordTok{QUERY} \KeywordTok{PLAN}                                                 
\CommentTok{------------------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{commandes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{7739.69} \NormalTok{rows=}\DecValTok{834} \NormalTok{width=}\DecValTok{80}\NormalTok{)}
   \NormalTok{Filter: (date_part(}\StringTok{'year'}\NormalTok{:}\CharTok{:text}\NormalTok{, (date_commande):}\CharTok{:timestamp} \KeywordTok{without} \DataTypeTok{time} \DataTypeTok{zone}\NormalTok{) = }\DecValTok{2014}\NormalTok{:}\CharTok{:double} \DataTypeTok{precision}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Ces 834 lignes correspondent à 0,5\% de la table \texttt{commandes} :

\begin{verbatim}
sql1=# SELECT relname, reltuples, round(reltuples*0.005) AS estimé
  FROM pg_class
 WHERE relname = 'commandes';
  relname  | reltuples | estimé 
-----------+-----------+--------
 commandes |    166725 |    834
(1 ligne)
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Problème avec LIKE}\label{probluxe8me-avec-like}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c2 }\KeywordTok{LIKE} \StringTok{'x%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  PostgreSQL peut utiliser un index dans ce cas
\item
  Si l'encodage n'est pas C, il faut déclarer l'index avec une classe
  d'opérateur

  \begin{itemize}
  \tightlist
  \item
    varchar\_pattern\_ops, text\_pattern\_ops, etc
  \end{itemize}
\item
  En 9.1, il faut aussi faire attention au collationnement
\item
  Ne pas oublier pg\_trgm (surtout en 9.1) et FTS
\end{itemize}

Dans le cas d'une recherche avec préfixe, PostgreSQL peut utiliser un
index sur la colonne. Il existe cependant une spécificité à PostgreSQL.
Si l'encodage est autre chose que C, il faut utiliser une classe
d'opérateur lors de la création de l'index. Cela donnera par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{i1 }\KeywordTok{ON} \NormalTok{t1 (c2 varchar_pattern_ops);}
\end{Highlighting}
\end{Shaded}

De plus, à partir de la version 9.1, il est important de faire attention
au collationnement. Si le collationnement de la requête diffère du
collationnement de la colonne de l'index, l'index ne pourra pas être
utilisé.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Problèmes avec LIMIT}\label{probluxe8mes-avec-limit}

\begin{itemize}
\tightlist
\item
  Exemple

  \begin{itemize}
  \tightlist
  \item
    EXPLAIN avec LIMIT 199
  \item
    EXPLAIN avec LIMIT 200
  \end{itemize}
\item
  Corrigé en 9.2
\end{itemize}

Le contexte :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (}
    \NormalTok{c1 }\DataTypeTok{integer} \KeywordTok{PRIMARY} \KeywordTok{KEY}
\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 }\KeywordTok{SELECT} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000}\NormalTok{);}

\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t2 (}
    \NormalTok{c2 }\DataTypeTok{integer}
\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t2 }\KeywordTok{SELECT} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000}\NormalTok{);}

\KeywordTok{ANALYZE}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Voici un problème survenant dans les versions antérieures à la 9.2.

\begin{verbatim}
EXPLAIN SELECT * FROM t1
WHERE c1 IN (SELECT c2 FROM t2 LIMIT 199);
                               QUERY PLAN
------------------------------------------------------------------------
 Hash Semi Join (cost=7.46..27.30 rows=199 width=4)
   Hash Cond: (t1.c1 = t2.c2)
   -> Seq Scan on t1 (cost=0.00..15.00 rows=1000 width=4)
   -> Hash (cost=4.97..4.97 rows=199 width=4)
         -> Limit (cost=0.00..2.98 rows=199 width=4)
               -> Seq Scan on t2 (cost=0.00..15.00 rows=1000 width=4)
(6 rows)
\end{verbatim}

Tout se passe bien. PostgreSQL fait tout d'abord un parcours séquentiel
sur la table \texttt{t2} et ne récupère que les 199 premières lignes
grâce à la clause \texttt{LIMIT}. Le hachage se fait sur les 199 lignes
et une comparaison est faite pour chaque ligne de \texttt{t1}.

Maintenant, cherchons à récupérer une ligne de plus avec un
\texttt{LIMIT} à 200 :

\begin{verbatim}
EXPLAIN SELECT * FROM t1
WHERE c1 IN (SELECT c2 FROM t2 LIMIT 200);
                                  QUERY PLAN
----------------------------------------------------------------------
 Hash Join (cost=10.00..30.75 rows=500 width=4)
   Hash Cond: (t1.c1 = t2.c2)
   -> Seq Scan on t1 (cost=0.00..15.00 rows=1000 width=4)
   -> Hash (cost=7.50..7.50 rows=200 width=4)
         -> HashAggregate (cost=5.50..7.50 rows=200 width=4)
               -> Limit (cost=0.00..3.00 rows=200 width=4)
                     -> Seq Scan on t2 (cost=0.00..15.00 rows=1000
                                        width=4)
(7 rows)
\end{verbatim}

La requête a légèrement changé : on passe d'un \texttt{LIMIT\ 199} à un
\texttt{LIMIT\ 200}. L'estimation explose, elle passe de 199 lignes
(estimation exacte) à 500 lignes (estimation plus que doublée). En fait,
le nombre de lignes est calculé très simplement : nombre de lignes de la
table t1 multiplié par 0,5. C'est codé en dur. La raison, jusqu'à
PostgreSQL 9.1, est que par défaut une table sans statistiques est
estimée posséder 200 valeurs distinctes. Quand l'optimiseur rencontre
donc 200 enregistrements distincts en estimation, il pense que la
fonction d'estimation de valeurs distinctes n'a pas de statistiques et
lui a retourné une valeur par défaut, et applique donc un algorithme de
sélectivité par défaut, au lieu de l'algorithme plus fin utilisé en
temps normal.

Sur cet exemple, cela n'a pas un gros impact vu la quantité de données
impliquées et le schéma choisi. Par contre, ça fait passer une requête
de 9ms à 527ms si le \texttt{LIMIT\ 199} est passé à un
\texttt{LIMIT\ 200} pour la même requête sur une table plus conséquente.

Ce problème est réglé en version 9.2 :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1}
\KeywordTok{WHERE} \NormalTok{c1 }\KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{c2 }\KeywordTok{FROM} \NormalTok{t2 }\KeywordTok{LIMIT} \DecValTok{200}\NormalTok{);}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Hash} \NormalTok{Semi }\KeywordTok{Join} \NormalTok{(cost=}\FloatTok{7.46}\NormalTok{..}\FloatTok{27.30} \NormalTok{rows=}\DecValTok{200} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (t1.c1 = t2.c2)}
   \NormalTok{-> Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1 (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{15.00} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{-> }\KeywordTok{Hash} \NormalTok{(cost=}\FloatTok{4.97}\NormalTok{..}\FloatTok{4.97} \NormalTok{rows=}\DecValTok{200} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{-> }\KeywordTok{Limit} \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2.98} \NormalTok{rows=}\DecValTok{200} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
               \NormalTok{-> Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t2 (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{15.00} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(}\DecValTok{6} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{DELETE lent}\label{delete-lent}

\begin{itemize}
\item
  DELETE lent
\item
  Généralement un problème de clé étrangère

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{Delete}  \NormalTok{(actual time=}\FloatTok{111.251}\NormalTok{..}\FloatTok{111.251} \NormalTok{rows=}\DecValTok{0} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
  \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(actual time=}\FloatTok{1.094}\NormalTok{..}\FloatTok{21.402} \NormalTok{rows=}\DecValTok{9347} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
    \KeywordTok{Hash} \NormalTok{Cond: (lot_a30_descr_lot.id_lot_a10_pdl = lot_a10_pdl.id)}
    \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{lot_a30_descr_lot}
        \NormalTok{(actual time=}\FloatTok{0.007}\NormalTok{..}\FloatTok{11.248} \NormalTok{rows=}\DecValTok{34934} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
    \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(actual time=}\FloatTok{0.501}\NormalTok{..}\FloatTok{0.501} \NormalTok{rows=}\DecValTok{561} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
          \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 20kB}
          \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{lot_a10_pdl}
              \NormalTok{(actual time=}\FloatTok{0.121}\NormalTok{..}\FloatTok{0.326} \NormalTok{rows=}\DecValTok{561} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                \NormalTok{Recheck Cond: (id_fantoir_commune = }\DecValTok{320013}\NormalTok{)}
                \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \NormalTok{on...}
                    \NormalTok{(actual time=}\FloatTok{0.101}\NormalTok{..}\FloatTok{0.101} \NormalTok{rows=}\DecValTok{561}
                     \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                      \KeywordTok{Index} \NormalTok{Cond:}
                      \NormalTok{(id_fantoir_commune = }\DecValTok{320013}\NormalTok{)}
\KeywordTok{Trigger} \KeywordTok{for} \KeywordTok{constraint} \NormalTok{fk_lotlocal_lota30descrlot:}
  \NormalTok{time=}\FloatTok{1010.358} \NormalTok{calls=}\DecValTok{9347}
\KeywordTok{Trigger} \KeywordTok{for} \KeywordTok{constraint} \NormalTok{fk_nonbatia21descrsuf_lota30descrlot:}
  \NormalTok{time=}\FloatTok{2311695.025} \NormalTok{calls=}\DecValTok{9347}
\NormalTok{Total runtime: }\FloatTok{2312835.032} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}
\end{itemize}

Parfois, un \texttt{DELETE} peut prendre beaucoup de temps à s'exécuter.
Cela peut être dû à un grand nombre de lignes à supprimer. Cela peut
aussi être dû à la vérification des contraintes étrangères.

Dans l'exemple ci-dessus, le \texttt{DELETE} met 38 minutes à s'exécuter
(2312835 ms), pour ne supprimer aucune ligne. En fait, c'est la
vérification de la contrainte
\texttt{fk\_nonbatia21descrsuf\_lota30descrlot} qui prend pratiquement
tout le temps. C'est d'ailleurs pour cette raison qu'il est recommandé
de positionner des index sur les clés étrangères car cet index permet
d'accélérer la recherche liée à la contrainte.

Attention donc aux contraintes de clés étrangères pour les instructions
DML.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Dédoublonnage}\label{duxe9doublonnage}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \KeywordTok{DISTINCT} \NormalTok{t1.* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{JOIN} \NormalTok{t2 }\KeywordTok{ON} \NormalTok{(t1.id=t2.t1_id);}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  \texttt{DISTINCT} est souvent utilisé pour dédoublonner les lignes de
  t1

  \begin{itemize}
  \tightlist
  \item
    mais génère un tri qui pénalise les performances
  \end{itemize}
\item
  \texttt{GROUP\ BY} est plus rapide
\item
  Une clé primaire permet de dédoublonner efficacement des lignes

  \begin{itemize}
  \tightlist
  \item
    à utiliser avec \texttt{GROUP\ BY}
  \end{itemize}
\end{itemize}

L'exemple ci-dessous montre une requête qui récupère les commandes qui
ont des lignes de commandes et réalise le dédoublonnage avec DISTINCT.
Le plan d'exécution montre une opération de tri qui a nécessité un
fichier temporaire de 60Mo. Toutes ces opérations sont assez gourmandes,
la requête répond en 5,9s :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE} \KeywordTok{on}\NormalTok{, COSTS }\KeywordTok{off}\NormalTok{)}
\NormalTok{tpc-# }\KeywordTok{SELECT} \KeywordTok{DISTINCT} \NormalTok{commandes.* }
      \KeywordTok{FROM} \NormalTok{commandes }\KeywordTok{JOIN} \NormalTok{lignes_commandes }\KeywordTok{USING} \NormalTok{(numero_commande);}
                                                                                                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                
                                                                                                   
\CommentTok{------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Unique} \NormalTok{(actual time=}\FloatTok{5146.904}\NormalTok{..}\FloatTok{5833.600} \NormalTok{rows=}\DecValTok{168749} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort} \NormalTok{(actual time=}\FloatTok{5146.902}\NormalTok{..}\FloatTok{5307.633} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: commandes.numero_commande, commandes.client_id, commandes.etat_commande, commandes.prix_total, commandes.date_commande, commandes.priorite_commande, commandes.vendeur, commandes.priorite_expedition, commandes.commentaire}
         \KeywordTok{Sort} \NormalTok{Method: external }\KeywordTok{sort}  \NormalTok{Disk: 60760kB}
         \NormalTok{->  }\KeywordTok{Merge} \KeywordTok{Join} \NormalTok{(actual time=}\FloatTok{0.061}\NormalTok{..}\FloatTok{601.674} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \KeywordTok{Merge} \NormalTok{Cond: (commandes.numero_commande = lignes_commandes.numero_commande)}
               \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{orders_pkey }\KeywordTok{on} \NormalTok{commandes (actual time=}\FloatTok{0.026}\NormalTok{..}\FloatTok{71.544} \NormalTok{rows=}\DecValTok{168750} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes (actual time=}\FloatTok{0.025}\NormalTok{..}\FloatTok{175.321} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                     \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{0}
 \NormalTok{Total runtime: }\FloatTok{5849.996} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

En restreignant les colonnes récupérées à celle réellement intéressante
et en utilisant \texttt{GROUP\ BY} au lieu du \texttt{DISTINCT}, le
temps d'exécution tombe à 4,5s :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE} \KeywordTok{on}\NormalTok{, COSTS }\KeywordTok{off}\NormalTok{)}
\KeywordTok{SELECT} \NormalTok{commandes.numero_commande, commandes.etat_commande,}
    \NormalTok{commandes.prix_total, commandes.date_commande,}
    \NormalTok{commandes.priorite_commande, commandes.vendeur,}
    \NormalTok{commandes.priorite_expedition}
 \KeywordTok{FROM} \NormalTok{commandes}
 \KeywordTok{JOIN} \NormalTok{lignes_commandes}
      \KeywordTok{USING} \NormalTok{(numero_commande)}
 \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{commandes.numero_commande, commandes.etat_commande,}
          \NormalTok{commandes.prix_total, commandes.date_commande,}
          \NormalTok{commandes.priorite_commande, commandes.vendeur,}
          \NormalTok{commandes.priorite_expedition;}
                                                                                             \KeywordTok{QUERY} \KeywordTok{PLAN}                                      
                                                       
\CommentTok{----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Group} \NormalTok{(actual time=}\FloatTok{4025.069}\NormalTok{..}\FloatTok{4663.992} \NormalTok{rows=}\DecValTok{168749} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort} \NormalTok{(actual time=}\FloatTok{4025.065}\NormalTok{..}\FloatTok{4191.820} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: commandes.numero_commande, commandes.etat_commande, commandes.prix_total, commandes.date_commande, commandes.priorite_commande, commandes.vendeur, commandes.priorite_expedition}
         \KeywordTok{Sort} \NormalTok{Method: external }\KeywordTok{sort}  \NormalTok{Disk: 46232kB}
         \NormalTok{->  }\KeywordTok{Merge} \KeywordTok{Join} \NormalTok{(actual time=}\FloatTok{0.062}\NormalTok{..}\FloatTok{579.852} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \KeywordTok{Merge} \NormalTok{Cond: (commandes.numero_commande = lignes_commandes.numero_commande)}
               \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{orders_pkey }\KeywordTok{on} \NormalTok{commandes (actual time=}\FloatTok{0.027}\NormalTok{..}\FloatTok{70.212} \NormalTok{rows=}\DecValTok{168750} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes (actual time=}\FloatTok{0.026}\NormalTok{..}\FloatTok{170.555} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                     \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{0}
 \NormalTok{Total runtime: }\FloatTok{4676.829} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

Mais, à partir de PostgreSQL 9.1, il est encore possible d'améliorer
encore les temps d'exécution de cette requête. Dans le plan d'exécution
précédent, on voit que l'opération \texttt{Sort} est très gourmande car
le tri des lignes est réalisé sur plusieurs colonnes. Or, la table
\texttt{commandes} a une clé primaire sur la colonne
\texttt{numero\_commande}. Cette clé primaire permet d'assurer que
toutes les lignes sont uniques dans la table \texttt{commandes}. Si
l'opération \texttt{GROUP\ BY} ne porte plus que la clé primaire,
PostgreSQL peut utiliser le résultat de la lecture par index sur
commandes pour faire le regroupement. Le temps d'exécution passe à
environ 580ms :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{tpc=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE} \KeywordTok{on}\NormalTok{, COSTS }\KeywordTok{off}\NormalTok{)}
\KeywordTok{SELECT} \NormalTok{commandes.numero_commande, commandes.etat_commande,}
    \NormalTok{commandes.prix_total, commandes.date_commande,}
    \NormalTok{commandes.priorite_commande, commandes.vendeur,}
    \NormalTok{commandes.priorite_expedition}
  \KeywordTok{FROM} \NormalTok{commandes}
  \KeywordTok{JOIN} \NormalTok{lignes_commandes}
       \KeywordTok{USING} \NormalTok{(numero_commande)}
 \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{commandes.numero_commande;}
                                                          \KeywordTok{QUERY} \KeywordTok{PLAN}                                                          
\CommentTok{------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Group} \NormalTok{(actual time=}\FloatTok{0.067}\NormalTok{..}\FloatTok{580.198} \NormalTok{rows=}\DecValTok{168749} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Merge} \KeywordTok{Join} \NormalTok{(actual time=}\FloatTok{0.061}\NormalTok{..}\FloatTok{435.154} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Merge} \NormalTok{Cond: (commandes.numero_commande = lignes_commandes.numero_commande)}
         \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{orders_pkey }\KeywordTok{on} \NormalTok{commandes (actual time=}\FloatTok{0.027}\NormalTok{..}\FloatTok{49.784} \NormalTok{rows=}\DecValTok{168750} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{lignes_commandes_pkey }\KeywordTok{on} \NormalTok{lignes_commandes (actual time=}\FloatTok{0.025}\NormalTok{..}\FloatTok{131.606} \NormalTok{rows=}\DecValTok{675543} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{0}
 \NormalTok{Total runtime: }\FloatTok{584.624} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

Les opérations de dédoublonnages sont régulièrement utilisées pour
assurer que les lignes retournées par une requête apparaissent de
manière unique. Elles sont souvent inutiles, ou peuvent à minima être
largement améliorées en utilisant les propriétés du modèle de données
(les clés primaires) et des opérations plus adéquates
(\texttt{GROUP\ BY\ clé\_primaire}). Lorsque vous rencontrez des
requêtes utilisant DISTINCT, vérifiez que le DISTINCT est vraiment
pertinent ou s'il ne peut pas être remplacé par un GROUP BY qui pourrait
tirer partie de la lecture d'un index.

Pour aller plus loin:

http://www.depesz.com/index.php/2010/04/19/getting-unique-elements/

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Index inutilisés}\label{index-inutilisuxe9s}

\begin{itemize}
\tightlist
\item
  Trop de lignes retournées
\item
  Prédicat incluant une transformation:
  \texttt{WHERE\ col1\ +\ 2\ \textgreater{}\ 5}
\item
  Statistiques pas à jour ou peu précises
\item
  Opérateur non-supporté par l'index:
  \texttt{WHERE\ col1\ \textless{}\textgreater{}\ \textquotesingle{}valeur\textquotesingle{};}
\item
  Paramétrage de PostgreSQL: \texttt{effective\_cache\_size}
\end{itemize}

PostgreSQL offre de nombreuses possibilités d'indexation des données: *
Type d'index : B-tree, GiST, GIN, SP-GiST, BRIN et hash. * Index
multi-colonnes : \texttt{CREATE\ INDEX\ ...\ ON\ (col1,\ col2...);} *
Index partiel : \texttt{CREATE\ INDEX\ ...\ WHERE\ colonne\ =\ valeur} *
Index fonctionnel : \texttt{CREATE\ INDEX\ ...\ ON\ (fonction(colonne))}
* Extension offrant des fonctionnalités supplémentaires : pg\_trgm

Malgré toutes ces possibilités, une question revient souvent lorsqu'un
index vient d'être ajouté: pourquoi cet index n'est pas utilisé ?

L'optimiseur de PostgreSQL est très avancé et il y a peu de cas où il
est mis en défaut. Malgré cela, certains index ne sont pas utilisés
comme on le souhaiterait. Il peut y avoir plusieurs raisons à cela.

\textbf{Problèmes de statistiques}

Le cas le plus fréquent concerne les statistiques qui ne sont pas à
jour. Cela arrive souvent après le chargement massif d'une table ou une
mise à jour massive sans avoir fait une nouvelle collecte des
statistiques à l'issue de ces changements.

On utilisera l'ordre \texttt{ANALYZE\ table} pour déclencher
explicitement la collecte des statistiques après un tel traitement. En
effet, bien qu'autovacuum soit présent, il peut se passer un certain
temps entre le moment où le traitement est fait et le moment où
autovacuum déclenche une collecte de statistiques. Ou autovacuum peut ne
simplement pas se déclencher car le traitement complet est imbriqué dans
une seule transaction.

Un traitement batch devra comporter des ordres \texttt{ANALYZE} juste
après les ordres SQL qui modifient fortement les données :

``sql COPY table\_travail FROM `/tmp/fichier.csv'; ANALYZE
table\_travail; SELECT \ldots{} FROM table\_travail; ```

Un autre problème qui peut se poser avec les statistiques concerne les
tables de très forte volumétrie. Dans certain cas, l'échantillon de
données ramené par \texttt{ANALYZE} n'est pas assez précis pour donner à
l'optimiseur de PostgreSQL une vision suffisamment précise des données.
Il choisira alors de mauvais plans d'exécution.

Il est possible d'augmenter la précision de l'échantillon de données
ramené à l'aide de l'ordre :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{ALTER} \KeywordTok{TABLE} \NormalTok{... }\KeywordTok{ALTER} \KeywordTok{COLUMN} \NormalTok{... }\KeywordTok{SET} \KeywordTok{STATISTICS} \NormalTok{...;}
\end{Highlighting}
\end{Shaded}

\textbf{Problèmes de prédicats}

Dans d'autres cas, les prédicats d'une requête ne permettent pas à
l'optimiseur de choisir un index pour répondre à une requête. C'est le
cas lorsque le prédicat inclut une transformation de la valeur d'une
colonne.

L'exemple suivant est assez naïf mais démontre bien le problème :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \KeywordTok{table} \KeywordTok{WHERE} \NormalTok{col1 + }\DecValTok{10} \NormalTok{= }\DecValTok{10}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Avec une telle construction, l'optimiseur ne saura pas tirer partie d'un
quelconque index, à moins d'avoir créé un index fonctionnel sur
\texttt{col1\ +\ 10}, mais cet index est largement contre-productif par
rapport à une réécriture de la requête.

Ce genre de problème se rencontre plus souvent sur des prédicats sur des
dates :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \KeywordTok{table} \KeywordTok{WHERE} \NormalTok{date_trunc(}\StringTok{'month'}\NormalTok{, date_debut) = }\DecValTok{12}
\end{Highlighting}
\end{Shaded}

ou encore plus fréquemment rencontré:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \KeywordTok{table} \KeywordTok{WHERE} \FunctionTok{extract}\NormalTok{(}\StringTok{'year'} \KeywordTok{from} \NormalTok{date_debut) = }\DecValTok{2013}
\end{Highlighting}
\end{Shaded}

\textbf{Opérateurs non-supportés}

Les index B-tree supportent la plupart des opérateurs généraux sur les
variables scalaires ((entiers, chaînes, dates, mais pas types composés
comme géométries, hstore\ldots{})), mais pas la différence
(\texttt{\textless{}\textgreater{}} ou \texttt{!=}). Par nature, il
n'est pas possible d'utiliser un index pour déterminer \textbf{toutes
les valeurs sauf une}. Mais ce type de construction est parfois utilisé
pour exclure les valeurs les plus fréquentes d'une colonne. Dans ce cas,
il est possible d'utiliser un index partiel, qui en plus sera très petit
car il n'indexera qu'une faible quantité de données par rapport à la
totalité de la table :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test (}\KeywordTok{id} \NormalTok{serial }\KeywordTok{PRIMARY} \KeywordTok{KEY}\NormalTok{, v }\DataTypeTok{integer}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (v) }\KeywordTok{SELECT} \DecValTok{0} \KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{10000}\NormalTok{);}
\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{test (v) }\KeywordTok{SELECT} \DecValTok{1}\NormalTok{;}
\KeywordTok{ANALYZE} \NormalTok{test;}
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_test_v }\KeywordTok{ON} \NormalTok{test(v);}
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{v <> }\DecValTok{0}\NormalTok{;}
                      \KeywordTok{QUERY} \KeywordTok{PLAN}                      
\CommentTok{------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{170.03} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{Filter: (v <> }\DecValTok{0}\NormalTok{)}

\KeywordTok{DROP} \KeywordTok{INDEX} \NormalTok{idx_test_v;}
\end{Highlighting}
\end{Shaded}

La création d'un index partiel permet d'en tirer partie :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_test_v_partiel }\KeywordTok{ON} \NormalTok{test (v) }\KeywordTok{WHERE} \NormalTok{v<>}\DecValTok{0}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{Temps : }\DecValTok{67}\NormalTok{,}\DecValTok{014} \NormalTok{ms}
\NormalTok{postgres=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{v <> }\DecValTok{0}\NormalTok{;}
                                  \KeywordTok{QUERY} \KeywordTok{PLAN}                                   
\CommentTok{-------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{idx_test_v_partiel }\KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{8.27} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\textbf{Paramétrage de PostgreSQL}

Plusieurs paramètres de PostgreSQL influencent le choix ou non d'un
index : * \texttt{random\_page\_cost} : indique à PostgreSQL la vitesse
d'un accès aléatoire par rapport à un accès séquentiel
(\texttt{seq\_page\_cost}). * \texttt{effective\_cache\_size} : indique
à PostgreSQL une estimation de la taille du cache disque du système.

Le paramètre \texttt{random\_page\_cost} a une grande influence sur
l'utilisation des index en général. Il indique à PostgreSQL le coût d'un
accès disque aléatoire. Il est à comparer au paramètre
\texttt{seq\_page\_cost} qui indique à PostgreSQL le coût d'un accès
disque séquentiel. Ces coûts d'accès sont purement arbitraires et n'ont
aucune réalité physique. Dans sa configuration par défaut, PostgreSQL
estime qu'un accès aléatoire est 4 fois plus coûteux qu'un accès
séquentiel. Les accès aux index étant par nature aléatoires alors que
les parcours de table étant par nature séquentiels, modifier ce
paramètre revient à favoriser l'un par rapport à l'autre. Cette valeur
est bonne dans la plupart des cas. Mais si le serveur de bases de
données dispose d'un système disque rapide, c'est-à-dire une bonne carte
RAID et plusieurs disques SAS rapides en RAID10, ou du SSD, il est
possible de baisser ce paramètre à 3 voir 2.

Enfin, le paramètre \texttt{effective\_cache\_size} indique à PostgreSQL
une estimation de la taille du cache disque du système. Une bonne
pratique est de positionner ce paramètre à 2/3 de la quantité totale de
RAM du serveur. Sur un système Linux, il est possible de donner une
estimation plus précise en s'appuyant sur la valeur de colonne
\texttt{cached} de la commande \texttt{free}. Mais si le cache n'est que
peu utilisé, la valeur trouvée peut être trop basse pour pleinement
favoriser l'utilisation des index.

Pour aller plus loin :

http://www.depesz.com/index.php/2010/09/09/why-is-my-index-not-being-used/

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Écriture du SQL}\label{uxe9criture-du-sql}

\begin{itemize}
\tightlist
\item
  \texttt{NOT\ IN} avec une sous-requête

  \begin{itemize}
  \tightlist
  \item
    à remplacer par \texttt{NOT\ EXISTS}
  \end{itemize}
\item
  Utilisation systématique de \texttt{UNION} au lieu de
  \texttt{UNION\ ALL}

  \begin{itemize}
  \tightlist
  \item
    entraîne un tri systématique
  \end{itemize}
\item
  Sous-requête dans le \texttt{SELECT}

  \begin{itemize}
  \tightlist
  \item
    utiliser \texttt{LATERAL}
  \end{itemize}
\end{itemize}

La façon dont une requête SQL est écrite peut aussi avoir un effet
négatif sur les performances. Il n'est pas possible d'écrire tous les
cas possibles, mais certaines formes d'écritures reviennent souvent.

La clause \texttt{NOT\ IN} n'est pas performance lorsqu'elle est
utilisée avec une sous-requête. L'optimiseur ne parvient pas à exécuter
ce type de requête efficacement.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
  \KeywordTok{FROM} \NormalTok{commandes}
 \KeywordTok{WHERE} \NormalTok{numero_commande }\KeywordTok{NOT} \KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{numero_commande}
                             \KeywordTok{FROM} \NormalTok{lignes_commandes);}
\end{Highlighting}
\end{Shaded}

Il est nécessaire de la réécrire avec la clause \texttt{NOT\ EXISTS},
par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }
  \KeywordTok{FROM} \NormalTok{commandes}
 \KeywordTok{WHERE} \KeywordTok{NOT} \KeywordTok{EXISTS} \NormalTok{(}\KeywordTok{SELECT} \DecValTok{1}
                     \KeywordTok{FROM} \NormalTok{lignes_commandes l}
                    \KeywordTok{WHERE} \NormalTok{l.numero_commande = commandes.numero_commande);}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Outils}\label{outils}

\begin{itemize}
\tightlist
\item
  pgAdmin3
\item
  explain.depesz.com
\item
  pev
\item
  auto\_explain
\item
  plantuner
\end{itemize}

Il existe quelques outils intéressants dans le cadre du planificateur :
deux applications externes pour mieux appréhender un plan d'exécution,
deux modules pour changer le comportement du planificateur.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{pgAdmin3}\label{pgadmin3}

\begin{itemize}
\tightlist
\item
  Vision graphique d'un EXPLAIN
\item
  Une icône par nœud
\item
  La taille des flèches dépend de la quantité de données
\item
  Le détail de chaque nœud est affiché en survolant les nœuds
\end{itemize}

pgAdmin propose depuis très longtemps un affichage graphique de l'
\texttt{EXPLAIN}. Cet affichage est intéressant car il montre simplement
l'ordre dans lequel les opérations sont effectuées. Chaque nœud est
représenté par une icône. Les flèches entre chaque nœud indiquent où
sont envoyés les flux de données, la taille de la flèche précisant la
volumétrie des données.

Les statistiques ne sont affichées qu'en survolant les nœuds.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{pgAdmin3 - copie
d'écran}\label{pgadmin3---copie-duxe9cran}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{}
\end{figure}

Voici un exemple d'un \texttt{EXPLAIN} graphique réalisé par pgAdmin. En
passant la souris sur les nœuds, un message affiche les informations
statistiques sur le nœud.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Site explain.depesz.com}\label{site-explain.depesz.com}

\begin{itemize}
\tightlist
\item
  Site web proposant un affichage particulier du EXPLAIN ANALYZE
\item
  Il ne travaille que sur les informations réelles
\item
  Les lignes sont colorées pour indiquer les problèmes

  \begin{itemize}
  \tightlist
  \item
    Blanc, tout va bien
  \item
    Jaune, inquiétant
  \item
    Marron, plus inquiétant
  \item
    Rouge, très inquiétant
  \end{itemize}
\item
  Installable en local
\end{itemize}

Hubert Lubaczewski est un contributeur très connu dans la communauté
PostgreSQL. Il publie notamment un grand nombre d'articles sur les
nouveautés des prochaines versions. Cependant, il est aussi connu pour
avoir créé un site web d'analyse des plans d'exécution. Ce site web est
disponible à l'adresse http://explain.depesz.com.

Il suffit d'aller sur ce site, de coller le résultat d'un
\texttt{EXPLAIN\ ANALYZE}, et le site affichera le plan d'exécution avec
des codes couleurs pour bien distinguer les nœuds performants des
autres.

Le code couleur est simple :

\begin{itemize}
\tightlist
\item
  Blanc, tout va bien
\item
  Jaune, inquiétant
\item
  Marron, plus inquiétant
\item
  Rouge, très inquiétant
\end{itemize}

Plutôt que d'utiliser ce serveur web, il est possible d'installer ce
site en local :

\begin{itemize}
\tightlist
\item
  https://github.com/depesz/Pg--Explain pour le module explain en Perl ;
\item
  https://github.com/depesz/explain.depesz.com pour la partie site web.
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{explain.depesz.com - copie
d'écran}\label{explain.depesz.com---copie-duxe9cran}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{}
\end{figure}

Cet exemple montre un affichage d'un plan sur le site
explain.depesz.com.

Voici la signification des différentes colonnes :

\begin{itemize}
\tightlist
\item
  \texttt{Exclusive}, durée passée exclusivement sur un nœud ;
\item
  \texttt{Inclusive}, durée passée sur un nœud et ses fils ;
\item
  \texttt{Rows\ x}, facteur d'échelle de l'erreur d'estimation du nombre
  de lignes ;
\item
  \texttt{Rows}, nombre de lignes renvoyées ;
\item
  \texttt{Loops}, nombre de boucles.
\end{itemize}

Sur une exécution de 600 ms, un tiers est passé à lire la table avec un
parcours séquentiel.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Site pev}\label{site-pev}

\begin{itemize}
\tightlist
\item
  Site web proposant un affichage particulier du EXPLAIN ANALYZE

  \begin{itemize}
  \tightlist
  \item
    mais différent de celui de Depesz
  \end{itemize}
\item
  Fournir un plan d'exécution en JSON
\item
  Installable en local
\end{itemize}

PEV est un outil librement téléchargeable sur
https://github.com/AlexTatiyants/pev. Il offre un affichage graphique du
plan d'exécution et indique le nœud le plus coûteux, le plus long, le
plus volumineux, etc.

Il est utilisable sur internet (http://tatiyants.com/pev/\#/plans) mais
aussi installable en local.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{pev - copie d'écran}\label{pev---copie-duxe9cran}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{}
\end{figure}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Extension auto\_explain}\label{extension-autoux5fexplain}

\begin{itemize}
\tightlist
\item
  Extension pour PostgreSQL \textgreater{}= 8.4
\item
  Connaître les requêtes lentes est bien
\item
  Mais difficile de connaître leur plan d'exécution au moment où elles
  ont été lentes
\item
  D'où le module auto\_explain
\end{itemize}

Le but est donc de tracer automatiquement le plan d'exécution des
requêtes. Pour éviter de trop écrire dans les fichiers de trace, il est
possible de ne tracer que les requêtes dont la durée d'exécution a
dépassé une certaine limite. Pour cela, il faut configurer le paramètre
\texttt{auto\_explain.log\_min\_duration}. D'autres options existent,
qui permettent d'activer ou non certaines options du \texttt{EXPLAIN} :
\texttt{log\_analyze}, \texttt{log\_verbose}, \texttt{log\_buffers},
\texttt{log\_format}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Extension plantuner}\label{extension-plantuner}

\begin{itemize}
\tightlist
\item
  Extension, pour PostgreSQL \textgreater{}= 8.4
\item
  Suivant la configuration

  \begin{itemize}
  \tightlist
  \item
    Interdit l'utilisation de certains index
  \item
    Force à zéro les statistiques d'une table vide
  \end{itemize}
\end{itemize}

Cette extension est disponible sur
http://www.sai.msu.su/\textasciitilde{}megera/wiki/plantuner.

Voici un exemple d'utilisation :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{LOAD }\StringTok{'plantuner'}\NormalTok{;}
\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{test(}\KeywordTok{id} \DataTypeTok{int}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{id_idx }\KeywordTok{ON} \NormalTok{test(}\KeywordTok{id}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{id_idx2 }\KeywordTok{ON} \NormalTok{test(}\KeywordTok{id}\NormalTok{);}
\NormalTok{\textbackslash{}d test}
     \KeywordTok{Table} \OtherTok{"public.test"}
 \KeywordTok{Column} \NormalTok{|  }\KeywordTok{Type}   \NormalTok{| Modifiers}
\CommentTok{--------+---------+-----------}
 \KeywordTok{id}     \NormalTok{| }\DataTypeTok{integer} \NormalTok{|}
\NormalTok{Indexes:}
    \OtherTok{"id_idx"} \NormalTok{btree (}\KeywordTok{id}\NormalTok{)}
    \OtherTok{"id_idx2"} \NormalTok{btree (}\KeywordTok{id}\NormalTok{)}

\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \KeywordTok{id} \KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{1}\NormalTok{;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{-----------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{4.34}\NormalTok{..}\FloatTok{15.03} \NormalTok{rows=}\DecValTok{12} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{Recheck Cond: (}\KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{id_idx2  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{4.34} \NormalTok{rows=}\DecValTok{12} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (}\KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}

\KeywordTok{SET} \NormalTok{enable_seqscan }\KeywordTok{TO} \KeywordTok{off}\NormalTok{;}
\KeywordTok{SET} \NormalTok{plantuner.forbid_index }\KeywordTok{TO} \StringTok{'id_idx2'}\NormalTok{;}
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \KeywordTok{id} \KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{1}\NormalTok{;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{4.34}\NormalTok{..}\FloatTok{15.03} \NormalTok{rows=}\DecValTok{12} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{Recheck Cond: (}\KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{id_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{4.34} \NormalTok{rows=}\DecValTok{12} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (}\KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}

\KeywordTok{SET} \NormalTok{plantuner.forbid_index }\KeywordTok{TO} \StringTok{'id_idx2,id_idx'}\NormalTok{;}
\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \KeywordTok{id} \KeywordTok{FROM} \NormalTok{test }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{1}\NormalTok{;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{-------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{test  (cost=}\FloatTok{10000000000.00}\NormalTok{..}\FloatTok{10000000040.00} \NormalTok{rows=}\DecValTok{12} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{Filter: (}\KeywordTok{id} \NormalTok{= }\DecValTok{1}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Un des intérêt de cette extension est de pouvoir interdire l'utilisation
d'un index, afin de pouvoir ensuite le supprimer de manière
transparente, c'est-à-dire sans bloquer aucune requête applicative.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Conclusion}\label{conclusion-4}

\begin{itemize}
\tightlist
\item
  Planificateur très avancé
\item
  Mais faillible
\item
  Cependant

  \begin{itemize}
  \tightlist
  \item
    ne pensez pas être plus intelligent que le planificateur
  \end{itemize}
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Questions}\label{questions-4}

N'hésitez pas, c'est le moment !

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsection{Annexe: Nœuds d'un plan}\label{annexe-nux153uds-dun-plan}

\begin{itemize}
\tightlist
\item
  Quatre types de nœuds

  \begin{itemize}
  \tightlist
  \item
    Parcours (de table, d'index, de TID, etc.)
  \item
    Jointures (Nested Loop, Sort/Merge Join, Hash Join)
  \item
    Opérateurs sur des ensembles (Append, Except, Intersect, etc.)
  \item
    Et quelques autres (Sort, Aggregate, Unique, Limit, Materialize)
  \end{itemize}
\end{itemize}

Un plan d'exécution est un arbre. Chaque nœud de l'arbre est une
opération à effectuer par l'exécuteur. Le planificateur arrange les
nœuds pour que le résultat final soit le bon, et qu'il soit récupéré le
plus rapidement possible.

Il y a quatre types de nœuds :

\begin{itemize}
\tightlist
\item
  les parcours, qui permettent de lire les données dans les tables en
  passant :

  \begin{itemize}
  \tightlist
  \item
    soit par la table ;
  \item
    soit par l'index ;
  \end{itemize}
\item
  les jointures, qui permettent de joindre deux ensembles de données
\item
  les opérateurs sur des ensembles, qui là-aussi vont joindre deux
  ensembles ou plus
\item
  et les opérations sur un seul ensemble : tri, limite, agrégat, etc.
\end{itemize}

Cette partie va permettre d'expliquer chaque type de nœuds, ses
avantages et inconvénients.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours}\label{parcours}

\begin{itemize}
\tightlist
\item
  Ne prend rien en entrée
\item
  Mais renvoie un ensemble de données

  \begin{itemize}
  \tightlist
  \item
    Trié ou non, filtré ou non
  \end{itemize}
\item
  Exemples typiques

  \begin{itemize}
  \tightlist
  \item
    Parcours séquentiel d'une table, avec ou sans filtrage des
    enregistrements produits
  \item
    Parcours par un index, avec ou sans filtrage supplémentaire
  \end{itemize}
\end{itemize}

Les parcours sont les seules opérations qui lisent les données des
tables (standards, temporaires ou non journalisées). Elles ne prennent
donc rien en entrée et fournissent un ensemble de données en sortie. Cet
ensemble peut être trié ou non, filtré ou non.

Il existe trois types de parcours que nous allons détailler : * le
parcours de table ; * le parcours d'index ; * le parcours de bitmap
index, tous les trois pouvant recevoir des filtres supplémentaires en
sortie.

Nous verrons aussi que PostgreSQL propose d'autres types de parcours.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours de table}\label{parcours-de-table}

\begin{itemize}
\tightlist
\item
  Parcours séquentiel de la table (Sequential Scan, ou SeqScan)
\item
  Aussi appelé FULL TABLE SCAN par d'autres SGBD
\item
  La table est lue entièrement

  \begin{itemize}
  \tightlist
  \item
    Même si seulement quelques lignes satisfont la requête
  \item
    Sauf dans le cas de la clause LIMIT sans ORDER BY
  \end{itemize}
\item
  Elle est lue séquentiellement par bloc de 8 Ko
\item
  Optimisation synchronize\_seqscans
\end{itemize}

Le parcours le plus simple est le parcours séquentiel. La table est lue
complètement, de façon séquentielle, par bloc de 8 Ko. Les données sont
lues dans l'ordre physique sur disque, donc les données ne sont pas
envoyées triées au nœud supérieur.

Cela fonctionne dans tous les cas car il n'y a besoin de rien de plus
pour le faire (un parcours d'index nécessite un index, un parcours de
table ne nécessite rien de plus que la table).

Le parcours de table est intéressant pour les performances dans deux cas
:

\begin{itemize}
\tightlist
\item
  les très petites tables ;
\item
  les grosses tables où la majorité des lignes doit être renvoyée.
\end{itemize}

Lors de son calcul de coût, le planificateur ajoute la valeur du
paramètre \texttt{seq\_page\_cost} à chaque bloc lu.

Une optimisation des parcours séquentiels a eu lieu en version 8.3.
Auparavant, quand deux processus parcouraient en même temps la même
table de façon séquentielle, ils lisaient chacun la table. À partir de
la 8.3, si le paramètre \texttt{synchronize\_seqscans} est activé, le
processus qui entame une lecture séquentielle cherche en premier lieu si
un autre processus ne ferait pas une lecture séquentielle de la même
table. Si c'est le cas, Le second processus démarre son scan de table à
l'endroit où le premier processus est en train de lire, ce qui lui
permet de profiter des données mises en cache par ce processus. L'accès
au disque étant bien plus lent que l'accès mémoire, les processus
restent naturellement synchronisés pour le reste du parcours de la
table, et les lectures ne sont donc réalisées qu'une seule fois. Le
début de la table restera à être lu indépendamment. Cette optimisation
permet de diminuer le nombre de blocs lus par chaque processus en cas de
lectures parallèles de la même table.

Il est possible, pour des raisons de tests, ou pour maintenir la
compatibilité avec du code partant de l'hypothèse (erronée) que les
données d'une table sont toujours retournées dans le même ordre, de
désactiver ce type de parcours en positionnant le paramètre
\texttt{synchronize\_seqscans} à \texttt{off}.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours d'index}\label{parcours-dindex-1}

\begin{itemize}
\tightlist
\item
  Parcours aléatoire de l'index
\item
  Pour chaque enregistrement correspondant à la recherche

  \begin{itemize}
  \tightlist
  \item
    Parcours non séquentiel de la table (pour vérifier la visibilité de
    la ligne)
  \end{itemize}
\item
  Sur d'autres SGBD, cela revient à un

  \begin{itemize}
  \tightlist
  \item
    INDEX RANGE SCAN, suivi d'un TABLE ACCESS BY INDEX ROWID
  \end{itemize}
\item
  Gros gain en performance quand le filtre est très sélectif
\item
  L'ensemble de lignes renvoyé est trié
\end{itemize}

Parcourir une table prend du temps, surtout quand on cherche à ne
récupérer que quelques lignes de cette table. Le but d'un index est donc
d'utiliser une structure de données optimisée pour satisfaire une
recherche particulière (on parle de prédicat).

Cette structure est un arbre. La recherche consiste à suivre la
structure de l'arbre pour trouver le premier enregistrement
correspondant au prédicat, puis suivre les feuilles de l'arbre jusqu'au
dernier enregistrement vérifiant le prédicat. De ce fait, et étant donné
la façon dont l'arbre est stocké sur disque, cela peut provoquer des
déplacements de la tête de lecture.

L'autre problème des performances sur les index (mais cette fois,
spécifique à PostgreSQL) est que les informations de visibilité des
lignes sont uniquement stockées dans la table. Cela veut dire que, pour
chaque élément de l'index correspondant au filtre, il va falloir lire la
ligne dans la table pour vérifier si cette dernière est visible pour la
transaction en cours. Il est de toutes façons, pour la plupart des
requêtes, nécessaire d'aller inspecter l'enregistrement de la table pour
récupérer les autres colonnes nécessaires au bon déroulement de la
requête, qui ne sont la plupart du temps pas stockées dans l'index. Ces
enregistrements sont habituellement éparpillés dans la table, et
retournés dans un ordre totalement différent de leur ordre physique par
le parcours sur l'index. Cet accès à la table génère donc énormément
d'accès aléatoires. Or, ce type d'activité est généralement le plus lent
sur un disque magnétique. C'est pourquoi le parcours d'une large portion
d'un index est très lent. PostgreSQL ne cherchera à utiliser un index
que s'il suppose qu'il aura peu de lignes à récupérer.

Voici l'algorithme permettant un parcours d'index avec PostgreSQL :

\begin{itemize}
\tightlist
\item
  Pour tous les éléments de l'index

  \begin{itemize}
  \tightlist
  \item
    Chercher l'élément souhaité dans l'index
  \item
    Lorsqu'un élément est trouvé
  \item
    Vérifier qu'il est visible par la transaction en lisant la ligne
    dans la table et récupérer les colonnes supplémentaires de la table
  \end{itemize}
\end{itemize}

Cette manière de procéder est identique à ce que proposent d'autres SGBD
sous les termes d'« INDEX RANGE SCAN », suivi d'un « TABLE ACCESS BY
INDEX ROWID ».

Un parcours d'index est donc très coûteux, principalement à cause des
déplacements de la tête de lecture. Le paramètre lié au coût de lecture
aléatoire d'une page est par défaut quatre fois supérieur à celui de la
lecture séquentielle d'une page. Ce paramètre s'appelle
\texttt{random\_page\_cost}. Un parcours d'index n'est préférable à un
parcours de table que si la recherche ne va ramener qu'un très faible
pourcentage de la table. Et dans ce cas, le gain possible est très
important par rapport à un parcours séquentiel de table. Par contre, il
se révèle très lent pour lire un gros pourcentage de la table (les accès
aléatoires diminuent spectaculairement les performances).

Il est à noter que, contrairement au parcours de table, le parcours
d'index renvoie les données triées. C'est le seul parcours à le faire.
Il peut même servir à honorer la clause \texttt{ORDER\ BY} d'une
requête. L'index est aussi utilisable dans le cas des tris descendants.
Dans ce cas, le nœud est nommé « Index Scan Backward ». Ce renvoi de
données triées est très intéressant lorsqu'il est utilisé en conjonction
avec la clause \texttt{LIMIT}.

Il ne faut pas oublier aussi le coût de mise à jour de l'index. Si un
index n'est pas utilisé, il coûte cher en maintenance (ajout des
nouvelles entrées, suppression des entrées obsolètes, etc).

Enfin, il est à noter que ce type de parcours est consommateur aussi en
CPU.

Voici un exemple montrant les deux types de parcours et ce que cela
occasionne comme lecture disque :

Commençons par créer une table, lui insérer quelques données et lui
ajouter un index :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1 (}\KeywordTok{id} \DataTypeTok{integer}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{TABLE}
\NormalTok{b1=# }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 (}\KeywordTok{id}\NormalTok{) }\KeywordTok{VALUES} \NormalTok{(}\DecValTok{1}\NormalTok{), (}\DecValTok{2}\NormalTok{), (}\DecValTok{3}\NormalTok{);}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{3}
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{i1 }\KeywordTok{ON} \NormalTok{t1(}\KeywordTok{id}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\end{Highlighting}
\end{Shaded}

Réinitialisons les statistiques d'activité :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{pg_stat_reset();}
 \NormalTok{pg_stat_reset }
\CommentTok{---------------}
 
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Essayons maintenant de lire la table avec un simple parcours séquentiel
:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{id=}\DecValTok{2}\NormalTok{;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.04} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
                 \NormalTok{(actual time=}\FloatTok{0.011}\NormalTok{..}\FloatTok{0.012} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (}\KeywordTok{id} \NormalTok{= }\DecValTok{2}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{0.042} \NormalTok{ms}
\NormalTok{(}\DecValTok{3} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\texttt{Seq\ Scan} est le titre du nœud pour un parcours séquentiel.
Profitons-en pour noter qu'il a fait de lui-même un parcours séquentiel.
En effet, la table est tellement petite (8 Ko) que utiliser l'index
couterait forcément plus cher. Maintenant regardons les statistiques sur
les blocs lus

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{relname, heap_blks_read, heap_blks_hit,}
 \NormalTok{idx_blks_read, idx_blks_hit}
 \KeywordTok{FROM} \NormalTok{pg_statio_user_tables}
 \KeywordTok{WHERE} \NormalTok{relname=}\StringTok{'t1'}\NormalTok{;}

 \NormalTok{relname | heap_blks_read | heap_blks_hit | idx_blks_read | idx_blks_hit }
\CommentTok{---------+----------------+---------------+---------------+--------------}
 \NormalTok{t1      |              }\DecValTok{0} \NormalTok{|             }\DecValTok{1} \NormalTok{|             }\DecValTok{0} \NormalTok{|            }\DecValTok{0}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Seul un bloc a été lu, et il a été lu dans la table (colonne
\texttt{heap\_blks\_hit} à 1).

Pour faire un parcours d'index, nous allons désactiver les parcours
séquentiels.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{enable_seqscan }\KeywordTok{TO} \KeywordTok{off}\NormalTok{;}
\KeywordTok{SET}
\end{Highlighting}
\end{Shaded}

Il existe aussi un paramètre, appelé \texttt{enable\_indexscan}, pour
désactiver les parcours d'index.

Nous allons de nouveau réinitialiser les statistiques :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SELECT} \NormalTok{pg_stat_reset();}
 \NormalTok{pg_stat_reset }
\CommentTok{---------------}
 
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Maintenant relançons la requête :

\texttt{sql\ b1=\#\ EXPLAIN\ ANALYZE\ SELECT\ *\ FROM\ t1\ WHERE\ id=2;\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ QUERY\ PLAN\ -\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\ \ Index\ Scan\ using\ i1\ on\ t1\ \ (cost=0.00..8.27\ rows=1\ width=4)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ (actual\ time=0.088..0.090\ rows=1\ loops=1)\ \ \ \ Index\ Cond:\ (id\ =\ 2)\ \ Total\ runtime:\ 0.121\ ms\ (3\ rows)}

Nous avons bien un parcours d'index. Vérifions les statistiques sur
l'activité :

\texttt{b1=\#\ SELECT\ relname,\ heap\_blks\_read,\ heap\_blks\_hit,\ \ idx\_blks\_read,\ idx\_blks\_hit\ \ FROM\ pg\_statio\_user\_tables\ \ WHERE\ relname=\textquotesingle{}t1\textquotesingle{};\ \ relname\ \textbar{}\ heap\_blks\_read\ \textbar{}\ heap\_blks\_hit\ \textbar{}\ idx\_blks\_read\ \textbar{}\ idx\_blks\_hit\ \ -\/-\/-\/-\/-\/-\/-\/-\/-+-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-+-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-+-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-+-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\ \ t1\ \ \ \ \ \ \textbar{}\ \ \ \ \ \ \ \ \ \ \ \ \ \ 0\ \textbar{}\ \ \ \ \ \ \ \ \ \ \ \ \ 1\ \textbar{}\ \ \ \ \ \ \ \ \ \ \ \ \ 0\ \textbar{}\ \ \ \ \ \ \ \ \ \ \ \ 1\ (1\ row)}

Une page disque a été lue dans l'index (colonne \texttt{idx\_blks\_hit}
à 1) et une autre a été lue dans la table (colonne
\texttt{heap\_blks\_hit} à 1). Le plus impactant est l'accès aléatoire
sur l'index et la table. Il serait bon d'avoir une lecture de l'index,
puis une lecture séquentielle de la table. C'est le but du Bitmap Index
Scan.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours d'index bitmap}\label{parcours-dindex-bitmap}

\begin{itemize}
\tightlist
\item
  En VO, Bitmap Index Scan / Bitmap Heap Scan
\item
  Disponible à partir de la 8.1
\item
  Diminuer les déplacements de la tête de lecture en découplant le
  parcours de l'index du parcours de la table

  \begin{itemize}
  \tightlist
  \item
    Lecture en un bloc de l'index
  \item
    Lecture en un bloc de la partie intéressante de la table
  \end{itemize}
\item
  Autre intérêt : pouvoir combiner plusieurs index en mémoire

  \begin{itemize}
  \tightlist
  \item
    Nœud BitmapAnd
  \item
    Nœud BitmapOr
  \end{itemize}
\item
  Coût de démarrage généralement important

  \begin{itemize}
  \tightlist
  \item
    Parcours moins intéressant avec une clause LIMIT
  \end{itemize}
\end{itemize}

Contrairement à d'autres SGBD, un index bitmap n'a aucune existence sur
disque. Il est créé en mémoire lorsque son utilisation a un intérêt. Le
but est de diminuer les déplacements de la tête de lecture en découplant
le parcours de l'index du parcours de la table : * Lecture en un bloc de
l'index ; * Lecture en un bloc de la partie intéressante de la table
(dans l'ordre physique de la table, pas dans l'ordre logique de
l'index).

Il est souvent utilisé quand il y a un grand nombre de valeurs à
filtrer, notamment pour les clauses \texttt{IN} et \texttt{ANY}. En
voici un exemple :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{t1(c1 }\DataTypeTok{integer}\NormalTok{, c2 }\DataTypeTok{integer}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{TABLE}
\NormalTok{b1=# }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{t1 }\KeywordTok{SELECT} \NormalTok{i, i}\DecValTok{+1} \KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{, }\DecValTok{1000}\NormalTok{) }\KeywordTok{AS} \NormalTok{i;}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{1000}
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \KeywordTok{ON} \NormalTok{t1(c1);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \KeywordTok{ON} \NormalTok{t1(c2);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1 }\KeywordTok{IN} \NormalTok{(}\DecValTok{10}\NormalTok{, }\DecValTok{40}\NormalTok{, }\DecValTok{60}\NormalTok{, }\DecValTok{100}\NormalTok{, }\DecValTok{600}\NormalTok{);}
                                \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{--------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{17.45}\NormalTok{..}\FloatTok{22.85} \NormalTok{rows=}\DecValTok{25} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{Recheck Cond: (c1 = }\KeywordTok{ANY} \NormalTok{(}\StringTok{'\{10,40,60,100,600\}'}\NormalTok{:}\CharTok{:integer}\NormalTok{[]))}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_c1_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.44} \NormalTok{rows=}\DecValTok{25} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (c1 = }\KeywordTok{ANY} \NormalTok{(}\StringTok{'\{10,40,60,100,600\}'}\NormalTok{:}\CharTok{:integer}\NormalTok{[]))}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La partie \texttt{Bitmap\ Index\ Scan} concerne le parcours de l'index,
et la partie \texttt{Bitmap\ Heap\ Scan} concerne le parcours de table.

L'algorithme pourrait être décrit ainsi : * Chercher tous les éléments
souhaités dans l'index * Les placer dans une structure (de TID) de type
bitmap en mémoire * Faire un parcours séquentiel partiel de la table

Ce champ de bits a deux codages possibles :

\begin{itemize}
\tightlist
\item
  1 bit par ligne
\item
  Ou 1 bit par bloc si trop de données.
\end{itemize}

Dans ce dernier (mauvais) cas, il y a une étape de revérification
(\texttt{Recheck\ Condition}).

Ce type d'index présente un autre gros intérêt : pouvoir combiner
plusieurs index en mémoire. Les bitmaps de TID se combinent facilement
avec des opérations booléennes AND et OR. Dans ce cas, on obtient les
nœuds \texttt{BitmapAnd} et \texttt{Nœud\ BitmapOr}. Voici un exemple de
ce dernier :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1}
 \KeywordTok{WHERE} \NormalTok{c1 }\KeywordTok{IN} \NormalTok{(}\DecValTok{10}\NormalTok{, }\DecValTok{40}\NormalTok{, }\DecValTok{60}\NormalTok{, }\DecValTok{100}\NormalTok{, }\DecValTok{600}\NormalTok{) }\KeywordTok{OR} \NormalTok{c2 }\KeywordTok{IN} \NormalTok{(}\DecValTok{300}\NormalTok{, }\DecValTok{400}\NormalTok{, }\DecValTok{500}\NormalTok{);}
                                                \KeywordTok{QUERY} \KeywordTok{PLAN}                                                 
\CommentTok{-----------------------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{30.32}\NormalTok{..}\FloatTok{36.12} \NormalTok{rows=}\DecValTok{39} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
   \NormalTok{Recheck Cond: ((c1 = }\KeywordTok{ANY} \NormalTok{(}\StringTok{'\{10,40,60,100,600\}'}\NormalTok{:}\CharTok{:integer}\NormalTok{[]))}
                 \KeywordTok{OR} \NormalTok{(c2 = }\KeywordTok{ANY} \NormalTok{(}\StringTok{'\{300,400,500\}'}\NormalTok{:}\CharTok{:integer}\NormalTok{[])))}
   \NormalTok{->  BitmapOr  (cost=}\FloatTok{30.32}\NormalTok{..}\FloatTok{30.32} \NormalTok{rows=}\DecValTok{40} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_c1_idx}
               \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{17.44} \NormalTok{rows=}\DecValTok{25} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
               \KeywordTok{Index} \NormalTok{Cond: (c1 = }\KeywordTok{ANY} \NormalTok{(}\StringTok{'\{10,40,60,100,600\}'}\NormalTok{:}\CharTok{:integer}\NormalTok{[]))}
         \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_c2_idx}
               \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{12.86} \NormalTok{rows=}\DecValTok{15} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
               \KeywordTok{Index} \NormalTok{Cond: (c2 = }\KeywordTok{ANY} \NormalTok{(}\StringTok{'\{300,400,500\}'}\NormalTok{:}\CharTok{:integer}\NormalTok{[]))}
\NormalTok{(}\DecValTok{7} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le coût de démarrage est généralement important à cause de la lecture
préalable de l'index et du tri des TID. Du coup, ce type de parcours est
moins intéressant si une clause LIMIT est présente. Un parcours d'index
simple sera généralement choisi dans ce cas.

Le paramètre \texttt{enable\_bitmapscan} permet d'activer ou de
désactiver l'utilisation des parcours d'index bitmap.

À noter que ce type de parcours n'est disponible qu'à partir de
PostgreSQL 8.1.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours d'index seul ===}\label{parcours-dindex-seul}

\begin{itemize}
\tightlist
\item
  Avant la 9.2, pour une requête de ce type

  \begin{itemize}
  \tightlist
  \item
    \texttt{SELECT\ c1\ FROM\ t1\ WHERE\ c1\textless{}10}
  \end{itemize}
\item
  PostgreSQL devait lire l'index et la table

  \begin{itemize}
  \tightlist
  \item
    car les informations de visibilité ne se trouvent que dans la table
  \end{itemize}
\item
  En 9.2, le planificateur peut utiliser la « Visibility Map »

  \begin{itemize}
  \tightlist
  \item
    nouveau nœud « Index Only Scan »
  \item
    Index B-Tree (9.2+)
  \item
    Index SP-GiST (9.2+)
  \item
    Index GiST (9.5+) =\textgreater{} Types : point, box, inet, range
  \end{itemize}
\end{itemize}

Voici un exemple en 9.1 :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{demo_i_o_scan (a }\DataTypeTok{int}\NormalTok{, b text);}
\KeywordTok{CREATE} \KeywordTok{TABLE}
\NormalTok{b1=# }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{demo_i_o_scan}
\NormalTok{b1-# }\KeywordTok{SELECT} \KeywordTok{random}\NormalTok{()*}\DecValTok{10000000}\NormalTok{, a}
\NormalTok{b1-# }\KeywordTok{FROM} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{,}\DecValTok{10000000}\NormalTok{) a;}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{10000000}
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{demo_idx }\KeywordTok{ON} \NormalTok{demo_i_o_scan (a,b);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{b1=# VACUUM }\KeywordTok{ANALYZE} \NormalTok{demo_i_o_scan ;}
\NormalTok{VACUUM}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                    \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{2299.83}\NormalTok{..}\FloatTok{59688.65} \NormalTok{rows=}\DecValTok{89565} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                              \NormalTok{(actual time=}\FloatTok{209.569}\NormalTok{..}\FloatTok{3314.717} \NormalTok{rows=}\DecValTok{89877} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2277.44} \NormalTok{rows=}\DecValTok{89565} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
                            \NormalTok{(actual time=}\FloatTok{197.177}\NormalTok{..}\FloatTok{197.177} \NormalTok{rows=}\DecValTok{89877} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
 \NormalTok{Total runtime: }\FloatTok{3323.497} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}

\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                    \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{2299.83}\NormalTok{..}\FloatTok{59688.65} \NormalTok{rows=}\DecValTok{89565} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                                \NormalTok{(actual time=}\FloatTok{48.620}\NormalTok{..}\FloatTok{269.907} \NormalTok{rows=}\DecValTok{89877} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2277.44} \NormalTok{rows=}\DecValTok{89565} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
                              \NormalTok{(actual time=}\FloatTok{35.780}\NormalTok{..}\FloatTok{35.780} \NormalTok{rows=}\DecValTok{89877} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
 \NormalTok{Total runtime: }\FloatTok{273.761} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Donc 3 secondes pour la première exécution (avec un cache pas forcément
vide), et 273 millisecondes pour la deuxième exécution (et les
suivantes, non affichées ici).

Voici ce que cet exemple donne en 9.2 :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{TABLE} \NormalTok{demo_i_o_scan (a }\DataTypeTok{int}\NormalTok{, b text);}
\KeywordTok{CREATE} \KeywordTok{TABLE}
\NormalTok{b1=# }\KeywordTok{INSERT} \KeywordTok{INTO} \NormalTok{demo_i_o_scan}
\NormalTok{b1-# }\KeywordTok{SELECT} \KeywordTok{random}\NormalTok{()*}\DecValTok{10000000}\NormalTok{, a}
\NormalTok{b1-# }\KeywordTok{FROM} \NormalTok{(}\KeywordTok{select} \NormalTok{generate_series(}\DecValTok{1}\NormalTok{,}\DecValTok{10000000}\NormalTok{)) }\KeywordTok{AS} \NormalTok{t(a);}
\KeywordTok{INSERT} \DecValTok{0} \DecValTok{10000000}
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{demo_idx }\KeywordTok{ON} \NormalTok{demo_i_o_scan (a,b);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{b1=# VACUUM }\KeywordTok{ANALYZE} \NormalTok{demo_i_o_scan ;}
\NormalTok{VACUUM}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                    \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{demo_idx }\KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{3084.77} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                                            \NormalTok{(actual time=}\FloatTok{0.080}\NormalTok{..}\FloatTok{97.942} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{0}
 \NormalTok{Total runtime: }\FloatTok{108.134} \NormalTok{ms}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}

\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                    \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{demo_idx }\KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{3084.77} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                                            \NormalTok{(actual time=}\FloatTok{0.024}\NormalTok{..}\FloatTok{26.954} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{0}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{347}
 \NormalTok{Total runtime: }\FloatTok{34.352} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Donc, même à froid, il est déjà pratiquement trois fois plus rapide que
la version 9.1, à chaud. La version 9.2 est dix fois plus rapide à
chaud.

Essayons maintenant en désactivant les parcours d'index seul :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{enable_indexonlyscan }\KeywordTok{TO} \KeywordTok{off}\NormalTok{;}
\KeywordTok{SET}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                    \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{2239.88}\NormalTok{..}\FloatTok{59818.53} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                               \NormalTok{(actual time=}\FloatTok{29.256}\NormalTok{..}\FloatTok{2992.289} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \KeywordTok{Index} \NormalTok{Recheck: }\DecValTok{6053582}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{346} \NormalTok{read=}\DecValTok{43834} \NormalTok{written=}\DecValTok{2022}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2218.21} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
                              \NormalTok{(actual time=}\FloatTok{27.004}\NormalTok{..}\FloatTok{27.004} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{346}
 \NormalTok{Total runtime: }\FloatTok{3000.502} \NormalTok{ms}
\NormalTok{(}\DecValTok{8} \KeywordTok{rows}\NormalTok{)}

\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                    \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{----------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{2239.88}\NormalTok{..}\FloatTok{59818.53} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                               \NormalTok{(actual time=}\FloatTok{23.533}\NormalTok{..}\FloatTok{1141.754} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \KeywordTok{Index} \NormalTok{Recheck: }\DecValTok{6053582}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{44178}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2218.21} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
                              \NormalTok{(actual time=}\FloatTok{21.592}\NormalTok{..}\FloatTok{21.592} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{344}
 \NormalTok{Total runtime: }\FloatTok{1146.538} \NormalTok{ms}
\NormalTok{(}\DecValTok{8} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

On retombe sur les performances de la version 9.1.

Maintenant, essayons avec un cache vide (niveau PostgreSQL et système) :

\begin{itemize}
\tightlist
\item
  en 9.1
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                           \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{-------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{2299.83}\NormalTok{..}\FloatTok{59688.65} \NormalTok{rows=}\DecValTok{89565} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                              \NormalTok{(actual time=}\FloatTok{126.624}\NormalTok{..}\FloatTok{9750.245} \NormalTok{rows=}\DecValTok{89877} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{44250}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{demo_idx  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2277.44} \NormalTok{rows=}\DecValTok{89565} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
                            \NormalTok{(actual time=}\FloatTok{112.542}\NormalTok{..}\FloatTok{112.542} \NormalTok{rows=}\DecValTok{89877} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{346}
 \NormalTok{Total runtime: }\FloatTok{9765.670} \NormalTok{ms}
\NormalTok{(}\DecValTok{7} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  en 9.2
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{ANALYZE}\NormalTok{,BUFFERS) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{demo_i_o_scan }\KeywordTok{WHERE} \NormalTok{a }\KeywordTok{BETWEEN} \DecValTok{10000} \KeywordTok{AND} \DecValTok{100000}\NormalTok{;}
                                           \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{-------------------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Only} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{demo_idx }\KeywordTok{on} \NormalTok{demo_i_o_scan  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{3084.77} \NormalTok{rows=}\DecValTok{86656} \NormalTok{width=}\DecValTok{11}\NormalTok{)}
                                           \NormalTok{(actual time=}\FloatTok{11.592}\NormalTok{..}\FloatTok{63.379} \NormalTok{rows=}\DecValTok{89432} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Index} \NormalTok{Cond: ((a >= }\DecValTok{10000}\NormalTok{) }\KeywordTok{AND} \NormalTok{(a <= }\DecValTok{100000}\NormalTok{))}
   \KeywordTok{Heap} \NormalTok{Fetches: }\DecValTok{0}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{345}
 \NormalTok{Total runtime: }\FloatTok{70.188} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

La version 9.1 met 10 secondes à exécuter la requête, alors que la
version 9.2 ne met que 70 millisecondes (elle est donc 142 fois plus
rapide).

Voir aussi
http://pgsnaga.blogspot.com/2011/10/index-only-scans-and-heap-block-reads.html

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Parcours : autres}\label{parcours-autres}

\begin{itemize}
\tightlist
\item
  TID Scan
\item
  Function Scan
\item
  Values
\item
  Result
\end{itemize}

Il existe d'autres parcours, bien moins fréquents ceci dit.

\texttt{TID} est l'acronyme de \texttt{Tuple\ ID}. C'est en quelque
sorte un pointeur vers une ligne. Un \texttt{TID\ Scan} est un parcours
de \texttt{TID}. Ce type de parcours est généralement utilisé en interne
par PostgreSQL. Notez qu'il est possible de le désactiver via le
paramètre \texttt{enable\_tidscan}.

Un \texttt{Function\ Scan} est utilisé par les fonctions renvoyant des
ensembles (appelées \texttt{SRF} pour
\texttt{Set\ Returning\ Functions}). En voici un exemple :

\texttt{sql\ b1=\#\ EXPLAIN\ SELECT\ *\ FROM\ generate\_series(1,\ 1000);\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ QUERY\ PLAN\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ -\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\/-\ \ Function\ Scan\ on\ generate\_series\ \ (cost=0.00..10.00\ rows=1000\ width=4)\ (1\ row)}

\texttt{VALUES} est une clause de l'instruction \texttt{INSERT}, mais
\texttt{VALUES} peut aussi être utilisé comme une table dont on spécifie
les valeurs. Par exemple :

```b1=\# VALUES (1), (2); column1 --------- 1 2 (2 rows)

b1=\# SELECT * FROM (VALUES (`a', 1), (`b', 2), (`c', 3)) AS tmp(c1,
c2); c1 \textbar{} c2 ----+---- a \textbar{} 1 b \textbar{} 2 c
\textbar{} 3 (3 rows) ```

Le planificateur utilise un nœud spécial appelé \texttt{Values\ Scan}
pour indiquer un parcours sur cette clause :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN}
\NormalTok{b1-# }\KeywordTok{SELECT} \NormalTok{*}
\NormalTok{b1-# }\KeywordTok{FROM} \NormalTok{(}\KeywordTok{VALUES} \NormalTok{(}\StringTok{'a'}\NormalTok{, }\DecValTok{1}\NormalTok{), (}\StringTok{'b'}\NormalTok{, }\DecValTok{2}\NormalTok{), (}\StringTok{'c'}\NormalTok{, }\DecValTok{3}\NormalTok{))}
\NormalTok{b1-# }\KeywordTok{AS} \NormalTok{tmp(c1, c2);}
                          \KeywordTok{QUERY} \KeywordTok{PLAN}                          
\CommentTok{--------------------------------------------------------------}
 \KeywordTok{Values} \KeywordTok{Scan} \KeywordTok{on} \OtherTok{"*VALUES*"}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.04} \NormalTok{rows=}\DecValTok{3} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Enfin, le nœud \texttt{Result} n'est pas à proprement parler un nœud de
type parcours. Il y ressemble dans le fait qu'il ne prend aucun ensemble
de données en entrée et en renvoie un en sortie. Son but est de renvoyer
un ensemble de données suite à un calcul. Par exemple :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \DecValTok{1+2}\NormalTok{;}
                \KeywordTok{QUERY} \KeywordTok{PLAN}                
\CommentTok{------------------------------------------}
 \NormalTok{Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.01} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Jointures}\label{jointures-1}

\begin{itemize}
\tightlist
\item
  Prend deux ensembles de données en entrée

  \begin{itemize}
  \tightlist
  \item
    L'une est appelée inner (interne)
  \item
    L'autre est appelée outer (externe)
  \end{itemize}
\item
  Et renvoie un seul ensemble de données
\item
  Exemples typiques

  \begin{itemize}
  \tightlist
  \item
    Nested Loop, Merge Join, Hash Join
  \end{itemize}
\end{itemize}

Le but d'une jointure est de grouper deux ensembles de données pour n'en
produire qu'un seul. L'un des ensembles est appelé ensemble interne
(\texttt{inner\ set}), l'autre est appelé ensemble externe
(\texttt{outer\ set}).

Le planificateur de PostgreSQL est capable de traiter les jointures
grâce à trois nœuds :

\begin{itemize}
\tightlist
\item
  \texttt{Nested\ Loop}, une boucle imbriquée ;
\item
  \texttt{Merge\ Join}, un parcours des deux ensembles triés ;
\item
  \texttt{Hash\ Join}, une jointure par tests des données hachées.
\end{itemize}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Nested Loop}\label{nested-loop}

\begin{itemize}
\tightlist
\item
  Pour chaque ligne de la relation externe

  \begin{itemize}
  \tightlist
  \item
    Pour chaque ligne de la relation interne
  \item
    Si la condition de jointure est avérée

    \begin{itemize}
    \tightlist
    \item
      Émettre la ligne en résultat
    \end{itemize}
  \end{itemize}
\item
  L'ensemble externe n'est parcouru qu'une fois
\item
  L'ensemble interne est parcouru pour chaque ligne de l'ensemble
  externe

  \begin{itemize}
  \tightlist
  \item
    Avoir un index utilisable sur l'ensemble interne augmente fortement
    les performances
  \end{itemize}
\end{itemize}

Étant donné le pseudo-code indiqué ci-dessus, on s'aperçoit que
l'ensemble externe n'est parcouru qu'une fois alors que l'ensemble
interne est parcouru pour chaque ligne de l'ensemble externe. Le coût de
ce nœud est donc proportionnel à la taille des ensembles. Il est
intéressant pour les petits ensembles de données, et encore plus lorsque
l'ensemble interne dispose d'un index satisfaisant la condition de
jointure.

En théorie, il s'agit du type de jointure le plus lent mais il a un gros
intérêt. Il n'est pas nécessaire de trier les données ou de les hacher
avant de commencer à traiter les données. Il a donc un coût de démarrage
très faible, ce qui le rend très intéressant si cette jointure est
couplée à une clause \texttt{LIMIT}, ou si le nombre d'itérations (donc
le nombre d'enregistrements de la relation externe) est faible.

Il est aussi très intéressant car il s'agit du seul nœud capable de
traiter des jointures sur des conditions différentes de l'égalité ainsi
que des jointures de type \texttt{CROSS\ JOIN}.

Voici un exemple avec deux parcours séquentiels :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{*}
    \KeywordTok{FROM} \NormalTok{pg_class, pg_namespace}
 \KeywordTok{WHERE} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
                                \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{-------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{37.18} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{307}\NormalTok{)}
   \KeywordTok{Join} \NormalTok{Filter: (pg_class.relnamespace = pg_namespace.oid)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_class  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{10.81} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{194}\NormalTok{)}
   \NormalTok{->  Materialize  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.09} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_namespace  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Et un exemple avec un parcours séquentiel et un parcours d'index :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{random_page_cost }\KeywordTok{TO} \FloatTok{0.5}\NormalTok{;}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{*}
    \KeywordTok{FROM} \NormalTok{pg_class, pg_namespace}
 \KeywordTok{WHERE} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
                                \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{-------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{33.90} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{307}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_namespace  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{pg_class_relname_nsp_index }\KeywordTok{on} \NormalTok{pg_class}
       \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{4.30} \NormalTok{rows=}\DecValTok{94} \NormalTok{width=}\DecValTok{194}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (relnamespace = pg_namespace.oid)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le paramètre \texttt{enable\_nestloop} permet d'activer ou de désactiver
ce type de nœud.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Merge Join}\label{merge-join}

\begin{itemize}
\tightlist
\item
  Trier l'ensemble interne
\item
  Trier l'ensemble externe
\item
  Tant qu'il reste des lignes dans un des ensembles

  \begin{itemize}
  \tightlist
  \item
    Lire les deux ensembles en parallèle
  \item
    Lorsque la condition de jointure est avérée
  \item
    Émettre la ligne en résultat
  \end{itemize}
\item
  Parcourir les deux ensembles triés (d'où Sort-Merge Join)
\item
  Ne gère que les conditions avec égalité
\item
  Produit un ensemble résultat trié
\item
  Le plus rapide sur de gros ensembles de données
\end{itemize}

Contrairement au \texttt{Nested\ Loop}, le \texttt{Merge\ Join} ne lit
qu'une fois chaque ligne, sauf pour les valeurs dupliquées. C'est
d'ailleurs son principal atout.

L'algorithme est assez simple. Les deux ensembles de données sont tout
d'abord triés, puis ils sont parcourus ensembles. Lorsque la condition
de jointure est vraie, la ligne résultante est envoyée dans l'ensemble
de données en sortie.

L'inconvénient de cette méthode est que les données en entrée doivent
être triées. Trier les données peut prendre du temps, surtout si les
ensembles de données sont volumineux. Cela étant dit, le
\texttt{Merge\ Join} peut s'appuyer sur un index pour accélérer
l'opération de tri (ce sera alors forcément un \texttt{Index\ Scan}).
Une table clusterisée peut aussi accélérer l'opération de tri.
Néanmoins, il faut s'attendre à avoir un coût de démarrage important
pour ce type de nœud, ce qui fait qu'il sera facilement disqualifié si
une clause LIMIT est à exécuter après la jointure.

Le gros avantage du tri sur les données en entrée est que les données
reviennent triées. Cela peut avoir son avantage dans certains cas.

Voici un exemple pour ce nœud :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{*}
    \KeywordTok{FROM} \NormalTok{pg_class, pg_namespace}
 \KeywordTok{WHERE} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
                                \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{-------------------------------------------------------------------------}
 \KeywordTok{Merge} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{23.38}\NormalTok{..}\FloatTok{27.62} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{307}\NormalTok{)}
   \KeywordTok{Merge} \NormalTok{Cond: (pg_namespace.oid = pg_class.relnamespace)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{1.14}\NormalTok{..}\FloatTok{1.15} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: pg_namespace.oid}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_namespace  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{22.24}\NormalTok{..}\FloatTok{22.94} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{194}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: pg_class.relnamespace}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_class  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{10.81} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{194}\NormalTok{)}
\NormalTok{(}\DecValTok{8} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le paramètre \texttt{enable\_mergejoin} permet d'activer ou de
désactiver ce type de nœud.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Hash Join}\label{hash-join}

\begin{itemize}
\tightlist
\item
  Calculer le hachage de chaque ligne de l'ensemble interne
\item
  Tant qu'il reste des lignes dans l'ensemble externe

  \begin{itemize}
  \tightlist
  \item
    Hacher la ligne lue
  \item
    Comparer ce hachage aux lignes hachées de l'ensemble interne
  \item
    Si une correspondance est trouvée
  \item
    Émettre la ligne trouvée en résultat
  \end{itemize}
\item
  Ne gère que les conditions avec égalité
\item
  Idéal pour joindre une grande table à une petite table
\item
  Coût de démarrage important à cause du hachage de la table
\end{itemize}

La vérification de la condition de jointure peut se révéler assez lente
dans beaucoup de cas: elle nécessite un accès à un enregistrement par un
index ou un parcours de la table interne à chaque itération dans un
Nested Loop par exemple. Le \texttt{Hash\ Join} cherche à supprimer ce
problème en créant une table de hachage de la table interne. Cela
sous-entend qu'il faut au préalable calculer le hachage de chaque ligne
de la table interne. Ensuite, il suffit de parcourir la table externe,
hacher chaque ligne l'une après l'autre et retrouver le ou les
enregistrements de la table interne pouvant correspondre à la valeur
hachée de la table externe. On vérifie alors qu'ils répondent bien aux
critères de jointure (il peut y avoir des collisions dans un hachage, ou
des prédicats supplémentaires à vérifier).

Ce type de nœud est très rapide à condition d'avoir suffisamment de
mémoire pour stocker le résultat du hachage de l'ensemble interne. Du
coup, le paramétrage de \texttt{work\_mem} peut avoir un gros impact. De
même, diminuer le nombre de colonnes récupérées permet de diminuer la
mémoire à utiliser pour le hachage et du coup d'améliorer les
performances d'un \texttt{Hash\ Join}. Cependant, si la mémoire est
insuffisante, il est possible de travailler par groupes de lignes
(\texttt{batch}). L'algorithme est alors une version améliorée de
l'algorithme décrit plus haut, permettant justement de travailler en
partitionnant la table interne (on parle de Hybrid Hash Join). Il est à
noter que ce type de nœud est souvent idéal pour joindre une grande
table à une petite table.

Le coût de démarrage peut se révéler important à cause du hachage de la
table interne. Il ne sera probablement pas utilisé par l'optimiseur si
une clause \texttt{LIMIT} est à exécuter après la jointure.

Attention, les données retournées par ce nœud ne sont pas triées.

De plus, ce type de nœud peut être très lent si l'estimation de la
taille des tables est mauvaise.

Voici un exemple de \texttt{Hash\ Join} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{*}
    \KeywordTok{FROM} \NormalTok{pg_class, pg_namespace}
 \KeywordTok{WHERE} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
                                \KeywordTok{QUERY} \KeywordTok{PLAN}                                
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1.14}\NormalTok{..}\FloatTok{15.81} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{307}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (pg_class.relnamespace = pg_namespace.oid)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_class  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{10.81} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{194}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1.06}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_namespace  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{117}\NormalTok{)}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le paramètre \texttt{enable\_hashjoin} permet d'activer ou de désactiver
ce type de nœud.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Suppression d'une
jointure}\label{suppression-dune-jointure}

\begin{itemize}
\item
  Requête d'exemple :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{pg_class.relname, pg_class.reltuples}
\KeywordTok{FROM} \NormalTok{pg_class}
\KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{pg_namespace}
   \KeywordTok{ON} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
\end{Highlighting}
\end{Shaded}
\item
  Un index unique existe sur la colonne oid de pg\_namespace
\item
  Jointure inutile

  \begin{itemize}
  \tightlist
  \item
    sa présence ne change pas le résultat
  \end{itemize}
\item
  PostgreSQL peut supprimer la jointure à partir de la 9.0
\end{itemize}

Sur la requête ci-dessus, la jointure est inutile. En effet, il existe
un index unique sur la colonne \texttt{oid} de la table
\texttt{pg\_namespace}. De plus, aucune colonne de la table
\texttt{pg\_namespace} ne va apparaître dans le résultat. Autrement dit,
que la jointure soit présente ou non, cela ne va pas changer le
résultat. Dans ce cas, il est préférable de supprimer la jointure. Si le
développeur ne le fait pas, PostgreSQL le fera (pour les versions 9.0 et
ultérieures de PostgreSQL). Cet exemple le montre.

Voici la requête exécutée en 8.4 :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{pg_class.relname, pg_class.reltuples}
    \KeywordTok{FROM} \NormalTok{pg_class}
 \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{pg_namespace }\KeywordTok{ON} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Hash} \KeywordTok{Left} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1.14}\NormalTok{..}\FloatTok{12.93} \NormalTok{rows=}\DecValTok{244} \NormalTok{width=}\DecValTok{68}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (pg_class.relnamespace = pg_namespace.oid)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_class  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{8.44} \NormalTok{rows=}\DecValTok{244} \NormalTok{width=}\DecValTok{72}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1.06}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_namespace  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.06} \NormalTok{rows=}\DecValTok{6} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Et la même requête exécutée en 9.0 :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{pg_class.relname, pg_class.reltuples}
    \KeywordTok{FROM} \NormalTok{pg_class}
 \KeywordTok{LEFT} \KeywordTok{JOIN} \NormalTok{pg_namespace }\KeywordTok{ON} \NormalTok{pg_class.relnamespace=pg_namespace.oid;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_class  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{10.81} \NormalTok{rows=}\DecValTok{281} \NormalTok{width=}\DecValTok{72}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

On constate que la jointure est ignorée.

Ce genre de requête peut fréquemment survenir surtout avec des
générateurs de requêtes comme les ORM. L'utilisation de vues imbriquées
peut aussi être la source de ce type de problème.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Ordre de jointure}\label{ordre-de-jointure}

\begin{itemize}
\tightlist
\item
  Trouver le bon ordre de jointure est un point clé dans la recherche de
  performances
\item
  Nombre de possibilités en augmentation factorielle avec le nombre de
  tables
\item
  Si petit nombre, recherche exhaustive
\item
  Sinon, utilisation d'heuristiques et de GEQO

  \begin{itemize}
  \tightlist
  \item
    Limite le temps de planification et l'utilisation de mémoire
  \item
    GEQO remplacé par Simulated Annealing ? (recuit simulé en VF)
  \end{itemize}
\end{itemize}

Sur une requête comme \texttt{SELECT\ *\ FROM\ a,\ b,\ c...}, les tables
a, b et c ne sont pas forcément jointes dans cet ordre. PostgreSQL teste
différents ordres pour obtenir les meilleures performances.

Prenons comme exemple la requête suivante :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{a }\KeywordTok{JOIN} \NormalTok{b }\KeywordTok{ON} \NormalTok{a.id=b.id }\KeywordTok{JOIN} \NormalTok{c }\KeywordTok{ON} \NormalTok{b.id=c.id;}
\end{Highlighting}
\end{Shaded}

Avec une table a contenant un million de lignes, une table b n'en
contenant que 1000 et une table c en contenant seulement 10, et une
configuration par défaut, son plan d'exécution est celui-ci :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{a }\KeywordTok{JOIN} \NormalTok{b }\KeywordTok{ON} \NormalTok{a.id=b.id }\KeywordTok{JOIN} \NormalTok{c }\KeywordTok{ON} \NormalTok{b.id=c.id;}
                                 \KeywordTok{QUERY} \KeywordTok{PLAN}                                  
\CommentTok{---------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{1.23}\NormalTok{..}\FloatTok{18341.35} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{12}\NormalTok{)}
   \KeywordTok{Join} \NormalTok{Filter: (a.id = b.id)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{b  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{15.00} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{->  Materialize  (cost=}\FloatTok{1.23}\NormalTok{..}\FloatTok{18176.37} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1.23}\NormalTok{..}\FloatTok{18176.32} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
               \KeywordTok{Hash} \NormalTok{Cond: (a.id = c.id)}
               \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{a  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{14425.00} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
               \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1.10}\NormalTok{..}\FloatTok{1.10} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
                     \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{c  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.10} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(}\DecValTok{9} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le planificateur préfère joindre tout d'abord la table a à la table c,
puis son résultat à la table b. Cela lui permet d'avoir un ensemble de
données en sortie plus petit (donc moins de consommation mémoire) avant
de faire la jointure avec la table b.

Cependant, si PostgreSQL se trouve face à une jointure de 25 tables, le
temps de calculer tous les plans possibles en prenant en compte l'ordre
des jointures sera très important. En fait, plus le nombre de tables
jointes est important, et plus le temps de planification va augmenter.
Il est nécessaire de prévoir une échappatoire à ce système. En fait, il
en existe plusieurs. Les paramètres \texttt{from\_collapse\_limit} et
\texttt{join\_collapse\_limit} permettent de spécifier une limite en
nombre de tables. Si cette limite est dépassée, PostgreSQL ne cherchera
plus à traiter tous les cas possibles de réordonnancement des jointures.
Par défaut, ces deux paramètres valent 8, ce qui fait que, dans notre
exemple, le planificateur a bien cherché à changer l'ordre des
jointures. En configurant ces paramètres à une valeur plus basse, le
plan va changer :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{join_collapse_limit }\KeywordTok{TO} \DecValTok{2}\NormalTok{;}
\KeywordTok{SET}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{a }\KeywordTok{JOIN} \NormalTok{b }\KeywordTok{ON} \NormalTok{a.id=b.id }\KeywordTok{JOIN} \NormalTok{c }\KeywordTok{ON} \NormalTok{b.id=c.id;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{---------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{27.50}\NormalTok{..}\FloatTok{18363.62} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{12}\NormalTok{)}
   \KeywordTok{Join} \NormalTok{Filter: (a.id = c.id)}
   \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{27.50}\NormalTok{..}\FloatTok{18212.50} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{8}\NormalTok{)}
         \KeywordTok{Hash} \NormalTok{Cond: (a.id = b.id)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{a  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{14425.00} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{15.00}\NormalTok{..}\FloatTok{15.00} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
               \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{b  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{15.00} \NormalTok{rows=}\DecValTok{1000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{->  Materialize  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.15} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{c  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.10} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(}\DecValTok{9} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Avec un \texttt{join\_collapse\_limit} à \texttt{2}, PostgreSQL décide
de ne pas tester l'ordre des jointures. Le plan fourni fonctionne tout
aussi bien, mais son estimation montre qu'elle semble être moins
performante (coût de 18363 au lieu de 18341 précédemment).

Une autre technique mise en place pour éviter de tester tous les plans
possibles est GEQO. Cette technique est très complexe, et dispose d'un
grand nombre de paramètres que très peu savent réellement configurer. Il
a par ailleurs le défaut d'être non-déterministe: deux exécutions
consécutives peuvent retourner deux plans différents.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Opérations
ensemblistes}\label{opuxe9rations-ensemblistes}

\begin{itemize}
\tightlist
\item
  Prend un ou plusieurs ensembles de données en entrée
\item
  Et renvoie un ensemble de données
\item
  Concernent principalement les requêtes sur des tables partitionnées ou
  héritées
\item
  Exemples typiques

  \begin{itemize}
  \tightlist
  \item
    Append
  \item
    Intersect
  \item
    Except
  \end{itemize}
\end{itemize}

Ce type de nœuds prend un ou plusieurs ensembles de données en entrée et
renvoie un seul ensemble de données. Cela concerne surtout les requêtes
visant des tables partitionnées ou héritées.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Append}\label{append}

\begin{itemize}
\tightlist
\item
  Prend plusieurs ensembles de données
\item
  Fournit un ensemble de données en sortie

  \begin{itemize}
  \tightlist
  \item
    Non trié
  \end{itemize}
\item
  Utilisé par les requêtes

  \begin{itemize}
  \tightlist
  \item
    Sur des tables héritées (partitionnement inclus)
  \item
    Ayant des UNION ALL et des UNION
  \item
    Attention que le UNION sans ALL élimine les duplicats, ce qui
    nécessite une opération supplémentaire de tri
  \end{itemize}
\end{itemize}

Un nœud \texttt{Append} a pour but de concaténer plusieurs ensembles de
données pour n'en faire qu'un, non trié. Ce type de nœud est utilisé
dans les requêtes concaténant explicitement des tables (clause
\texttt{UNION}) ou implicitement (requêtes sur une table mère).

Supposons que la table t1 est une table mère. Plusieurs tables héritent
de cette table : t1\_0, t1\_1, t1\_2 et t1\_3. Voici ce que donne un
\texttt{SELECT} sur la table mère :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{89.20} \NormalTok{rows=}\DecValTok{4921} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
   \NormalTok{->  Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{89.20} \NormalTok{rows=}\DecValTok{4921} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.00} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_0 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{22.30} \NormalTok{rows=}\DecValTok{1230} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_1 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{22.30} \NormalTok{rows=}\DecValTok{1230} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_2 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{22.30} \NormalTok{rows=}\DecValTok{1230} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_3 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{22.30} \NormalTok{rows=}\DecValTok{1230} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
\NormalTok{(}\DecValTok{7} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Nouvel exemple avec un filtre sur la clé de partitionnement :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# SHOW constraint_exclusion ;}
 \NormalTok{constraint_exclusion }
\CommentTok{----------------------}
 \KeywordTok{off}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1>}\DecValTok{250}\NormalTok{;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{-----------------------------------------------------------------------}
 \NormalTok{Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{101.50} \NormalTok{rows=}\DecValTok{1641} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
   \NormalTok{->  Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{101.50} \NormalTok{rows=}\DecValTok{1641} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.00} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_0 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{25.38} \NormalTok{rows=}\DecValTok{410} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_1 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{25.38} \NormalTok{rows=}\DecValTok{410} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_2 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{25.38} \NormalTok{rows=}\DecValTok{410} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_3 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{25.38} \NormalTok{rows=}\DecValTok{410} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
\NormalTok{(}\DecValTok{12} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le paramètre \texttt{constraint\_exclusion} permet d'éviter de parcourir
les tables filles qui ne peuvent pas accueillir les données qui nous
intéressent. Pour que le planificateur comprenne qu'il peut ignorer
certaines tables filles, ces dernières doivent avoir des contraintes
\texttt{CHECK} qui assurent le planificateur qu'elles ne peuvent pas
contenir les données en question :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# SHOW constraint_exclusion ;}
 \NormalTok{constraint_exclusion }
\CommentTok{----------------------}
 \KeywordTok{on}
\NormalTok{(}\DecValTok{1} \KeywordTok{row}\NormalTok{)}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{t1 }\KeywordTok{WHERE} \NormalTok{c1>}\DecValTok{250}\NormalTok{;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{-----------------------------------------------------------------------}
 \NormalTok{Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{50.75} \NormalTok{rows=}\DecValTok{821} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
   \NormalTok{->  Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{50.75} \NormalTok{rows=}\DecValTok{821} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.00} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_2 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{25.38} \NormalTok{rows=}\DecValTok{410} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t1_3 t1  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{25.38} \NormalTok{rows=}\DecValTok{410} \NormalTok{width=}\DecValTok{36}\NormalTok{)}
               \NormalTok{Filter: (c1 > }\DecValTok{250}\NormalTok{)}
\NormalTok{(}\DecValTok{8} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Une requête utilisant \texttt{UNION\ ALL} passera aussi par un nœud
\texttt{Append} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{UNION} \KeywordTok{ALL} \KeywordTok{SELECT} \DecValTok{2}\NormalTok{;}
                      \KeywordTok{QUERY} \KeywordTok{PLAN}                      
\CommentTok{------------------------------------------------------}
 \NormalTok{Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.04} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{->  Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.04} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{->  Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.01} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \NormalTok{->  Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.01} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\texttt{UNION\ ALL} récupère toutes les lignes des deux ensembles de
données, même en cas de duplicat. Pour n'avoir que les lignes
distinctes, il est possible d'utiliser \texttt{UNION} sans la clause
\texttt{ALL} mais cela nécessite un tri des données pour faire la
distinction (un peu comme un \texttt{Merge\ Join}).

Attention que le UNION sans ALL élimine les duplicats, ce qui nécessite
une opération supplémentaire de tri :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{UNION} \KeywordTok{SELECT} \DecValTok{2}\NormalTok{;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{------------------------------------------------------------}
 \KeywordTok{Unique}  \NormalTok{(cost=}\FloatTok{0.05}\NormalTok{..}\FloatTok{0.06} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{0.05}\NormalTok{..}\FloatTok{0.06} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: (}\DecValTok{1}\NormalTok{)}
         \NormalTok{->  Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.04} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
               \NormalTok{->  Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.01} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
               \NormalTok{->  Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.01} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
\NormalTok{(}\DecValTok{6} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{MergeAppend}\label{mergeappend}

\begin{itemize}
\tightlist
\item
  Append avec optimisation
\item
  Fournit un ensemble de données en sortie trié
\item
  Utilisé par les requêtes

  \begin{itemize}
  \tightlist
  \item
    UNION ALL ou partitionnement/héritage
  \item
    Utilisant des parcours triés
  \item
    Idéal avec Limit
  \end{itemize}
\end{itemize}

Le nœud MergeAppend est une optimisation spécifiquement conçue pour le
partitionnement, introduite en 9.1.

Cela permet de répondre plus efficacement aux requêtes effectuant un tri
sur un \texttt{UNION\ ALL}, soit explicite, soit induit par un
héritage/partitionnement.

Considérons la requête suivante:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
\KeywordTok{FROM} \NormalTok{(}
 \KeywordTok{SELECT} \NormalTok{t1.a, t1.b }\KeywordTok{FROM} \NormalTok{t1 }
 \KeywordTok{UNION} \KeywordTok{ALL} 
 \KeywordTok{SELECT} \NormalTok{t2.a, t2.c }\KeywordTok{FROM} \NormalTok{t2}
\NormalTok{) t }
\KeywordTok{ORDER} \KeywordTok{BY} \NormalTok{a;}
\end{Highlighting}
\end{Shaded}

Il est facile de répondre à cette requête si l'on dispose d'un index sur
les colonnes \texttt{a} des tables \texttt{t1} et \texttt{t2}: il suffit
de parcourir chaque index en parallèle (assurant le tri sur a), en
renvoyant la valeur la plus petite.

Pour comparaison, avant la 9.1 et l'introduction du nœud
\texttt{MergeAppend}, le plan obtenu était celui-ci:

\begin{verbatim}
                                                         QUERY PLAN                                                          
-----------------------------------------------------------------------------------------------------------------------------
 Sort  (cost=24129.64..24629.64 rows=200000 width=22) (actual time=122.705..133.403 rows=200000 loops=1)
   Sort Key: t1.a
   Sort Method:  quicksort  Memory: 21770kB
   ->  Result  (cost=0.00..6520.00 rows=200000 width=22) (actual time=0.013..76.527 rows=200000 loops=1)
         ->  Append  (cost=0.00..6520.00 rows=200000 width=22) (actual time=0.012..54.425 rows=200000 loops=1)
               ->  Seq Scan on t1  (cost=0.00..2110.00 rows=100000 width=23) (actual time=0.011..19.379 rows=100000 loops=1)
               ->  Seq Scan on t2  (cost=0.00..4410.00 rows=100000 width=22) (actual time=1.531..22.050 rows=100000 loops=1)
 Total runtime: 141.708 ms
\end{verbatim}

Depuis la 9.1, l'optimiseur est capable de détecter qu'il existe un
\textbf{parcours paramétré}, renvoyant les données triées sur la clé
demandée (a), et utilise la stratégie \texttt{MergeAppend}:

\begin{verbatim}
                                                           QUERY PLAN                                                            
---------------------------------------------------------------------------------------------------------------------------------
 Merge Append  (cost=0.72..14866.72 rows=300000 width=23) (actual time=0.040..76.783 rows=300000 loops=1)
   Sort Key: t1.a
   ->  Index Scan using t1_pkey on t1  (cost=0.29..3642.29 rows=100000 width=22) (actual time=0.014..18.876 rows=100000 loops=1)
   ->  Index Scan using t2_pkey on t2  (cost=0.42..7474.42 rows=200000 width=23) (actual time=0.025..35.920 rows=200000 loops=1)
 Total runtime: 85.019 ms
\end{verbatim}

Cette optimisation est d'autant plus intéressante si l'on utilise une
clause \texttt{LIMIT}.

Sans \texttt{MergeAppend}:

\begin{verbatim}
                                                            QUERY PLAN                                                             
-----------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=9841.93..9841.94 rows=5 width=22) (actual time=119.946..119.946 rows=5 loops=1)
   ->  Sort  (cost=9841.93..10341.93 rows=200000 width=22) (actual time=119.945..119.945 rows=5 loops=1)
         Sort Key: t1.a
         Sort Method:  top-N heapsort  Memory: 25kB
         ->  Result  (cost=0.00..6520.00 rows=200000 width=22) (actual time=0.008..75.482 rows=200000 loops=1)
               ->  Append  (cost=0.00..6520.00 rows=200000 width=22) (actual time=0.008..53.644 rows=200000 loops=1)
                     ->  Seq Scan on t1  (cost=0.00..2110.00 rows=100000 width=23) (actual time=0.006..18.819 rows=100000 loops=1)
                     ->  Seq Scan on t2  (cost=0.00..4410.00 rows=100000 width=22) (actual time=1.550..22.119 rows=100000 loops=1)
 Total runtime: 119.976 ms
(9 lignes)
\end{verbatim}

Avec \texttt{MergeAppend}:

\begin{verbatim}
 Limit  (cost=0.72..0.97 rows=5 width=23) (actual time=0.055..0.060 rows=5 loops=1)
   ->  Merge Append  (cost=0.72..14866.72 rows=300000 width=23) (actual time=0.053..0.058 rows=5 loops=1)
         Sort Key: t1.a
         ->  Index Scan using t1_pkey on t1  (cost=0.29..3642.29 rows=100000 width=22) (actual time=0.033..0.036 rows=3 loops=1)
         ->  Index Scan using t2_pkey on t2  (cost=0.42..7474.42 rows=200000 width=23) (actual time=0.019..0.021 rows=3 loops=1)
 Total runtime: 0.117 ms
\end{verbatim}

On voit ici que chacun des parcours d'index renvoie 3 lignes, ce qui est
suffisant pour renvoyer les 5 lignes ayant la plus faible valeur pour a.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Autres}\label{autres}

\begin{itemize}
\tightlist
\item
  Nœud HashSetOp Except

  \begin{itemize}
  \tightlist
  \item
    instructions EXCEPT et EXCEPT ALL
  \end{itemize}
\item
  Nœud HashSetOp Intersect

  \begin{itemize}
  \tightlist
  \item
    instructions INTERSECT et INTERSECT ALL
  \end{itemize}
\end{itemize}

La clause \texttt{UNION} permet de concaténer deux ensembles de données.
Les clauses \texttt{EXCEPT} et \texttt{INTERSECT} permettent de
supprimer une partie de deux ensembles de données.

Voici un exemple basé sur \texttt{EXCEPT} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \KeywordTok{oid} \KeywordTok{FROM} \NormalTok{pg_proc}
    \KeywordTok{EXCEPT}  \KeywordTok{SELECT} \KeywordTok{oid} \KeywordTok{FROM} \NormalTok{pg_proc;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{--------------------------------------------------------------------}
 \NormalTok{HashSetOp }\KeywordTok{Except}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{219.39} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{->  Append  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{207.68} \NormalTok{rows=}\DecValTok{4684} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{->  Subquery }\KeywordTok{Scan} \KeywordTok{on} \OtherTok{"*SELECT* 1"}
             \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{103.84} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
               \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc}
                   \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{->  Subquery }\KeywordTok{Scan} \KeywordTok{on} \OtherTok{"*SELECT* 2"}
             \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{103.84} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
               \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc}
                   \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(}\DecValTok{6} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Et un exemple basé sur \texttt{INTERSECT} :

\begin{verbatim}
b1=# EXPLAIN   SELECT oid FROM pg_proc
    INTERSECT SELECT oid FROM pg_proc;
                               QUERY PLAN
--------------------------------------------------------------------
 HashSetOp Intersect  (cost=0.00..219.39 rows=2342 width=4)
   ->  Append  (cost=0.00..207.68 rows=4684 width=4)
         ->  Subquery Scan on "*SELECT* 1"
             (cost=0.00..103.84 rows=2342 width=4)
               ->  Seq Scan on pg_proc
                   (cost=0.00..80.42 rows=2342 width=4)
         ->  Subquery Scan on "*SELECT* 2"
             (cost=0.00..103.84 rows=2342 width=4)
               ->  Seq Scan on pg_proc
                   (cost=0.00..80.42 rows=2342 width=4)
(6 rows)
\end{verbatim}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Divers}\label{divers}

\begin{itemize}
\tightlist
\item
  Prend un ensemble de données en entrée
\item
  Et renvoie un ensemble de données
\item
  Exemples typiques

  \begin{itemize}
  \tightlist
  \item
    Sort
  \item
    Aggregate
  \item
    Unique
  \item
    Limit
  \item
    InitPlan, SubPlan
  \end{itemize}
\end{itemize}

Tous les autres nœuds que nous allons voir prennent un seul ensemble de
données en entrée et en renvoient un aussi. Ce sont des nœuds
d'opérations simples comme le tri, l'agrégat, l'unicité, la limite, etc.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Sort}\label{sort}

\begin{itemize}
\tightlist
\item
  Utilisé pour le ORDER BY

  \begin{itemize}
  \tightlist
  \item
    Mais aussi DISTINCT, GROUP BY, UNION
  \item
    Les jointures de type Merge Join
  \end{itemize}
\item
  Gros délai de démarrage
\item
  Trois types de tri

  \begin{itemize}
  \tightlist
  \item
    En mémoire, tri quicksort
  \item
    En mémoire, tri top-N heapsort (si clause LIMIT)
  \item
    Sur disque
  \end{itemize}
\end{itemize}

PostgreSQL peut faire un tri de trois façons.

Les deux premières sont manuelles. Il lit toutes les données nécessaires
et les trie en mémoire. La quantité de mémoire utilisable dépend du
paramètre \texttt{work\_mem}. S'il n'a pas assez de mémoire, il
utilisera un stockage sur disque. La rapidité du tri dépend
principalement de la mémoire utilisable mais aussi de la puissance des
processeurs. Le tri effectué est un tri quicksort sauf si une clause
\texttt{LIMIT} existe, auquel cas, le tri sera un top-N heapsort.

La troisième méthode est de passer par un index Btree. En effet, ce type
d'index stocke les données de façon triée. Dans ce cas, PostgreSQL n'a
pas besoin de mémoire.

Le choix entre ces trois méthodes dépend principalement de
\texttt{work\_mem}. En fait, le pseudo-code ci-dessous explique ce choix
:

Si les données de tri tiennent dans work\_mem Si une clause LIMIT est
présente Tri top-N heapsort Sinon Tri quicksort Sinon Tri sur disque

Voici quelques exemples :

\begin{itemize}
\tightlist
\item
  un tri externe
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{FROM} \NormalTok{t2 }\KeywordTok{ORDER} \KeywordTok{BY} \KeywordTok{id}\NormalTok{;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{150385.45}\NormalTok{..}\FloatTok{153040.45} \NormalTok{rows=}\DecValTok{1062000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
       \NormalTok{(actual time=}\FloatTok{807.603}\NormalTok{..}\FloatTok{941.357} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Sort} \NormalTok{Key: }\KeywordTok{id}
   \KeywordTok{Sort} \NormalTok{Method: external }\KeywordTok{sort}  \NormalTok{Disk: 17608kB}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t2  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{15045.00} \NormalTok{rows=}\DecValTok{1062000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
                \NormalTok{(actual time=}\FloatTok{0.050}\NormalTok{..}\FloatTok{143.918} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{1021.725} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  un tri en mémoire

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{SET} \NormalTok{work_mem }\KeywordTok{TO} \StringTok{'100MB'}\NormalTok{;}
\KeywordTok{SET}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{FROM} \NormalTok{t2 }\KeywordTok{ORDER} \KeywordTok{BY} \KeywordTok{id}\NormalTok{;}
                          \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{121342.45}\NormalTok{..}\FloatTok{123997.45} \NormalTok{rows=}\DecValTok{1062000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{(actual time=}\FloatTok{308.129}\NormalTok{..}\FloatTok{354.035} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Sort} \NormalTok{Key: }\KeywordTok{id}
   \KeywordTok{Sort} \NormalTok{Method: quicksort  Memory: 71452kB}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t2  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{15045.00} \NormalTok{rows=}\DecValTok{1062000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
            \NormalTok{(actual time=}\FloatTok{0.088}\NormalTok{..}\FloatTok{142.787} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{425.160} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}
\item
  un tri en mémoire

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{FROM} \NormalTok{t2 }\KeywordTok{ORDER} \KeywordTok{BY} \KeywordTok{id} \KeywordTok{LIMIT} \DecValTok{10000}\NormalTok{;}
                             \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Limit}  \NormalTok{(cost=}\FloatTok{85863.56}\NormalTok{..}\FloatTok{85888.56} \NormalTok{rows=}\DecValTok{10000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
    \NormalTok{(actual time=}\FloatTok{271.674}\NormalTok{..}\FloatTok{272.980} \NormalTok{rows=}\DecValTok{10000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{85863.56}\NormalTok{..}\FloatTok{88363.56} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \NormalTok{(actual time=}\FloatTok{271.671}\NormalTok{..}\FloatTok{272.240} \NormalTok{rows=}\DecValTok{10000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
     \KeywordTok{Sort} \NormalTok{Key: }\KeywordTok{id}
     \KeywordTok{Sort} \NormalTok{Method: top-N heapsort  Memory: 1237kB}
     \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{t2  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{14425.00} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
                   \NormalTok{(actual time=}\FloatTok{0.031}\NormalTok{..}\FloatTok{146.306} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{273.665} \NormalTok{ms}
\NormalTok{(}\DecValTok{6} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}
\item
  un tri par un index

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \KeywordTok{ON} \NormalTok{t2(}\KeywordTok{id}\NormalTok{);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{ANALYZE} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{FROM} \NormalTok{t2 }\KeywordTok{ORDER} \KeywordTok{BY} \KeywordTok{id}\NormalTok{;}
                          \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{t2_id_idx }\KeywordTok{on} \NormalTok{t2}
\NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{30408.36} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(actual time=}\FloatTok{0.145}\NormalTok{..}\FloatTok{308.651} \NormalTok{rows=}\DecValTok{1000000} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{355.175} \NormalTok{ms}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}
\end{itemize}

Le paramètre \texttt{enable\_sort} permet de défavoriser l'utilisation
d'un tri. Dans ce cas, le planificateur tendra à préférer l'utilisation
d'un index, qui retourne des données déjà triées.

Augmenter la valeur du paramètre \texttt{work\_mem} aura l'effet inverse
: favoriser un tri plutôt que l'utilisation d'un index.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Aggregate}\label{aggregate}

\begin{itemize}
\tightlist
\item
  Agrégat complet
\item
  Pour un seul résultat
\end{itemize}

Il existe plusieurs façons de réaliser un agrégat :

\begin{itemize}
\tightlist
\item
  l'agrégat standard;
\item
  l'agrégat par tri des données;
\item
  et l'agrégat par hachage;
\end{itemize}

ces deux derniers sont utilisés quand la clause \texttt{SELECT} contient
des colonnes en plus de la fonction d'agrégat.

Par exemple, pour un seul résultat count(*), nous aurons ce plan
d'exécution :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \NormalTok{pg_proc;}
                           \KeywordTok{QUERY} \KeywordTok{PLAN}                            
\CommentTok{-----------------------------------------------------------------}
 \NormalTok{Aggregate  (cost=}\FloatTok{86.28}\NormalTok{..}\FloatTok{86.29} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Seul le parcours séquentiel est possible ici car count() doit compter
toutes les lignes.

Autre exemple avec une fonction d'agrégat max.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \FunctionTok{max}\NormalTok{(proname) }\KeywordTok{FROM} \NormalTok{pg_proc;}
                            \KeywordTok{QUERY} \KeywordTok{PLAN}                            
\CommentTok{------------------------------------------------------------------}
 \NormalTok{Aggregate  (cost=}\FloatTok{92.13}\NormalTok{..}\FloatTok{92.14} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Il existe une autre façon de récupérer la valeur la plus petite ou la
plus grande : passer par l'index. Ce sera très rapide car l'index est
trié.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \FunctionTok{max}\NormalTok{(}\KeywordTok{oid}\NormalTok{) }\KeywordTok{FROM} \NormalTok{pg_proc;}
                            \KeywordTok{QUERY} \KeywordTok{PLAN}
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Result  (cost=}\FloatTok{0.13}\NormalTok{..}\FloatTok{0.14} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
   \NormalTok{InitPlan }\DecValTok{1} \NormalTok{(returns $}\DecValTok{0}\NormalTok{)}
     \NormalTok{->  }\KeywordTok{Limit}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.13} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
           \NormalTok{->  }\KeywordTok{Index} \KeywordTok{Scan} \NormalTok{Backward }\KeywordTok{using} \NormalTok{pg_proc_oid_index }\KeywordTok{on} \NormalTok{pg_proc}
               \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{305.03} \NormalTok{rows=}\DecValTok{2330} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
                 \KeywordTok{Index} \NormalTok{Cond: (}\KeywordTok{oid} \KeywordTok{IS} \KeywordTok{NOT} \KeywordTok{NULL}\NormalTok{)}
\NormalTok{(}\DecValTok{5} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Il est à noter que ce n'est pas valable pour les valeurs de type booléen
jusqu'en 9.2.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Hash Aggregate}\label{hash-aggregate}

\begin{itemize}
\tightlist
\item
  Hachage de chaque n-uplet de regroupement (group by)
\item
  accès direct à chaque n-uplet pour appliquer fonction d'agrégat
\item
  Intéressant si l'ensemble des valeurs distinctes tient en mémoire,
  dangereux sinon
\end{itemize}

Voici un exemple de ce type de nœud :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{proname, }\FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \NormalTok{pg_proc }\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{proname;}
                            \KeywordTok{QUERY} \KeywordTok{PLAN}                            
\CommentTok{------------------------------------------------------------------}
 \NormalTok{HashAggregate  (cost=}\FloatTok{92.13}\NormalTok{..}\FloatTok{111.24} \NormalTok{rows=}\DecValTok{1911} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Le hachage occupe de la place en mémoire, le plan n'est choisi que si
PostgreSQL estime que si la table de hachage générée tient dans
work\_mem. \textbf{C'est le seul type de nœud qui peut dépasser
work\_mem:} la seule façon d'utiliser le HashAggregate est en mémoire,
il est donc agrandi s'il est trop petit.

Quant au paramètre \texttt{enable\_hashagg}, il permet d'activer et de
désactiver l'utilisation de ce type de nœud.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Group Aggregate}\label{group-aggregate}

\begin{itemize}
\tightlist
\item
  Reçoit des données déjà triées
\item
  Parcours des données

  \begin{itemize}
  \tightlist
  \item
    Regroupement du groupe précédent arrivé à une donnée différente
  \end{itemize}
\end{itemize}

Voici un exemple de ce type de nœud :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \NormalTok{proname, }\FunctionTok{count}\NormalTok{(*) }\KeywordTok{FROM} \NormalTok{pg_proc }\KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{proname;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{GroupAggregate  (cost=}\FloatTok{211.50}\NormalTok{..}\FloatTok{248.17} \NormalTok{rows=}\DecValTok{1911} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{211.50}\NormalTok{..}\FloatTok{217.35} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: proname}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{64}\NormalTok{)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Un parcours d'index est possible pour remplacer le parcours séquentiel
et le tri.

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Unique}\label{unique}

\begin{itemize}
\tightlist
\item
  Reçoit des données déjà triées
\item
  Parcours des données

  \begin{itemize}
  \tightlist
  \item
    Renvoi de la donnée précédente une fois arrivé à une donnée
    différente
  \end{itemize}
\item
  Résultat trié
\end{itemize}

Le nœud \texttt{Unique} permet de ne conserver que les lignes
différentes. L'opération se réalise en triant les données, puis en
parcourant le résultat trié. Là-aussi, un index aide à accélérer ce type
de nœud.

En voici un exemple :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \KeywordTok{DISTINCT} \NormalTok{pronamespace }\KeywordTok{FROM} \NormalTok{pg_proc;}
                              \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{-----------------------------------------------------------------------}
 \KeywordTok{Unique}  \NormalTok{(cost=}\FloatTok{211.57}\NormalTok{..}\FloatTok{223.28} \NormalTok{rows=}\DecValTok{200} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Sort}  \NormalTok{(cost=}\FloatTok{211.57}\NormalTok{..}\FloatTok{217.43} \NormalTok{rows=}\DecValTok{2343} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
         \KeywordTok{Sort} \NormalTok{Key: pronamespace}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{sample4  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.43} \NormalTok{rows=}\DecValTok{2343} \NormalTok{width=}\DecValTok{4}\NormalTok{)}
\NormalTok{(}\DecValTok{4} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{center}\rule{0.5\linewidth}{\linethickness}\end{center}

\subsubsection{Limit}\label{limit}

\begin{itemize}
\tightlist
\item
  Permet de limiter le nombre de résultats renvoyés
\item
  Utilisé par

  \begin{itemize}
  \tightlist
  \item
    clauses LIMIT et OFFSET d'une requête SELECT
  \item
    fonctions min() et max() quand il n'y a pas de clause WHERE et qu'il
    y a un index
  \end{itemize}
\item
  Le nœud précédent sera de préférence un nœud dont le coût de démarrage
  est peu élevé (SeqScan, NestedLoop)
\end{itemize}

Voici un exemple de l'utilisation d'un nœud \texttt{Limit} :

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{b1=# }\KeywordTok{EXPLAIN} \KeywordTok{SELECT} \DecValTok{1} \KeywordTok{FROM} \NormalTok{pg_proc }\KeywordTok{LIMIT} \DecValTok{10}\NormalTok{;}
                           \KeywordTok{QUERY} \KeywordTok{PLAN}                            
\CommentTok{-----------------------------------------------------------------}
 \KeywordTok{Limit}  \NormalTok{(cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.34} \NormalTok{rows=}\DecValTok{10} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{pg_proc  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{80.42} \NormalTok{rows=}\DecValTok{2342} \NormalTok{width=}\DecValTok{0}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\subsection{Énoncés}\label{uxe9noncuxe9s-4}

\subsubsection{Préambule}\label{pruxe9ambule-3}

\begin{itemize}
\item
  Utilisez \texttt{\textbackslash{}timing} dans \texttt{psql} pour
  afficher les temps d'exécution de la recherche.
\item
  Afin d'éviter tout mécanisme de cache, autant du plan que des pages de
  données, nous utilisons parfois une sous-requête avec un résultat non
  déterministe (\texttt{random}).
\item
  N'oubliez pas de lancer plusieurs fois les requêtes (que vous pouvez
  rappeler avec \texttt{\textbackslash{}g}. Vous pouvez aussi utiliser
  la touche \textbf{flêche haut} du clavier si votre installation
  utilise \texttt{readline} ou \texttt{libedit})
\item
  Vous devrez disposer de la base cave pour ce TP
\item
  Les valeurs (taille, temps d'exécution) varieront à cause de plusieurs
  critères:

  \begin{itemize}
  \tightlist
  \item
    Les machines sont différentes
  \item
    Le jeu de données peut avoir partiellement changé depuis la
    rédaction du TP.
  \end{itemize}
\end{itemize}

\subsubsection{Affichage de plans de requêtes
simples}\label{affichage-de-plans-de-requuxeates-simples}

\textbf{Recherche de motif texte}

\begin{itemize}
\tightlist
\item
  Affichez le plan de cette requête.
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Que constatez-vous?

\begin{itemize}
\item
  Affichez maintenant le nombre de blocs accédés par cette requête.
\item
  Cette requête ne passe pas par un index. Essayez de lui forcer la
  main.
\item
  L'index n'est toujours pas utilisé. L'index «par défaut» n'est pas
  capable de répondre à des questions sur motif.
\item
  Créez un index capable de réaliser ces opérations. Testez à nouveau le
  plan.
\item
  Réactivez \texttt{enable\_seqscan}. Testez à nouveau le plan.
\item
  Quelle est la conclusion ?
\end{itemize}

** Recherche de motif texte avancé **

La base cave ne contient pas de données textuelles appropriées.

\begin{itemize}
\tightlist
\item
  Lancez textes.sql ou textes\_10pct.sql (préférable sur une machine peu
  puissante, ou une instance PostgreSQL non paramétrée)
\end{itemize}

\begin{verbatim}
psql < textes_10pct.sql
\end{verbatim}

Ce script crée une table textes, contenant le texte intégral d'un grand
nombre de livres en français du projet Gutenberg, soit 10 millions de
lignes, pour 85 millions de mots.

Nous allons rechercher toutes les références à «Fantine» dans les
textes. On devrait trouver beaucoup d'enregistrements provenant des
«Misérables».

\begin{itemize}
\tightlist
\item
  La méthode SQL standard pour écrire cela est:
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{textes }\KeywordTok{WHERE} \NormalTok{contenu ILIKE }\StringTok{'%fantine%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Exécutez cette requête, et regardez son plan d'exécution.

Nous lisons toute la table à chaque fois. C'est normal, et classique
avec une base de données: non seulement la recherche est insensible à la
casse, mais elle commence par \texttt{\%}, ce qui est incompatible avec
une indexation btree classique.

Nous allons donc utiliser l'extension pg\_trgm:

\begin{itemize}
\tightlist
\item
  Créez un index trigramme:
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{CREATE} \NormalTok{EXTENSION pg_trgm;}
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_trgm }\KeywordTok{ON} \NormalTok{textes }\KeywordTok{USING} \NormalTok{gist (contenu gist_trgm_ops);}
\CommentTok{-- ou CREATE INDEX idx_trgm ON textes USING gin (contenu gin_trgm_ops);}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Quelle est la taille de l'index ?
\item
  Ré-exécutez la requête.
\item
  Que constatez-vous ?
\item
  Suivant que vous ayez opté pour GiST ou Gin, refaites la manipulation
  avec l'autre méthode d'indexation
\item
  Essayez de créer un index Full Text à la place de l'index trigramme.
  Quels sont les résultats ?
\end{itemize}

\subsubsection{Optimisation d'une
requête}\label{optimisation-dune-requuxeate}

\textbf{Schéma de la base cave}

\begin{figure}[htbp]
\centering
\includegraphics{tests/test.png}
\caption{Schema de la base cave}
\end{figure}

\textbf{Optimisation 1}

Nous travaillerons sur la requête contenue dans le fichier
``requete1.sql'' pour cet exercice :

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{-- \textbackslash{}timing }

\CommentTok{-- explain analyze}
\KeywordTok{select}
      \NormalTok{m.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from} 
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{(}\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee) m}
       \KeywordTok{on} \NormalTok{s.annee =  m.annee}
     \KeywordTok{join} \NormalTok{vin v}
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a}
     \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{group} \KeywordTok{by} \NormalTok{m.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

\begin{enumerate}
\def\labelenumi{\arabic{enumi}.}
\item
  Exécuter la requête telle quelle et noter le plan et le temps
  d'exécution
\item
  Créer un index sur la colonne ``stock.annee''
\item
  Exécuter la requête juste après la création de l'index
\item
  Faire un ``ANALYZE stock''
\item
  Exécuter à nouveau la requête
\item
  Interdire à PostgreSQL de faire des ``Sequential Scans'' : Envoyer
  pour cela la commande ``set enable\_seqscan to off;'' dans
  \texttt{psql}
\item
  Exécuter à nouveau la requête
\item
  Tenter de ré-écrire la requête pour l'optimiser?
\end{enumerate}

** Optimisation 2 **

L'exercice nous a amené à la réécriture de la requete 1. Voici la
requête que nous avons à présent :

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \KeywordTok{analyze}
\KeywordTok{select}
      \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from}
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{vin v}
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a }
       \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{where} \NormalTok{s.annee =  (}\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee)}
\KeywordTok{group} \KeywordTok{by} \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

Cette écriture n'est pas optimale, cherchez pourquoi\ldots{}

\textbf{Indices}

\begin{itemize}
\item
  Vérifiez le schéma de données de la base ``cave''
\item
  Faites les requêtes de vérification nécessaires pour vous assurer que
  vous avez bien trouvé une anomalie dans la requête
\item
  Réécrivez la requête une nouvelle fois et faites un
  \texttt{EXPLAIN\ ANALYZE} pour vérifier que le plan d'exécution est
  plus simple et plus rapide avec cette nouvelle écriture
\end{itemize}

\textbf{Optimisation 3}

Un dernier problème existe dans cette requête. Il n'est visible qu'en
observant le plan d'exécution de la requête précédente.

\textbf{Indice}

Cherchez une opération présente dans le plan qui n'apparaît pas dans la
requête. Comment modifier la requête pour éviter cette opération ?

\subsubsection{Corrélation entre
colonnes}\label{corruxe9lation-entre-colonnes}

Importez le fichier correlations.sql

Dans la table «villes», on a les villes et leur code postal. Ces
colonnes sont très fortement corrélées, mais pas identiques: plusieurs
villes peuvent partager le même code postal, et une ville peut avoir
plusieurs codes postaux. On peut aussi, bien sûr, avoir plusieurs villes
avec le même nom, mais pas le même code postal (quand elles sont dans
des départements différents par exemple). Pour obtenir la liste des
villes pouvant poser problème:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{*}
\KeywordTok{FROM} \NormalTok{villes}
\KeywordTok{WHERE} \NormalTok{localite }\KeywordTok{IN}
    \NormalTok{(}\KeywordTok{SELECT} \NormalTok{localite}
     \KeywordTok{FROM} \NormalTok{villes}
     \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{localite }\KeywordTok{HAVING} \FunctionTok{count}\NormalTok{(*) >}\DecValTok{1}\NormalTok{)}
  \KeywordTok{AND} \NormalTok{codepostal }\KeywordTok{IN}
    \NormalTok{(}\KeywordTok{SELECT} \NormalTok{codepostal}
     \KeywordTok{FROM} \NormalTok{villes}
     \KeywordTok{GROUP} \KeywordTok{BY} \NormalTok{codepostal }\KeywordTok{HAVING} \FunctionTok{count}\NormalTok{(*) >}\DecValTok{1}\NormalTok{);}
\end{Highlighting}
\end{Shaded}

Avec cette requête, on récupére toutes les villes ayant plusieurs
occurrences, et dont au moins une possède un code postal partagé. Ces
villes ont donc besoin du code postal ET du nom pour être identifiées.

Un exemple de requête problématique est le suivant:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{colis}
\KeywordTok{WHERE} \NormalTok{id_ville }\KeywordTok{IN}
   \NormalTok{(}\KeywordTok{SELECT} \NormalTok{id_ville }\KeywordTok{FROM} \NormalTok{villes }
    \KeywordTok{WHERE} \NormalTok{localite =}\StringTok{'PARIS'} 
      \KeywordTok{AND} \NormalTok{codepostal }\KeywordTok{LIKE} \StringTok{'75%'}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  Exécutez cette requête, et regardez son plan d'exécution. Où est le
  problème ?
\item
  Exécutez cette requête sans le
  \texttt{AND\ codepostal\ LIKE\ \textquotesingle{}75\%\textquotesingle{}}.
  Que constatez-vous ?
\item
  Quelle solution pourrait-on adopter, si on doit réellement spécifier
  ces deux conditions ?
\end{itemize}

\subsection{Solutions}\label{solutions-4}

\subsubsection{Affichage de plans de requêtes
simples}\label{affichage-de-plans-de-requuxeates-simples-1}

\textbf{Recherche de motif texte}

\begin{itemize}
\tightlist
\item
  Affichez le plan de cette requête (sur la base cave).
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{explain} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6.99} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{24}\NormalTok{)}
   \NormalTok{Filter: (libelle ~~ }\StringTok{'Brouilly%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Que constatez-vous?

\begin{itemize}
\tightlist
\item
  Affichez maintenant le nombre de blocs accédés par cette requête.
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
                                              \KeywordTok{QUERY} \KeywordTok{PLAN}                                              
\CommentTok{------------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6.99} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{24}\NormalTok{) (actual time=}\FloatTok{0.066}\NormalTok{..}\FloatTok{0.169} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (libelle ~~ }\StringTok{'Brouilly%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{318}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{3}
 \NormalTok{Total runtime: }\FloatTok{0.202} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\item
  Cette requête ne passe pas par un index. Essayez de lui forcer la
  main.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{set} \NormalTok{enable_seqscan }\KeywordTok{TO} \KeywordTok{off}\NormalTok{;}
\KeywordTok{SET}
\NormalTok{cave=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
                                                    \KeywordTok{QUERY} \KeywordTok{PLAN}                                                        
\CommentTok{--------------------------------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{10000000000.00}\NormalTok{..}\FloatTok{10000000006.99} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{24}\NormalTok{) (actual time=}\FloatTok{0.073}\NormalTok{..}\FloatTok{0.197} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (libelle ~~ }\StringTok{'Brouilly%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{318}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{3}
 \NormalTok{Total runtime: }\FloatTok{0.238} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}
\end{itemize}

Passer enable\_seqscan à off n'interdit pas l'utilisation de scans
séquentiels. Il ne fait que les défavoriser fortement: regardez le coût
estimé du scan séquentiel.

\begin{itemize}
\tightlist
\item
  L'index n'est toujours pas utilisé. L'index «par défaut» n'est pas
  capable de répondre à des questions sur motif.
\end{itemize}

En effet, l'index par défaut trie les données par la collation de la
colonne de la table. Il lui est impossible de savoir que «libelle LIKE
`Brouilly\%'» est équivalent à «libelle \textgreater{}= `Brouilly' AND
libelle \textless{} `Brouillz'. Ce genre de transformation n'est
d'ailleurs pas forcément trivial, ni même possible. Il existe dans
certaines langues des équivalences (ß et ss en allemand par exemple) qui
rendent ce genre de transformation au mieux hasardeuse.

\begin{itemize}
\tightlist
\item
  Créez un index capable de ces opérations. Testez à nouveau le plan.
\end{itemize}

Pour pouvoir répondre à cette question, on doit donc avoir un index
spécialisé, qui compare les chaînes non plus par rapport à leur
collation, mais à leur valeur binaire (octale en fait).

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{appellation_libelle_key_search }\KeywordTok{ON} \NormalTok{appellation (libelle text_pattern_ops);}
\end{Highlighting}
\end{Shaded}

On indique par cette commande à PostgreSQL de ne plus utiliser la classe
d'opérateurs habituelle de comparaison de texte, mais la classe
text\_pattern\_ops, qui est spécialement faite pour les recherches like
`xxxx\%': cette classe ne trie plus les chaînes par leur ordre
alphabétique, mais par leur valeur octale.

Si on redemande le plan:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
                                                                 \KeywordTok{QUERY} \KeywordTok{PLAN}                                                                  
\CommentTok{---------------------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{using} \NormalTok{appellation_libelle_key_search }\KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{0.27}\NormalTok{..}\FloatTok{8.29} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{24}\NormalTok{) (actual time=}\FloatTok{0.057}\NormalTok{..}\FloatTok{0.059} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Index} \NormalTok{Cond: ((libelle ~>=~ }\StringTok{'Brouilly'}\NormalTok{:}\CharTok{:text}\NormalTok{) }\KeywordTok{AND} \NormalTok{(libelle ~<~ }\StringTok{'Brouillz'}\NormalTok{:}\CharTok{:text}\NormalTok{))}
   \NormalTok{Filter: (libelle ~~ }\StringTok{'Brouilly%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{1} \NormalTok{read=}\DecValTok{2}
 \NormalTok{Total runtime: }\FloatTok{0.108} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

On utilise enfin un index.

\begin{itemize}
\tightlist
\item
  Réactivez enable\_seqscan. Testez à nouveau le plan.
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{cave=# }\KeywordTok{reset} \NormalTok{enable_seqscan ;}
\KeywordTok{RESET}
\NormalTok{cave=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{appellation }\KeywordTok{WHERE} \NormalTok{libelle }\KeywordTok{LIKE} \StringTok{'Brouilly%'}\NormalTok{;}
                                              \KeywordTok{QUERY} \KeywordTok{PLAN}                                              
\CommentTok{------------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6.99} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{24}\NormalTok{) (actual time=}\FloatTok{0.063}\NormalTok{..}\FloatTok{0.172} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (libelle ~~ }\StringTok{'Brouilly%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{318}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{3}
 \NormalTok{Total runtime: }\FloatTok{0.211} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  Quelle est la conclusion ?
\end{itemize}

PostgreSQL choisit de ne pas utiliser cet index. Le temps d'exécution
est un peu meilleur avec l'index (60 microsecondes contre 172
microsecondes). Néanmoins, cela n'est vrai que parce que les données
sont en cache. En cas de données hors du cache, le plan par scan
séquentiel est probablement meilleur: il prend plus de temps CPU
puisqu'il doit consulter 318 enregistrements inutiles. Par contre, il ne
fait qu'un accès à 3 blocs séquentiels (les 3 blocs de la table), ce qui
est le plus sûr.

La table est trop petite pour que PostgreSQL considère l'utilisation
d'un index.

** Recherche de motif texte avancé**

La base cave ne contient pas de données textuelles appropriées.

\begin{itemize}
\tightlist
\item
  Lancez textes.sql.
\end{itemize}

\begin{verbatim}
psql < textes.sql
\end{verbatim}

Ce script crée une table textes, contenant le texte intégral de tous les
livres en français du projet Gutenberg.

Nous allons rechercher toutes les références à «Fantine» dans le texte.
On devrait trouver beaucoup d'enregistrements provenant des
«Misérables».

\begin{itemize}
\tightlist
\item
  La méthode SQL standard pour écrire cela est:
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{textes }\KeywordTok{WHERE} \NormalTok{contenu ILIKE }\StringTok{'%fantine%'}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Exécutez cette requête, et regardez son plan d'exécution.

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{textes }\KeywordTok{WHERE} \NormalTok{contenu ILIKE }\StringTok{'%fantine%'}\NormalTok{;}
                                                   \KeywordTok{QUERY} \KeywordTok{PLAN}                                                    
\CommentTok{-----------------------------------------------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{textes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{325809.40} \NormalTok{rows=}\DecValTok{874} \NormalTok{width=}\DecValTok{102}\NormalTok{) (actual time=}\FloatTok{224.634}\NormalTok{..}\FloatTok{22567.231} \NormalTok{rows=}\DecValTok{921} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Filter: (contenu ~~* }\StringTok{'%fantine%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{11421523}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{130459} \NormalTok{read=}\DecValTok{58323}
 \NormalTok{Total runtime: }\FloatTok{22567.679} \NormalTok{ms}
\NormalTok{(}\DecValTok{5} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Cette requête ne peut pas être optimisée avec les index standard
(btree): on fait une recherche insensible à la casse, et avec plusieurs
\%, dont un initial.

** Avec GiST **

\begin{itemize}
\tightlist
\item
  Créez un index trigramme:
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{CREATE} \NormalTok{EXTENSION pg_trgm;}

\NormalTok{textes=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_trgm }\KeywordTok{ON} \NormalTok{textes }\KeywordTok{USING} \NormalTok{gist (contenu }\KeywordTok{USING} \NormalTok{gist_trgm_ops);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{Temps : }\DecValTok{962794}\NormalTok{,}\DecValTok{399} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

L'index fait cette taille (pour une table de 1,5Go):

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{select} \NormalTok{pg_size_pretty(pg_relation_size(}\StringTok{'idx_trgm'}\NormalTok{));}
 \NormalTok{pg_size_pretty }
\CommentTok{----------------}
 \DecValTok{2483} \NormalTok{MB}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  Ré-exécutez la requête. ```sql textes=\# explain (analyze,buffers)
  SELECT * FROM textes WHERE contenu ILIKE `\%fantine\%'; QUERY PLAN\\
  -----------------------------------------------------------------------------------------------------------------------------
  Bitmap Heap Scan on textes (cost=111.49..3573.39 rows=912 width=102)
  (actual time=1942.872..1949.393 rows=922 loops=1) Recheck Cond:
  (contenu \textasciitilde{}\textasciitilde{}* `\%fantine\%'::text) Rows
  Removed by Index Recheck: 75 Buffers: shared hit=16030 read=144183
  written=14741 -\textgreater{} Bitmap Index Scan on idx\_trgm
  (cost=0.00..111.26 rows=912 width=0) (actual time=1942.671..1942.671
  rows=997 loops=1) Index Cond: (contenu
  \textasciitilde{}\textasciitilde{}* `\%fantine\%'::text) Buffers:
  shared hit=16029 read=143344 written=14662 Total runtime: 1949.565 ms
  (8 lignes)
\end{itemize}

Temps : 1951,175 ms ```

\begin{itemize}
\tightlist
\item
  Que constatez-vous ?
\end{itemize}

PostgreSQL dispose de mécanismes spécifiques avancés pour certains types
de données. Ils ne sont pas toujours installés en standard, mais leur
connaissance peut avoir un impact énorme sur les performances.

Le mécanisme GiST est assez efficace pour répondre à ce genre de
questions. Il nécessite quand même un accès à un grand nombre de blocs,
d'après le plan: 160 000 blocs lus, 15000 écrits (espace temporaire, on
pourrait s'en débarrasser en augmentant le work\_mem). Le gain est donc
conséquent, mais pas gigantesque: le plan initial lisait 190 000 blocs.
On gagne surtout en temps de calcul, car on accède directement aux bons
enregistrements. Le parcours de l'index, par contre, est coûteux.

** Avec Gin **

\begin{itemize}
\tightlist
\item
  Créez un index trigramme:
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{CREATE} \NormalTok{EXTENSION pg_trgm;}

\NormalTok{textes=# }\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_trgm }\KeywordTok{ON} \NormalTok{textes }\KeywordTok{USING} \NormalTok{gin (contenu }\KeywordTok{USING} \NormalTok{gin_trgm_ops);}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{Temps : }\DecValTok{591534}\NormalTok{,}\DecValTok{917} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

L'index fait cette taille (pour une table de 1,5Go):

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{select} \NormalTok{pg_size_pretty(pg_total_relation_size(}\StringTok{'textes'}\NormalTok{));}
 \NormalTok{pg_size_pretty }
\CommentTok{----------------}
 \DecValTok{4346} \NormalTok{MB}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}
\end{Highlighting}
\end{Shaded}

L'index est très volumineux.

\begin{itemize}
\tightlist
\item
  Ré-exécutez la requête.
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{textes }\KeywordTok{WHERE} \NormalTok{contenu ILIKE }\StringTok{'%fantine%'}\NormalTok{;}
                                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                                                         
\CommentTok{---------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{textes  (cost=}\FloatTok{103.06}\NormalTok{..}\FloatTok{3561.22} \NormalTok{rows=}\DecValTok{911} \NormalTok{width=}\DecValTok{102}\NormalTok{) (actual time=}\FloatTok{777.469}\NormalTok{..}\FloatTok{780.834} \NormalTok{rows=}\DecValTok{921} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: (contenu ~~* }\StringTok{'%fantine%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
   \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \KeywordTok{Index} \NormalTok{Recheck: }\DecValTok{75}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2666}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{idx_trgm  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{102.83} \NormalTok{rows=}\DecValTok{911} \NormalTok{width=}\DecValTok{0}\NormalTok{) (actual time=}\FloatTok{777.283}\NormalTok{..}\FloatTok{777.283} \NormalTok{rows=}\DecValTok{996} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (contenu ~~* }\StringTok{'%fantine%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{1827}
 \NormalTok{Total runtime: }\FloatTok{780.954} \NormalTok{ms}
\NormalTok{(}\DecValTok{8} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

\begin{itemize}
\tightlist
\item
  Que constatez-vous ?
\end{itemize}

PostgreSQL dispose de mécanismes spécifiques avancés pour certains types
de données. Ils ne sont pas toujours installés en standard, mais leur
connaissance peut avoir un impact énorme sur les performances. Le
mécanisme Gin est vraiment très efficace pour répondre à ce genre de
questions. Il s'agit de répondre en moins d'une seconde à «quelles
lignes contiennent la chaîne fantine» sur 12 millions de lignes de
texte. Les Index Gin sont par contre très coûteux à maintenir. Ici, on
n'accède qu'à 2 666 blocs, ce qui est vraiment excellent. L'index est
bien plus volumineux que l'index GiST.

** Avec le Full Text Search **

Le résultat sera bien sûr différent, et le FTS est moins souple.

Version GiST:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{create} \KeywordTok{index} \NormalTok{idx_fts }\KeywordTok{on} \NormalTok{textes }\KeywordTok{using} \NormalTok{gist (to_tsvector(}\StringTok{'french'}\NormalTok{,contenu));}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{Temps : }\DecValTok{1807467}\NormalTok{,}\DecValTok{811} \NormalTok{ms}


\NormalTok{textes=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{select} \NormalTok{* }\KeywordTok{from} \NormalTok{textes }\KeywordTok{where} \NormalTok{to_tsvector(}\StringTok{'french'}\NormalTok{,contenu) @@ to_tsquery(}\StringTok{'french'}\NormalTok{,}\StringTok{'fantine'}\NormalTok{);}
                                                         \KeywordTok{QUERY} \KeywordTok{PLAN}                                                          
\CommentTok{-----------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{textes  (cost=}\FloatTok{2209.51}\NormalTok{..}\FloatTok{137275.87} \NormalTok{rows=}\DecValTok{63109} \NormalTok{width=}\DecValTok{97}\NormalTok{) (actual time=}\FloatTok{648.596}\NormalTok{..}\FloatTok{659.733} \NormalTok{rows=}\DecValTok{311} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: (to_tsvector(}\StringTok{'french'}\NormalTok{:}\CharTok{:regconfig}\NormalTok{, contenu) @@ `}\StringTok{'fantin`'}\NormalTok{:}\CharTok{:tsquery}\NormalTok{)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{37165}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{idx_fts  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{2193.74} \NormalTok{rows=}\DecValTok{63109} \NormalTok{width=}\DecValTok{0}\NormalTok{) (actual time=}\FloatTok{648.493}\NormalTok{..}\FloatTok{648.493} \NormalTok{rows=}\DecValTok{311} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (to_tsvector(}\StringTok{'french'}\NormalTok{:}\CharTok{:regconfig}\NormalTok{, contenu) @@ `}\StringTok{'fantin`'}\NormalTok{:}\CharTok{:tsquery}\NormalTok{)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{37016}
 \NormalTok{Total runtime: }\FloatTok{659.820} \NormalTok{ms}
\NormalTok{(}\DecValTok{7} \NormalTok{lignes)}

\NormalTok{Temps : }\DecValTok{660}\NormalTok{,}\DecValTok{364} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

Et la taille de l'index:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{select} \NormalTok{pg_size_pretty(pg_relation_size(}\StringTok{'idx_fts'}\NormalTok{));}
 \NormalTok{pg_size_pretty }
\CommentTok{----------------}
 \DecValTok{671} \NormalTok{MB}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}
\end{Highlighting}
\end{Shaded}

Version Gin:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{create} \KeywordTok{index} \NormalTok{idx_fts }\KeywordTok{on} \NormalTok{textes }\KeywordTok{using} \NormalTok{gin (to_tsvector(}\StringTok{'french'}\NormalTok{,contenu));}
\KeywordTok{CREATE} \KeywordTok{INDEX}
\NormalTok{Temps : }\DecValTok{491499}\NormalTok{,}\DecValTok{599} \NormalTok{ms}
\NormalTok{textes=# }\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{select} \NormalTok{* }\KeywordTok{from} \NormalTok{textes }\KeywordTok{where} \NormalTok{to_tsvector(}\StringTok{'french'}\NormalTok{,contenu) @@ to_tsquery(}\StringTok{'french'}\NormalTok{,}\StringTok{'fantine'}\NormalTok{);}
                                                       \KeywordTok{QUERY} \KeywordTok{PLAN}                                                       
\CommentTok{------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{textes  (cost=}\FloatTok{693.10}\NormalTok{..}\FloatTok{135759.45} \NormalTok{rows=}\DecValTok{63109} \NormalTok{width=}\DecValTok{97}\NormalTok{) (actual time=}\FloatTok{0.278}\NormalTok{..}\FloatTok{0.699} \NormalTok{rows=}\DecValTok{311} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Recheck Cond: (to_tsvector(}\StringTok{'french'}\NormalTok{:}\CharTok{:regconfig}\NormalTok{, contenu) @@ `}\StringTok{'fantin`'}\NormalTok{:}\CharTok{:tsquery}\NormalTok{)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{153}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{idx_fts  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{677.32} \NormalTok{rows=}\DecValTok{63109} \NormalTok{width=}\DecValTok{0}\NormalTok{) (actual time=}\FloatTok{0.222}\NormalTok{..}\FloatTok{0.222} \NormalTok{rows=}\DecValTok{311} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Index} \NormalTok{Cond: (to_tsvector(}\StringTok{'french'}\NormalTok{:}\CharTok{:regconfig}\NormalTok{, contenu) @@ `}\StringTok{'fantin`'}\NormalTok{:}\CharTok{:tsquery}\NormalTok{)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{4}
 \NormalTok{Total runtime: }\FloatTok{0.793} \NormalTok{ms}
\NormalTok{(}\DecValTok{7} \NormalTok{lignes)}

\NormalTok{Temps : }\DecValTok{1}\NormalTok{,}\DecValTok{534} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

Taille de l'index:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{textes=# }\KeywordTok{select} \NormalTok{pg_size_pretty(pg_relation_size(}\StringTok{'idx_fts'}\NormalTok{));}
 \NormalTok{pg_size_pretty }
\CommentTok{----------------}
 \DecValTok{593} \NormalTok{MB}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}
\end{Highlighting}
\end{Shaded}

On constate donc que le Full Text Search est bien plus efficace que le
trigramme, du moins pour le Full Text Search + Gin: trouver 1 mot parmi
plus de cent million, dans 300 endroits différents dure 1,5 ms. Par
contre, le trigramme permet des recherches floues (orthographe
approximative), et des recherches sur autre chose que des mots, même si
ces points ne sont pas abordés ici.

\subsubsection{Optimisation d'une
requête}\label{optimisation-dune-requuxeate-1}

** Optimisation 1 **

Nous travaillerons sur la requête contenue dans le fichier
\texttt{requete1.sql} pour cet exercice:

\begin{Shaded}
\begin{Highlighting}[]
\CommentTok{-- \textbackslash{}timing }

\CommentTok{-- explain analyze}
\KeywordTok{select}
      \NormalTok{m.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from} 
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{(}\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee) m}
       \KeywordTok{on} \NormalTok{s.annee =  m.annee}
     \KeywordTok{join} \NormalTok{vin v}
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a}
     \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{group} \KeywordTok{by} \NormalTok{m.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

L'exécution de la requête donne le plan suivant, avec un temps qui peut
varier en fonction de la machine utilisée et de son activité:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{HashAggregate  (cost=}\FloatTok{12763.56}\NormalTok{..}\FloatTok{12773.13} \NormalTok{rows=}\DecValTok{319} \NormalTok{width=}\DecValTok{32}\NormalTok{) (actual time=}\FloatTok{1542.472}\NormalTok{..}\FloatTok{1542.879} \NormalTok{rows=}\DecValTok{319} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Left} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{184.59}\NormalTok{..}\FloatTok{12741.89} \NormalTok{rows=}\DecValTok{2889} \NormalTok{width=}\DecValTok{32}\NormalTok{) (actual time=}\FloatTok{180.263}\NormalTok{..}\FloatTok{1520.812} \NormalTok{rows=}\DecValTok{11334} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \KeywordTok{Hash} \NormalTok{Cond: (v.appellation_id = a.id)}
         \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{174.42}\NormalTok{..}\FloatTok{12663.10} \NormalTok{rows=}\DecValTok{2889} \NormalTok{width=}\DecValTok{20}\NormalTok{) (actual time=}\FloatTok{179.426}\NormalTok{..}\FloatTok{1473.270} \NormalTok{rows=}\DecValTok{11334} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \KeywordTok{Hash} \NormalTok{Cond: (s.contenant_id = c.id)}
               \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{173.37}\NormalTok{..}\FloatTok{12622.33} \NormalTok{rows=}\DecValTok{2889} \NormalTok{width=}\DecValTok{20}\NormalTok{) (actual time=}\FloatTok{179.401}\NormalTok{..}\FloatTok{1446.687} \NormalTok{rows=}\DecValTok{11334} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                     \KeywordTok{Hash} \NormalTok{Cond: (s.vin_id = v.id)}
                     \NormalTok{->  }\KeywordTok{Hash} \KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{0.04}\NormalTok{..}\FloatTok{12391.22} \NormalTok{rows=}\DecValTok{2889} \NormalTok{width=}\DecValTok{20}\NormalTok{) (actual time=}\FloatTok{164.388}\NormalTok{..}\FloatTok{1398.643} \NormalTok{rows=}\DecValTok{11334} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                           \KeywordTok{Hash} \NormalTok{Cond: ((s.annee):}\CharTok{:double} \DataTypeTok{precision} \NormalTok{= ((}\FunctionTok{round}\NormalTok{((}\KeywordTok{random}\NormalTok{() * }\DecValTok{50}\NormalTok{:}\CharTok{:double} \DataTypeTok{precision}\NormalTok{)) + }\DecValTok{1950}\NormalTok{:}\CharTok{:double} \DataTypeTok{precision}\NormalTok{)))}
                           \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{stock s  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{9472.86} \NormalTok{rows=}\DecValTok{577886} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{0.003}\NormalTok{..}\FloatTok{684.039} \NormalTok{rows=}\DecValTok{577886} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                           \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{0.03}\NormalTok{..}\FloatTok{0.03} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.009}\NormalTok{..}\FloatTok{0.009} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                                 \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 1kB}
                                 \NormalTok{->  Result  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{0.02} \NormalTok{rows=}\DecValTok{1} \NormalTok{width=}\DecValTok{0}\NormalTok{) (actual time=}\FloatTok{0.005}\NormalTok{..}\FloatTok{0.006} \NormalTok{rows=}\DecValTok{1} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                     \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{97.59}\NormalTok{..}\FloatTok{97.59} \NormalTok{rows=}\DecValTok{6059} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{14.987}\NormalTok{..}\FloatTok{14.987} \NormalTok{rows=}\DecValTok{6059} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                           \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 237kB}
                           \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{vin v  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{97.59} \NormalTok{rows=}\DecValTok{6059} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.009}\NormalTok{..}\FloatTok{7.413} \NormalTok{rows=}\DecValTok{6059} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1.02}\NormalTok{..}\FloatTok{1.02} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.013}\NormalTok{..}\FloatTok{0.013} \NormalTok{rows=}\DecValTok{2} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
                     \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 1kB}
                     \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{contenant c  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1.02} \NormalTok{rows=}\DecValTok{2} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{0.003}\NormalTok{..}\FloatTok{0.005} \NormalTok{rows=}\DecValTok{2} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{6.19}\NormalTok{..}\FloatTok{6.19} \NormalTok{rows=}\DecValTok{319} \NormalTok{width=}\DecValTok{20}\NormalTok{) (actual time=}\FloatTok{0.806}\NormalTok{..}\FloatTok{0.806} \NormalTok{rows=}\DecValTok{319} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 17kB}
               \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{appellation a  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6.19} \NormalTok{rows=}\DecValTok{319} \NormalTok{width=}\DecValTok{20}\NormalTok{) (actual time=}\FloatTok{0.004}\NormalTok{..}\FloatTok{0.379} \NormalTok{rows=}\DecValTok{319} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
 \NormalTok{Total runtime: }\FloatTok{1543.242} \NormalTok{ms}
\NormalTok{(}\DecValTok{23} \KeywordTok{rows}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Nous créons à présent un index sur ``stock.annee'' comme suit:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{create} \KeywordTok{index} \NormalTok{stock_annee }\KeywordTok{on} \NormalTok{stock (annee);}
\end{Highlighting}
\end{Shaded}

Et exécutons à nouveau la requête. Hélas nous constatons que rien ne
change, ni le plan, ni le temps pris par la requête.

Nous n'avons pas lancé \texttt{ANALYZE}, cela explique que l'optimiseur
n'utilise pas l'index: il n'en a pas encore la connaissance:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{analyze} \NormalTok{stock;}
\end{Highlighting}
\end{Shaded}

Le plan n'a toujours pas changé! Ainsi que le temps d'exécution?!

Interdisons donc de faire du \texttt{Seq\ Scan} à l'optimiseur en
initiant la commande suivante dans notre \texttt{psql}:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{set} \NormalTok{enable_seqscan }\KeywordTok{to} \KeywordTok{off}\NormalTok{;}
\end{Highlighting}
\end{Shaded}

Nous remarquons que le plan d'exécution et le temps pris par la requête
est encore pire:

\begin{verbatim}
 HashAggregate  (cost=40763.39..40772.96 rows=319 width=32) (actual time=2022.971..2023.390 rows=319 loops=1)
   ->  Hash Left Join  (cost=313.94..40741.72 rows=2889 width=32) (actual time=18.149..1995.889 rows=11299 loops=1)
         Hash Cond: (v.appellation_id = a.id)
         ->  Hash Join  (cost=290.92..40650.09 rows=2889 width=20) (actual time=17.172..1937.644 rows=11299 loops=1)
               Hash Cond: (s.vin_id = v.id)
               ->  Nested Loop  (cost=0.04..40301.43 rows=2889 width=20) (actual time=0.456..1882.531 rows=11299 loops=1)
                     Join Filter: (s.contenant_id = c.id)
                     ->  Hash Join  (cost=0.04..40202.48 rows=2889 width=20) (actual time=0.444..1778.149 rows=11299 loops=1)
                           Hash Cond: ((s.annee)::double precision = ((round((random() * 50::double precision)) + 1950::double precision)))
                           ->  Index Scan using stock_pkey on stock s  (cost=0.00..37284.12 rows=577886 width=16) (actual time=0.009..1044.061 rows=577886 loops=1)
                           ->  Hash  (cost=0.03..0.03 rows=1 width=8) (actual time=0.011..0.011 rows=1 loops=1)
                                 Buckets: 1024  Batches: 1  Memory Usage: 1kB
                                 ->  Result  (cost=0.00..0.02 rows=1 width=0) (actual time=0.005..0.006 rows=1 loops=1)
                     ->  Materialize  (cost=0.00..12.29 rows=2 width=8) (actual time=0.001..0.003 rows=2 loops=11299)
                           ->  Index Scan using contenant_pkey on contenant c  (cost=0.00..12.28 rows=2 width=8) (actual time=0.004..0.010 rows=2 loops=1)
               ->  Hash  (cost=215.14..215.14 rows=6059 width=8) (actual time=16.699..16.699 rows=6059 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 237kB
                     ->  Index Scan using vin_pkey on vin v  (cost=0.00..215.14 rows=6059 width=8) (actual time=0.010..8.871 rows=6059 loops=1)
         ->  Hash  (cost=19.04..19.04 rows=319 width=20) (actual time=0.936..0.936 rows=319 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 17kB
               ->  Index Scan using appellation_pkey on appellation a  (cost=0.00..19.04 rows=319 width=20) (actual time=0.016..0.461 rows=319 loops=1)
 Total runtime: 2023.742 ms
(22 rows)
\end{verbatim}

Que faire alors?

Il convient d'autoriser à nouveau les \texttt{Seq\ Scan}, puis,
peut-être, de réécrire la requête\ldots{}

Nous réécrivons la requête comme suit (fichier ``requete2.sql''):

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \KeywordTok{analyze}
\KeywordTok{select}
      \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from}
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{vin v}
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a }
       \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{where} \NormalTok{s.annee =  (}\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee)}
\KeywordTok{group} \KeywordTok{by} \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

Cela provoque une jointure de moins, ce qui est toujours appréciable.
Nous pouvons faire cette réécriture parce que la requête «select
round(random()*50)+1950 as annee» ne ramène qu'un seul enregistrement.

Voici le résultat:

\begin{verbatim}
 HashAggregate  (cost=12734.64..12737.10 rows=82 width=28) (actual time=265.899..266.317 rows=319 loops=1)
   InitPlan 1 (returns $0)
     ->  Result  (cost=0.00..0.02 rows=1 width=0) (actual time=0.005..0.006 rows=1 loops=1)
   ->  Hash Left Join  (cost=184.55..12712.96 rows=2889 width=28) (actual time=127.787..245.314 rows=11287 loops=1)
         Hash Cond: (v.appellation_id = a.id)
         ->  Hash Join  (cost=174.37..12634.17 rows=2889 width=16) (actual time=126.950..208.077 rows=11287 loops=1)
               Hash Cond: (s.contenant_id = c.id)
               ->  Hash Join  (cost=173.33..12593.40 rows=2889 width=16) (actual time=126.925..181.867 rows=11287 loops=1)
                     Hash Cond: (s.vin_id = v.id)
                     ->  Seq Scan on stock s  (cost=0.00..12362.29 rows=2889 width=16) (actual time=112.101..135.932 rows=11287 loops=1)
                           Filter: ((annee)::double precision = $0)
                     ->  Hash  (cost=97.59..97.59 rows=6059 width=8) (actual time=14.794..14.794 rows=6059 loops=1)
                           Buckets: 1024  Batches: 1  Memory Usage: 237kB
                           ->  Seq Scan on vin v  (cost=0.00..97.59 rows=6059 width=8) (actual time=0.010..7.321 rows=6059 loops=1)
               ->  Hash  (cost=1.02..1.02 rows=2 width=8) (actual time=0.013..0.013 rows=2 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  Seq Scan on contenant c  (cost=0.00..1.02 rows=2 width=8) (actual time=0.004..0.006 rows=2 loops=1)
         ->  Hash  (cost=6.19..6.19 rows=319 width=20) (actual time=0.815..0.815 rows=319 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 17kB
               ->  Seq Scan on appellation a  (cost=0.00..6.19 rows=319 width=20) (actual time=0.004..0.387 rows=319 loops=1)
 Total runtime: 266.663 ms
(21 rows)
\end{verbatim}

Nous sommes ainsi passés de 2s à 250 millisecondes, et la requête est
donc environ 10 fois plus rapide.

Que peut on conclure de cet exercice?

\begin{itemize}
\item
  que la création d'un index est une bonne idée. Cependant l'optimiseur
  peut ne pas l'utiliser, pour de bonnes raisons
\item
  qu'interdire les \texttt{Seq\ Scan} est toujours une mauvaise idée (ne
  présumez pas de votre supériorité sur l'optimiseur!)
\end{itemize}

\textbf{Optimisation 2}

Voici la requête 2 telle que nous l'avons trouvé dans l'exercice
précédent:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \KeywordTok{analyze}
\KeywordTok{select}
      \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from}
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{vin v}
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a }
       \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{where} \NormalTok{s.annee =  (}\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee)}
\KeywordTok{group} \KeywordTok{by} \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

On peut se demander si la jointure externe (LEFT JOIN) est
fondée\ldots{} C'est à dire, on va vérifier l'utilité de la ligne
suivante:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{vin v }\KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a }\KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\end{Highlighting}
\end{Shaded}

Cela se traduit par ``récupérer tous les tuples de la table vin, et pour
chaque correspondance dans appellation, la récupérer, si elle existe''.

En regardant la description de la table \texttt{vin}
(\texttt{\textbackslash{}d\ vin} dans \texttt{psql}), on remarque la
contrainte de clé étrangère suivante:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{«vin_appellation_id_fkey» }
\KeywordTok{FOREIGN} \KeywordTok{KEY} \NormalTok{(appellation_id)}
\KeywordTok{REFERENCES} \NormalTok{appellation(}\KeywordTok{id}\NormalTok{)}
\end{Highlighting}
\end{Shaded}

Cela veut dire qu'on a la certitude que pour chaque vin, si une
référence à la table appellation est présente, elle est nécessairement
vérifiable.

De plus, on remarque que:

\begin{Shaded}
\begin{Highlighting}[]
\NormalTok{appellation_id | }\DataTypeTok{integer} \NormalTok{| }\KeywordTok{not} \KeywordTok{null}
\end{Highlighting}
\end{Shaded}

Ce qui veut dire que la valeur de ce champ ne peut être nulle. Elle
contient donc obligatoirement une valeur qui est présente dans la table
``appellation''.

On peut vérifier au niveau des tuples en faisant un \texttt{count(*)} du
résultat, une fois en \texttt{inner\ join} et une fois en
\texttt{left\ join}: si le résultat est identique, la jointure externe
ne sert à rien:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{select} \FunctionTok{count}\NormalTok{(*)}
\KeywordTok{from} \NormalTok{vin v }
  \KeywordTok{inner} \KeywordTok{join} \NormalTok{appellation a }\KeywordTok{on} \NormalTok{(v.appellation_id = a.id);}

 \FunctionTok{count}
\CommentTok{-------}
  \DecValTok{6057}
\end{Highlighting}
\end{Shaded}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{select} \FunctionTok{count}\NormalTok{(*) }
\KeywordTok{from} \NormalTok{vin v }
  \KeywordTok{left} \KeywordTok{join} \NormalTok{appellation a }\KeywordTok{on} \NormalTok{(v.appellation_id = a.id);}

 \FunctionTok{count}
\CommentTok{-------}
  \DecValTok{6057}
\end{Highlighting}
\end{Shaded}

On peut donc réécrire la requête 2 en une nouvelle version, sans
risques, afin de supprimer la jointure externe inutile, comme on vient
de le démontrer:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \KeywordTok{analyze}
\KeywordTok{select}
      \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from}
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{vin v }
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{join} \NormalTok{appellation a}
       \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{where} \NormalTok{s.annee =  (}\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee)}
\KeywordTok{group} \KeywordTok{by} \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

Voici le résultat:

\begin{verbatim}
 HashAggregate  (cost=12734.64..12737.10 rows=82 width=28) (actual time=266.916..267.343 rows=319 loops=1)
   InitPlan 1 (returns $0)
     ->  Result  (cost=0.00..0.02 rows=1 width=0) (actual time=0.005..0.006 rows=1 loops=1)
   ->  Hash Join  (cost=184.55..12712.96 rows=2889 width=28) (actual time=118.759..246.391 rows=11299 loops=1)
         Hash Cond: (v.appellation_id = a.id)
         ->  Hash Join  (cost=174.37..12634.17 rows=2889 width=16) (actual time=117.933..208.503 rows=11299 loops=1)
               Hash Cond: (s.contenant_id = c.id)
               ->  Hash Join  (cost=173.33..12593.40 rows=2889 width=16) (actual time=117.914..182.501 rows=11299 loops=1)
                     Hash Cond: (s.vin_id = v.id)
                     ->  Seq Scan on stock s  (cost=0.00..12362.29 rows=2889 width=16) (actual time=102.903..135.451 rows=11299 loops=1)
                           Filter: ((annee)::double precision = $0)
                     ->  Hash  (cost=97.59..97.59 rows=6059 width=8) (actual time=14.979..14.979 rows=6059 loops=1)
                           Buckets: 1024  Batches: 1  Memory Usage: 237kB
                           ->  Seq Scan on vin v  (cost=0.00..97.59 rows=6059 width=8) (actual time=0.010..7.387 rows=6059 loops=1)
               ->  Hash  (cost=1.02..1.02 rows=2 width=8) (actual time=0.009..0.009 rows=2 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 1kB
                     ->  Seq Scan on contenant c  (cost=0.00..1.02 rows=2 width=8) (actual time=0.002..0.004 rows=2 loops=1)
         ->  Hash  (cost=6.19..6.19 rows=319 width=20) (actual time=0.802..0.802 rows=319 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 17kB
               ->  Seq Scan on appellation a  (cost=0.00..6.19 rows=319 width=20) (actual time=0.004..0.397 rows=319 loops=1)
 Total runtime: 267.688 ms
(21 rows)
\end{verbatim}

Cette réécriture n'a pas d'effet sur le temps d'exécution de la requête
dans notre cas. Mais il est probable qu'avec des cardinalités
différentes dans la base, cette réécriture aurait eu un impact.
Remplacer un LEFT JOIN par un JOIN est le plus souvent intéressant, car
il laisse davantage de liberté au moteur sur le sens de planification
des requêtes.

\textbf{Optimisation 3 }

Si on observe attentivement le plan, on constate qu'on a toujours le
parcours séquentiel de la table stock, qui est notre plus grosse table.
Pourquoi a-t-il lieu ?

Si on regarde le filtre (ligne filter) du parcours de la table stock, on
constate qu'il est écrit:

\begin{verbatim}
Filter: ((annee)::double precision = $0)
\end{verbatim}

Ceci signifie que pour tous les enregistrements de la table, l'année est
convertie en nombre à double précision (un nombre à virgule flottante,
real double précision), afin d'être comparé à \$0, une valeur filtre
appliquée à la table . Cette valeur est bien évidemment le résultat du
calcul:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{select} \FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee}
\end{Highlighting}
\end{Shaded}

comme indiquée par le début du plan (les lignes de l'initplan 1).

Pourquoi compare-t-il l'année (qui est déclarée comme un integer) en la
convertissant en un nombre à virgule flottante?

Parce que la fonction round() retourne un nombre à virgule flottante. La
somme d'un nombre à virgule flottante et d'un entier est évidemment un
nombre à virgule flottante. Si on veut que la fonction round() retourne
un entier, il faut forcer explicitement sa conversion, via CAST(xxx as
int) ou ::int

Réécrivons encore une fois cette requête:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \KeywordTok{analyze}
\KeywordTok{select}
      \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle }\KeywordTok{as} \NormalTok{millesime_region,}
      \FunctionTok{sum}\NormalTok{(s.nombre) }\KeywordTok{as} \NormalTok{contenants,}
      \FunctionTok{sum}\NormalTok{(s.nombre*c.contenance) }\KeywordTok{as} \NormalTok{litres}
\KeywordTok{from}
     \NormalTok{contenant c}
     \KeywordTok{join} \NormalTok{stock s}
       \KeywordTok{on} \NormalTok{s.contenant_id = c.id}
     \KeywordTok{join} \NormalTok{vin v }
       \KeywordTok{on} \NormalTok{s.vin_id = v.id}
     \KeywordTok{join} \NormalTok{appellation a}
       \KeywordTok{on} \NormalTok{v.appellation_id = a.id}
\KeywordTok{where} \NormalTok{s.annee =  (}\KeywordTok{select} \FunctionTok{cast}\NormalTok{(}\FunctionTok{round}\NormalTok{(}\KeywordTok{random}\NormalTok{()*}\DecValTok{50}\NormalTok{) }\KeywordTok{as} \DataTypeTok{int}\NormalTok{)+}\DecValTok{1950} \KeywordTok{as} \NormalTok{annee)}
\KeywordTok{group} \KeywordTok{by} \NormalTok{s.annee||}\StringTok{' - '}\NormalTok{||a.libelle;}
\end{Highlighting}
\end{Shaded}

Voici son plan:

\begin{verbatim}
 HashAggregate  (cost=1251.12..1260.69 rows=319 width=28) (actual time=138.418..138.825 rows=319 loops=1)
   InitPlan 1 (returns $0)
     ->  Result  (cost=0.00..0.02 rows=1 width=0) (actual time=0.005..0.006 rows=1 loops=1)
   ->  Hash Join  (cost=267.86..1166.13 rows=11329 width=28) (actual time=31.108..118.193 rows=11389 loops=1)
         Hash Cond: (s.contenant_id = c.id)
         ->  Hash Join  (cost=266.82..896.02 rows=11329 width=28) (actual time=31.071..80.980 rows=11389 loops=1)
               Hash Cond: (s.vin_id = v.id)
               ->  Index Scan using stock_annee on stock s  (cost=0.00..402.61 rows=11331 width=16) (actual time=0.049..17.191 rows=11389 loops=1)
                     Index Cond: (annee = $0)
               ->  Hash  (cost=191.08..191.08 rows=6059 width=20) (actual time=31.006..31.006 rows=6059 loops=1)
                     Buckets: 1024  Batches: 1  Memory Usage: 313kB
                     ->  Hash Join  (cost=10.18..191.08 rows=6059 width=20) (actual time=0.814..22.856 rows=6059 loops=1)
                           Hash Cond: (v.appellation_id = a.id)
                           ->  Seq Scan on vin v  (cost=0.00..97.59 rows=6059 width=8) (actual time=0.005..7.197 rows=6059 loops=1)
                           ->  Hash  (cost=6.19..6.19 rows=319 width=20) (actual time=0.800..0.800 rows=319 loops=1)
                                 Buckets: 1024  Batches: 1  Memory Usage: 17kB
                                 ->  Seq Scan on appellation a  (cost=0.00..6.19 rows=319 width=20) (actual time=0.002..0.363 rows=319 loops=1)
         ->  Hash  (cost=1.02..1.02 rows=2 width=8) (actual time=0.013..0.013 rows=2 loops=1)
               Buckets: 1024  Batches: 1  Memory Usage: 1kB
               ->  Seq Scan on contenant c  (cost=0.00..1.02 rows=2 width=8) (actual time=0.003..0.006 rows=2 loops=1)
 Total runtime: 139.252 ms
(21 rows)
\end{verbatim}

On constate qu'on utilise enfin l'index de stock. Le temps d'exécution a
encore été divisé par deux.

\textbf{NB:} ce problème était la cause du plus gros ralentissement de
la requête. En reprenant la requête initiale, et en y ajoutant
directement le cast, la requête s'exécute déjà en 160 millisecondes.

\subsubsection{Corrélation entre
colonnes}\label{corruxe9lation-entre-colonnes-1}

Importez le fichier correlations.sql

\begin{verbatim}
createdb correlations
psql correlations < correlations.sql
\end{verbatim}

\begin{itemize}
\tightlist
\item
  Exécutez cette requête, et regardez son plan d'exécution. Où est le
  problème ?
\end{itemize}

Cette requête a été exécutée dans un environnement où le cache a été
intégralement vidé, pour être dans la situation la plus défavorable
possible. Vous obtiendrez probablement des performances meilleures,
surtout si vous ré-exécutez cette requête.

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{colis }\KeywordTok{WHERE} \NormalTok{id_ville }\KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{id_ville }\KeywordTok{FROM} \NormalTok{villes }\KeywordTok{WHERE} \NormalTok{localite =}\StringTok{'PARIS'} \KeywordTok{AND} \NormalTok{codepostal }\KeywordTok{LIKE} \StringTok{'75%'}\NormalTok{);}
                                                             \KeywordTok{QUERY} \KeywordTok{PLAN}                                                             
\CommentTok{------------------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Nested} \KeywordTok{Loop}  \NormalTok{(cost=}\FloatTok{6.75}\NormalTok{..}\FloatTok{13533.81} \NormalTok{rows=}\DecValTok{3265} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{38.020}\NormalTok{..}\FloatTok{364383.516} \NormalTok{rows=}\DecValTok{170802} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{91539} \NormalTok{read=}\DecValTok{82652}
   \NormalTok{I/O Timings: read=}\FloatTok{359812.828}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1209.32} \NormalTok{rows=}\DecValTok{19} \NormalTok{width= (actual time=}\FloatTok{23.979}\NormalTok{..}\FloatTok{45.383} \NormalTok{rows=}\DecValTok{940} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{Filter: ((codepostal ~~ }\StringTok{'75%'}\NormalTok{:}\CharTok{:text}\NormalTok{) }\KeywordTok{AND} \NormalTok{(localite = }\StringTok{'PARIS'}\NormalTok{:}\CharTok{:text}\NormalTok{))}
         \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{54015}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{1} \NormalTok{read=}\DecValTok{384}
         \NormalTok{I/O Timings: read=}\FloatTok{22.326}
   \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Heap} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{colis  (cost=}\FloatTok{6.75}\NormalTok{..}\FloatTok{682.88} \NormalTok{rows=}\DecValTok{181} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{1.305}\NormalTok{..}\FloatTok{387.239} \NormalTok{rows=}\DecValTok{182} \NormalTok{loops=}\DecValTok{940}\NormalTok{)}
         \NormalTok{Recheck Cond: (id_ville = villes.id_ville)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{91538} \NormalTok{read=}\DecValTok{82268}
         \NormalTok{I/O Timings: read=}\FloatTok{359790.502}
         \NormalTok{->  }\KeywordTok{Bitmap} \KeywordTok{Index} \KeywordTok{Scan} \KeywordTok{on} \NormalTok{idx_colis_ville  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{6.70} \NormalTok{rows=}\DecValTok{181} \NormalTok{width=}\DecValTok{0}\NormalTok{) (actual time=}\FloatTok{0.115}\NormalTok{..}\FloatTok{0.115} \NormalTok{rows=}\DecValTok{182} \NormalTok{loops=}\DecValTok{940}\NormalTok{)}
               \KeywordTok{Index} \NormalTok{Cond: (id_ville = villes.id_ville)}
               \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2815} \NormalTok{read=}\DecValTok{476}
               \NormalTok{I/O Timings: read=}\FloatTok{22.862}
 \NormalTok{Total runtime: }\FloatTok{364466.458} \NormalTok{ms}
\NormalTok{(}\DecValTok{17} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

On constate que l'optimiseur part sur une boucle extrêmement coûteuse:
il fait 940 parcours sur colis, par id\_ville. En moyenne, ces parcours
durent environ 400ms. Le résultat est vraiment très mauvais.

Il fait ce choix parce qu'il estime que la condition
\texttt{localite\ =\textquotesingle{}PARIS\textquotesingle{}\ AND\ codepostal\ LIKE\ \textquotesingle{}75\%\textquotesingle{}}
va ramener 19 enregistrements. En réalité, elle en ramène 940, soit 50
fois plus, d'où un très mauvais choix. Pourquoi PostgreSQL fait-il cette
erreur ?

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{villes;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                          
\CommentTok{-------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{934.55} \NormalTok{rows=}\DecValTok{54955} \NormalTok{width=}\DecValTok{27}\NormalTok{)}
\NormalTok{(}\DecValTok{1} \NormalTok{ligne)}


\KeywordTok{explain} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{villes }\KeywordTok{WHERE} \NormalTok{localite=}\StringTok{'PARIS'}\NormalTok{;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                         
\CommentTok{------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1071.94} \NormalTok{rows=}\DecValTok{995} \NormalTok{width=}\DecValTok{27}\NormalTok{)}
   \NormalTok{Filter: (localite = }\StringTok{'PARIS'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}

\KeywordTok{explain} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{villes }\KeywordTok{WHERE} \NormalTok{codepostal }\KeywordTok{LIKE} \StringTok{'75%'}\NormalTok{;}
                         \KeywordTok{QUERY} \KeywordTok{PLAN}                          
\CommentTok{-------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1071.94} \NormalTok{rows=}\DecValTok{1042} \NormalTok{width=}\DecValTok{27}\NormalTok{)}
   \NormalTok{Filter: (codepostal ~~ }\StringTok{'75%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}


\KeywordTok{explain} \KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{villes }\KeywordTok{WHERE} \NormalTok{localite=}\StringTok{'PARIS'} \KeywordTok{AND} \NormalTok{codepostal }\KeywordTok{LIKE} \StringTok{'75%'}\NormalTok{;}
                               \KeywordTok{QUERY} \KeywordTok{PLAN}                               
\CommentTok{------------------------------------------------------------------------}
 \NormalTok{Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1209.32} \NormalTok{rows=}\DecValTok{19} \NormalTok{width=}\DecValTok{27}\NormalTok{)}
   \NormalTok{Filter: ((codepostal ~~ }\StringTok{'75%'}\NormalTok{:}\CharTok{:text}\NormalTok{) }\KeywordTok{AND} \NormalTok{(localite = }\StringTok{'PARIS'}\NormalTok{:}\CharTok{:text}\NormalTok{))}
\NormalTok{(}\DecValTok{2} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

D'après les statistiques, villes contient 54955 enregistrements, 995
contenant PARIS (presque 2\%), 1042 commençant par 75 (presque 2\%).

Il y a donc 2\% d'enregistrements vérifiant chaque critère (c'est
normal, ils sont presque équivalents). PostgreSQL, n'ayant aucune autre
information, part de l'hypothèse que les colonnes ne sont pas liées, et
qu'il y a donc 2\% de 2\% (soit environ 0,04\%) des enregistrements qui
vérifient les deux.

Si on fait le calcul exact, on a donc:
\texttt{(995/54955)*(1042/54955)*54955}, soit 18,8 enregistrements
(arrondi à 19) qui vérifient le critère. Ce qui est évidemment faux.

\begin{itemize}
\tightlist
\item
  Exécutez cette requête sans le
  \texttt{AND\ codepostal\ LIKE\ \textquotesingle{}75\%\textquotesingle{}}.
  Que constatez-vous ?
\end{itemize}

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{explain} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{select} \NormalTok{* }\KeywordTok{from} \NormalTok{colis }\KeywordTok{where} \NormalTok{id_ville }\KeywordTok{in} \NormalTok{(}\KeywordTok{select} \NormalTok{id_ville }\KeywordTok{from} \NormalTok{villes }\KeywordTok{where} \NormalTok{localite =}\StringTok{'PARIS'}\NormalTok{);}
                                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                                                        
\CommentTok{--------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Hash} \NormalTok{Semi }\KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1083.86}\NormalTok{..}\FloatTok{183312.59} \NormalTok{rows=}\DecValTok{173060} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{48.975}\NormalTok{..}\FloatTok{4362.348} \NormalTok{rows=}\DecValTok{170802} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (colis.id_ville = villes.id_ville)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{7} \NormalTok{read=}\DecValTok{54435}
   \NormalTok{I/O Timings: read=}\FloatTok{1219.212}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{colis  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{154053.55} \NormalTok{rows=}\DecValTok{9999955} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{6.178}\NormalTok{..}\FloatTok{2228.259} \NormalTok{rows=}\DecValTok{9999911} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{54052}
         \NormalTok{I/O Timings: read=}\FloatTok{1199.307}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1071.94}\NormalTok{..}\FloatTok{1071.94} \NormalTok{rows=}\DecValTok{954} \NormalTok{width= (actual time=}\FloatTok{42.676}\NormalTok{..}\FloatTok{42.676} \NormalTok{rows=}\DecValTok{940} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 37kB}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{383}
         \NormalTok{I/O Timings: read=}\FloatTok{19.905}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1071.94} \NormalTok{rows=}\DecValTok{954} \NormalTok{width= (actual time=}\FloatTok{35.900}\NormalTok{..}\FloatTok{41.957} \NormalTok{rows=}\DecValTok{940} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \NormalTok{Filter: (localite = }\StringTok{'PARIS'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
               \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{54015}
               \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{2} \NormalTok{read=}\DecValTok{383}
               \NormalTok{I/O Timings: read=}\FloatTok{19.905}
 \NormalTok{Total runtime: }\FloatTok{4375.105} \NormalTok{ms}
\NormalTok{(}\DecValTok{17} \NormalTok{lignes)}
\end{Highlighting}
\end{Shaded}

Cette fois-ci le plan est bon, et les estimations aussi

\begin{itemize}
\tightlist
\item
  Quelle solution pourrait-on adopter, si on doit réellement spécifier
  ces deux conditions ?
\end{itemize}

On pourrait indexer sur une fonction des deux. C'est maladroit, mais
malheureusement la seule solution sûre:

\begin{Shaded}
\begin{Highlighting}[]
\KeywordTok{CREATE} \KeywordTok{FUNCTION} \NormalTok{test_ville (ville text,codepostal text) RETURNS text IMMUTABLE LANGUAGE SQL}
\KeywordTok{as} \NormalTok{$$}
\KeywordTok{SELECT} \NormalTok{ville || }\StringTok{'-'} \NormalTok{|| codepostal}
\NormalTok{$$ ;}

\KeywordTok{CREATE} \KeywordTok{INDEX} \NormalTok{idx_test_ville }\KeywordTok{ON} \NormalTok{villes (test_ville(localite , codepostal));}

\KeywordTok{ANALYZE} \NormalTok{villes;}

\KeywordTok{EXPLAIN} \NormalTok{(}\KeywordTok{analyze}\NormalTok{,buffers) }\KeywordTok{SELECT} \NormalTok{* }\KeywordTok{FROM} \NormalTok{colis }\KeywordTok{WHERE} \NormalTok{id_ville }\KeywordTok{IN} \NormalTok{(}\KeywordTok{SELECT} \NormalTok{id_ville }\KeywordTok{FROM} \NormalTok{villes }\KeywordTok{WHERE} \NormalTok{test_ville(localite,codepostal) }\KeywordTok{like} \StringTok{'PARIS-75%'}\NormalTok{);}
                                                        \KeywordTok{QUERY} \KeywordTok{PLAN}                                                        
\CommentTok{--------------------------------------------------------------------------------------------------------------------------}
 \KeywordTok{Hash} \NormalTok{Semi }\KeywordTok{Join}  \NormalTok{(cost=}\FloatTok{1360.59}\NormalTok{..}\FloatTok{183924.46} \NormalTok{rows=}\DecValTok{203146} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{46.127}\NormalTok{..}\FloatTok{3530.348} \NormalTok{rows=}\DecValTok{170802} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
   \KeywordTok{Hash} \NormalTok{Cond: (colis.id_ville = villes.id_ville)}
   \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{454} \NormalTok{read=}\DecValTok{53989}
   \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{colis  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{154054.11} \NormalTok{rows=}\DecValTok{9999911} \NormalTok{width=}\DecValTok{16}\NormalTok{) (actual time=}\FloatTok{0.025}\NormalTok{..}\FloatTok{1297.520} \NormalTok{rows=}\DecValTok{9999911} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{66} \NormalTok{read=}\DecValTok{53989}
   \NormalTok{->  }\KeywordTok{Hash}  \NormalTok{(cost=}\FloatTok{1346.71}\NormalTok{..}\FloatTok{1346.71} \NormalTok{rows=}\DecValTok{1110} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{46.024}\NormalTok{..}\FloatTok{46.024} \NormalTok{rows=}\DecValTok{940} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
         \NormalTok{Buckets: }\DecValTok{1024}  \NormalTok{Batches: }\DecValTok{1}  \NormalTok{Memory Usage: 37kB}
         \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{385}
         \NormalTok{->  Seq }\KeywordTok{Scan} \KeywordTok{on} \NormalTok{villes  (cost=}\FloatTok{0.00}\NormalTok{..}\FloatTok{1346.71} \NormalTok{rows=}\DecValTok{1110} \NormalTok{width=}\DecValTok{8}\NormalTok{) (actual time=}\FloatTok{37.257}\NormalTok{..}\FloatTok{45.610} \NormalTok{rows=}\DecValTok{940} \NormalTok{loops=}\DecValTok{1}\NormalTok{)}
               \NormalTok{Filter: (((localite || }\StringTok{'-'}\NormalTok{:}\CharTok{:text}\NormalTok{) || codepostal) ~~ }\StringTok{'PARIS-75%'}\NormalTok{:}\CharTok{:text}\NormalTok{)}
               \KeywordTok{Rows} \NormalTok{Removed }\KeywordTok{by} \NormalTok{Filter: }\DecValTok{54015}
               \NormalTok{Buffers: }\KeywordTok{shared} \NormalTok{hit=}\DecValTok{385}
 \NormalTok{Total runtime: }\FloatTok{3543.838} \NormalTok{ms}
\end{Highlighting}
\end{Shaded}

On constate qu'avec cette méthode on n'a plus d'erreur d'estimation.
Elle est bien sûr très pénible à utiliser, et ne doit donc être réservée
qu'aux quelques rares requêtes ayant été identifiées comme ayant un
comportement pathologique.

On peut aussi créer une colonne supplémentaire maintenue par un trigger,
plutôt qu'un index: cela sera moins coûteux à maintenir, et permettra
d'avoir la même statistique.

\subsubsection{Conclusion}\label{conclusion-5}

Que peut on conclure de cet exercice?

\begin{itemize}
\tightlist
\item
  que la ré-écriture est souvent la meilleure des solutions:
  interrogez-vous toujours sur la façon dont vous écrivez vos requêtes,
  plutôt que de mettre en doute PostgreSQL \textbf{a priori}
\item
  que la ré-écriture de requête est souvent complexe. Néanmoins,
  surveillez un certain nombre de choses:

  \begin{itemize}
  \tightlist
  \item
    Casts implicites suspects
  \item
    Jointures externes inutiles
  \item
    Sous requêtes imbriquées
  \item
    Jointures inutiles (données constantes)
  \end{itemize}
\end{itemize}

\end{document}
