Documente Academic
Documente Profesional
Documente Cultură
Une base de données relationnelle est une base de données structurée suivant les
principes de l’algèbre relationnelle.
La théorie des bases de données relationnelles est due à Edgar Frank Codd.
Remarque : l'adjectif relationnel ne fait pas référence ici aux relations entre
les tables mais aux tables elles-mêmes.
Elle est mise en œuvre au moyen d’un Système de Gestion de Bases de Données
Relationnelles (SGBDR).
De plus les données d'une table sont souvent subordonnées à un des champs (une
clé).
Ce modèle relationnel conduit à :
Une grande simplicité d’usage
Une transparence pour l’utilisateur de toute réorganisation technique de la
base (la seule différence pour l’utilisateur se situera, si l’opération est
réussie, dans les temps de réponse).
Une facilité de combinaison du contenu de plusieurs tables (opération join ou
jointure).
Les tables possèdent un certain nombre de colonnes ou champs permettant de
décrire des n-uplets (lignes ou enregistrements).
clé externe
c’est un attribut d’une relation qui est clé primaire dans une autre relation.
Elle permet donc de lier deux tables entre elles.
Dans une base de données relationnelle, le but est de séparer les informations
au maximum pour éviter les doublons et la redondance, et d'empêcher la perte de
qualité d’information (par exemple, l'adresse d'un fournisseur n'est mise à jour
qu'une et une seule fois : la modification sera alors prise en compte sur
l'ensemble des courriers).
Chaque attribut définit une information élémentaire à l’intérieur d’une ligne (aussi
appelée tuple) de la table. Il ne peut exister deux fois le même tuple dans une relation.
Les
attributs sont parfois aussi appelés colonnes.
On peut définir des clés, qui sont des contraintes d’intégrité portant sur une
relation. Elles consistent à imposer qu’il ne puisse exister deux tuples ayant
même valeur pour un sous-groupe d’attributs (la clé) de la relation. Si on
reprend l’exemple de la table PERSONNE, la clé pourrait être PersID, donc deux
tuples différents ne pourraient pas avoir une même valeur pour l’attribut PersID
(mais les valeurs des autres attributs peuvent être identiques).
Certaines clés sont dites clés étrangères ; ce sont des contraintes d’intégrité
portant sur une relation R1, consistant à imposer que la valeur d’un groupe
d’attributs apparaisse comme valeur de clé dans une autre relation R2.
Si l’on reprend l’exemple des deux tables PERSONNE et VILLE, la clé étrangère de la
table PERSONNE pourrait être ville_naiss, qui pointe sur la table VILLE. Il est
impératif que le nombre d’attributs formant la clé étrangère de la table R1
corresponde au nombre d’attributs formant la clé primaire de la table R2.
Ces clés étrangères sont issues du processus de normalisation du modèle des
données.
Lors de l’implémentation d’une base de données, il faut penser à certains
aspects :
Personne ne doit pouvoir mettre à jour des données dans une table pendant
qu’une autre personne les modifie déjà, car cela pourrait aboutir à des
incohérences. Un système comme Paradox l’autorise cependant grâce à un
mécanisme ingénieux mettant à jour automatiquement tous les affichages en
cours au même instant.
Les transactions sont atomiques, c’est-à-dire qu’en cas de panne majeure du
système informatique au milieu d’une modification, un mécanisme doit permettre
d’annuler les transactions en cours si elle n’ont pas pu être exécutées
totalement (mécanisme dit du COMMIT).
Des vérifications d’intégrité doivent assurer que chaque valeur inscrite dans
un tuple soit une valeur permise (par exemple, on peut interdire de mettre une
valeur supérieure à 12 dans un attribut « mois »).
Exemple :
On a une table « personne » contenant le nom, le prénom, la date de
naissance et la ville de naissance pour chaque personne. Une ligne de la
table contiendra donc les informations relatives à une
personne.PERSONNE
PersIDnomprénomdate_naissville_naiss
1Dupontbob01-01-19501
2yyyymeurise29-04-19992
3zzzzcodd26-12-20001
Les SGBDR sont livrés avec des APIs propriétaire (c’est-à-dire propres à chaque
SGBDR) pour communiquer avec eux.
Pour permettre d'utiliser une ou plusieurs bases de données avec un logiciel
sans devoir récrire le code source, des APIs standardisées pour accéder au SGBDR
ont été crées :
ODBC dans l'univers Microsoft
JDBC dans l'univers Java
Comme il existe une différence conceptuelle entre le monde objet (C++, Java,
.Net, etc.) et la représentation relationnelle, il est apparu plusieurs
solutions pour les réconcilier. Une solution est le mapping objet relationnel
dont le framework open source Hibernate est un des meilleurs exemples pour
l'univers Java.[réf. nécessaire]
Ainsi, dans l'univers Java, de nouveaux standards pour l'accès aux SGBDR sont
apparus :
les EJB entité
JDO
Les bases de données relationnelles étaient pressenties dans les années 1970
comme remplaçants des fichiers classiques dans les systèmes d’exploitation (voir
projet FS). Cela fut implémenté dans les ordinateurs du type IBM Système 38 ou
AS/400, ainsi que dans un système d’exploitation nommé Pick, mais sans se
généraliser. Les brevets de cette époque étant maintenant dans le domaine
public, l’idée redevient d’actualité dans les années 2000 avec le système WinFS
(voir Microsoft Windows).
Préambule
1. SQL un langage ?
2. SQL une histoire...
2.1. SQL une norme
2.2. SQL un standard
3. Remarques préliminaires sur les SGBDR
Soit une table contenant le personnel de la société " VIVE LE SQL " et une autre
contenant
les salaires mensuels desdits employés.
" VIVE LE SQL " compte 1000 employés soit autant de lignes dans la table du personnel,
et
la moyenne de durée d’emploi étant de 6 ans, la table des salaires compte donc environ
72 000 lignes.
Soit la requête suivante : rechercher les employés dont le nom commence par Z ou qui
sont
domiciliés à Trifouilly et qui ont eu au moins un salaire supérieur à 50 000 Francs.
Nous pauvres humains, aurions immédiatement traité la table des salaires, car à ce niveau
d’émolument il ne devrait pas y avoir grand monde dans la table des salariés…
Eh bien, croyez moi ou pas… certains optimiseurs statistiques auraient eût la même
appréciation !
En effet les plus performants ne se contentent pas seulement de conserver le nombre de
lignes ou
le volume des données d’une table, mais aussi les moyennes, médianes, maximums et
minimums de
certaines colonnes, voir même le nombre de valeurs distinctes (indice de dispersion) ainsi
que
le nombre d’occurrences des valeurs les plus fréquentes, notamment pour les colonnes
pourvues
d’index... C'est le cas en particulier du SGBDR INGRES.
L’unité de travail est indivisible. Une transaction ne peut être partiellement effectuée.
Isolation : les effets de la transaction ne sont pas perceptibles tant que celle-ci n’est pas
terminée.
Une transaction n’est pas affectée par le traitement des autres transactions.
6. Type de données
Dernier point que nous allons aborder dans ce premier
article, les différents types de données spécifiés par
SQL et leur disponibilité sur les 5 systèmes que nous
avons retenus pour notre étude.
Exemple :
NOM_CLIENT CHAR(32)
OBSERVATIONS VARCHAR(32000)
Exemple :
NOM_CLIENT NCHAR(32)
OBSERVATIONS NCHAR VARYING(32000)
Nota : la valeur maximale de la longueur est fonction du
SGBDR.
Exemple :
NUMERIC (15,2)
Exemple :
SELECT CAST(3.14159 AS FLOAT (16,4)) AS PI_FLT, CAST(3.14159 AS DECIMAL
(16,4)) AS PI_DEC
PI_FLT PI_DEC
----------------------------------------------------- ------------------
3.1415899999999999 3.1416
NOTA : on peut utiliser une notation particulière pour
forcer le typage implicite. Il s'agit d'une lettre
précédent la chaîne à transtyper. Les lettres
autorisées, sont : N (pour Unicode), B pour binary
(chaine de 0 et 1) et X pour binary (chaine hexadécimale
constituées de caractères allant de 0 à F).
Exemple :
SELECT N'toto' AS MY_NCHAR, B'01010111' AS MY_BINARY_BIT, X'F0A1' AS
MY_BINARY_HEX
Exemple :
1999-03-26 22:54:28.123 est le 26 mars 1999 à 22h 54m, 28s et 123
millisecondes.
Exemple :
JOURS INTERVAL DAY
TRIMESTRE INTERVAL MONTH TO DAY
TACHE INTERVAL HOUR TO SECOND
DUREE_FILM INTERVAL MINUTE
ATTENTION
Veuillez noter que les réceptacles de valeurs
temporelles ainsi créés par le type INTERVAL sont des
entiers, et que leur valeur est contrainte lorsqu'ils
sont définis avec une précision maximum sur tous les
éléments les composant sauf le premier. Ainsi, dans une
colonne définie par le type INTERVAL MONTH TO DAY, on
pourra stocker une valeur de 48 mois et 31 jours, mais
pas une valeur de 48 mois et 32 jours. De même dans un
INTERVAL HOUR TO SECOND la valeur en heure est
illimitée, mais celle en minute et en seconde ne peut
dépasser 59.
Exemple :
CREATE DOMAIN DOM_CODE_POSTAL AS CHAR(5)
7. Contraintes de données
Dans la plupart des SGBDR il est possible de contraindre
le formatage des données à l'aide de différents
mécanismes.
Parmi les contraintes les plus courantes au sein des
données de la table on trouve :
valeur minimum
valeur maximum
valeur par défaut
valeur obligatoire
valeur unique
clef primaire
index secondaire
format ou modèle (par exemple 3 caractères majuscules
suivi de 2 caractères numériques)
table de référence (recopie d'une valeur d'une table
dans un champ d'une autre table en sélectionnant par
la clef) aussi appelé CHECK en SQL
liste de choix
Enfin entre deux tables liées, il est souvent nécessaire
de définir une contrainte de référence qui oblige un
enregistrement référencé par sa clef à être présent ou
détruit en même temps que l'enregistrement visé est
modifié, inséré ou supprimé. Ce mécanisme est appelé
INTÉGRITÉ RÉFÉRENTIELLE.
CLIENT :
NO_CLIENTINTEGER
NOM_CLIENTCHAR(32)
COMMANDE :
REF_COMMANDECHAR(16)
DATE_COMMANDEDATE
MONTANT_COMMANDEMONEY
NO_CLIENTINTEGER
CLIENT :
143DUPONT
212MARTIN
823DUBOIS
COMMANDE :
1999-1111/7/19991235.52212
1999-1217/7/199945234.63823
1999-1318/7/19995485.23142
1999-1421/7/199911542.23212
Table "TARIF_BASE" :
TYPESEXEAGE_MINTARIF
ADHERENTHOMME161500
ADHERENTHOMME651800
ADHERENTFEMME161400
ADHERENTFEMME651700
CONJOINTHOMME161200
CONJOINTHOMME651500
CONJOINTFEMME161100
CONJOINTHOMME651300
ENFANTHOMME0400
ENFANTHOMME8600
ENFANTHOMME14800
ENFANTHOMME181000
ENFANTFEMME0300
ENFANTFEMME8500
ENFANTFEMME14700
ENFANTFEMME18850
ASCENDANTHOMME351200
ASCENDANTHOMME651400
ASCENDANTFEMME351100
ASCENDANTHOMME651300
Table "TARIF_MAJO"
PAIEMENTMAJORATION
MOIS12%
TRIMESTRE8%
SEMESTRE4%
ANNEE0%
Table "TARIF_MINO"
Nb_ENFANT_MAXMINORATION
110%
225%
350%
4100%
Table "PARAMS" :
TYPESEXEDATE_NAISSANCE
ADHERENTHOMME11/5/1950
CONJOINTFEMME21/6/1965
ENFANTHOMME16/3/1992
ENFANTFEMME11/1/1981
ASCENDANTFEMME21/12/1922
var
unTarif : mney
leTarif : money
i,j : smallint ; indice de boucle
n : samllint ; nombre d'enfants
; tableau multicellulaire représentant les données des tables
BaseTarif : Array ; table TARIF_BASE
MajoTarif : Array ; table TARIF_MAJO
Minotarif : Array ; table TARIF_MINO
endVar
leTarif := 0
endProcedure
9. Résumé
Voici les différentes implémentations du SQL sur
quelques uns des différents moteurs relationnels que
nous avons choisi d’analyser.
INTEGEROuiNonOuiOuiOui
SMALLINTOuiNonOuiOuiOui
DATEOuiNonNonNonOui
TIMEOuiNonNonNonNon
TIMESTAMPOuiOuiOuiOuiNon
INTERVALNonNonNonNonNon
BITNonOuiOui
BOOLEANOui (LOGICAL)OuiNonNonNon
MONEYOuiOuiOuiOuiNon
BYTESOuiNonNonNonOui (RAW)
AUTOINCOuiOuiNon (d)Non (d)Non (d)
BLOBOui (4 types différents : MEMO, MEMO FORMATE
en RTF, IMAGE et BINARY) limités à 2 GoOui (2
types différents : MEMO, HYPERLIEN limité à 64
ko)Oui (2 types différents TEXT IMAGE)Oui (2 types
différents IMAGE, TEXT) Oui (7 types différents :
LONG, LONG RAW, LONG VARCHAR, BFILE, BLOB, CLOB,
NCLOB)
Autres typesOCTET (1 à 255), OLEOLE, liste de
choixBIT, BINARYBIT, BINARY, CURSOR, TINYINT, GUID
ROWID (N° d'enregistrement)
INTEGRITÉ RÉFÉRENTIELLEOui, stricte ou cascade
(suppression et modif.)Oui, stricte ou cascade
(suppression et modif.)OuiOui, pas en cascadeOui
TRIGGERSNonNonOuiOui, limitésBEFORE INSERT, BEFORE
UPDATE, BEFORE DELETE, AFTER INSERT, AFTER UPDATE,
AFTER DELETE
PROCÉDURES STOCKÉESNonNonOui, langage propriétaire
Transact SQLOui, langage propriétaire Transact
SQLOui, langage propriétaire PL/SQL
10. Conclusion
En matière de SGBDR " fichier ", Paradox se révèle plus
pauvre au niveau du DDL et du TCL, mais plus riche en
matière de DML qu’Access. Quant aux types de données,
Paradox se révèle bien plus complet qu’Access qui
n’intègre même pas de champ de type " image "... Pensez
que dans Access le type entier n’est même pas défini !
Enfin en matière de BLOB la plupart des SGBDR acceptent
jusqu’à 2Go de données, sauf Access qui est limité à 64
Ko...
En ce qui concerne la capacité de stockage Access révèle
très rapidement de nombreuses limites, comme en nombre
d'utilisateurs en réseau.
En matière de contrôle des transactions Paradox est
limité à 255 enregistrements en RollBack. Mais la
présence de tables auxiliaires permet de dépasser ces
limites sans encombre, à condition de prévoir le code à
mettre en œuvre.
Différence fondamentale pour Paradox, pas de DCL. Mais
cela est largement compensé par un niveau de sécurité a
forte granularité qui n’est pas compatible avec le SQL
normalisé. Ainsi dans Paradox on peut placer des droits
au niveau des tables mais aussi de chaque champ et le
moteur crypte les données dès qu’un mot de passe est
défini (SQL Base de Centura permet aussi de crypter les
données à l’aide des plus récents algorithmes de
chiffrage)
Point très négatif pour Access dans sa catégorie : il
pratique le verrouillage de pages !
Enfin les vues n’existent pas dans Access mais elles
sont présentent dans Paradox sous une forme non SQL
appelée " vue de requête reliés " (QBE).
Pour ceux qui ne seraient pas familiarisé avec cet outil et la méthode MERISE, voir
l'article consacré à la méthode MERISE sur ce même site.
Quelques explications
La plupart des clefs sont des entiers (I) qui pourront être auto générés par exemple
par un type AUTOINCREMENT (Paradox, Access) ou encore via un trigger (identity de
SQL Server...). Pour certaines entités, notamment celles servant de références à la
saisie (MODE_PAIEMENT, TYPE, CODE) la clef est un code. Enfin pour les entités
TARIF et PLANNING, nous avons choisi une date comme clef.
Chaque entité est repérée à l'aide d'un trigramme (code de 3 lettres) qui sert de
préfixe pour chaque attribut. Exemple : CHB pour CHAMBRE, LIF pour
LIGNE_FACTURE, etc...
Les booléens seront représentés par des valeurs numériques 0 (faux) et 1 (vrai),
chaque attribut ayant obligatoirement une valeur par défaut.
NOTA : ce modèle est incomplet. Si l'on devait faire figurer l'adresse sur la facture il
faudrait choisir une adresse du client. La meilleure façon de régler le problème est de
faire glisser la clef du client dans la table des adresses et d'ajouter dans la table
facture l'ID de l'adresse choisie pour la facture. C'est ce que l'on apelle un "lien
identifiant" qui se positionne au niveau du lien entre l'association "domicilié" et
l'entité "adresse". On rajoute alors une association entre la facture et l'adresse de
cardinalité 0,1.
2. Le modèle physique
Nous avons demandé à générer un modèle basé sur le SQL ANSI de manière à
pouvoir être compatible avec la plupart des SGBDR :
Vous constaterez que toutes les tables ont été préfixées avec la lettre T lorsquelles
proviennent d'entités, et de TJ lorsqu'elles proviennent d'associations. Dans ce
dernier cas, leur nom a été constitué des trigrammes des tables en jeu dans la
jointure (TJ_TRF_LIF et TJ_CHB_PLN_CLI).
3. Script de création de la base de données
La génération de la base de données au format SQL standard donne le code suivant :
-- ============================================================
-- Nom de la base : MCD_HOTEL
-- Nom de SGBD : ANSI Niveau 2
-- Date de création : 16/01/2001 22:24
-- Copyright : Frédéric BROUARD
-- ============================================================
-- ============================================================
-- Table : T_CHAMBRE
-- ============================================================
create table T_CHAMBRE
(
CHB_ID INTEGER not null,
CHB_NUMERO SMALLINT not null,
CHB_ETAGE CHAR(3) ,
CHB_BAIN NUMERIC(1) not null default
0,
CHB_DOUCHE NUMERIC(1) not null default
1,
CHB_WC NUMERIC(1) not null default
1,
CHB_COUCHAGE SMALLINT not null,
CHB_POSTE_TEL CHAR(3) ,
primary key (CHB_ID)
);
-- ============================================================
-- Index : T_CHAMBRE_PK
-- ============================================================
create unique index T_CHAMBRE_PK on T_CHAMBRE (CHB_ID asc);
-- ============================================================
-- Table : T_TARIF
-- ============================================================
create table T_TARIF
(
TRF_DATE_DEBUT DATE not null,
TRF_TAUX_TAXES NUMERIC not null,
TRF_PETIT_DEJEUNE NUMERIC(8,2) not null,
primary key (TRF_DATE_DEBUT)
);
-- ============================================================
-- Index : T_TARIF_PK
-- ============================================================
create unique index T_TARIF_PK on T_TARIF (TRF_DATE_DEBUT asc);
-- ============================================================
-- Table : T_PLANNING
-- ============================================================
create table T_PLANNING
(
PLN_JOUR DATE not null,
primary key (PLN_JOUR)
);
-- ============================================================
-- Index : T_PLANNING_PK
-- ============================================================
create unique index T_PLANNING_PK on T_PLANNING (PLN_JOUR asc);
-- ============================================================
-- Table : T_TITRE
-- ============================================================
create table T_TITRE
(
TIT_CODE CHAR(8) not null,
TIT_LIBELLE VARCHAR(32) not null,
primary key (TIT_CODE)
);
-- ============================================================
-- Index : T_TITRE_PK
-- ============================================================
create unique index T_TITRE_PK on T_TITRE (TIT_CODE asc);
-- ============================================================
-- Table : T_TYPE
-- ============================================================
create table T_TYPE
(
TYP_CODE CHAR(8) not null,
TYP_LIBELLE VARCHAR(32) not null,
primary key (TYP_CODE)
);
-- ============================================================
-- Index : T_TYPE_PK
-- ============================================================
create unique index T_TYPE_PK on T_TYPE (TYP_CODE asc);
-- ============================================================
-- Table : T_MODE_PAIEMENT
-- ============================================================
create table T_MODE_PAIEMENT
(
PMT_CODE CHAR(8) not null,
PMT_LIBELLE VARCHAR(64) not null,
primary key (PMT_CODE)
);
-- ============================================================
-- Index : T_MODE_PAIEMENT_PK
-- ============================================================
create unique index T_MODE_PAIEMENT_PK on T_MODE_PAIEMENT (PMT_CODE
asc);
-- ============================================================
-- Table : T_CLIENT
-- ============================================================
create table T_CLIENT
(
CLI_ID INTEGER not null,
TIT_CODE CHAR(8) ,
CLI_NOM CHAR(32) not null,
CLI_PRENOM VARCHAR(25) ,
CLI_ENSEIGNE VARCHAR(100) ,
primary key (CLI_ID)
);
-- ============================================================
-- Index : T_CLIENT_PK
-- ============================================================
create unique index T_CLIENT_PK on T_CLIENT (CLI_ID asc);
-- ============================================================
-- Index : L_CLI_TIT_FK
-- ============================================================
create index L_CLI_TIT_FK on T_CLIENT (TIT_CODE asc);
-- ============================================================
-- Table : T_FACTURE
-- ============================================================
create table T_FACTURE
(
FAC_ID INTEGER not null,
CLI_ID INTEGER not null,
PMT_CODE CHAR(8) ,
FAC_DATE DATE not null,
FAC_PMT_DATE DATE ,
primary key (FAC_ID)
);
-- ============================================================
-- Index : T_FACTURE_PK
-- ============================================================
create unique index T_FACTURE_PK on T_FACTURE (FAC_ID asc);
-- ============================================================
-- Index : L_FAC_CLI_FK
-- ============================================================
create index L_FAC_CLI_FK on T_FACTURE (CLI_ID asc);
-- ============================================================
-- Index : TJ_FAC_PMT_FK
-- ============================================================
create index TJ_FAC_PMT_FK on T_FACTURE (PMT_CODE asc);
-- ============================================================
-- Table : T_ADRESSE
-- ============================================================
create table T_ADRESSE
(
ADR_ID INTEGER not null,
CLI_ID INTEGER not null,
ADR_LIGNE1 VARCHAR(32) not null,
ADR_LIGNE2 VARCHAR(32) ,
ADR_LIGNE3 VARCHAR(32) ,
ADR_LIGNE4 VARCHAR(32) ,
ADR_CP CHAR(5) not null,
ADR_VILLE CHAR(32) not null,
primary key (ADR_ID)
);
-- ============================================================
-- Index : T_ADRESSE_PK
-- ============================================================
create unique index T_ADRESSE_PK on T_ADRESSE (ADR_ID asc);
-- ============================================================
-- Index : L_ADR_CLI_FK
-- ============================================================
create index L_ADR_CLI_FK on T_ADRESSE (CLI_ID asc);
-- ============================================================
-- Table : T_TELEPHONE
-- ============================================================
create table T_TELEPHONE
(
TEL_ID INTEGER not null,
CLI_ID INTEGER not null,
TYP_CODE CHAR(8) not null,
TEL_NUMERO CHAR(20) not null,
TEL_LOCALISATION VARCHAR(64) ,
primary key (TEL_ID)
);
-- ============================================================
-- Index : T_TELEPHONE_PK
-- ============================================================
create unique index T_TELEPHONE_PK on T_TELEPHONE (TEL_ID asc);
-- ============================================================
-- Index : L_TEL_CLI_FK
-- ============================================================
create index L_TEL_CLI_FK on T_TELEPHONE (CLI_ID asc);
-- ============================================================
-- Index : L_TEL_TYP_FK
-- ============================================================
create index L_TEL_TYP_FK on T_TELEPHONE (TYP_CODE asc);
-- ============================================================
-- Table : T_EMAIL
-- ============================================================
create table T_EMAIL
(
EML_ID INTEGER not null,
CLI_ID INTEGER not null,
EML_ADRESSE VARCHAR(100) not null,
EML_LOCALISATION VARCHAR(64) ,
primary key (EML_ID)
);
-- ============================================================
-- Index : T_EMAIL_PK
-- ============================================================
create unique index T_EMAIL_PK on T_EMAIL (EML_ID asc);
-- ============================================================
-- Index : L_EML_CLI_FK
-- ============================================================
create index L_EML_CLI_FK on T_EMAIL (CLI_ID asc);
-- ============================================================
-- Table : T_LIGNE_FACTURE
-- ============================================================
create table T_LIGNE_FACTURE
(
LIF_ID INTEGER not null,
FAC_ID INTEGER not null,
LIF_QTE NUMERIC not null,
LIF_REMISE_POURCENT NUMERIC ,
LIF_REMISE_MONTANT NUMERIC(8,2) ,
LIF_MONTANT NUMERIC(8,2) not null,
LIF_TAUX_TVA NUMERIC(8,2) not null,
primary key (LIF_ID)
);
-- ============================================================
-- Index : T_LIGNE_FACTURE_PK
-- ============================================================
create unique index T_LIGNE_FACTURE_PK on T_LIGNE_FACTURE (LIF_ID asc);
-- ============================================================
-- Index : L_LIF_FAC_FK
-- ============================================================
create index L_LIF_FAC_FK on T_LIGNE_FACTURE (FAC_ID asc);
-- ============================================================
-- Table : TJ_TRF_CHB
-- ============================================================
create table TJ_TRF_CHB
(
CHB_ID INTEGER not null,
TRF_DATE_DEBUT DATE not null,
TRF_CHB_PRIX NUMERIC(8,2) not null,
primary key (CHB_ID, TRF_DATE_DEBUT)
);
-- ============================================================
-- Index : TJ_TRF_CHB_PK
-- ============================================================
create unique index TJ_TRF_CHB_PK on TJ_TRF_CHB (CHB_ID asc,
TRF_DATE_DEBUT asc);
-- ============================================================
-- Index : L_CHB_TRF_FK
-- ============================================================
create index L_CHB_TRF_FK on TJ_TRF_CHB (CHB_ID asc);
-- ============================================================
-- Index : L_TRF_CHB_FK
-- ============================================================
create index L_TRF_CHB_FK on TJ_TRF_CHB (TRF_DATE_DEBUT asc);
-- ============================================================
-- Table : TJ_CHB_PLN_CLI
-- ============================================================
create table TJ_CHB_PLN_CLI
(
CHB_ID INTEGER not null,
PLN_JOUR DATE not null,
CLI_ID INTEGER not null,
CHB_PLN_CLI_NB_PERS SMALLINT not null,
CHB_PLN_CLI_RESERVE NUMERIC(1) not null default
0,
CHB_PLN_CLI_OCCUPE NUMERIC(1) not null default
1,
primary key (CHB_ID, PLN_JOUR)
);
-- ============================================================
-- Index : TJ_CHB_PLN_CLI_PK
-- ============================================================
create unique index TJ_CHB_PLN_CLI_PK on TJ_CHB_PLN_CLI (CHB_ID asc,
PLN_JOUR asc, CLI_ID asc);
-- ============================================================
-- Index : L_CHB_PLN_CLI_FK
-- ============================================================
create index L_CHB_PLN_CLI_FK on TJ_CHB_PLN_CLI (CHB_ID asc);
-- ============================================================
-- Index : L_PLN_CHB_CLI_FK
-- ============================================================
create index L_PLN_CHB_CLI_FK on TJ_CHB_PLN_CLI (PLN_JOUR asc);
-- ============================================================
-- Index : L_CLI_CHB_PLN_FK
-- ============================================================
create index L_CLI_CHB_PLN_FK on TJ_CHB_PLN_CLI (CLI_ID asc);
Vous noterez que nous avons volontairement omis les intégrités référentielles de
manière à alléger le code mais aussi pour le rendre le plus compatible possible.
ACCESS 95/97
DB2 C/S 2
DBASE 5.0 Windows
FOXPRO 5 Windows
INFORMIX SQL 7.1
INGRES 6.4
INTERBASE 4.0
ORACLE 7
PARADOX 7 Windows
SQL SERVER 6
SYBASE SQL SERVER 10
NOTA : nous n'avons pas introduit de colonne de type auto incrémenté dans les
scripts de création de base de données, mais vous pouvez les modifier en y
introduisant un trigger. Ne le faites pas si vous voulez pouvoir exploiter le jeu de
données nécessaire aux exercices qui se trouvent dans les chapitres qui suivent.
Exemples : pour le SGBDR InterBase de Borland / Inprise, vous pouvez utiliser un
générateur de nombre séquentiel utilisable par tous. Il faut donc créer autant de
générateur qu'il existe dans la base de colonnes nécessitant une auto
incrémentation, puis dans chacun des triggers de type BEFORE INSERT, appeler ce
générateur.
Pour SQL Server de Microsoft, vous pouvez utiliser le type 'identity', mais vous
devrez certainement modifier le type des colonnes des clefs étrangères dans le script
de création de la base.
Pour Paradox il suffit de remplacer le type "I" par le type "+" dans les colonnes où
cela s'avère nécessaire.
A la création de la base :
1. mettre le fichier des ordres SQL d'insertion dans une colonne de table via un
import de données. Par exemple dans une table Paradox de nom
INSERT_EXEMPLE possédant une colonne de nom SQL_ORDER.
2. jouer le script ci dessus afin d'insérer les données :
var
tc TCursor
svar String
sqlVar SQL
db Database
endvar
errorTrapOnWarnings(True)
db.open(...) => chemin de la base de données cible
tc.open("INSERT_EXEMPLE.db")
scan tc :
svar = TC.SQL_ORDER
try
sqlVar.readFromString(svar)
sqlVar.executeSQL(db)
onFail
errorShow()
msgInfo("ORDRE SQL",sVar)
quitLoop
endTry
endscan
errorTrapOnWarnings(False)
endMethod
I. Préambule
II. Les jointures ou comment faire des requêtes sur plusieurs
tables
II-A. Premiers essais de jointure
II-B. Différents type de jointures (naturelles, équi, non
equi, auto, externes, hétérogènes, croisée et union)
III. Syntaxe normalisée des jointures
III-A. Opérateur de jointure naturelle
III-B. Les jointures internes
III-C. Les jointures externes
III-D. Différence entre jointure externe et jointure interne
III-D-1. L'hypothèse du monde clos
III-D-2. Mécanisme en jeu
III-D-3. Discussion sur la jointure externe
III-E. La jointure croisée
III-F. La jointure d'union
IV. Nature des conditions de jointures
IV-A. Équi-jointure
IV-B. Non équi-jointure
IV-C. Auto-jointure
IV-D. La jointure hétérogène
V. Récapitulatif des jointures normalisées
V-A. Terminologie et syntaxe des jointures
V-B. Arbre de jointure
VI. Note importante
VII. Résumé
Retenez cependant que la plupart des jointures entre tables s'effectuent en imposant
l'égalité
des valeurs d'une colonne d'une table à une colonne d'une autre table. On parle alors
de jointure naturelle ou équi-jointure. Mais on trouve aussi des jointures d'une
table sur elle-même. On parle alors d'auto-jointure. De même, il arrive que l'on
doive procéder à des jointures externe, c'est-à-dire joindre une table à une autre, même si
la valeur de liaison est absente dans une table ou l'autre. Enfin, dans quelques cas, on
peut procéder à des jointures hétérogènes, c'est-à-dire que l'on remplace le critère
d'égalité par un critère d'inégalité ou de différence.
Nous verrons au moins un cas de cette espèce.
Exemple 1 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
DUPONT 06-11-86-78-89
DUPONT 02-41-58-89-52
DUPONT 01-51-58-52-50
DUPONT 01-54-11-43-21
DUPONT 06-55-41-42-95
DUPONT 01-48-98-92-21
DUPONT 01-44-22-56-21
...
Exemple 2 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
DUPONT 06-11-86-78-89
DUPONT 02-41-58-89-52
DUPONT 01-51-58-52-50
DUPONT 01-54-11-43-21
DUPONT 06-55-41-42-95
DUPONT 01-48-98-92-21
DUPONT 01-44-22-56-21
...
Nous n'avons pas fait mieux, car nous avons créé une clause
toujours vraie, un peu à la manière de 1 = 1 !
En fait il nous manque une précision : il s'agit de déterminer
de quelles tables proviennent les colonnes CLI_ID de droite et
de gauche. Cela se précise à l'aide d'une notation pointée en
donnant le nom de la table.
Exemple 3 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOUVIER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOUVIER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
Exemple 5 :
Exemple 6 :
Exemple 7 :
En effet :
Les jointures faites dans la clause WHERE (ancienne syntaxe
de 1986 !) ne permettent pas de faire la distinction de
prime abord entre ce qui relève du filtrage et ce qui relève
de la jointure.
Il est à priori absurde de vouloir filtrer dans le WHERE (ce
qui restreint les données du résultat) et de vouloir
"élargir" ce résultat par une jointure dans la même clause
WHERE de filtrage.
La lisibilité des requêtes est plus grande en utilisant la
syntaxe à base de JOIN, en isolant ce qui est du filtrage et
de la jointure, mais aussi en isolant avec clarté chaque
condition de jointures entre chaque couples de table.
L'optimisation d'exécution de la requête est souvent plus
pointue du fait de l'utilisation du JOIN.
Lorsque l'on utilise l'ancienne syntaxe et que l'on supprime
la clause WHERE à des fins de tests, le moteur SQL réalise
le produit cartésiens des tables ce qui revient la plupart
du temps à mettre à genoux le serveur !
Jointure croisée
SELECT ...
FROM <table gauche>
CROSS JOIN <table droite>
Jointure d'union
SELECT ...
FROM <table gauche>
UNION JOIN <table droite>
Exemple 8 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOUVIER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
Mais cette syntaxe est rarement acceptée par les moteurs SQL
actuels !
La partie optionnelle USING permet de restreindre les colonnes
concernées, lorsque plusieurs colonnes servent à définir la
jointure naturelle. Ainsi la commande SQL :
Exemple 9 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOUVIER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
Ou en utilisant le surnommage :
Exemple 10 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOUVIER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOYER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
Exemple 12 :
SELECT CLI_NOM, TEL_NUMERO
FROM T_CLIENT C
INNER JOIN T_TELEPHONE T
ON C.CLI_ID = T.CLI_ID
WHERE TYP_CODE = 'FAX'
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-44-28-52-50
MARTIN 01-44-22-56-21
DUHAMEL 01-54-11-43-89
DUPONT 05-59-45-72-42
MARTIN 01-47-66-29-55
DUBOIS 04-66-62-95-64
DREYFUS 04-92-19-18-58
DUHAMEL 01-55-60-93-81
PHILIPPE 01-48-44-86-19
DAUMIER 01-48-28-17-95
...
Exemple 13 :
CLI_NOM TEL_NUMERO
------- --------------
DUPONT 01-44-28-52-50
DUPONT 05-59-45-72-42
MARTIN 01-47-66-29-55
BOUVIER NULL
DUBOIS 04-66-62-95-64
DREYFUS 04-92-19-18-58
FAURE NULL
LACOMBE NULL
DUHAMEL 01-54-11-43-89
DUHAMEL 01-55-60-93-81
...
ou encore :
SELECT ...
FROM <table gauche>
LEFT | RIGHT | FULL OUTER JOIN <table droite 1>
ON <condition de jointure>
[LEFT | RIGHT | FULL OUTER JOIN <table droite 2>
ON <condition de jointure 2>]
...
SELECT colonnes
FROM TGauche LEFT OUTER JOIN TDroite ON condition de jointure
On recherche toutes les valeurs satisfaisant la
condition de jointure précisée dans prédicat, puis on
rajoute toutes les lignes de la table TGauche qui n'ont
pas été prises en compte au titre de la satisfaction du
critère.
SELECT colonnes
FROM TGauche RIGHT OUTER JOIN TDroite ON condition de jointure
SELECT colonnes
FROM TGauche FULL OUTER JOIN TDroite ON condition de jointure
est :
Exemple 15 :
est :
Exemple 16 :
est :
Exemple 17 :
SELECT CLI_ID, CLI_NOM, TYP_CODE || ' : ' || TEL_NUMERO AS
TEL_CONTACT, EML_ADRESSE, ADR_VILLE
FROM T_CLIENT C
LEFT OUTER JOIN T_TELEPHONE T
ON C.CLI_ID = T.CLI_ID
LEFT OUTER JOIN T_EMAIL E
ON C.CLI_ID = E.CLI_ID
LEFT OUTER JOIN T_ADRESSE A
ON C.CLI_ID = A.CLI_ID
SELECT colonnes
FROM TGauche FULL JOIN TDroite ON condition de jointure
ou encore :
SELECT colonnes
FROM table_1 t1, table_2 t2
WHERE t1.id1 (+)= t2.id2
-- test de jointure
Tu peux alors m'expliquer pourquoi cette requête renvoie le même résultat que ta
dernière ?
2° TJ1.COL2 = 'taratata' => encore une fois aucune ligne n'est récupérée
donc aucune ligne de TEST_JOIN1 n'a de correspondance avec TEST_JOIN2
conclusion : toutes les lignes de TEST_JOIN1 seront reprises du fait
de la jointure externe
???
et voilà !
SELECT colonnes
FROM table_1 CROSS JOIN table_2
SELECT colonnes
FROM table_1, table_2
Exemple 18 :
Exemple 19 :
SELECT colonnes
FROM table_1 UNION JOIN table_2
Exemple 20 :
SELECT *
FROM T_TITRE UNION JOIN T_TYPE
TIT_CODE TIT_LIBELLE TYP_CODE TYP_LIBELLE
-------- -------------------------------- -------- -------------------
M. Monsieur NULL NULL
Melle. Mademoiselle NULL NULL
Mme. Madame NULL NULL
NULL NULL FAX Télécopie
NULL NULL GSM Téléphone portable
NULL NULL TEL Téléphone fixe
SELECT *
FROM <table gauche>
FULL OUTER JOIN <table droite>
ON <critère>
IV-A. Équi-jointure
L'équi-jointure consiste à opérer une jointure avec une
condition d'égalité. Cette condition d'égalité dans la
jointure peut ne pas porter nécessairement sur les clefs
(primaires et étrangères).
Recherchons par exemple les clients dont le nom est celui
d'une ville contenu dans la table des adresses :
Exemple 21 :
Exemple 22 :
> supérieur
>= supérieur ou égal
< inférieur
<= inférieur ou égal
<> différent de
IN dans un ensemble
LIKE correspondance partielle
BETWEEN ... AND ... entre deux valeurs
EXISTS dans une table
Exemple 23 :
SELECT F.*
FROM T_FACTURE F
INNER JOIN T_TARIF T
ON F.FAC_DATE < T.TRF_DATE_DEBUT
WHERE TRF_PETIT_DEJEUNE >= 6
Exemple 24 :
IV-C. Auto-jointure
Le problème consiste à joindre une table à elle-même. Il est
assez fréquent que l'on ait besoin de telles auto-jointures
car elle permettent notamment de modéliser des structures de
données complexes comme des arbres. Voici quelques exemples de
relation nécessitant une auto-jointure de tables :
Exemple 25 :
CHB_ID CHB_COMMUNIQUE
------ --------------
7 9
9 7
12 14
14 12
où la table T_CHAMBRE figure deux fois par l'entremise de deux
surnommages différents c1 et c2.
Exemple 26 :
CHB_ID CHB_COMMUNIQUE
------ --------------
7 9
12 14
Exemple 27 :
NOM_CLI TEL_NUMERO
------- --------------
DUPONT 01-45-42-56-63
DUPONT 01-44-28-52-52
DUPONT 01-44-28-52-50
BOUVIER 06-11-86-78-89
DUBOIS 02-41-58-89-52
DREYFUS 01-51-58-52-50
DUHAMEL 01-54-11-43-21
BOYER 06-55-41-42-95
MARTIN 01-48-98-92-21
MARTIN 01-44-22-56-21
...
SELECT colonnes
FROM table1 NATURAL JOIN table2 [USING col1, col2 ... ]
[WHERE prédicat] ...
SELECT colonnes
FROM table1 t1 [INNER ] JOIN table2 t2 ON condition
[WHERE prédicat] ...
SELECT colonnes
FROM table1 t1 [RIGHT OUTER | LEFT OUTER | FULL OUTER ] JOIN table2 t2 ON
condition
[WHERE prédicat] ...
SELECT colonnes
FROM table1 t1 CROSS JOIN table2 t2
[WHERE prédicat] ...
SELECT colonnes
FROM table1 UNION JOIN table2
SELECT *
FROM T_CLIENT CLI -- le client (racine de l'arbre)
JOIN T_ADRESSE ADR -- adresse, table d'entité (feuille de l'arbre)
ON CLI.CLI_ID = ADR.CLI_ID
JOIN T_TITRE TIT -- titre, table d'entité (feuille de l'arbre)
ON CLI.TIT_CODE = TIT.TIT_CODE
JOIN T_EMAIL EML -- mail, table d'entité (feuille de l'arbre)
ON CLI.CLI_ID = EML.CLI_ID
JOIN T_TELEPHONE TEL -- téléphone, table d'entité servant de jointure
(noeud dans l'arbre)
ON CLI.CLI_ID = TEL.CLI_ID
JOIN T_TYPE TYP -- type de téléphone, table d'entité (feuille de l'arbre)
ON TEL.TYP_CODE = TYP.TYP_CODE
JOIN TJ_CHB_PLN_CLI CPC -- table de jointure (noeud dans l'arbre)
ON CLI.CLI_ID = CPC.CLI_ID
JOIN T_PLANNING PLN -- date du planning, table d'entité (feuille de
l'arbre)
ON CPC.PLN_JOUR = PLN.PLN_JOUR
JOIN T_CHAMBRE CHB -- chambre, table d'entité servant de jointure
(noeud dans l'arbre)
ON CPC.CHB_ID = CHB.CHB_ID
JOIN TJ_TRF_CHB TC -- table de jointure (noeud dans l'arbre)
ON CHB.CHB_ID = TC.CHB_ID
JOIN T_TARIF TRF -- tarif, table d'entité (feuille de l'arbre)
ON TC.TRF_DATE_DEBUT = TRF.TRF_DATE_DEBUT
JOIN T_FACTURE FAC -- facture, table d'entité servant de jointure
ON CLI.CLI_ID = FAC.CLI_ID
JOIN T_LIGNE_FACTURE LIF -- ligne de facture, table d'entité (feuille de
l'arbre)
ON FAC.FAC_ID = LIF.FAC_ID
JOIN T_MODE_PAIEMENT PMT -- mode de paiement, table d'entité (feuille
de l'arbre)
ON FAC.PMT_CODE = PMT.PMT_CODE
VII. Résumé
Voici les différences entre les moteurs des bases de données :
(1) Oracle ne connaît toujours pas le JOIN (ça fait quand même
plus de dix ans de retard pour cet éditeur pionnier qui semble
s'endormir sur ses lauriers). Il faut donc utiliser une
syntaxe propriétaire. Exception : la version 9 supporte enfin
les jointures normalisées.
SELECT *
FROM tablegauche
LEFT OUTER JOIN tabledroite
ON références de jointure
WHERE tabledroite.clef IS NULL
Préambule
1. L'environnement
2. Le serveur physique ou "la machine"
3. Le Serveur logique ou SGBDR
4. La base de données
5. Le modèle de données
6. En développement
7. En exploitation
8. Optimiseurs et plan de requêtes
9. Transformations usuelles
10. Quelques trucs
11. CONCLUSION
Préambule
NOTA : La structure de la base de données exemple, ainsi
qu'une version des principales bases utilisées sont
disponibles dans la page "La base de données exemple"
1. L'environnement
L'environnement informatique, c'est à dire
l'architecture globale du SI, a une influence
déterminante sur les performances du SGBDR et donc sur
la vitesse à laquelle vos utilisateurs vont accéder aux
données.
utilisateurs simultanés15155050300300
débitfaiblefortfaiblefortfaiblefort
architecture10 Mo/s, 1 carte réseau100 Mo/s, 1
carte réseau100 Mo/s, 1 à 2 cartes réseau100 Mo/s,
1 à 4 cartes réseau + switch frontal1 Go/s, 1
carte réseau FO + switch frontal1 Go/s, 2 à 4
cartes réseau en FO + switch frontal cascadé
FO : fibre optique
4. La base de données
Lors de la création d'une base il est possible de
demander la création d'un fichier doté d'une taille
précise. Il convient de toujours créer le fichier de la
base de données avec la taille qu'aura la base de
données au cours de son exploitation. Ainsi si votre
base de données doit faire à terme 3 Go, lors de la
création de la base, donnez cette valeur comme taille du
fichier. Les dispositifs d'auto adaptation de la taille
de la base font généralement perdre du temps par le fait
qu'ils fragmentent le fichier constituant la base.
5. Le modèle de données
Normalisez au maximum... Créez des tables les plus
petites possible en externalisant dans des tables de
références toutes les informations susceptible d'être
utilisées plusieurs fois.
Exemple :
Standardisez vos types de données et leur format en
utilisant des domaines. Voir définir les domaines et les
utiliser... Vous n'aurez donc pas d'effort à à demander
au SGBDR lors de comparaisons sur des colonnes de
contenu similaire (trantypage implicite). En effet si
vous voulez comparer le nom d'un client définit comme
VARCHAR(32) au nom d'un prospect défini comme NCHAR(25),
le SGBDR devra fournir un effort supplémentaire pour
homogénéiser les types de colonnes avant d'opérer la
comparaison.
MAUVAISBON
FormatRègles
DateFrretire tous les caractères autres que
chiffres et pose des barres JJ/MM/AAAA
Téléphoneformate une chaine de chiffre en
téléphone :
33 1 45 78 45 78 (11 chiffres) ou
01 45 78 45 78 (10 chiffres)
dans tous les autres cas :
groupes de 2 et si impair, alors commence par 1
chiffre isolé
7. En exploitation
Surveillez le volume des données : la base est-elle trop
grosse ? Pourquoi ? Le fichier du journal trop grand ?
Pouvez vous le tronquer ? La place libre du disque
est-elle assez conséquente (au moins 33%) ?...
9. Transformations usuelles
Voici quelques transformations usuelles qu'il convient
d'avoir à l'esprit afin d'optimiser vos requêtes.
N°ÉVITEZPRÉFÉREZ
1évitez d'employer l'étoile dans la clause
SELECT...
SELECT *
FROM T_CLIENT...préférez nommer les colonnes une
à une
SELECT CHB_ID
FROM T_CHAMBRE T1
WHERE NOT EXISTS (SELECT CHB_ID
FROM TJ_CHB_PLN_CLI T2
WHERE PLN_JOUR = '2000-11-11'
AND T2.CHB_ID = T1.CHB_ID)...utilisez
l'étoile ou une constante
SELECT CHB_ID
FROM T_CHAMBRE T1
WHERE NOT EXISTS (SELECT *
FROM TJ_CHB_PLN_CLI T2
WHERE PLN_JOUR = '2000-11-11'
AND T2.CHB_ID = T1.CHB_ID)
4évitez de compter une colonne...
SELECT *
FROM T_CLIENT
WHERE CLI_NOM LIKE 'D%'...si une fourchette de
recherche le permet
SELECT *
FROM T_CLIENT
WHERE CLI_NOM BETWEEN 'D' AND 'E '
6évitez les jointures dans le WHERE...
SELECT *
FROM T_CLIENT C, T_FACTURE F
WHERE EXTRACT(YEAR FROM F.FAC_DATE) = 2000
AND F.CLI_ID = C.CLI_ID...préférez l'opérateur
normalisé JOIN
SELECT *
FROM T_CLIENT C
JOIN T_FACTURE F
ON F.CLI_ID = C.CLI_ID
WHERE EXTRACT(YEAR FROM F.FAC_DATE) = 2000
7évitez les fourchettes < et > pour des valeurs
discrètes...
SELECT *
FROM T_FACTURE
WHERE FAC_DATE > '2000-06-18'
AND FAC_DATE < '2000-07-15'...préférez le
BETWEEN
SELECT *
FROM T_FACTURE
WHERE FAC_DATE BETWEEN '2000-06-18'
AND '2000-07-14'
8évitez le IN avec des valeurs discrètes
recouvrantes...
SELECT *
FROM T_CHAMBRE
WHERE CHB_NUMERO IN (11, 12, 13, 14)...préférez
le BETWEEN
SELECT *
FROM T_CHAMBRE
WHERE CHB_NUMERO BETWEEN 11 AND 14
9évitez d'employer le DISTINCT...
SELECT CHB_ID
FROM T_CHAMBRE
WHERE CHB_ID NOT IN (SELECT CHB_ID
FROM TJ_CHB_PLN_CLI
WHERE PLN_JOUR = '2000-11-11')...quand
vous pouvez utiliser les jointures
SELECT CHB_ID
FROM T_CHAMBRE
WHERE CHB_ID NOT IN (SELECT CHB_ID
FROM TJ_CHB_PLN_CLI
WHERE PLN_JOUR = '2000-11-11')...lorsque
vous pouvez utiliser EXISTS
SELECT CHB_ID
FROM T_CHAMBRE T1
WHERE NOT EXISTS (SELECT *
FROM TJ_CHB_PLN_CLI T2
WHERE PLN_JOUR = '2000-11-11'
AND T2.CHB_ID = T1.CHB_ID)
12transformez les COALESCE...
SELECT LIF_ID,
(LIF_QTE * LIF_MONTANT)
* (1 - COALESCE(LIF_REMISE_POURCENT, 0)/100)
- COALESCE(LIF_REMISE_MONTANT, 0) AS TOTAL_LIGNE
FROM T_LIGNE_FACTURE...en UNION
SELECT CHB_ID
FROM T_CHAMBRE
EXCEPT
SELECT CHB_ID
FROM TJ_CHB_PLN_CLI
WHERE PLN_JOUR = '2000-11-11'...en jointures
SELECT CHB_ID
FROM T_CHAMBRE
INTERSECT
SELECT CHB_ID
FROM TJ_CHB_PLN_CLI
WHERE PLN_JOUR = '2000-11-11'...en jointure
SELECT LIF_ID,
(LIF_QTE * LIF_MONTANT) AS LIF_MONTANT
FROM T_LIGNE_FACTURE
ORDER BY LIF_ID, LIF_MONTANT
Exemple :
Exemple :
Exemple :
SELECT *
FROM T_LIGNE_FACTURE
WHERE LIF_QTE + 10 = LIF_MONTANT / 5
-- l'index sur la colonne LIF_QTE peut être activé
SELECT *
FROM T_LIGNE_FACTURE
WHERE LIF_QTE = LIF_MONTANT / 5 - 10
Exemple :
SELECT *
FROM T_MOT
WHERE MOT LIKE '%and'
MOT
-------------------------
marchand
flamand
-- l'index sur la colonne MOT ne peut être activé
ALTER TABLE T_MOT
ADD TOM VARCHAR(25)
UPDATE T_MOT
SET TOM = REVERSE(MOT) -- REVERSE renvoie la chaine en inversant l'ordre des
lettres
SELECT *
FROM T_MOT
WHERE TOM LIKE 'dna%'
MOT TOM
------------------------- -------------------------
marchand dnahcram
flamand dnamalf
Exemple :
SELECT *
FROM T_FACTURE
WHERE FAC_PMT_DATE NOT BETWEEN FAC_DATE AND FAC_DATE +
INTEVAL 30 DAY
SELECT *
FROM T_FACTURE
WHERE FAC_PMT_DATE < FAC_DATE
OR FAC_PMT_DATE > FAC_DATE + NTEVAL 30 DAY
Exemple :
Exemple :
-- mauvais
INSERT INTO T_CLIENT VALUES (198, 'M.', 'DUCORNET', 'Archibald', NULL)
-- bon
INSERT INTO T_CLIENT (CLI_ID, TIT_CODE, CLI_NOM, CLI_PRENOM,
CLI_ENSEIGNE)
VALUES (198, 'M.', 'DUCORNET', 'Archibald', NULL)
-- excellent (insertion implicite des NULL)
INSERT INTO T_CLIENT (CLI_ID, TIT_CODE, CLI_NOM, CLI_PRENOM)
VALUES (198, 'M.', 'DUCORNET', 'Archibald')
Exemple :
-- vous voulez présenter une liste de noms de pays ayant en premier la France
-- en second les états de l'union européenne en ordre alphabétique
-- et en troisième tous les autres pays en orde alphabétique.
SELECT PAYS, 1 AS N
FROM T_PAYS
WHERE PAYS = 'France'
UNION
SELECT PAYS, 2 AS N
FROM T_PAYS
WHERE UNION_EUROPEENE = 1
AND PAYS <> 'France'
UNION
SELECT PAYS, 3 AS N
FROM T_PAYS
WHERE UNION_EUROPEENE = 0
ORDER BY N, PAYS
-- mauvais : requête lourde !
-- ajout d'une colonne d'ordre
ALTER TABLE T_PAYS ADD ORDRE INTEGER
SELECT *
FROM T_CLIENT
ORDRE BY PRENOM
-- l'index ne peut être activé sur la seule colonne CLI_PRENOM car il contient :
AIACHAlexandre
ALBERTChristian
AUZENATMichel
BACQUEMichel
BAILLYJean-François
...Activez le calculs des statistiques après des mises à
jour massives.
CLI_ID CLI_NOM
------- ------------
17 DURAND
192 DUPONT
44 DUVAL
11 DUMOULIN
741 DULIN
82 DUPOND
177 DURANDCréation du jeu d'essai :
1.1. RÉPONSE 1
Si l'on applique strictement cette question, alors la
réponse est :
CLI_NOM RANG
------------- -----
DULIN 1
DUMOULIN 2
DUPOND 3
DUPONT 4
DURAND 5
DURAND 5
DUVAL 7En effet, les deux DURAND se trouvant ex æquo occupent
le 5eme rang, tandis que DUVAL occupe non pas le 6eme,
mais le 7eme rang !
1.2. RÉPONSE 2
Une autre solution possible est :
CLI_NOM RANG
------------- -----
DULIN 1
DUMOULIN 2
DUPOND 3
DUPONT 4
DURAND 5
DURAND 6
DUVAL 7C'est à dire une numérotation franche et directe sans
tenir compte des doublons ou de l'ambiguïté des
informations sélectionnées. C'est un peu ce que font les
colonnes d'auto incrémentation de certains SGBDR.
1.3. RÉPONSE 3
Enfin on peut raffiner cette dernière solution en
introduisant un comptage pour faire disparaître les
doublons :
1.4. RÉPONSE 4
En poussant les choses à l'extrême, on peut exiger que
la numérotation de rang soit stricte et sans "trou",
comme ceci :
Requête de la réponse 1 :
PLC_REF
-------
A01
A02
A03
A04
A05
B01
B02
B03
B04
B05
C01
C02
C03
C04
C05Création du jeu d'essai :
CLI_NOM PLC_REF
------------ -------
DURAND A01
DUPONT A02
DUVAL A03
DUMOULIN A04
DULIN A05
DUPOND B01
DURAND B02Néanmoins nous n'avons pas ces colonnes à disposition…
Comment faire ?
Il suffit d'appliquer ce que nous venons de voir dans
l'exemple précédent, à la fois pour les clients, mais
aussi pour les fauteuils et de joindre le tout sur les
colonnes de numérotation ainsi générées.
ENT_I
-------
0
1
2
3
4
5
6
7
8
9
10
...CREATE TABLE T_I_ENT
(ENT_I INTEGER)
CLI_NOM T_I_ENT
---------- ------------
DULIN 1
DUMOULIN 2
DUPOND 3
DUPONT 4
DURAND 5
DURAND 6
DUVAL 7C'est pourquoi je propose le nouvel opérateur de
jointure linéaire : LINEAR JOIN permettant de faire
correspondre à la ligne de rang un de la table de
gauche, la ligne de rang un + offset de la table de
droite et ainsi de suite.
CLI_NOM T_I_ENT
---------- ------------
DULIN 0
DUMOULIN 1
DUPOND 2
DUPONT 3
DURAND 4
DURAND 5
DUVAL 6
NULL 7
NULL 8
...
NULL 1000Pour résoudre notre problème d'affectation des places de
théâtre, il suffit de faire :
5. CONCLUSION
Ces requêtes s'apparentent aux T-JOIN (théta jointures)
du Docteur Codd en vue d'obtenir une correspondance
optimale des inégalités (typiquement le problème
d'affectation des élèves dans des salles de capacités
données).
Je laisse à votre sagacité la représentation d'une telle
jointure en algèbre relationnelle !
Exemple:
OU
SELECT * FROM (
SELECT TOP 10 Field1, Field2 FROM (
SELECT TOP 30 Field1, Field2
FROM matable
ORDER BY monchamp asc
) AS tbl1 ORDER BY monchamp desc
) AS tbl2 ORDER BY monchamp asc
set nocount on
set rowcount n
Set rowcount 0
Comment récupérer le résultat d'une requete
dynamique ?[haut]
auteur : HULK
Si la requête dynamique ne retourne qu'une seule valeur
<Elements>
<Element>
<Key/>
<Value/>
</Element>
</Elements>
lien : Exemple
auteur : davidou2001
Voici le code pour réaliser ceci
-- Le fichier binaire
DECLARE @s varbinary(2048)
SET @s = 0x47494...code hexa du fichier
DECLARE @o int
DECLARE @r int
EXEC sp_oacreate 'adodb.stream', @o output
EXEC sp_oasetproperty @o, 'type', 2
EXEC sp_oamethod @o, 'open'
EXEC sp_oamethod @o, 'writetext', NULL, @s
SELECT *
FROM T1
LEFT OUTER JOIN T2 ON T1.C1 = T2.C2
LEFT OUTER JOIN T3 ON T1.C1 = T3.C3
C1 C2 C3
----------- ----------- -----------
1 NULL NULL
0 0 0
1 NULL NULL
SELECT *
FROM T1, T2, T3
WHERE C1 *= C2 AND C1 *= C3
C1 C2 C3
----------- ----------- -----------
1 NULL NULL
0 0 0
1 NULL NULL
-- exact !
SELECT *
FROM T1
LEFT OUTER JOIN T2 ON T1.C1 = T2.C2
LEFT OUTER JOIN T3 ON T2.C2 = T3.C3
C1 C2 C3
----------- ----------- -----------
1 NULL NULL
0 0 0
1 NULL NULL
SELECT *
FROM T1, T2, T3
WHERE C1 *= C2
AND C2 *= C3
SELECT *
FROM T1
INNER JOIN (SELECT *
FROM T2) AS T
ON T1.C1 = T.C2
RIGHT OUTER JOIN T3 ON T.C2 = T3.C3
C1 C2 C3
----------- ----------- -----------
NULL NULL 3
0 0 0
NULL NULL 3
SELECT *
FROM T1, (SELECT * FROM T2) AS T, T3
WHERE C1 = C2
AND C2 =* C3
SELECT *
FROM T1
RIGHT OUTER JOIN T2 ON T1.C1 = T2.C2
RIGHT OUTER JOIN T3 ON T1.C1 = T3.C3
C1 C2 C3
----------- ----------- -----------
NULL NULL 3
0 0 0
NULL NULL 3
SELECT *
FROM T1, T2, T3
WHERE C1 =* C2
AND C1 =* C3
C1 C2 C3
----------- ----------- -----------
NULL 2 3
NULL 2 0
NULL 2 3
NULL 0 3
0 0 0
NULL 0 3
NULL 2 3
NULL 2 0
NULL 2 3
sp_configure connections, n
RECONFIGURE WITH OVERRIDE
--n est le nombre de connexions souhaitées.
select CASE
WHEN convert(sysname, serverproperty('Edition')) IS NULL THEN 'ERREUR'
WHEN convert(sysname, serverproperty('Edition'))=0 THEN 'SECURITE INTEGREE'
WHEN convert(sysname, serverproperty('Edition'))=1 THEN 'SECURITE NON
INTEGREE'
END AS AUTHENTIFICATION
OPEN Process_cur
WHILE (@@FETCH_STATUS = 0)
begin
set @SQL_Text = 'KILL '+ @spid
exec sp_executesql @T
FETCH NEXT FROM Process_cur into @spid;
end
CLOSE Process_cur
DEALLOCATE Process_cur
END
GO
O : Oui
N : Non
X : Existe mais syntaxe hors norme
! : Même nom mais fonction différente
Dès lors, tout MCD peut être tansformé en un MPD ("Modèle Physique des Données")
c'est à dire un modèle directement exploitable par la base de données que vous
voulez utiliser...
Tout l'intérêt de cet outil d'analyse est de permettre de modéliser plus aisément les
relations existant entre les entités et d'automatiser le passage du schéma muni
d'attributs aux tables de la base de données pourvues de leurs champs.
Exemple :
Dans ce cas une étude approfondie de la solution à adopter est nécessaire, mais ce
type de relation est en général assez rare et peu performante...
Exemple :
5.1.3. Relation de type n:m (plusieurs à plusieurs)
Règle n°4 : Dans le cas d'entités reliées par des associations de type n:m,
une table intermédiaire dite table de jointure, doit être créée, et doit
posséder comme clef primaire une conjonction des clefs primaires des deux
tables pour lesquelles elle sert de jointure.
Exemple :
sp_configure connections, n
RECONFIGURE WITH OVERRIDE
--n est le nombre de connexions souhaitées.
select CASE
WHEN convert(sysname, serverproperty('Edition')) IS NULL THEN 'ERREUR'
WHEN convert(sysname, serverproperty('Edition'))=0 THEN 'SECURITE INTEGREE'
WHEN convert(sysname, serverproperty('Edition'))=1 THEN 'SECURITE NON
INTEGREE'
END AS AUTHENTIFICATION
OPEN Process_cur
WHILE (@@FETCH_STATUS = 0)
begin
set @SQL_Text = 'KILL '+ @spid
exec sp_executesql @T
FETCH NEXT FROM Process_cur into @spid;
end
CLOSE Process_cur
DEALLOCATE Process_cur
END
GO
I. Introduction
II. Récursivité avec la norme SQL:1999
III. Une expression de table commune (CTE : Common Table
Expression)
IV. Deux astuces pour la récursion
V. Premier exemple, une hiérarchie basique
VI. Indentation hiérarchique
VI. Arbres SQL sans récursion
VI-A. PREMIÈRES IMPRESSIONS
VIII. Second exemple : un réseau complexe (et des requêtes
plus sexy !)
IX. Troisième exemple : découper une chaîne de caractères
X. Quatrième exemple : concaténer des mots pour former une
phrase
XI. Que faire de plus ?
XII. CONCLUSIONS
XIII. En bonus (CTE, requête récursive appliquée)
XIV. Bibliographie :
I. Introduction
Tout le monde a déjà eu affaire au moins une fois dans sa vie
à la récursion. Lorsque j'étais enfant, mes parents et moi
vivions dans un immeuble parisien où figuraient dans le hall
deux glaces se faisant face. Lorsque je passais entre ces deux
miroirs, mon image se reflétait à l'infini et j'étais assez
fier de palper le concept de récursion sur ma personne ! C'est
cela la récursion : un processus capable de se reproduire
aussi longtemps que nécessaire.
-- creation de la table
CREATE TABLE T_NEWS
(NEW_ID INTEGER NOT NULL PRIMARY KEY,
NEW_FORUM VARCHAR(16),
NEW_QUESTION VARCHAR(32))
GO
-- population de la table
INSERT INTO T_NEWS VALUES (1, 'SQL', 'What is SQL ?')
INSERT INTO T_NEWS VALUES (2, 'SQL', 'What do we do now ?')
INSERT INTO T_NEWS VALUES (3, 'Microsoft', 'Is SQL 2005 ready for use ?')
INSERT INTO T_NEWS VALUES (4, 'Microsoft', 'Did SQL2000 use RECURSION ?')
INSERT INTO T_NEWS VALUES (5, 'Microsoft', 'Where am I ?')
SELECT
COUNT(NEW_ID) AS NEW_NBR, NEW_FORUM
FROM T_NEWS
GROUP BY NEW_FORUM
WITH
Q_COUNT_NEWS (NBR, FORUM)
AS
(SELECT COUNT(NEW_ID), NEW_FORUM
FROM T_NEWS
GROUP BY NEW_FORUM)
SELECT NBR, FORUM
FROM Q_COUNT_NEWS
WHERE NBR = (SELECT MAX(NBR)
FROM Q_COUNT_NEWS)
Comme dans la cadre d'une vue SQL, vous devez nommer la CTE et
vous pouvez donner des noms particuliers aux colonnes du
SELECT qui construit l'expression de la CTE, mais cette
dernière disposition n'est pas obligatoire.
Dans les faits on peut enchaîner deux, trois ou autant de CTE
que vous voulez dans une même requête, chaque CTE pouvant être
construite à partie des expression des CTE précédentes. Voici
un exemple de ce concept de CTE gigogne :
Exemple 4
WITH
Q_COUNT_NEWS (NBR, FORUM)
AS
(SELECT COUNT(NEW_ID), NEW_FORUM
FROM T_NEWS
GROUP BY NEW_FORUM),
Q_MAX_COUNT_NEWS (NBR)
AS (SELECT MAX(NBR)
FROM Q_COUNT_NEWS)
SELECT T1.*
FROM Q_COUNT_NEWS T1
INNER JOIN Q_MAX_COUNT_NEWS T2
ON T1.NBR = T2.NBR
-- creation de la table
CREATE TABLE T_VEHICULE
(VHC_ID INTEGER NOT NULL PRIMARY KEY,
VHC_ID_FATHER INTEGER FOREIGN KEY REFERENCES T_VEHICULE
(VHC_ID),
VHC_NAME VARCHAR(16))
-- population
INSERT INTO T_VEHICULE VALUES (1, NULL, 'ALL')
INSERT INTO T_VEHICULE VALUES (2, 1, 'SEA')
INSERT INTO T_VEHICULE VALUES (3, 1, 'EARTH')
INSERT INTO T_VEHICULE VALUES (4, 1, 'AIR')
INSERT INTO T_VEHICULE VALUES (5, 2, 'SUBMARINE')
INSERT INTO T_VEHICULE VALUES (6, 2, 'BOAT')
INSERT INTO T_VEHICULE VALUES (7, 3, 'CAR')
INSERT INTO T_VEHICULE VALUES (8, 3, 'TWO WHEELS')
INSERT INTO T_VEHICULE VALUES (9, 3, 'TRUCK')
INSERT INTO T_VEHICULE VALUES (10, 4, 'ROCKET')
INSERT INTO T_VEHICULE VALUES (11, 4, 'PLANE')
INSERT INTO T_VEHICULE VALUES (12, 8, 'MOTORCYCLE')
INSERT INTO T_VEHICULE VALUES (13, 8, 'BICYCLE')
ALL
|--SEA
| |--SUBMARINE
| |--BOAT
|--EARTH
| |--CAR
| |--TWO WHEELS
| | |--MOTORCYCLE
| | |--BICYCLE
| |--TRUCK
|--AIR
|--ROCKET
|--PLANE
WITH
tree (data, id)
AS (SELECT VHC_NAME, VHC_ID_FATHER
FROM T_VEHICULE
WHERE VHC_NAME = 'MOTORCYCLE'
UNION ALL
SELECT VHC_NAME, VHC_ID_FATHER
FROM T_VEHICULE V
INNER JOIN tree t
ON t.id = V.VHC_ID)
SELECT *
FROM tree
data id
---------------- -----------
MOTORCYCLE 8
TWO WHEELS 3
EARTH 1
ALL NULL
correlation
_________________________________
| |
v |
WITH tree (data, id) |
AS (SELECT VHC_NAME, VHC_ID_FATHER |
FROM T_VEHICULE |
WHERE VHC_NAME = 'MOTORCYCLE' |
UNION ALL |
SELECT VHC_NAME, VHC_ID_FATHER |
FROM T_VEHICULE V |
INNER JOIN tree t <-----------
ON t.id = V.VHC_ID)
SELECT *
FROM tree
SELECT *
FROM T_VEHICULE
WHERE RIGHT_BOUND > 12
AND LEFT_BOUND < 13
data
-----------------------
ALL
SEA
SUBMARINE
BOAT
EARTH
CAR
TWO WHEELS
MOTORCYCLE
BICYCLE
TRUCK
AIR
ROCKET
PLANE
PARIS
|
------------------------------------
| | |
385 420 470
| | |
NANTES CLERMONT FERRAND LYON
| | |
| | 335 305 | 320
| ---------- -----------------
| | | |
375 | MONTPELLIER MARSEILLE
| | |
---------------------- 205
| 240 |
TOULOUSE NICE
-- creation de la table :
CREATE TABLE T_JOURNEY
(JNY_FROM_TOWN VARCHAR(32),
JNY_TO_TOWN VARCHAR(32),
JNY_KM INTEGER)
-- population :
INSERT INTO T_JOURNEY VALUES ('PARIS', 'NANTES', 385)
INSERT INTO T_JOURNEY VALUES ('PARIS', 'CLERMONT-FERRAND', 420)
INSERT INTO T_JOURNEY VALUES ('PARIS', 'LYON', 470)
INSERT INTO T_JOURNEY VALUES ('CLERMONT-FERRAND', 'MONTPELLIER',
335)
INSERT INTO T_JOURNEY VALUES ('CLERMONT-FERRAND', 'TOULOUSE', 375)
INSERT INTO T_JOURNEY VALUES ('LYON', 'MONTPELLIER', 305)
INSERT INTO T_JOURNEY VALUES ('LYON', 'MARSEILLE', 320)
INSERT INTO T_JOURNEY VALUES ('MONTPELLIER', 'TOULOUSE', 240)
INSERT INTO T_JOURNEY VALUES ('MARSEILLE', 'NICE', 205)
TO_TOWN
--------------------------------
CLERMONT-FERRAND
LYON
MARSEILLE
MONTPELLIER
PARIS
NANTES
CLERMONT-FERRAND
LYON
MONTPELLIER
MARSEILLE
NICE
TOULOUSE
MONTPELLIER
TOULOUSE
TOULOUSE
TOULOUSE
NICE
MONTPELLIER
MARSEILLE
NICE
TOULOUSE
MONTPELLIER
TOULOUSE
TOULOUSE
Constatez avec moi que ce n'est pas très intéressant car nous
ne savons pas d'où nous venons. Seule la destination figure
dans la réponse et il est probable que plusieurs trajets
figurent pour un même voyage... Pouvons-nous avoir plus
d'informations ?
TO_TOWN
--------------------------------
PARIS
NANTES
CLERMONT-FERRAND
LYON
MONTPELLIER
MARSEILLE
NICE
TOULOUSE
MONTPELLIER
TOULOUSE
TOULOUSE
TO_TOWN
--------------------------------
TOULOUSE
TOULOUSE
TOULOUSE
TO_TOWN STEPS
-------------------------------- -----------
TOULOUSE 3
TOULOUSE 2
TOULOUSE 3
WITH
journey (TO_TOWN, STEPS, DISTANCE, WAY)
AS
(SELECT DISTINCT JNY_FROM_TOWN, 0, 0, CAST('PARIS' AS
VARCHAR(MAX))
FROM T_JOURNEY
WHERE JNY_FROM_TOWN = 'PARIS'
UNION ALL
SELECT JNY_TO_TOWN, departure.STEPS + 1,
departure.DISTANCE + arrival.JNY_KM,
departure.WAY + ', ' + arrival.JNY_TO_TOWN
FROM T_JOURNEY AS arrival
INNER JOIN journey AS departure
ON departure.TO_TOWN = arrival.JNY_FROM_TOWN),
short (DISTANCE)
AS
(SELECT MIN(DISTANCE)
FROM journey
WHERE TO_TOWN = 'TOULOUSE')
SELECT *
FROM journey j
INNER JOIN short s
ON j.DISTANCE = s.DISTANCE
WHERE TO_TOWN = 'TOULOUSE'
--------------------------------------------
-- soit la table :
CREATE TABLE TRolePers
(
idPers int,
Role varchar(50)
)
-- contenant :
INSERT INTO TRolePers VALUES (1, 'RespX, RespY')
INSERT INTO TRolePers VALUES (2, 'Admin')
INSERT INTO TRolePers VALUES (3, 'RespX, RespZ, RespY')
/*
idPers Role
----------- --------------------------------------------------
1 RespX
1 RespY
2 Admin
3 RespX
3 respY
3 RespZ
Solution :
WITH T
AS
(
SELECT idPers,
CASE
WHEN CHARINDEX(',', Role) > 0 THEN LTRIM(SUBSTRING(Role, 1,
CHARINDEX(',', Role) - 1))
ELSE Role
END AS UnRole,
LTRIM(SUBSTRING(Role, CHARINDEX(',', Role) + 1, LEN(Role) -
CHARINDEX(',', Role))) AS LesAutresRoles
FROM TRolePers
UNION ALL
SELECT RP.idPers,
CASE
WHEN CHARINDEX(',', LesAutresRoles) > 0 THEN
LTRIM(SUBSTRING(LesAutresRoles, 1, CHARINDEX(',', LesAutresRoles) - 1))
ELSE LesAutresRoles
END,
CASE
WHEN CHARINDEX(',', LesAutresRoles) > 0 THEN
LTRIM(SUBSTRING(LesAutresRoles, CHARINDEX(',', LesAutresRoles) + 1,
LEN(LesAutresRoles) - CHARINDEX(',', LesAutresRoles)))
ELSE NULL
END
FROM TRolePers RP
INNER JOIN T
ON T.idPers = RP.idPers
WHERE LesAutresRoles IS NOT NULL
)
SELECT DISTINCT idPers, UnRole As Role
FROM T
ORDER BY 1, 2
idPers Role
----------- --------------------------------------------------
1 RespX
1 RespY
2 Admin
3 RespX
3 respY
3 RespZ
(6 ligne(s) affectée(s))
X. Quatrième exemple : concaténer des mots pour former une
phrase
Soit la table :
Contenant :
Solution :
WITH
phrases (phrase, id, position)
AS
(
SELECT CAST(PHR_MOT AS VARCHAR(max))
+ CASE
WHEN SUBSTRING(PHR_MOT, LEN(PHR_MOT), 1) = '''' THEN ''
ELSE ' '
END, PHR_ID, PHR_MOT_POSITION
FROM T_PHRASE_PHR
WHERE PHR_MOT_POSITION = 1
UNION ALL
SELECT phrase + CAST(PHR_MOT AS VARCHAR(max))
+ CASE
WHEN SUBSTRING(PHR_MOT, LEN(PHR_MOT), 1) = '''' THEN ''
ELSE ' '
END AS PHRASE,
PHR_ID, PHR_MOT_POSITION
FROM T_PHRASE_PHR AS suiv
INNER JOIN phrases
ON suiv.PHR_ID = phrases.id
AND suiv.PHR_MOT_POSITION = phrases.position + 1
),
maxphrase
AS
(
SELECT id, MAX(position) AS maxposition
FROM phrases
GROUP BY id
)
SELECT P.id, RTRIM(phrase) + '.' AS PHRASE
FROM phrases AS P
INNER JOIN maxphrase AS M
ON P.id = M.id
AND P.position = M.maxposition
ORDER BY id
id PHRASE
----------- ------------------------------------------------------------------------------------------
1 Le petit chat est mort.
2 Les sanglots longs des violons de l'automne blessent mon coeur d'une langueur
monotone.
(2 ligne(s) affectée(s))
*/
Cela peut être fait par une requête on ne peut plus simple :
Exemple 26
Exemple 27
TO_TOWN
--------------------------------
PARIS
NANTES
CLERMONT-FERRAND
LYON
....
LYON
MONTPELLIER
MARSEILLE
PARIS
XII. CONCLUSIONS
Avec les CTE et donc la possibilité d'écrire des requêtes
récursives, le langage SQL devient un langage complet capable
de traiter en une seule requête le problème le plus complexe
qui soit.
with :
<clause_cycle_recherche> ::=
<clause_recherche>
| <clause_cycle>
| <clause_recherche> <clause_cycle>
and :
<clause_recherche> ::=
SEARCH { DEPTH FIRTS BY
| BREADTH FIRST BY } <liste_specification_ordre>
SET <colonne_sequence>
<clause_cycle> ::=
CYCLE <colonne_cycle1> [ { , <colonne_cycle2> } ... ]
SET <colonne_marquage_cycle>
TO <valeur_marque_cycle>
DEFAULT <valeur_marque_non_cycle>
USING <colonne_chemin>
WITH
SELECT DISTINCT *
FROM T_TREE_CONTRAINTES
ORDER BY level
GO
Le cas d'auto-référence est en principe intégré. Les
paramètres sont :
@DB : nom de la base,
@USR : nom du schema, par défaut dbo,
@TABLE_TO_DELETE : nom de la table que vous voulez vider
SELECT IDENT_CURRENT('t_produit')
O : Oui
N : Non
X : Existe mais syntaxe hors norme
! : Même nom mais fonction différente
USE VotreBase
GO
SELECT destination_database_name,
restore_date,
b.database_name,
physical_name,
backup_start_date
FROM msdb.dbo.RestoreHistory h
INNER JOIN msdb.dbo.BackupSet b ON h.backup_set_id = b.backup_set_id
INNER JOIN msdb.dbo.BackupFile f ON f.backup_set_id = b.backup_set_id
WHERE b.database_name = db_name()
GO
transact sql
1. Positionnement du langage
1.1. Historique
1.2. Utilisation
1.3. Conclusion
2. Syntaxe
2.1. Identifiant
2.2. Variables
2.3. Structures basiques
2.4. Variable de retour
2.5. Variables "système"
2.6. Flags
2.7. Batch et GO
2.8. Quelques fonctions de base
2.9. Commentaires
3. UDF : fonction utilisateur
3.1. Fonction renvoyant une valeur
3.2. Fonction renvoyant une table
4. Procédures stockées
4.1. Entête de procédure
4.2. Paramètres, variables de retour et ensemble de
données
4.3. Gestion des erreurs
4.4. Procédures stockées prédéfinies
4.5. Verrouillage
4.6. Gestion de transactions
4.7. Les curseurs
5. Les triggers
5.1. Mise en place d'un trigger
5.2. Syntaxe d'un trigger MS SQL Server 2000
5.3. Eléments du langage spécifique aux triggers
5.3.1. Pseudo tables INSERTED et DELETED
5.3.2. Fonctions UPDATE et COLUMNS_UPDATED
5.3.3. Annulation des effets d'un trigger
5.4. Exemples de triggers
6. Cryptage du code, liaison au schema et recompilation
7. ANNEXE BIBLIOGRAPHIQUE
1. Positionnement du langage
Transact SQL est un langage procédural (par opposition à
SQL qui est un langage déclaratif) qui permet de
programmer des algorithmes de traitement des données au
sein des SGBDR Sybase Adaptive Server et Microsoft SQL
Server.
1.1. Historique
Il a été créé par Sybase INC. à la fin des années 80
pour répondre aux besoins d'extension de la
programmation des bases de données pour son SGBDR.
SGBDRLangageRespect normeRichesse
Oracle 8PL/SQL3/55/5
MS SQL Server v7Transact SQL2/53/5
PostGreSQLSQL 24/54/5
OCELOTSQL35/54/5
InterBaseISQL3/53/5
1.2. Utilisation
Transact SQL doit être utilisé :
1.3. Conclusion
Nous retiendrons que ce qui compte dans l'étude de ce
langage, c'est plus de comprendre ses grands concepts et
son esprit afin de pouvoir le transposer sur d'autres
SGBDR plutôt que d'apprendre par cœur telle ou telle
instruction, d'autant que la documentation sur ce
langage est assez fournie.
2. Syntaxe
2.1. Identifiant
Voici les règles de codage des identifiants (nom des
objets)
Les identifiants SQL :
Exemple :
SELECT *
FROM #tempTLe symbole dédoublé ## commence le nom de toute table
temporaire globale.
Conséquence : la libération des tables temporaires est
consécutif à la libération des utilisteurs.
Nota : SQL Server utilise un base particulière "tempDB"
pour stocker la définition des objets temporaires.
serveur.base_cible.utilisateur.objetExemple :
2.2. Variables
Les types disponibles sont ceux de SQL :
bit, int, smallint, tinyint, decimal, numeric, money, smallmoney, float, real, datetime,
smalldatetime, timestamp,
uniqueidentifier, char, varchar, text, nchar, nvarchar, ntext, binary, varbinary,
image.Auxquels il faut ajouter le type :
Remarques :
BEGIN
...
ENDpermet de définir des blocs d'instructions.
BREAK
CONTINUEpermettent respectivement d'interrompre ou de continuer
autoritairement une boucle.
Remarques :
RETURN 1445
VariableDescription
@@connectionsnombre de connexions actives
@@datefirtspremier jour d'une semaine (1:lundi à
7:dimanche)
@@errorcode de la dernière erreur rencontrée (0 si
aucune)
@@fetch_statusétat d'un curseur lors de la lecture
(0 si lecture proprement exécutée)
@@identitydernière valeur insérée dans une colonne
auto incrémentée pour l'utilisateur en cours
@@max_connectionsnombre maximums d'utilisateurs
concurrents
@@procididentifiant de la procédure stockée en
cours
@@rowcountnombre de lignes concernées par le
dernier ordre SQL
@@servernamenom du serveur SGBDR courant
@@spididentifiant du processus en cours
@@trancountnombre de transaction en cours
2.6. Flags
Pour manipuler les effets de certaines variables, comme
pour paramétrer la base de données, il est nécessaire de
recourir à des "flags".
2.7. Batch et GO
Un batch est un ensemble d'ordres SQL ou Transact SQL
passé en un lot. Il peut être exécuté en lançant un
fichier vers le moteur SQL ou encore en l'exécutant dans
l'analyseur de requêtes.
la plupart du temps un batch est utilisé pour créer les
objets d'une base et lancer au coup par coup certaines
procédures lourdes (administration notamment).
Attention :
USE NEW_MABASE
GO
DECLARE
@TableName Varchar(128), -- nom de la table passé en argument
@SearchWord Varchar(32) -- mot recherché
2.9. Commentaires
Comme dans le cadre du langage SQL de base, pour placer
des commentaries il suffit d'utiliser les marqueurs
suivants :
DébutFinCommentaire
-- pour une ligne de commentaire
/**/pour un bloc de ligne ou de caractères
RETURNS type_résultant
[ AS ]
BEGIN
code
RETURN valeur_résultante
RETURNS DATETIME
AS
BEGIN
DECLARE @G INT
DECLARE @I INT
DECLARE @J INT
DECLARE @C INT
DECLARE @H INT
DECLARE @L INT
DECLARE @JourPaque INT
DECLARE @MoisPaque INT
DECLARE @DimPaque DATETIME
SET @G = @AN % 19
SET @C = @AN / 100
SET @H = (@C - @C / 4 - (8 * @C + 13) / 25 + 19 * @G + 15) % 30
SET @I = @H - (@H / 28) * (1 - (@H / 28) * (29 / (@H + 1)) * ((21 - @G) / 11))
SET @J = (@AN + @AN / 4 + @I + 2 - @C + @C / 4) % 7
SET @L = @I - @J
SET @MoisPaque = 3 + (@L + 40) / 44
SET @JourPaque = @L + 28 - 31 * (@MoisPaque / 4)
RETURN @DimPaque
Exemple :
[ AS ]
BEGIN
code
RETURN
DECLARE @N INT
DECLARE @T TABLE (N int)
SET @N = 0
RETURN
END
SELECT *
FROM dbo.FN_ENTIERS (13, 1)
N
-----------
1
2
3
4
5
6
7
8
9
10
11
12
13
RETURN @N
END
SELECT dbo.FN_DELTA_MONTH('20020101')
4. Procédures stockées
Le but d'une procédure stockée est triple :
ATTENTION :
Les procédures stockées (v7) sont limitées à :
128 Mo de code
1024 paramètres en argument (y compris curseurs)
Exemple :
Exemple :
/*---------------------------------------------------\
| recherche d'une occurrence de mot dans n'importe |
| quelle colonne de type caractères d'une table donnée |
|----------------------------------------------------- |
| Frédéric BROUARD - COMMUNICATIC SA - 2001-12-17 |
\-------------------------------------------------- */
CREATE PROCEDURE SP_SEARCH_STRING_ANYFIELD
@TableName Varchar(128), -- nom de la table passé en argument
@SearchWord Varchar(32) -- mot recherché
AS
IF @ColumnList IS NULL
RAISERROR ('Aucune colonne de recherche trouvé dans la table %s',
16, 1, @TableName)
IF @@ERROR <> 0 GOTO LBL_ERROR
LBL_ERROR:
PRINT 'ERREUR LORS DE L''EXÉCUTION DE LA PROCÉDURE STOCKÉE
SP_SEARCH_STRING_ANYFIELD'
4.5. Verrouillage
Le verrouillage est la technique de base pour assurer
les concurrences d'accès aux ressources.
On peut observer les verrous posés lors des
manipulations de données à l'aide de la procédure
stockée sp_lock. De même on peut savoir qui utilise quoi
à l'aide de la procédure stockée sp_who.
VerrouSELECTUPDATE
NOLOCKaucun verrouimpossible
HOLDLOCKmaintient du verrou jusqu'à la fin de la
transactionpas nécessaire
UPDLOCKpose un verrou de mise à jour au lieu d'un
verrou partagéredondant
PAGLOCKforce la pose d'un verrou de pageforce la
pose d'un verrou de page
TABLOCKforce un verrou partagé sur la tableforce
un verrou exclusif sur la table
TABLOCKXforce un verrou exclusif sur la table
pendant toute la durée de la transactionforce un
verrou exclusif sur la table pendant toute la
durée de la transaction
Exemples :
SELECT *
FROM T_CLIENT C WITH (NOLOCK)
JOIN T_FACTURE F WITH (NOLOCK)
ON C.CLI_ID = F.CLI_IDVerrouillage exclusif de la table le temps de la mise à
jour :
... WITH (PAGLOCK HOLDLOCK) ...Attention : la manipulation des verrous est affaire
de
spécialistes. Une pose de verrous sans étude préalable
de la concurrence d'exécution des différentes procédures
stockées d'une base, peut conduire à des scénarios de
blocage, comme "l'étreinte fatale".
4.6. Gestion de transactions
Toute ordre SQL du DML est une transaction (SELECT
INSERT, UPDATE, DELETE).
BEGIN TRANSACTION
-- validation de l'ensemble
COMMIT TRANSACTION
RETURN
NiveauEffetsID
READ UNCOMMITTEDImplémente la lecture incorrecte,
ou le verrouillage de niveau 0, ce qui signifie
qu'aucun verrou partagé n'est généré et qu'aucun
verrou exclusif n'est respecté. Lorsque cette
option est activée, il est possible de lire des
données non validées, ou données incorrectes ; les
valeurs des données peuvent être modifiées et des
lignes peuvent apparaître ou disparaître dans le
jeu de données avant la fin de la transaction.
Cette option a le même effet que l'activation de
l'option NOLOCK dans toutes les tables de toutes
les instructions SELECT d'une transaction. Il
s'agit du niveau d'isolation le moins restrictif
parmi les quatre disponibles.0
READ COMMITTEDSpécifie que les verrous partagés
sont maintenus durant la lecture des données pour
éviter des lectures incorrectes. Les données
peuvent néanmoins être modifiées avant la fin de
la transaction, ce qui donne des lectures non
renouvelées ou des données fantômes. Cette option
est l'option SQL Server par défaut.1
REPEATABLE READDes verrous sont placés dans toutes
les données utilisées dans une requête, afin
d'empêcher les autres utilisateurs de les mettre à
jour. Toutefois, un autre utilisateur peut ajouter
de nouvelles lignes fantômes dans un jeu de
données par un utilisateur ; celles-ci seront
incluses dans des lectures ultérieures dans la
transaction courante.2
SERIALIZABLEPlace un verrou sur une plage de
données, empêchant les autres utilisateurs de les
mettre à jour ou d'insérer des lignes dans le jeu
de données, jusqu'à la fin de la transaction. Il
s'agit du niveau d'isolation le plus restrictif
parmi les quatre niveaux disponibles. Utilisez
cette option uniquement lorsque cela s'avère
nécessaire, car la concurrence d'accès est
moindre. Cette option a le même effet que
l'utilisation de l'option HOLDLOCK dans toutes les
tables de toutes les instructions SELECT d'une
transaction.3
SET NOCOUNT ON
-- démarrage transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION INSERT_TREE_FILS_AINE
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_FILS_AINE
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_FILS_AINE
SELECT -1
RETURN
END
-- insertion
INSERT INTO T_DEVELOPPEMENT_DEV (DEV_NIVEAU, DEV_LIBELLE,
DEV_BORNE_GAUCHE, DEV_BORNE_DROITE)
VALUES (@niveau, @libelle, @bg_pere + 1, @bg_pere + 2)
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_FILS_AINE
SELECT -1
RETURN
END
SET NOCOUNT ON
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_FILS_CADET
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_FILS_CADET
SELECT -1
RETURN
END
-- insertion
INSERT INTO T_DEVELOPPEMENT_DEV (DEV_NIVEAU, DEV_LIBELLE,
DEV_BORNE_GAUCHE, DEV_BORNE_DROITE)
VALUES (@niveau, @libelle, @bd_pere , @bd_pere + 1)
SELECT @id_ins = @@identity
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_FILS_CADET
SELECT -1 as ID
RETURN
END
SET NOCOUNT ON
-- démarrage transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION INSERT_TREE_RIGHT
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_RIGHT
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_RIGHT
SELECT -1
RETURN
END
-- insertion
INSERT INTO T_DEVELOPPEMENT_DEV (DEV_NIVEAU, DEV_LIBELLE,
DEV_BORNE_GAUCHE, DEV_BORNE_DROITE)
VALUES (@niveau, @libelle, @bd_frere + 1, @bd_frere+2)
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_RIGHT
SELECT -1
RETURN
END
SET NOCOUNT ON
-- recherche du bg_frere
SELECT @bg_frere = DEV_BORNE_GAUCHE, @niveau = DEV_NIVEAU
FROM T_DEVELOPPEMENT_DEV
WHERE DEV_ID = @id_frere
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_LEFT
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION INSERT_TREE_LEFT
SELECT -1
RETURN
END
-- insertion
INSERT INTO T_DEVELOPPEMENT_DEV (DEV_NIVEAU, DEV_LIBELLE,
DEV_BORNE_GAUCHE, DEV_BORNE_DROITE)
VALUES (@niveau, @libelle, @bg_frere, @bg_frere+1)
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION
SELECT -1
RETURN
END
SET NOCOUNT ON
-- démarrage transaction
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
BEGIN TRANSACTION DELETE_TREE
IF @recursif = 0
BEGIN
-- suppression de l'élément
DELETE FROM T_DEVELOPPEMENT_DEV
WHERE DEV_ID = @id_element
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
END
IF @recursif = 1
BEGIN
-- suppression des éléments du sous arbre
DELETE FROM T_DEVELOPPEMENT_DEV
WHERE DEV_BORNE_GAUCHE >= @bg_element AND DEV_BORNE_DROITE
<= @bd_element
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
IF @@ERROR <>0
BEGIN
ROLLBACK TRANSACTION DELETE_TREE
SELECT -1
RETURN
END
END
-- declaration du curseur
DECLARE MyCursor CURSOR
FOR
SELECT COL1, COL2, COL3 …
FROM MyTable
-- ouverture du curseur
OPEN MyCursor
-- boucle de traitement
WHILE @@fetch_Status = 0
BEGIN
traitement
-- lecture de l'enregistrement suivant
FETCH MyCursor INTO @Col1, @Col2, @Col3...
END
-- fermeture du curseur
CLOSE myCursor
-- libération de la mémoire
DEALLOCATE myCursorOn constate que l'instruction FETCH apparaît deux fois.
Une première fois avant la boucle WHILE une seconde fois
à l'intérieur et en dernière ligne de la boucle WHILE.
C'est la façon la plus classique et la plus portable
d'utiliser des curseurs.
/***************************************************/
-- Frédéric BROUARD - Communicatic SA - 2001-12-24 --
--=================================================--
-- Informe du nombre de ligne de chaque table de --
-- la base de données --
/***************************************************/
-- ouverture du curseur
OPEN CursBase
/*---------------------------------------------------\
| recherche d'une occurrence de mot dans n'importe |
| quelle colonne de type caractères de n'importe |
| quelle table de la base de données |
|----------------------------------------------------- |
| Frédéric BROUARD - COMMUNICATIC SA - 2001-12-18 |
\-------------------------------------------------- */
CREATE PROCEDURE SP_SEARCH_STRING_ANYFIELD_ANYTABLE
@SearchWord Varchar(32) -- mot recherché
AS
-- variables de travail
DECLARE @TableName VARCHAR(128), -- nom de la table passé en argument
@ColumnList1 VARCHAR(2000),-- liste des colonnes pour clause SELECT
@ColumnList2 VARCHAR(2000),-- liste des colonnes pour clause WHERE
@SQL VARCHAR(5000) -- requête dynamique
-- ouverture du cuseur
OPEN CurTables
-- les variables contenant les listes des colonnes sont initialisée à vide
SET @ColumnList1 = ''
SET @ColumnList2 = ''
-- fermeture du curseur
CLOSE CurTables
RETURN
Exemple :
Emplois typiques :
IF [NOT] UPDATE(<colonne>)
BEGIN
<traitement>
ENDElle ne peut être utilisée que dans les triggers de type
INSERT et UPDATE.
UPDATE T_CLIENT
SET CLI_TEL = '01 02 03 04 05'
WHERE CLI_ID = 1
Serveur : Msg 8114, Niveau 16, État 5, Procédure E_CLI_INS, Ligne 6
Erreur de conversion du type de données varchar en numeric.provoque une erreur et
l'insertion n'a pas lieu.
UPDATE T_CLIENT
SET CLI_TEL = '91.92.93.94.95'
WHERE CLI_ID = 1Le seul inconvénient est que cette façon de procéder
rejette toutes les lignes insérées ou mise à jour sans
accepter celles qui peuvent être correctement formatées.
D'autre part on exécute cette procédure jusqu'au bout,
même si la colonne CLI_TEL ne subie aucune modification.
Néanmoins ce cas peut être résolu par un traitement
spécifique utilisant la fonction UPDATE :
UPDATE T_CLIENT
SET CLI_TEL = '88 77-66 55.44'
WHERE CLI_ID = 1donne pour résultat :
-- variable de travail
DECLARE @IdCli int, @TelAvant VARCHAR(20), @TelApres VARCHAR(20),
@car CHAR(1), @i int, @j int
-- ouverture du curseur
OPEN CurIns
IF @@error <> 0 GOTO LBL_ERROR
-- lecture première ligne
FETCH CurIns INTO @IdCli, @TelAvant
-- boucle de lecture
WHILE @@Fetch_Status = 0
BEGIN
-- si vide reboucle immédiatement
IF @TelAvant = ''
BEGIN
FETCH CurIns INTO @IdCli, @TelAvant
CONTINUE
END
-- scrutation de la valeur du téléphone
SET @i = 1
SET @j = 0
SET @TelApres = ''
-- boucle de nettoyage sur tous les caractères
WHILE @i <= LEN(@TelAvant)
BEGIN
-- reprise du caractère d'ordre i
SET @car = SUBSTRING(@TelAvant,@i,1)
-- on ne traite que les caractères de 0 à 9
IF @car = '0' or @car = '1' or @Car = '2' or @Car = '3'
or @car = '4' or @car = '5' or @Car = '6' or @Car = '7'
or @car = '8' or @car = '9'
BEGIN
SET @TelApres = @TelApres + @Car
SET @j = @j + 1
END
SET @i =@i + 1
END
-- si vide reboucle immédiatement
IF @TelApres = ''
BEGIN
FETCH CurIns INTO @IdCli, @TelAvant
CONTINUE
END
-- découpage par tranche de 2 nombres
SET @TelAvant = @TelApres
SET @i = 1
SET @TelApres = ''
-- boucle de découpage
WHILE @i <= LEN(@TelAvant)
BEGIN
SET @car = SUBSTRING(@TelAvant,@i,1)
SET @TelApres = @TelApres + @Car
IF @i % 2 = 0
SET @TelApres = @TelApres + '-'
SET @i =@i + 1
END
-- petit effet de bord si @TelApres se termine par un nombre pair,
-- alors tiret en trop !
IF @j % 2 = 0 -- au pasage % est la fonction MODULO dans SQL Server
SET @TelApres = SUBSTRING(@TelApres, 1, LEN(@TelApres)-1)
-- mise à jour si différence
IF @TelAvant <> @TelApres
UPDATE CLIENT
SET CLI_TEL = @TelApres
WHERE CLI_ID = @IdCli
IF @@error <> 0 GOTO LBL_ERROR
FETCH CurIns INTO @IdCli, @TelAvant
END
RETURN
NOT EXISTS(SELECT *
FROM T_MOT MOT
JOIN T_MOT_NOIR MNR
ON MOT.MOT_MOT = MNR.MNR_MOT)Soit toujours évaluée à vrai !
-- une table dont on ne désire pas que n'importe qui insére dedans
CREATE TABLE T_CRYPTEE_CRP
(CRP_ID INT NOT NULL PRIMARY KEY,
CRP_NOM CHAR(32) NOT NULL,
CRP_CONTROL VARBINARY(8))
-- un trigger qui vérifie la concordance entre le nom et le code de controle
CREATE TRIGGER TRG_CRP_INS_UPD
ON T_CRYPTEE_CRP
WITH ENCRYPTION
FOR INSERT, UPDATE
AS
IF NOT EXISTS(SELECT *
FROM INSERTED
WHERE CRP_CONTROL = CAST(SUBSTRING(CRP_NOM, 1, 8) AS
VARBINARY(8)))
ROLLBACK
-- tentative d'insertion sans connaissance de l'algoritme de controle
INSERT INTO T_CRYPTEE_CRP VALUES (1, 'Dupont', CAST(' ' AS
VARBINARY(8)))
SELECT * FROM T_CRYPTEE_CRP
CRP_ID CRP_NOM CRP_CONTROL
----------- -------------------------------- ------------------
BEGIN TRANSACTION A
BEGIN TRANSACTION B
ROLLBACK TRANSACTION B
COMMIT TRANSACTION A
La transaction B valide les parties de code a1 et a2, mais le
code b étant annulé, la transaction A est clairement
incohérente. C'est pourquoi dans le principe les transactions
imbriquées ne sont pas possible !
En fait il n'y a donc jamais qu'une seule transaction. Et
c'est toujours la première...
BEGIN TRANSACTION A
COMMIT TRANSACTION A
Démonstration :
BEGIN TRANSACTION
COMMIT TRANSACTION
RETURN (0)
LBL_ERROR:
IF @@TRANCOUNT > 1
COMMIT TRANSACTION
IF @@ROWCOUNT = 1
ROLLBACK TRANSACTION
RETURN (-1)
GO
BEGIN TRANSACTION
-- insertion valide
INSERT INTO T_TRN VALUES (33)
SELECT @ERROR = @@ERROR, @ROWCOUNT = @@ROWCOUNT
IF @ERROR <> 0 OR @ROWCOUNT = 0
BEGIN
RAISERROR('Procédure P_TRN_EXTERNE : Erreur à l''insertion', 16, 1)
GOTO LBL_ERROR
END
EXEC @RETVAL = P_TRN_INTERNE
SELECT @ERROR = @@ERROR, @ROWCOUNT = @@ROWCOUNT
IF @RETVAL = -1 -- la transaction a été pseudo validée mais elle doit être annulée
BEGIN
RAISERROR('Procédure P_TRN_EXTERNE : Erreur à l''appel de la procédure
P_TRN_INTERNE', 16, 1)
GOTO LBL_ERROR
END
COMMIT TRANSACTION
RETURN (0)
LBL_ERROR:
IF @@TRANCOUNT > 1
COMMIT TRANSACTION
IF @@ROWCOUNT = 1
ROLLBACK TRANSACTION
RETURN (-1)
GO
-- exécution teste
EXEC P_TRN_EXTERNE
GO
-- succès
COMMIT TRANSACTION
RETURN (0)
-- échec
LBL_ERROR:
IF @@TRANCOUNT > 1
COMMIT TRANSACTION
IF @@ROWCOUNT = 1
ROLLBACK TRANSACTION
RETURN (-1)
...
DECLARE @RETVAL INT
...
-- succès
COMMIT TRANSACTION
SET @RETVAL = 0
GOTO RESUME
-- échec
LBL_ERROR:
IF @@TRANCOUNT > 1
COMMIT TRANSACTION
IF @@ROWCOUNT = 1
ROLLBACK TRANSACTION
SET @RETVAL = -1
LBL_RESUME:
SET TRANSACTION ISOLATION LEVEL READ COMMITTED
LES TRIGGERS
Comment désactiver et réactiver un déclencheur ?
Comment désactiver et réactiver une contrainte ?
Comment tester qu'une colonne a été modifiée dans un
trigger Insert ou Update ?
Comment récuperer la date système dans une fonction
utilisateur ?
Comment débugger une procédure stockée ?
Comment savoir si un ordre SQL s'est bien déroulé ?
Comment retrouver le libellé d'un message d'erreur par
rapport à son n° ?
Comment requêter sur 2 tables de deux bases
différentes ?
SELECT DateHeure_Courante
FROM V_DateHeure_Courante
SELECT description
FROM master..sysmessages
WHERE langid=@@langid
AND error= VotreNoDErreur
NomDeTable
NomDuSchema.NomDeTable
NomDeLaBase.NomDuSchema.NomDeTable
NomDeLaBase..NomDeLaTable
NomDuServeurNomDeLaBase.NomDuSchema.NomDeTable
Si l'on souhaite, dans le cas extrême, lier 2 tables de
2 bases distinctes, situées chacune sur un serveur
distinct, il faudra donc utiliser la nomenclature
complète, en prenant soin au préalable de déterminer le
serveur distant/lié
Exemple
SELECT P.Nom, P.Prenom, L.NomLocalite
FROM Personnes P INNER JOIN ServeurDistant.BaseDistante..Localites L
ON P.CodeLocalite=L.IDLocalite
LES TRIGGERS
SELECT description
FROM master..sysmessages
WHERE langid=@@langid
AND error= VotreNoDErreur
NomDeTable
NomDuSchema.NomDeTable
NomDeLaBase.NomDuSchema.NomDeTable
NomDeLaBase..NomDeLaTable
NomDuServeurNomDeLaBase.NomDuSchema.NomDeTable
Si l'on souhaite, dans le cas extrême, lier 2 tables de
2 bases distinctes, situées chacune sur un serveur
distinct, il faudra donc utiliser la nomenclature
complète, en prenant soin au préalable de déterminer le
serveur distant/lié
Exemple
SELECT P.Nom, P.Prenom, L.NomLocalite
FROM Personnes P INNER JOIN ServeurDistant.BaseDistante..Localites L
ON P.CodeLocalite=L.IDLocalite
LES VUES
SELECT table_name
FROM information_schema.tables
WHERE table_type='BASE TABLE'
SELECT name
FROM sysobjects
WHERE type='V'
SELECT *
FROM information_schema.views
SELECT name
FROM sysobjects
WHERE type='FN'
SELECT name
FROM sysobjects
WHERE type='P'
auteur : spidetra
La liste des triggers de SQL-Server est accessible grâce
à une requête sur les tables systèmes : sysobjects,
syscomments et sysusers.
SELECT
o.name, o.xtype, c.text, u.name, o.crdate
FROM
dbo.sysobjects o
INNER JOIN dbo.syscomments c
ON c.id = o.id
INNER JOIN dbo.sysusers u
ON u.uid = c.uid
WHERE
xtype = 'TR'
Ou
select
column_name as champ,
COALESCE(domain_name,
cast(data_type as varchar(128))+ ISNULL(' ' + cast(character_maximum_length as
varchar(10)) ,'')) as type_donnee,
CASE UPPER(IS_NULLABLE)
when 'YES' then ''
when 'NO' then 'Oui'
when Null then ''
else IS_NULLABLE
END as Obligatoire,
'' as description
from INFORMATION_SCHEMA.columns
where
table_name = 'Matable'
order by table_name, ordinal_position
select cdefault
from syscolumns
where id = object_id('VotreTable')
and name = 'VotreColonne'
SELECT CATALOG_NAME
FROM INFORMATION_SCHEMA.SCHEMATA
Go
USE master
Go
SELECT name as BaseDedonneeDuServeur
FROM sysdatabases
Go
EXEC sp_databases
go
Comment changer le type de données d'une colonne
?[haut]
Exemple :
--Supposons que nous ayant une table T_Person dont la definition est :
CREATE TABLE Tmp_T_PERSONNE
(
PER_ID int NOT NULL,
PER_NOM varchar(50) NOT NULL,
PER_PRENOM varchar(50) NULL,
PER_NE_LE smalldatetime NOT NULL,
) ON [PRIMARY]
GO
--Et que nous voulons changer le type Per_Nom du type varchar(50) au type varchar(100)
--Nous aurons :
BEGIN TRANSACTION
SET TRANSACTION ISOLATION LEVEL SERIALIZABLE
--Créer une table temporaire ayant même structure que la première
CREATE TABLE Tmp_T_PERSONNE
(
PER_ID int NOT NULL,
PER_NOM varchar(100) NOT NULL,
PER_PRENOM varchar(50) NULL,
PER_NE_LE smalldatetime NOT NULL,
) ON [PRIMARY]
GO
-- Peupler la table
IF EXISTS(SELECT * FROM T_PERSONNE)
EXEC('INSERT INTO Tmp_T_PERSONNE (PER_ID,PER_NOM, PER_PRENOM,
PER_NE_LE, PAY_ID, PER_NE_A)
SELECT PER_ID, PER_NOM, PER_PRENOM, PER_NE_LE FROM T_PERSONNE
TABLOCKX')
GO
--Supprimer la table
DROP TABLE dbo.T_PERSONNE
GO
--Renommer la nouvelle table avec l'ancien nom
EXECUTE sp_rename N'Tmp_T_PERSONNE', N'T_PERSONNE', 'OBJECT'
GO
COMMIT
EXEC sp_renamedb('MyOldDB','MyNiewDB')
ValeurDescription
COLUMNUne colonne qui doit être renommée..
BASE DE DONNEESBase de données définie par
l'utilisateur. Cette option est nécessaire pour
renommer une base de données.
INDEXUn index défini par l'utilisateur.
OBJECTÉlément d'un type repris dans sysobjects.
Par exemple, OBJECT peut être utilisé pour
renommer les objets dont les contraintes (CHECK,
FOREIGN KEY, PRIMARY/UNIQUE KEY), des tables
utilisateur, des affichages, des procédures
stockées, des déclencheurs et des règles.
USERDATATYPEType de données défini par
l'utilisateur ajouté en exécutant sp_addtype.
select text
from dbo.syscomments, dbo.sysobjects
where syscomments.id = sysobjects.id
And sysobjects.xtype = 'P'
AND sysobjects.name='MaProcédure'
sp_helptext 'MaProcédure'
Database diagram support objects cannot be installed because this database does not have
a valid owner. To continue, first use the Files page of the Database Properties dialog box
or the ALTER AUTHORIZATION statement to set the database owner to a valid login,
then add the database diagram support objects.
SELECT *
FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
WHERE TABLE_NAME = 'matable'
CONNECTIVITE A LA SGBD
USE MaBase
GO
SELECT COUNT(*)
FROM master..sysprocesses
WHERE dbid=db_id()
GO
ou
SELECT COUNT(*)
FROM master..sysprocesses
WHERE dbid=db_id('MabaseDeDonnée')
SELECT COUNT(*)
FROM master..sysprocesses
WHERE dbid=db_id()
declare @i int
if @db is null
select @db=db_name()
else
if not exists(select name from master..sysdatabases where name = @db)
begin
Print 'La base '+@db+' n''existe pas dans le serveur '+ @@servername
return -1
end
/* Nombre d'utilisateurs */
select @i=count(spid) from master..sysprocesses where dbid = db_id(@db) and status
<> 'BACKGROUND'
if @i = 0
begin
print ''
print 'Cette base est inutilisée'
print ''
end
else
begin
/* Liste des utilisateurs */
declare @snum varchar(4)
select @snum = convert(varchar(4), @i)
print ''
print @snum+' utilisateur(s) trouvés dans la base '+@db+', serveur '+ @@servername
print ''
select spid, loginame Utilisateur , cmd, program_name from master..sysprocesses
where dbid = db_id(@db) and status <> 'BACKGROUND'
/* membres de groupes */
select 'GRANT ROLE '+ g.name +' TO '''+u.name+''''
from sysmembers m inner join sysusers u
on m.memberuid = u.uid
inner join sysusers g
on m.groupuid=g.uid
where u.uid > 2
and u.name = @login
/*Droits*/
select
case p.protecttype
when 206 then 'REVOKE'
else 'GRANT ' end +
case p.action
when 26 then 'REFERENCES'
when 178 then 'CREATE FUNCTION'
when 193 then 'SELECT'
when 195 then 'INSERT'
when 196 then 'DELETE'
when 197 then 'UPDATE'
when 198 then 'CREATE TABLE'
when 203 then 'CREATE DATABASE'
when 207 then 'CREATE VIEW'
when 222 then 'CREATE PROCEDURE'
when 224 then 'EXECUTE'
when 228 then 'BACKUP DATABASE'
when 233 then 'CREATE DEFAULT'
when 235 then 'BACKUP LOG'
when 236 then 'CREATE RULE' end +
' ON ' + o.name +
case when p.action < 200 then
case when p.protecttype = 206 then ' FROM ' else ' TO ' END +u.name
else '' end +
case when p.protecttype = 204 then ' WITH GRANT OPTION' else '' end
from sysprotects p
inner join sysusers u on u.uid=p.uid
inner join sysobjects o on o.id=p.id
where p.columns = 0x01 OR p.columns is null
and u.name = @login
order by o.name
end
declare @i int
if @db is null
select @db=db_name()
else
if not exists(select name from master..sysdatabases where name = @db)
begin
Print 'La base '+@db+' n''existe pas sur le serveur SQL '+@@servername
return -1
end
/* Number of users */
select @i=count(spid) from master..sysprocesses where dbid = db_id(@db) and status
<> 'background'
if @i = 0
begin
print ''
print 'Cette base est actuellement inutilisée'
print ''
end
else
begin
/* List of the users */
declare @snum varchar(4)
select @snum = convert(varchar(4), @i)
print ''
print @snum+' utilisateurs(s) actifs dans la base '+@db+' du serveur '+ @@servername
print ''
select spid, nt_username Utilisateur , cmd, program_name Programme from
master..sysprocesses
where dbid = db_id(@db) and status <> 'background'
2) Revenir à l'ancien
update master..sysxlogins
set password=O.password
from old_login O, sysxlogins L
where L.name=O.name
and O.name='MonUtilisateur'
6. Les assertions
Exemple 56
CREATE ASSERTION AST_VERIFACTURE
CHECK (SELECT SUM(FCT_MONTANT)
FROM T_FACTURE F
WHERE FCT_PAYE = 0
GROUP BY CLI_ID, FCT_PAYE) < (SELECT 0.2 * (SUM(CPT_DEBIT) -
SUM(CPT_CREDIT))
FROM T_COMPTE
WHERE CLI_ID = F.CLI_ID)Autre exemple, considérons que
l'unicité d'une
clef doit porter sur deux tables. Par exemple que
la clef identifiant un client ou un prospect doit
être unique au sein des deux tables afin qu'un
prospect puisse devenir un client sans changement
de clef. Dans ce cas l'assertion suivante peut
être mise en place :
Exemple 57
7. Les tables
Exemple 58
Exemple 59
Exemple 60
Exemple 62
Exemple 63
contrainte[NOT] NULL
DEFAULT
COLLATE
PRIMARY KEY
UNIQUE
CHECK
FOREIGN KEY
colonne (verticale)OUIOUIOUIOUIOUIOUIOUI
ligne (horizontale)NONNONNONOUIOUIOUIOUI
Exemple 64
Exemple 67
Exemple 68
Exemple 70
Exemple 72
Sa syntaxe est :
CHECK ( prédicat )où prédicat peut contenir le mot clef VALUE pour
faire référence à la colonne pour laquelle la
contrainte est définie.
Exemple 73
Exemple 74
Exemple 75
Sa syntaxe est :
Exemple 80
CONSTRAINT nom_contrainte
FOREIGN KEY (liste_colonne_table)
REFERENCES table_référencée (liste_colonne_référencées)
[ MATCH { FULL | PARTIAL | SIMPLE } ]
[ { ON UPDATE { NO ACTION | CASCADE | RESTRICT | SET NULL | SET
DEFAULT } ]
[ { ON DELETE { NO ACTION | CASCADE | RESTRICT | SET NULL | SET
DEFAULT } ]
[ { INITIALLY DEFERRED | INITIALLY IMMEDIATE } [ [ NOT ] DEFERRABLE ]
| [ NOT ] DEFERRABLE [ { INITIALLY DEFERRED | INITIALLY IMMEDIATE }
] ]Clause
MATCH de gestion de la référence
Clause ON UPDATE de mise à jour
Clause ON DELETE de suppression
Clause de déférabilité
Exemple 81
Exemple 83
Exemple 85
Exemple 86
IMPORTANT
Exemple 87
UPDATE T_CLIENT
SET CDE_ID = 587
WHERE CLI_ID = 118
COMMIT
Exemple 88
UPDATE T_CLIENT
SET CDE_ID = 587
WHERE CLI_ID = 118
COMMIT
Exemple 89
Exemple 91
-- condition de départ
-- le script de modification
ALTER TABLE
DROP IMP_DATE
ALTER TABLE
ADD IMP_DATE DATE
UPDATE T_IMPORT
SET IMP_DATE = CAST(CASE SUBSTRING(TMP_IMP_DATE, 5, 2)
WHEN < '03' THEN '20'
ELSE '19'
END || SUBSTRING(TMP_IMP_DATE, 5, 2)
|| '-' || SUBSTRING(TMP_IMP_DATE, 3, 2)
|| '-' || SUBSTRING(TMP_IMP_DATE, 1, 2) AS DATE)
ALTER TABLE
DROP TMP_IMP_DATE
COMMIT
-- on aura noté que le pivot de date pour changement de siècle aura été géré dans le
dernier update...
-- condition de départ
UPDATE T_IMPORT
SET TMP_IMP_NOM = COALESCE(IMP_NOM, '')
ALTER TABLE
DROP IMP_NOM
ALTER TABLE
ADD IMP_NOM VARCHAR(16) NOT NULL
UPDATE T_IMPORT
SET IMP_NOM = TMP_IMP_NOM
ALTER TABLE
DROP TMP_IMP_NOM
COMMIT
8. Les vues
-- permet de stocker l'évolution d'un tarif, sachant que celui-ci n'est applicable
-- pour un produit donné (PRD_ID) qu'à partir de la date TRF_DATE
INSERT INTO T_TARIF VALUES (1, '1996-01-01', 53, 123.45)
INSERT INTO T_TARIF VALUES (2, '1998-09-15', 53, 128.52)
INSERT INTO T_TARIF VALUES (3, '1999-12-31', 53, 147.28)
INSERT INTO T_TARIF VALUES (4, '1997-01-01', 89, 254.89)
INSERT INTO T_TARIF VALUES (5, '1999-12-31', 89, 259.99)
INSERT INTO T_TARIF VALUES (6, '1996-01-01', 97, 589.52)
UPDATE T_EMPLOYE
SET EMP_SALAIRE = EMP_SALAIRE + 100
WHERE STATUT = 'CADRE'
Exemple 96
Exemple 97
-- les triggers
SELECT CAST('TRIGGER' AS VARCHAR(32)) AS TYPE_OBJET,
o1.name AS NOM_OBJET, o2.name AS TABLE_ASSOCIEE
FROM sysobjects o1
JOIN sysobjects o2
ON o1.parent_obj = o2.id
WHERE o1.xtype = 'TR'
AND o1.status >= 0
AND o1.category = 0
UNION
-- les fonctions
SELECT CAST('FONCTION' AS VARCHAR(32)),
name, ''
FROM sysobjects
WHERE xtype = 'FN'
AND status >= 0
AND category = 0
UNION
-- les procédures stockées
SELECT CAST('PROCEDURE' AS VARCHAR(32)),
name, ''
FROM sysobjects
WHERE xtype = 'P'
AND status >= 0
AND category = 0
UNION
-- les vues
SELECT CAST('VUE' AS VARCHAR(32)),
name, ''
FROM sysobjects
WHERE xtype = 'V'
AND status >= 0
AND category = 0
ORDER BY TYPE_OBJET, NOM_OBJET, TABLE_ASSOCIEE
11. Résumé
partie en construction
1. Constat
2. Le remède
2.1. Détection des failles
2.1.1. Accès serveur par compte par défaut ("sa")
2.1.2. Comptes d'accès serveur non sécurisés
2.1.3. Rôles et utilisateurs de la base non sécurisés
2.2. Création de mots de passe
2.3. Sécurisation de l'accès au serveur
2.4. Stratégie SQL Server de gestion de la sécurité
2.5. La connexion
3. Création de la sécurité
3.1. Protection du compte "sa"
3.2. Ajout d'utilisateurs et de connexions
4. La problématique de mise en oeuvre
4.1. Faille dans le système
4.2. Mise en place à travers les applications clientes
1. Constat
La plupart du temps, l'accès au serveur se fait par le
compte d'administration système (SA) sans aucun mot de
passe.
2. Le remède
Le remède consiste à établir :
select *
from sysusers
where (password is null) or (password = CAST('' as VARBINARY(128)))La faille de
sécurité à ce niveau est la présence de
lignes dans la table. Ces lignes indiquent quels
utilisateurs des différentes bases sont dépourvus de mot
de passe ou bien dotés d'un mot de passe constitué par
une chaîne de vide.
http://www.astalavista.com/library/auditing/password/lists/defaultpasswords.shtml
http://www.phenoelit.de/dpl/dpl.html
Exemple :
NAME PASSWORD
------------------------------ -------------------------------
sa ????????Tant est si bien que la perte de mots de passe est une
opération non récupérable sauf depuis une sauvegarde.
roleutilisation
publicpar défaut
db_ownercréateur par défaut des objets de la base,
il possède toutes les autorisations sur la base de
données.
db_accessadmingère les accès : ajoute ou supprime
des ID utilisateur.
db_securituadmingère les droits : accès,
propriétés d'objet, rôles et membres des rôles
db_ddladmingestion des droits au niveau du sous
ensemble DDl du SQL : lance l'instruction ALL DDL
mais pas les instructions GRANT, REVOKE ou DENY
db_backupoperatoropérateur de sauvegarde (mais pas
de restauration !). Lance les instructions DBCC,
CHECKPOINT et BACKUP.
db_datareaderdroit de consultation des données de
la base (lecture uniquement). Sélectionne toutes
les données de toutes les tables utilisateur dans
la base de données.
db_datawriterdroit en lecture, écriture des
données de la base. Modifie les données de toutes
les tables utilisateur dans la base de données.
db_denydatareaderrévocation des droits en lecture
sur tous les objets de la base
db_denydatawriterrévocation des droits en écriture
sur tous les objets de la base
2.5. La connexion
En définitive, ce que voit l'utilisateur de
l'application, comme ce que fait l'utilisateur d'un
SGBDR MS SQL Server, c'est d'activer sa connexion. Il
faut donc automatiser la gestion des droits par le biais
de la connexion.
3. Création de la sécurité
La sécurité doit être mise en place en deux phases :
protection du compte "sa" puis création de nouvelles
connexions dotés de droits.
Use master
sp_password NULL, 'd1A*cv4:', 'sa'Donne le mot de passe "d1A*cv4:" au compte "sa".
création de la connexion
ajout d'un utilisateur et de son rôle
Les procédures stockées à utiliser sont les suivantes :