Bonjour,
Il aurait été utile de respecter la charte de postage, et donc de donner le code de création de vos tables, celui de la procédure (VB ou SQL ?), et l'erreur que vous obtenez lorsque vous faites les dixième insertion ... et n'oubliez pas des lettres dans vos mots, même si vous êtes vraiment très préssé
Par ailleurs l'énoncé initial de votre situation n'est pas clair : vous semblez confondre une base de données et des tables. Une base de données est un ensemble de tables (et encore j'ai vu des cas biscornus où ce n'était pas le cas), de contraintes, de vues, d'index, de déclencheurs, de procédures stockées, de fonctions, de types définis par l'utilisateur et bien d'autres types d'objets encore avec lesquels on peut faire des choses fantastiques . La colonne nom est-elle celle d'un employé, d'un utilisateur de l'application, ... ? Exposez votre contexte, et vous verrez qu'on peut vous aider bien au-delà de ce que espériez
Revenons-en à votre problème, et commençons par voir ce que l'on peut faire pour la table des pointages :
- On peut faire générer un identifiant au moteur en adjoignant à la colonne supportant la clé primaire la propriété IDENTITY
- On peut définir une contrainte de valorisation par défaut pour la colonne debut, ce qui évite d'avoir à le spécifier dans l'INSERT. Délégons ce travail au serveur !
- Lorsque la valeur de fin n'est pas à NULL, il faut vérifier qu'elle est supérieure (ou égale ?) à la date de début
- Enfin, il ne peut y avoir qu'un seul pointage par employé pour un horaire donné, ce que l'on peut imposer à l'aide d'une contrainte supplémentaire
Ceci donne la table suivante :
1 2 3 4 5 6 7 8 9 10 11 12
| CREATE TABLE pointage
(
id int NOT NULL IDENTITY
CONSTRAINT PK_Pointage PRIMARY KEY
, nom varchar(50) NOT NULL
, debut datetime2(3) NOT NULL
CONSTRAINT DF_Pointage__debut DEFAULT (SYSDATETIME())
, fin datetime2(3)
, commentaire varchar(1024)
, CONSTRAINT CHK_Pointage__debut__fin CHECK (fin IS NULL OR fin >= debut)
, CONSTRAINT UQ_Pointage__debut__fin__nom UNIQUE (debut, fin, nom)
) |
Le type de données datetime2(3) permet de stocker une date et une heure avec une précision à la milliseconde. Si vous n'avez besoin d'une précision qu'à la seconde, remplacez le 3 par un zéro.
Les contraintes d'unicité sont nécéssairement supportées par un index, ce qui permet d'accélérer les recherches. Comme il est probable que la plupart des recherches dans cette table se fassent sur la base d'une date, il est donc doublement utile d'avoir cette contrainte en place.
Enfin, il faut vérifier qu'on n'a qu'un seul pointage ouvert par personne, et comme il faut compter combien il y a de connexion ouvertes, on doit d'abord créer la fonction suivante :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| CREATE FUNCTION dbo.f_s_chk_pointage_ouvert
(
@_nom varchar(50)
)
RETURNS bit
WITH SCHEMABINDING
AS
BEGIN
DECLARE @ok bit = 1
SELECT @ok = CASE WHEN COUNT(*) > 1 THEN 0 ELSE 1 END
FROM dbo.pointage
WHERE nom = @_nom
AND fin IS NULL
RETURN @ok
END
GO |
Ce qui nous permet de créer la contrainte suivante :
1 2 3
| ALTER TABLE dbo.pointage
ADD CONSTRAINT CHK_pointage__nom__debut__fin CHECK(dbo.f_s_chk_pointage_ouvert(nom) = 1)
GO |
Lors de la connexion, il suffit de soumettre la requête suivante :
1 2 3 4 5 6 7 8 9 10
| INSERT INTO dbo.pointage
(
nom
, commentaire
)
VALUES
(
'ElSuket'
, 'Séquence de boot cerveau en cours ...'
) |
Si l'on exécute un SELECT * FROM dbo.pointage, on obtient alors :
On a laissé au moteur de base de données le soin de générer les valeurs, et il l'a fait proprement, comme un grand
Si on ré-exécute ce même INSERT, on obtient l'erreur suivante, ce qui est le comportement recherché :
Soumettons maintenant l'INSERT suivant :
1 2 3 4 5 6 7 8 9 10
| INSERT INTO dbo.pointage
(
nom
, commentaire
)
VALUES
(
'Dachetache'
, 'Début de journée pour l''application des pointages !'
) |
Puis exécutons une nouvelle fois un SELECT * FROM dbo.pointage, pour voir ce que la table contient :
On a bien deux lignes, et la deuxième porte l'id 3 ... Comment cela se fait-il ? Lors de la 2e exécution de l'INSERT qui a levé une exception du fait de la contrainte CHK_pointage__nom__debut__fin, la valeur de l'id a bien été générée, mais comme la transaction a échoué, celle-ci a été annulée. Et comme les valeurs pour l'auto-incrément sont dans un cache, nous sommes donc à la valeur 3 pour le dernier INSERT. On s'en moque puisque cette colonne n'a pas de valeur sémantique : elle est purement technique, et permet, entre autres, de supporter les jointures de façon très performante. On peut récupérer la valeur d'id qui a été générée en utilisant la fonction SCOPE_IDENTITY().
Voyons une déconnexion maintenant : je me déconnecte :
1 2 3
| UPDATE dbo.pointage
SET fin = SYSDATETIME()
WHERE id = 1 |
Une nouvelle exécution de SELECT * FROM dbo.pointage nous donne :
Vous faites une erreur importante en soumettant les valeurs en "dur" dans votre requête. En effet, SQL Server dispose d'une cache de requêtes, ce qui permet de consommer un minimum de CPU. Pour ce faire, il se base sur le texte de la requête. Un changement, fût-il mineur (un espace, un point virgule de fin de requête, ...) du texte de la requête entraîne donc la création d'une nouvelle entrée dans ce cache de requêtes, et la consommation de temps CPU. Comme le texte de votre requête va changer à peu près à chaque exécution, on va donc créer un grand nombre d'entrées dans ce cache, qui le rendra donc sous-efficace, alors que la requête ne change que par ses valeurs. Voyez à utiliser une procédure stockée, ou à défaut, spécifier des appels à sp_executesql, ou, à défaut, à activer l'option d'instance optimize for ad hoc workloads, qui gère ce type de problème pour vous.
La procédure de connexion pourrait être, par exemple :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| CREATE PROCEDURE p_pointage_ajoute
@_nom varchar(50)
, @_commentaire varchar(1024) = NULL
AS
BEGIN
SET NOCOUNT ON
INSERT INTO dbo.pointage
(
nom
, commentaire
)
VALUES
(
@_nom
, @_commentaire
);
END |
Et à l'exécution, on aurait, si l'on ne souhaite pas ajouter de commentaire :
EXEC dbo.p_pointage_ajoute @_nom = 'ElSuket'
Et si on veut attacher un commentaire :
EXEC dbo.p_pointage_ajoute @_nom = 'ElSuket', @_commentaire = 'Un appel de procédure stockée'
Avec sp_executesql, on aurait :
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| EXEC sp_executesql N'
INSERT INTO dbo.pointage
(
nom
, commentaire
)
VALUES
(
@_nom
, @_commentaire
);'
----
, N'@_nom varchar(50), @_commentaire varchar(1024)'
, @_nom = 'Dachetache'
, @_commentaire = 'Connexion !' |
Je vous laisse deviner ce que cela pourrait être pour les déconnexions
@++
Partager