You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1312 lines
39 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

\documentclass[a4paper,11pt]{article}
\usepackage[utf8x]{inputenc}
\usepackage[T1]{fontenc}
\usepackage[french]{babel}
\usepackage[a4paper,hmargin=20mm,vmargin=30mm]{geometry}%\usepackage{fullpage}
\usepackage{lastpage}
\usepackage{tikz,pgflibraryarrows,pgffor,pgflibrarysnakes}
\usetikzlibrary{decorations.pathreplacing}
\usepackage{url}
\usepackage{comment}
\usepackage{amsmath}
\usepackage{amsthm}
\theoremstyle{definition}
\newtheorem{exemple}{Exemple}[section]
\newtheorem{exercice}{Exercice}[section]
\newtheorem{remarque}{Remarque}[section]
\newtheorem{definition}{Définition}[section]
\usepackage{makeidx}
\usepackage[columnsep=9pt]{idxlayout}
\usepackage{fancyvrb}
%\makeindex
\selectlanguage{french}
\usepackage[font=small,labelfont=bf,justification=centering]{caption}
\captionsetup[table]{name=Tableau}
\usepackage{latexsym}
\usepackage{amsfonts}
\usepackage[normalem]{ulem}
\usepackage{array}
\usepackage{amssymb}
\usepackage{graphicx}
\usepackage{subfig}
\usepackage{wrapfig}
\usepackage{wasysym}
\usepackage{enumitem}
\usepackage{adjustbox}
\usepackage{longtable}
\usepackage{changepage}
\usepackage{setspace}
\usepackage{hhline}
\usepackage{multicol}
\usepackage{float}
\usepackage{multirow}
\usepackage{slashbox}
\usepackage{color, colortbl}
\definecolor{Gray}{gray}{0.9}
\usepackage{fancyvrb}
\usepackage{fancyhdr}% fancy header
\usepackage{varwidth}
\usepackage{alltt}
\fancypagestyle{monstyle}{
%\fancyhead{}
\renewcommand{\headrulewidth}{1pt}
%% %\renewcommand{\footrulewidth}{0.4pt}
\newcommand{\proc}{Pro$^{*}$C}
% \fancyhead[LE]{\slshape \thepage/ \pageref{LastPage}}
%% \fancyhead[RO]{\slshape \thepage/ \pageref{LastPage}}
%\fancyhf{}
%\fancyhead[LE]{\slshape LE}
%\fancyhead[CE]{\slshape CE}
%\fancyhead[RE]{\slshape RE}
\fancyhead[LO]{\bfseries\rightmark}
%\fancyhead[CO]{\slshape APF}
\fancyhead[RO]{\bfseries\leftmark}
%% %\fancyfoot{}
% \fancyfoot[LE,RO]{}
\fancyfoot[CO,CE]{\slshape\thepage/\pageref{LastPage}}
%% %\fancyfoot[LO,RE]{\small\slshape \ddmmyyyydate version du \today}
}
% \pagestyle{fancy}
\pagestyle{monstyle}
\newcommand{\code}[1]{\texttt{#1}}
\usepackage{boxedminipage}
\newsavebox\svbx
\newif\ifcache
\long\def\cache#1{\ \newline
\setbox\svbx=\vbox{\leavevmode \newline \begin{spacing}{1}#1\end{spacing}}
\smallskip\par\noindent
\begin{boxedminipage}{\linewidth}
\ifcache
\leavevmode\hrule height 0pt\vskip \ht\svbx\hrule height 0pt
\else \unvbox\svbx
\fi
\end{boxedminipage}
\par\smallskip}
\cachefalse % version prof
%\cachetrue % version etudiant
\makeindex
\begin{document}
\begin{titlepage}
\begin{center}
\textsc{\Large IUT Informatique Aubière \hfill 2020 - 2021} \\[.5cm]
\hrule
\ \\[.5cm]
\vfill
\textsc{\LARGE Bases de données}
\vfill
\textsc{\LARGE \proc{}}
\vfill
{\Large Pascale \textsc{Brigoulet}, Franck \textsc{Glaziou}, }
{\Large Pascal \textsc{Lafourcade} et Marie-Fran\c{c}oise
\textsc{Servajean}}
\vfill
\begin{tikzpicture}
%%% un triangle
%% horizonatale
\draw[blue,line width=1pt] (-1,0) -- (1,0);
\draw[blue,line width=1pt] (-2.5,-.875) -- (1.5,-.875);
\draw[blue,line width=1pt] (-1.5,-1.75) -- (3,-1.75);
\draw[blue,line width=1pt] (0,3.5) -- (1,3.5);
%% Croissante
\draw[blue,line width=1pt] (0,0) -- (1,1.75);
\draw[blue,line width=1pt] (-2.5,-.875) -- (0,3.5);
\draw[blue,line width=1pt] (-1,0) -- (1,3.5);
\draw[blue,line width=1pt] (3,-1.75) -- (3.5,-0.75);
%% Decroissante
\draw[blue,line width=1pt] (0.5,.875) -- (1.5,-.875);
\draw[blue,line width=1pt] (1,1.75) -- (3,-1.75);
\draw[blue,line width=1pt] (1,3.5) -- (3.5,-0.75);
\draw[blue,line width=1pt] (-2.5,-.875) -- (-1.5,-1.75);
\end{tikzpicture}
%% \begin{tikzpicture}
%% %%% un cube
%% \draw[red,line width=1pt] (0,.5) -- (1,0);
%% \draw[red,line width=1pt] (2,.5) -- (1,0);
%% \draw[red,line width=1pt] (0,1.5) -- (1,1);
%% %\draw[red,line width=1pt] (0,1.5) -- (1,2);
%% \draw[red,line width=1pt] (0,1.5) -- (.5,1.75);
%% %\draw[red,line width=1pt] (2,1.5) -- (1,2);
%% \draw[red,line width=1pt] (2,1.5) -- (1.5,1.75);
%% \draw[red,line width=1pt] (2,1.5) -- (1,1);
%% \draw[red,line width=1pt] (1,1) -- (1,0);
%% \draw[red,line width=1pt] (0,1.5) -- (0,.5);
%% \draw[red,line width=1pt] (2,1.5) -- (2,.5);
%% %% droite
%% %\draw[red,line width=1pt] (1.5,1.25) -- (2.5,0.75);
%% \draw[red,line width=1pt] (2,1) -- (2.5,0.75);
%% \draw[red,line width=1pt] (3.5,1.25) -- (2.5,0.75);
%% \draw[red,line width=1pt] (1.5,2.25) -- (2.5,1.75);
%% \draw[red,line width=1pt] (1.5,2.25) -- (2.5,2.75);
%% \draw[red,line width=1pt] (3.5,2.25) -- (2.5,2.75);
%% \draw[red,line width=1pt] (3.5,2.25) -- (2.5,1.75);
%% \draw[red,line width=1pt] (2.5,1.75) -- (2.5,.75);
%% %\draw[red,line width=1pt] (1.5,2.25) -- (1.5,1.25);
%% \draw[red,line width=1pt] (1.5,2.25) -- (1.5,1.75);
%% \draw[red,line width=1pt] (3.5,2.25) -- (3.5,1.25);
%% %% Haut
%% \draw[red,line width=1pt] (0,2) -- (1,1.5);
%% %\draw[red,line width=1pt] (2,2) -- (1,1.5);
%% \draw[red,line width=1pt] (1.5,1.75) -- (1,1.5);
%% \draw[red,line width=1pt] (0,3) -- (1,2.5);
%% \draw[red,line width=1pt] (0,3) -- (1,3.5);
%% \draw[red,line width=1pt] (2,3) -- (1,3.5);
%% \draw[red,line width=1pt] (2,3) -- (1,2.5);
%% \draw[red,line width=1pt] (1,2.5) -- (1,1.5);
%% \draw[red,line width=1pt] (0,3) -- (0,2);
%% %\draw[red,line width=1pt] (2,3) -- (2,2);
%% \draw[red,line width=1pt] (2,3) -- (2,2.5);
%% \end{tikzpicture}
\vfill
\includegraphics[width=5cm]{iut-uca.png}
\end{center}
\vfill
{\Large Nom : \\
Prénom : \\
Groupe : \\
}
%\url{http://mocodo.wingi.net/}
%\url{http://mirror.hmc.edu/ctan/graphics/pgf/contrib/tkz-orm/tkz-orm.pdf}
\end{titlepage}
%% \section*{Avant Propos}
%% Lobjectif de ce cours de base de données avancées est de
%% présenter
%% \newpage
\tableofcontents
\newpage
\section*{Interface \proc{}}
\proc{} est une interface entre le langage C et le SGBD Oracle. Il
s'agit d'une interface de précompilation, c'est-à-dire que le
programmeur écrit ses ordres SQL dans son code C et ceux-ci sont
traduits par un précompilateur. Le rôle du précompilateur est de
traduire un programme comprenant des commandes SQL en un programme ne
comprenant que des instructions du langage C et pouvant accéder à la
base de données (voir Figure~\ref{fig:proc}). Il s'agit de remplacer
les commandes SQL par des appels à des modules Oracle combinant ainsi
les avantages d'un langage procédural C à ceux d'un langage non
procédural SQL.
\begin{figure}[htb]
\begin{center}
\includegraphics[width=12cm]{proc.png}
\end{center}
\caption{Illustration du processus permettant d'utiliser un fichier \proc{}}\label{fig:proc}
\end{figure}
La précompilation, la compilation et l'édition de liens de tels programmes sont masquées par lexécution dune commande sur les fichiers sources qui doivent être suffixés par « \code{.pc} ».
\section{Structure dun programme \proc{}}
Un fichier \proc{} se structure de la même manière quun fichier C. En
plus des commandes SQL, il faut simplement penser à la connexion et la
déconnexion à la base de données. Il est donc important de préparer la
liaison entre C et Oracle. Pour cela il faut déclarer un ensemble de
variables de communication et une zone de communication pour récupérer
les comptes rendus Oracle.
\subsection{La section \code{INCLUDE} : (même fonction que le include du C).}
Le fichier \code{SQLCA} permet de connaître le résultat d'une commande
\proc{} (erreur, nombre de lignes résultant de la requête, ...). La
syntaxe est la suivante : \code{EXEC SQL INCLUDE SQLCA.H;}
\subsection{La déclaration de variables}
La déclaration des variables hôtes, c'est-à-dire les variables
utilisées dans les commandes SQL, seffectue exactement de la même
manière quen C.
\begin{verbatim}
int pemno;
VARCHAR pname[11];
int pdeptno;
\end{verbatim}
Il est important de noter quune variable hôte :
\begin{itemize}
\item doit être précédée de '\code{:}' lorsqu'elle est utilisée (sauf
lors de la déclaration),
\item ne doit pas être un mot réservé SQL.
\end{itemize}
\proc{} permet l'utilisation du type \code{VARCHAR} pour travailler
avec les chaînes de caractères de longueur variable. Cest le seul
type SQL qui peut être utilisé. Il est important de toujours choisir
avec attention les types C car ils vont contenir les informations
issues des types SQL. Par exemple, \code{NUMBER(8,2)} ne peut pas
tenir dans un \code{int}. Il est important de noter que chaque
variable de type \code{VARCHAR} va être traduite par le précompilateur
par une structure C. \code{VARCHAR poste[40];} Le code ci-dessus va
être traduit ainsi :
\begin{verbatim}
struct {
unsigned int len; /* longueur de la chaîne */
unsigned char arr[40]; /* la chaîne elle-même */
} poste ;
\end{verbatim}
Lutilisation de \code{VARCHAR} permet donc de sabstraire de la
gestion du \verb+\0+ qui nest pas reconnu par Oracle. Ainsi la
longueur de la chaîne est celle située dans l'entier \code{len}. En
sortie d'une commande, Oracle complète la chaîne avec des espaces à
droite et met à jour la longueur. Pour entrer une commande, il faut
faire attention \`a cela.
\subsection{La connexion à la base de données}
Le plus simple est souvent de définir une fonction \`a réutiliser à
chaque fois.
\begin{verbatim}
void connexion()
{ VARCHAR uid[50];
char login[20];
char passwd[20];
printf("Donner votre login : ");
scanf("%s",login);
printf("\nDonnez votre mot de passe Oracle : ");
scanf("%s",passwd);
printf("\n");
strcpy(uid.arr,login);
strcat(uid.arr,"/");
strcat(uid.arr,passwd);
strcat(uid.arr,"@kirov");
uid.len=strlen(uid.arr);
EXEC SQL CONNECT :uid;
if (sqlca.sqlcode==0)
printf(" Connexion réussie avec succès.\n\n");
else
{
printf ("Problème à la connexion.\n\n");
exit(1);
}
}
\end{verbatim}
Pour la déconnexion, le même principe est à utiliser. Cela permet
dappeler la fonction déclarée à chaque fois que nécessaire. Il est
possible de pr\'evoir le cas où il faut valider les transactions et
les cas où il faut les annuler.
\begin{verbatim}
void deconnexion(int validation)
{
if (validation == 1)
{
EXEC SQL COMMIT WORK RELEASE;
} else
{
EXEC SQL ROLLBACK WORK RELEASE;
}
printf("Déconnexion sans problème.\n");
}
\end{verbatim}
\subsection{Le corps de l'application}
Il contient essentiellement des ordres SQL aussi bien pour la
manipulation de données que pour la définition des données. La syntaxe
utilisée est la même que celle de PL/SQL avec en plus l'utilisation
possible de variables hôtes partout où une constante peut être
employée (nom de relation, nom d'attribut,...).
\begin{verbatim}
EXEC SQL CREATE TABLE empTest(empno NUMBER(2));
EXC SQL INSERT INTO tligne VALUES('exception produit inexistant');
EXEC SQL SELECT job INTO :function FROM emp WHERE noemp=301;
EXEC SQL UPDATE emp SET sal = :sal WHERE noemp = :noemp;
EXEC SQL DELETE FROM emp WHERE noemp = :noemp;
\end{verbatim}
Toutes les sortes de requêtes peuvent être utilisées. Les requêtes
\code{SELECT} suivent exactement les mêmes règles quen PL/SQL. Dans
lexemple précédent, le \code{SELECT} doit retourner une et une seule
ligne. Lutilisation des curseurs pour les multi-lignes est présentée
après.
\begin{exercice}~
\begin{enumerate}
\item \'Ecrire un programme Pro-C qui se connecte à Kirov puis affiche
le nombre de produits contenus dans la base de données, puis se
déconnecte.
\begin{SaveVerbatim}{countprod}
int main(void) {
int nbProd;
EXEC SQL WHENEVER SQLERROR DO sql_error(``Oracle error \n'');
connexion();
EXEC SQL SELECT COUNT(*) INTO :nbProd FROM TPRODUIT;
printf("Il y a %d produits en base.\n", nbProd);
deconnexion();
}
\end{SaveVerbatim}
\cache{\tiny \BUseVerbatim{countprod}}
\item \'Ecrire un programme Pro-C qui demande à lutilisateur un
produit et lajoute à la BD.
\begin{SaveVerbatim}{addprod}
void q2(void) {
char codeRayon[REFERENCE_LEN], dateStock[DATE_LEN], nCodeRayon[REFERENCE_LEN],
nDateStock[DATE_LEN], noProd[REFERENCE_LEN];
int choix, nStock, stock;
float nPrixV, prixV;
VARCHAR des[VARCHAR_LEN], nDes[VARCHAR_LEN];
printf("Saisir numéro produit : ");
scanf("%s%*c", noProd);
EXEC SQL SELECT des, stock, prixV, codeRayon, dateStock
INTO :des, :stock, :prixV, :codeRayon, :dateStock
FROM TPRODUIT
WHERE noProd = :noProd;
printf("1 - Désignation : %.*s.\n", des.len, des.arr);
printf("2 - Stock : %d.\n", stock);
printf("3 - Prix de vente : %.2f euro.\n", prixV);
printf("4 - Code rayon : %s.\n", codeRayon);
printf("5 - Date : %s.\n", dateStock);
while (choix != 9) {
printf("Que voulez-vous modifier (9: quitter) ?\n");
scanf("%d%*c", &choix);
switch (choix) {
case 1:
printf("Entrez la nouvelle désignation : ");
fgets(nDes, sizeof nDes, stdin);
if (nDes[strlen(nDes) - 1] == '\n') {
nDes[strlen(nDes) - 1] = '\0';
}
EXEC SQL UPDATE TPRODUIT SET des = :nDes WHERE noProd = :noProd;
if (sqlca.sqlcode < SQL_SUCCESS) {erreur_sql(STOP);}
break;
case 2:
printf("Entrez le nouveau stock : ");
scanf("%d%*c", &nStock);
EXEC SQL UPDATE TPRODUIT SET stock = :nStock WHERE noProd = :noProd;
if (sqlca.sqlcode < SQL_SUCCESS) {erreur_sql(STOP);}
break;
case 3:
printf("Entrez le nouveau prix : ");
scanf("%f%*c", &nPrixV);
EXEC SQL UPDATE TPRODUIT SET prixV = :nPrixV WHERE noProd = :noProd;
if (sqlca.sqlcode < SQL_SUCCESS) {erreur_sql(STOP);}
break;
case 4:
printf("Entrez le nouveau code rayon : ");
scanf("%s", &nCodeRayon);
EXEC SQL UPDATE TPRODUIT SET codeRayon = :nCodeRayon WHERE noProd = :noProd;
if (sqlca.sqlcode < SQL_SUCCESS) {erreur_sql(STOP);} break;
case 5:
printf("Entrez la date : ");
scanf("%s", &nDateStock);
EXEC SQL UPDATE TPRODUIT SET dateStock = :nDateStock WHERE noProd = :noProd;
if (sqlca.sqlcode < SQL_SUCCESS) {erreur_sql(STOP);}
break;
default:
printf("Choix inconnu.\n");
break; }
EXEC SQL COMMIT;}}
\end{SaveVerbatim}
\cache{\scriptsize \BUseVerbatim{addprod}}
\end{enumerate}
\end{exercice}
\subsection{Le traitement des erreurs}
\subsubsection{Le fichier \code{SQLCA.H}}
Il définit une structure de données '\code{sqlca}' dont les champs
sont mis à jour après chaque exécution d'un ordre SQL et qui en donne
le compte rendu.
\begin{verbatim}
struct {
long sqlcode; /* code resultant de l'exécution
=0 -> ok,
>0 -> ok avec un code d'état,
<0 -> erreur */
struct {
unsigned short sqlerrml; /*longueur du message*/
char sqlerrmc[70]; /*message d'erreur*/
} sqlerrm;
long sqlerrd[6]; /* seul sqlerrd[2] est utilisé -> donne le nombre de lignes modifiées
UPDATE ou rajoutées par INSERT ou ramenées par un SELECT*/
char sqlwarn[8]; /*sqlwarn[0] 'W -> warning*/
sqlwarn[0] = '' /*-> pas de warning*/
sqlwarn[1] = 'W' /*-> troncation numérique ou char*/
sqlwarn[2] = 'W' /*-> valeur Null est ignore*/
sqlwarn[3] = 'W' /*-> plus de champs dans SELECT que de variables pour recevoir*/
sqlwarn[4] = 'W' /*-> toutes les lignes d'une table sont touchées (par DELETE ou UPDATE par
exemple)*/
sqlwarn[5] /*inutilisé*/
sqlwarn[6] = 'W' /*-> Oracle a dû exécuter un rollback*/
sqlwarn[7] = 'W' /*-> la donnée ramenée par un FETCH a été modifié depuis que la clause
SELECT a été execute*/
} sqlca;
\end{verbatim}
Après chaque commande SQL il est possible de tester le champ de
\code{sqlca} correspondant à une erreur possible dans le cadre de
cette commande.
\subsection{La commande \code{WHENEVER}}
Oracle permet d'utiliser des directives qui spécifient le traitement à
effectuer en cas d'erreur.
\begin{verbatim}
EXEC SQL WHENEVER [SQLERROR|SQLWARNING|NOT FOUND][STOP|CONTINUE|GOTO <label>|DO <fonction>];
\end{verbatim}
La valeur du premier paramètre est le type d'erreur à tester :
\begin{verbatim}
SQLERROR <=> sqlca.sqlcode<0
SQLWARNING <=> sqlca.sqlwarn[0]='W'
NOT FOUND <=> sqlca.sqlcode=+1403 (not row found)
\end{verbatim}
La valeur du deuxième paramètre est le type d'action à entreprendre
dans le cas où il y a erreur :
\begin{itemize}
\item \code{STOP} cause l'arrêt du programme avec \code{ROLLBACK};
\item \code{CONTINUE} ignore l'erreur et continue le programme;
\item \code{GOTO <label>} passe le contrôle du programme au \code{<label>};
\item \code{DO <fonction>} appelle une fonction.
\end{itemize}
La commande \code{WHENEVER} doit précéder la commande SQL à tester.
\begin{verbatim}
EXEC SQL WHENEVER SQLERROR GOTO labx;
EXEC SQL SELECT ...
...
labx : printf ("Programme terminé sur ERREUR ! \n");
printf ("Le sqlcode est %d \n", sqlca.sqlcode);
printf ("Le message SQL est : %70s",sqlca.sqlerrm.sqlerrmc);
EXEC SQL ROLLBACK WORK RELEASE;
exit(1);
\end{verbatim}
\begin{remarque} Pour garder une programmation structurée, il est préférable
de tester, après chaque commande SQL, la valeur du sqlca.sqlcode
plutôt que d'utiliser la commande \code{WHENEVER}.
\end{remarque}
\begin{exercice} Rajouter aux 2 exercices précédents la gestion des erreurs.
\begin{enumerate}
\item \'Ecrire un programme Pro-C qui affiche le nombre de produits
contenus dans la BD.
\begin{SaveVerbatim}{produits}
void q3(void) {
char noProd[REFERENCE_LEN], codeRayon[REFERENCE_LEN], dateStock[DATE_LEN];
int cpt = 0, nbProd, stock;
float prixV;
VARCHAR des[REFERENCE_LEN1];
nbProd = q1bis();
if (nbProd > 0) {
EXEC SQL
DECLARE cprod CURSOR FOR
SELECT noProd, des, stock, prixV, codeRayon, dateStock
FROM TPRODUIT;
EXEC SQL OPEN cprod;
EXEC SQL FETCH cprod INTO :noProd, :des, :stock, :prixV, :codeRayon, :dateStock;
while (sqlca.sqlcode != NOT_FOUND) {
printf("----- Produit n°%d -----\n", ++cpt);
printf("Numéro produit : %s.\n", noProd);
printf("Désignation : %.*s.\n", des.len, des.arr);
printf("Stock : %d.\n", stock);
printf("Prix de vente : %.2f.\n", prixV);
printf("Code rayon : %s.\n", codeRayon);
printf("Date stock : %s.\n", dateStock);
EXEC SQL
FETCH cprod INTO :noProd, :des, :stock, :prixV, :codeRayon, :dateStock;
if (sqlca.sqlcode < SQL_SUCCESS) {erreur_sql(STOP);
} }
EXEC SQL CLOSE cprod;
}}
\end{SaveVerbatim}
\cache{\BUseVerbatim{produits}}
\item \'Ecrire un programme Pro-C qui demande à lutilisateur un
produit et lajoute à la BD.
\begin{SaveVerbatim}{produitserr}
void q4(void) {
char choix, noProd[REFERENCE_LEN], dateStock[DATE_LEN];
int stock; float prixV;
short indicateurDes = 0, indicateurStock = 0, indicateurPrixV = 0, indicateurCodeRayon = 0, indicateurDateStock = 0;
VARCHAR des[VARCHAR_LEN];
printf("Saisir numéro produit : ");
scanf("%s%*c", noProd);
printf("Saisir une désignation (O/*) ? ");
scanf("%c%*c", &choix);
if (toUpper(choix) == 'O') {
printf("Saisir la désignation : ");
fgets(des, sizeof des, stdin);
if (des[strlen(des) - 1] == '\n') {
des[strlen(des) - 1] = '\0';
}
}
else {indicateurDes = -1; }
printf("Saisir un stock (O/*) ? ");
scanf("%c%*c", &choix);
if (toUpper(choix) == 'O') {
printf("Saisir le stock : ");
scanf("%d%*c", &stock);
}
else { indicateurStock = -1;}
printf("Saisir un prix de vente : ");
scanf("%c%*c", &choix);
if (toUpper(choix) == 'O') {
printf("Saisir le prix de vente : ");
scanf("%f%*c", &prixV);
}
else { indicateurPrixV = -1;}
printf("Saisir un code rayon (O/*) ? ");
scanf("%c%*c", &choix);
if (toUpper(choix) == 'O') {
printf("Saisir le code rayon : ");
scanf("%d%*c", codeRayon);
}
else {indicateurCodeRayon = -1;}
if (indicateurStock == 0) {
EXEC SQL SELECT SYSDATE INTO :dateStock FROM DUAL;
}
else { indicateurDateStock = -1;}
EXEC SQL INSERT INTO TPRODUIT
VALUES(:noProd,
:des INDICATOR :indicateurDes,
:stock INDICATOR :indicateurStock,
:codeRayon INDICATOR :indicateurCodeRayon,
:dateStock INDICATOR :indicateurDateStock);
EXEC SQL COMMIT;
}
\end{SaveVerbatim}
\cache{\small \BUseVerbatim{produitserr}}
\end{enumerate}
\end{exercice}
\subsubsection{Les indicateurs de variables}
À chaque variable hôte il est possible d'associer un indicateur de
variable pour connaître ses valeurs particulières. Il est utilisé
essentiellement pour travailler avec les valeurs \code{NULL}. Par
exemple lors d'une requête SQL, il indique si le contenu de la
variable associée est \code{NULL} car il nest pas possible de tester
en C si une variable est nulle (cette variable nest pas un pointeur).
Un indicateur de variable hôte :
\begin{itemize}
\item doit être du type \code{short};
\item doit être précédé de '\code{:}' lorsqu'il est utilisé dans une
requête SQL;
\item ne doit pas être un mot réservé SQL;
\item doit être précédé par une variable hôte dans un ordre SQL.
\end{itemize}
Une variable indicateur a pour valeur :
\begin{itemize}
\item $0$ $\Rightarrow$ de problème (variable \code{NOT NULL} et non tronquée),
\item $-1$ $\Rightarrow$ la valeur de la variable hôte associée est
\code{NULL},
\item $>0$ $\Rightarrow$ la valeur retournée dans la variable hôte est
tronquée.
\end{itemize}
Une variable indicateur peut être utilisée en entrée ou en sortie d'une commande SQL. En sortie elle indique le contenu effectif de la variable associée; en entrée elle permet de donner la valeur NULL à une variable hôte pour pouvoir, par exemple, insérer des valeurs NULL dans une relation.
\begin{verbatim}
EXEC SQL SELECT nomemp INTO :nom:indicateur FROM emp;
IF (indicateur == -1) {
//Le nom est NULL dans la base
}
\end{verbatim}
Dans un \code{INSERT}, il faut penser à utiliser le mot clef \code{INDICATOR :
indicateur = -1;}
\begin{verbatim}
EXEC SQL INSERT INTO emp VALUES(:nom INDICATOR :indicateur);
//Quelque soit le contenu de la variable nom, nous avons inséré NULL
\end{verbatim}
\subsection{Les \code{SELECT} multi-lignes}
Dans le cas où la requête retournera un certain nombre de lignes, ou
dans le cas où seules les x premières lignes sont souhait\'ees, il est
possible d'utiliser les variables tableaux.
\begin{verbatim}
VARCHAR nom[100][10];
int empno[100];
float salaire[100];
...
EXEC SQL SELECT nomemp, noemp, sal INTO :nom, :empno, :salaire FROM emp;
\end{verbatim}
Cette requête retournera au maximum 100 lignes dans les 100 éléments
des tableaux. Le nombre de lignes retournées se trouve dans
\code{sqlca.sqlerrd[2]}. S'il y a plus de 100 lignes et si la même
requête est exécut\'ee une deuxième fois alors les mêmes lignes seront
r\'ecup\'er\'ees. La clause \code{FOR} permet d'utiliser un nombre
d'éléments différents de la dimension du tableau.
\begin{verbatim}
EXEC SQL BEGIN DECLARE SECTION;
int nbMax;
EXEC SQL END DECLARE SECTION;
nbMax = 9;
EXEC SQL FOR :nbMax UPDATE TIMM2018 SET deptimm = '01';
\end{verbatim}
Cette requête ne met \`a jour que les 9 lignes.
Dans le cas général où on ne connaît pas le nombre de lignes résultant d'une requête, on doit utiliser un curseur.
Comme en PL/SQL, pour utiliser un curseur il y a 4 commandes \proc{} :
\begin{itemize}
\item Le curseur est d\'efini par son nom et par la requête qui lui
est associée.
\verb+EXEC SQL DECLARE <nom_curseur> CURSOR FOR SELECT ...FROM ...;+
\item L'ouverture d'un curseur implique la création de la table contenant le résultat de la requête associée à ce curseur. De plus le curseur est positionné sur la première ligne de cette table.
\verb+EXEC SQL OPEN <nom_curseur>;+
\item L'instruction \code{FETCH} renvoie dans les variables hôtes les valeurs des attributs de la ligne de la table de travail sur lequel est positionné le curseur puis positionne le curseur sur la prochaine ligne de la table.
\verb+EXEC SQL FETCH <nom curseur> INTO <liste de variables hôtes>;+
Si lors d'une instruction \code{FETCH}, il n'y a plus de lignes dans
la table ou si la table est vide, \code{sqlca.sqlcode} prend la valeur
\code{+1403} qu'il est possible de tester avec l'instruction :
\verb+EXEC SQL WHENEVER NOT FOUND.+
\item Enfin, linstruction \code{CLOSE} ferme le curseur. Toute instruction \code{FETCH} qui accède à un curseur fermé produit une erreur.
\verb+EXEC SQL CLOSE <nom du curseur>;+
\end{itemize}
\begin{exercice}
\begin{enumerate}
\item \'Ecrire un programme Pro-C qui liste les produits dun
fournisseur saisie par lutilisateur.
\begin{SaveVerbatim}{produitfourn}
void q5(void) {
char noProd[REFERENCE_LEN], refFourn[REFERENCE_LEN];
printf("Saisir référence fournisseur : ");
scanf("%s%*c", refFourn);
EXEC SQL DECLARE cprod CURSOR FOR
SELECT noProd
FROM TPRODUITFOURN
WHERE pf.refFourn = :refFourn;
EXEC SQL OPEN cprod;
EXEC SQL FETCH cprod INTO :noProd;
if (sqlca.sqlcode == NOT_FOUND) {
printf("Aucun produit trouvé !\n");
}
printf("Liste des numéros de produits pour ce fournisseur :\n");
while (sqlca.sqlcode != NOT_FOUND) {
printf("\t%s\n", noProd);
EXEC SQL FETCH cprod INTO :noProd;
}
EXEC SQL CLOSE cprod;
}
\end{SaveVerbatim}
\cache{\BUseVerbatim{produitfourn}}
\item \'Ecrire un programme Pro-C qui ajoute un fournisseur.
\begin{SaveVerbatim}{addfourn}
void q6(void) {
char refFourn[REFERENCE_LEN], dept[DEPT_LEN];
VARCHAR nom[NOM_LEN];
printf("Saisir la référence fournisseur : ");
scanf("%s%*c", refFourn);
printf("Saisir le département : ");
scanf("%s%*c", dept);
printf("Saisir le nom du fournisseur : ");
fgets(nom, sizeof nom, stdin);
if (nom[strlen(nom) - 1] == '\n') {
nom[strlen(nom) - 1] = '\0';
}
EXEC SQL INSERT INTO TFOURNISSEUR VALUES(:refFourn, :nom, :dept);
EXEC SQL COMMIT;
}
\end{SaveVerbatim}
\cache{\BUseVerbatim{addfourn}}
\item \'Ecrire un programme Pro-C qui efface un fournisseur demandé à
lutilisateur et tous les produits dun fournisseur.
\begin{SaveVerbatim}{deletefourn}
void q7(void) {
char refFourn[REFERENCE_LEN];
printf("Saisir la référence du fournisseur à supprimer : ");
scanf("%s%*c", refFourn);
EXEC SQL DELETE FROM TPRODUITFOURN WHERE refFourn = :refFourn;
EXEC SQL DELETE FROM TFOURNISSEUR WHERE refFourn = :refFourn;
}
\end{SaveVerbatim}
\cache{\BUseVerbatim{deletefourn}}
\item \'Ecrire un programme Pro-C qui modifie un produit, en
commencant par afficher les anciennes valeurs.
\begin{SaveVerbatim}{modifyfourn}
void q6(void) {
char refFourn[REFERENCE_LEN], dept[DEPT_LEN];
VARCHAR nom[NOM_LEN];
printf("Saisir la référence fournisseur : ");
scanf("%s%*c", refFourn);
printf("Saisir le département : ");
scanf("%s%*c", dept);
printf("Saisir le nom du fournisseur : ");
fgets(nom, sizeof nom, stdin);
if (nom[strlen(nom) - 1] == '\n') {
nom[strlen(nom) - 1] = '\0';
}
EXEC SQL INSERT INTO TFOURNISSEUR VALUES(:refFourn, :nom, :dept);
EXEC SQL COMMIT;
}
\end{SaveVerbatim}
\cache{\BUseVerbatim{modifyfourn}}
\end{enumerate}
\end{exercice}
\begin{exercice}[Indicator]
\begin{enumerate}
\item Afficher les num\'eros de produits et les prix pour un code de rayon
saisi par l'utilisateur. Si \code{prix=NULL} afficher ``le prix
non saisi''.
\end{enumerate}
\begin{SaveVerbatim}{indic}
char noProd[REFERENCE_LEN], refFourn[REFERENCE_LEN];
printf("Saisir référence fournisseur : ");
scanf("%s%*c", refFourn);
EXEC SQL DECLARE cprod CURSOR FOR
SELECT noProd
FROM TPRODUITFOURN
WHERE pf.refFourn = :refFourn;
EXEC SQL OPEN cprod;
EXEC SQL FETCH cprod INTO :noProd;
if (sqlca.sqlcode == NOT_FOUND) {
printf("Aucun produit trouvé !\n");
}
printf("Liste des numéros de produits pour ce fournisseur :\n");
while (sqlca.sqlcode != NOT_FOUND) {
printf("\t%s\n", noProd);
EXEC SQL FETCH cprod INTO :noProd;
}
EXEC SQL CLOSE cprod;
}
\end{SaveVerbatim}
\cache{\BUseVerbatim{produitfourn}}
\begin{enumerate}\setcounter{enumi}{1}
\item \'Ecrire un programme Pro-C qui ajoute un fournisseur.
\end{enumerate}
\begin{SaveVerbatim}{addfourn}
void qindic(void) {
VARCHAR noproduit[6];
VARCHAR dcode[8];
int dprix;
short indicprix;
printf("Saisir le code du rayon : ");
scanf("%s", dcode.arr);
code.len=strlen(dcode.arr);
EXEC SQL
DECLARE ccode CURSOR FOR
SELECT noProd, prix
FROM TPRODUIT
WHERE code=dcode;
EXEC SQL OPEN ccode;
EXEC SQL FETCH ccode INTO :dnoproduit, :dprix:indicprix;
while (sqlca.sqlcode != NOT_FOUND)
{
printf(``Le no produit est %s'',dnoproduit.arr);
IF (indicprix==-1){
printf("Le prix n est pas rentre'')
}
else
{
printf(``le prix est :%d'',dprix);
}
EXEC SQL FETCH ccode INTO :noProd, :prix:indicprix;
}
EXEC SQL CLOSE ccode;
}
\end{SaveVerbatim}
\cache{\BUseVerbatim{indic}}
\begin{enumerate}\setcounter{enumi}{2}
\item Afficher par \'etages la liste des num\'ero des produits avec leur
designation. Si designation est null alors afficher ``Pas de
designation''.
\end{enumerate}
\begin{verbatim}
Etage 1
Designation YY n produit XXX
Designation YY n produit XXX
Etage 2
Designation YY n produit XXX
Designation YY n produit XXX
...
\end{verbatim}
\end{exercice}
\section{Exemple}
\begin{verbatim}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define size_t long
EXEC SQL INCLUDE SQLCA.H;
EXEC SQL INCLUDE SQLDA.H;
EXEC SQL INCLUDE ORACA.H;
void connexion()
{ VARCHAR uid[50];
char login[20];
char passwd[20];
printf("Donner votre login : ");
scanf("%s",login);
printf("\nDonnez votre mot de passe Oracle : ");
scanf("%s",passwd);
printf("\n");
strcpy(uid.arr,login);
strcat(uid.arr,"/");
strcat(uid.arr,passwd);
strcat(uid.arr,"@kirov");
uid.len=strlen(uid.arr);
EXEC SQL CONNECT :uid;
if (sqlca.sqlcode==0)
printf(" Connexion réussie avec succès.\n\n");
else
{
printf ("Problème à la connexion.\n\n");
exit(1);
}
}
void deconnexion(int validation)
{
if (validation == 1)
{
EXEC SQL COMMIT WORK RELEASE;
} else {
EXEC SQL ROLLBACK WORK RELEASE;
}
printf("Déconnexion sans problème.\n");
}
void sql_error(char *msg)
{
char err_msg[128];
long buf_len, msg_len;
EXEC SQL WHENEVER SQLERROR CONTINUE;
printf("%s\n", msg);
buf_len = sizeof (err_msg);
sqlglm(err_msg, &buf_len, &msg_len);
if (msg_len > buf_len)
msg_len = buf_len;
printf("%.*s\n", msg_len, err_msg);
deconnexion(0);
exit(1);
}
int main(int argc, char** argv)
{
VARCHAR msg_buf[51];
EXEC SQL WHENEVER SQLERROR DO sql_error("Oracle error\n");
connexion();
EXEC SQL CREATE TABLE hello_world(msg VARCHAR2(50));
EXEC SQL INSERT INTO hello_world VALUES ('Hello world!');
EXEC SQL COMMIT;
EXEC SQL SELECT msg INTO :msg_buf FROM hello_world WHERE rownum <= 1;
printf("%.*s\n", msg_buf.len,msg_buf.arr);
deconnexion(1);
return(0);
}
\end{verbatim}
\section{Extrait de la traduction}
\begin{verbatim}
void connexion()
{ /* VARCHAR uid[50]; */
struct { unsigned short len; unsigned char arr[50]; } uid;
char login[20];
char passwd[20];
printf("Donner votre login : ");
scanf("%s",login);
printf("\nDonnez votre mot de passe oracle : ");
scanf("%s",passwd);
printf("\n");
strcpy(uid.arr,login);
strcat(uid.arr,"/");
strcat(uid.arr,passwd);
strcat(uid.arr,"@kirov");
uid.len=strlen(uid.arr);
{ /* EXEC SQL CONNECT :uid; */
struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 4;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.iters = (unsigned int)10;
sqlstm.offset = (unsigned int)5;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short) 4352;
sqlstm.occurs = (unsigned int) 0;
sqlstm.sqhstv[0] = (unsigned char *)&uid;
sqlstm.sqhstl[0] = (unsigned long ) 52;
sqlstm.sqhsts[0] = (int) 52;
sqlstm.sqindv[0] = (short *) 0;
sqlstm.sqinds[0] = (int) 0;
sqlstm.sqharm[0] = (unsigned long) 0;
sqlstm.sqadto[0] = (unsigned short) 0;
sqlstm.sqtdso[0] = (unsigned short) 0;
sqlstm.sqphsv = sqlstm.sqhstv;
sqlstm.sqphsl = sqlstm.sqhstl;
sqlstm.sqphss = sqlstm.sqhsts;
sqlstm.sqpind = sqlstm.sqindv;
sqlstm.sqpins = sqlstm.sqinds;
sqlstm.sqparm = sqlstm.sqharm;
sqlstm.sqparc = sqlstm.sqharc;
sqlstm.sqpadto = sqlstm.sqadto;
sqlstm.sqptdso = sqlstm.sqtdso;
sqlstm.sqlcmax = (unsigned int) 100;
sqlstm.sqlcmin = (unsigned int) 2;
sqlstm.sqlcincr = (unsigned int)1;
sqlstm.sqlctimeout = (unsigned int) 0;
sqlstm.sqlcnowait = (unsigned int) 0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
}
if (sqlca.sqlcode==0)
printf(" Connexion réussie avec succès.\n\n");
else
{
printf ("Problème à la connexion.\n\n");
exit(1);
}
}
...
/* EXEC SQL CREATE TABLE hello_world(msg VARCHAR2(50)); */
{
struct sqlexd sqlstm;
sqlstm.sqlvsn = 12;
sqlstm.arrsiz = 4;
sqlstm.sqladtp = &sqladt;
sqlstm.sqltdsp = &sqltds;
sqlstm.stmt = "create TABLE hello_world (msg VARCHAR2(50))";
sqlstm.iters = (unsigned int) 1;
sqlstm.offset = (unsigned int) 81;
sqlstm.cud = sqlcud0;
sqlstm.sqlest = (unsigned char *)&sqlca;
sqlstm.sqlety = (unsigned short) 4352;
sqlstm.occurs = (unsigned int) 0;
sqlcxt((void **)0, &sqlctx, &sqlstm, &sqlfpn);
if (sqlca.sqlcode < 0) sql_error("ORACLE error\n");
}
\end{verbatim}
%% \begin{exercice}
%% Vérifier que la requête suivante qui donne tous les enfants dont la
%% mère s'appelle 'Annie' ou le père est né avant le 1er janvier 1950
%% est bien écrite.
%% \begin{verbatim}
%% SELECT i.id_ind, i.prenom||' '||i.nom||' est l''enfant de
%% '||ip.prenom||' '||ip.nom||' et
%% '||im.prenom||' '||im.nom as "parents"
%% FROM individu i, parent p, individu ip, parent m, individu im
%% WHERE p.id_enfant=i.id_ind
%% AND m.id_enfant=i.id_ind
%% AND ip.id_ind=p.id_parent
%% AND im.id_ind=m.id_parent
%% AND ip.sexe='M'
%% AND im.sexe='F'
%% AND im.prenom='Annie'
%% OR ip.ddn < to_date('01/01/1950','DD/MM/YYYY')
%% ORDER BY i.nom, i.prenom;
%% \end{verbatim}
%% \cache{Produit cartésien, il manquait les parenthèses autour des 2
%% deux dernières conditions.
%% \code{AND ( im.prenom='Annie'
%% OR ip.ddn $<$ to\_date('01/01/1950','DD/MM/YYYY') )}
%% }
%% Définissez les index pertinents et donner le plan d'exécution.
%% \cache{Les clés primaires sont déjà indexées. Il faut un index sur
%% prénom et un index sur \code{ddn}. Après création des index si
%% nécessaire, il n'a plus le produit cartésien et les index sont
%% utilisés.
%% }
%% \end{exercice}
\newpage
\begin{center}
{\bf Commandes utiles pour les TP}
\end{center}
\paragraph{Premi\`ere s\'eance :} Connexion : \verb+sqlplus <user>/<password>@KIROV+
\verb+source /etc/profile+
Lancer \verb+sqlplus+ avec cette commande \verb+rlwrap sqlplus+ vous
permet d'avoir l'historique.
Lors de la première connexion modifier le mot de passe avec la
commande SQL : \verb+PASSWORD;+
Ce qui est équivalent à :
\verb+ALTER USER dupond IDENTIFIED BY password;+
Si le mot de passe est \'egar\'e, il faut se connecter en \code{ssh}
sur \code{londres} et faire \code{oracle\_passwd}
Pour lancer un fichier \verb+.sql+ en sqlplus, il suffit de taper :
\verb+@toto.sql;+
Pour quitter sqlplus, il suffit de taper : \verb+quit;+
\paragraph{Mise en forme : }
Sous SQL/PLUS :
\begin{verbatim}
Set linesize 150 -- positionne la taille d'une ligne
Set pagesize 300 -- positionne le nombre de lignes avant de réafficher les entêtes
Set pages 0 -- n'affiche pas les entêtes
Col <nom_colonne> for A10 -- défini que la colonne nom_colonne va être affiché sur 10 ca$
alphanumériques, 999.99 pour les valeurs numériques.
\end{verbatim}
\paragraph{Corbeille :} Vider les tables \verb+BIN$$xxxx+ : avec la
commande \verb+PURGE RECYCLEBIN;+
\paragraph{D\'ebug :} Afficher la strucutre de la table \code{nba} : \verb+describle nba;+
Connaître l'utilisateur connect\'e : \verb+show user;+
Liste des tables d'un \verb+user+ : \verb+SELECT table_name FROM user_tables;+
Lister les tables accessibles par l'utilisateur
: \verb+ SELECT table_name FROM all_tables;+
Lister toutes les tables possédées par un utilisateur
\verb+SELECT * FROM all_tables WHERE owner='PALAFOUR';+
Liste des vues d'un \code{user} : \verb+SELECT view_name FROM user_views;+
Liste des contraintes : \verb+SELECT * FROM user_constraints WHERE table_name=<table>;+
\verb+SELECT * FROM user_cons_columns WHERE table_name='<table>";+
\verb+show errros+ affiche les erreurs des fonctions.
\paragraph{Contraintes :} Description d'une table : \verb+describe <table>+ ou
\verb+desc <table>+;
Liste des colonnes concernées par les contraintes :
\verb+SELECT * FROM user_cons_columns;+
Lire une table d'un autre schéma :
\verb+SELECT * FROM <schema>.<table_name>; -- ou schma = login de connexion de l'utilisateur+
\paragraph{Affichage :}
\verb+SET HEADING OFF+
\verb+SET FEEDBACK OFF+
\paragraph{Travailler \`a la maison}
Il est possible d'acc\`eder par SSH \`a la machine
\texttt{londres}. L'adresse de la passerelle est
\texttt{ssh.iut-clermont.uca.fr} qui n'est accessible que par une
authentification avec des clefs SSH.
Cet acc\`es vous permettra d'acc\`eder au serveur de base de donn\'ees
comme lorsque vous travailler \`a l'IUT. Avant de pouvoir se
connecter, il faut activer l'acc\`es \`a l'IUT sur
\texttt{http://berlin.iut.local} ce qui peut prendre 30 minutes.
Pour d\'eposer vos clefs vous pouvez utiliser
\url{https://homeweb.iut-clermont.uca.fr}
%.ssh/authorized_keys
%londres.iut.local
%SSH http://cr2i.intranet.iut.local/site/page/documentation/ssh/
%% Faire du SQLPLUS depuis chez-vous en faisant du SSH en tapant cette
%% commande où vous devez remplacer LOGIN par votre login pour vous
%% connecter sur Linux à l'IUT: \verb+ssh LOGIN@193.49.118.206+
%% Le workflow le plus propre est d'avoir un fichier sur votre machine
%% personnelle que vous copier en faisant un \code{scp} sur la machine
%% Par exemple la commande ci-dessous copie le fichier
%% creation-donnees.sql de mon ordintaeur sur la machine virtuelle
%% \verb+scp creation-donnees.sql palafour@193.49.118.206:.+
%% Par contre une fois que je suis déconnecté de ma connexion ssh tous
%% les fichiers peuvent \^etre détruits car votre repertoire personnel
%% (home) sur ce serveur est TEMPORAIRE. Vos donnees peuvent etre
%% perdues a tout moment. Ne stocker RIEN ici.
\paragraph{En cas de blocage :}
Dans un terminal executer la commande suivante pour voir les \code{pid}
des processus zombies : \verb+ps -aux | grep sqlplus+
Ensuite tuer ces zombies gr\^ace \`a la commande \code{kill -9 numerodepid}
\newpage
%\newpage
%\printindex
\end{document}