mardi 14 décembre 2010

Heritage de tables

En statistique nous sommes souvent confronté à trois grands types de variables:
- Binaire
- Catégorielle (qualitative)
- Quantitative (continue ou discrète).

Lorsque l'on doit stocker ce type de variable dans une base de donnée, il serait tentant d'utiliser trois tables différentes et de stocker le type de la variable dans une table de définition des variables:
-- Définition de la variable (métadonnée)
tblVariable
VarID int;
type int; (1-Binaire, 2-Categorielle, 3-Quantitative)
Code varchar(10)

-- valeur de la variable binaire
tblBinaire
ID int;
VarID int;
Valeur BIT;

-- valeur de la variable catégorielle
tblCategorielle
ID int;
VarID int;
Valeur varchar(10);

-- valeur de la variable quantitative
tblQuantitative
ID int;
VarID int;
Valeur DECIMAL 10.4;

Le problème de cette approche est qu'il est difficile de créer une intégrité référencielle entre la table Variable et les autres tables puisque le type de variable définira avec quelle table on doit faire le lien et donc les ID entre les différentes tables pourront se recouvrir. Il est donc difficile de définir une FOREIGN key entre 3 tables pouvant avoir des ID Identique et une table source...

Une approche plus simple pourrait considérer la table variable comme une table de base dont hérite les autres tables.

CREATE TABLE tblVariable
(
varid SERIAL PRIMARY KEY,
Code varchar(10);
)

CREATE TABLE tblVariableInst
(
id SERIAL PRIMARY KEY, -- idéalement autonumber
varid INT
FOREIGN KEY (varid) REFERENCES tblVariable(varid)
)

CREATE TABLE tblBinaire
(
id INT UNSIGNED PRIMARY KEY NOT NULL,
Valeur BIT,
FOREIGN KEY (id) REFERENCES tblVariableInst(varid)
)

CREATE TABLE tblCategorielle
(
id INT UNSIGNED PRIMARY KEY NOT NULL,
Valeur Varchar(10),
FOREIGN KEY (id) REFERENCES tblVariableInst(varid)
)

CREATE TABLE tblQuantitative
(
id INT UNSIGNED PRIMARY KEY NOT NULL,
Valeur NUMERIC 10.4;
FOREIGN KEY (id) REFERENCES tblVariableInst(varid)
)

Pour créer une nouvelle variable liée à une variable modèle 1234 par exemple.
@var_id=INSERT INTO tblVariableInst (1234) RETURN ID
INSERT INTO tblQuantitative (@var_id, 9.25)

Ces deux lignes devront être placée dans une transaction, pour éviter d'avoir des ID de VarInstance perdu en cas de problème.

L'avantage de cette approche est que l'intégrité référentielle est parfaitement maintenue entre la table de définition tblVariable (métadonnée) et les tables filles.

Rmk: En sql server on utilisera @@IDENTITY_SCOPE pour obtenir l'ID crée dans la table de base. Par contre en PostgreSQL il est possible de demander au INSERT de retourner le dernier ID crée. L'ID crée pour toutes les tables filles est stocké dans la table tblVariableInst (Variable instance) qui contient une clé étrangère sur la table de définition.

vendredi 10 décembre 2010

Comprendre la nullité

La valeur NULL (pour SQL) ne signifie pas 0, FALSE ou chaine vide. Il s'agit d'une erreur classique dans le développement.
NULL signifie indéterminé ou inconnu. Beaucoup de développeurs ont cette incompréhension de l'inconnu.

Exemple:

date
13/10/2010
NULL
13/12/2010

SELECT COUNT(DATE) where DATE<'13/12/2010' retournera 1 et non pas 2.
Car NULL représente une date inconnue et pas une date nulle (par ex 31/12/1899), comme la date est inconnue il est impossible de savoir si elle est plus petite ou plus grande que le 13/12/2010. Un autre exemple, si je dit que Jacques à 32 ans et que Pierre à un age à NULL. Dans ce cas de figure, cela signifie que l'age de Pierre n'a pas été entré dans le système. Si je demande si Jacques est plus vieuw que Pierre la réponse sera je ne sais pas.
=> Comparer un numérique avec un NULL donne un résultat indéterminé.
Si maintenant je ne connais pas l'age d'Alain. Comme savoir si Alain est plus vieux que Pierre, après tout ils ont peut-être le même age.
=> Comparer un NULL avec un NULL donne un résultat indéterminé.

Si j'ajoute l'age d'Alain à celui de Jacques le résultat peut aussi être n'importe quoi et ne sera certainement pas32
NULL{+,-,*,/}Nombre donne un NULL.
Même chose avec les concaténation de chaine de caractères. Concaténer un NULL avec une autre chaîne donne une chaîne indéterminée.

Cas particuliers dans les opérations booléennes:
NULL true=true
NULL & false=false

Toutes les autres opérations incluants des NULL donnent des NULL.

mardi 7 décembre 2010

Eclipse et Tomcat sous Debian lenny

Il y a quelques temps j'ai commencé quelques développements en Java, Tomcat et Postgre SQL sous Debian Lenny. Tout semblait fonctionner pafaitement, je pouvais lancer et arrêter le démon Tomcat à partir d'Eclipse et effectuer du debugging sans problèmes. Toutefois, quelques mise à jour de Lenny plus tard, à chaque tentative de démarrage du démon, je me retrouvais avec le message suivant:

SEVERE: Class loader creation threw exception
java.lang.InternalError: One of the management beans is not compliant.
at java.lang.management.ManagementFactory.getPlatformMBeanServer(libgcj.so.90)
at org.apache.catalina.startup.Bootstrap.createClassLoader(Bootstrap.java:183)
at org.apache.catalina.startup.Bootstrap.initClassLoaders(Bootstrap.java:92)
at org.apache.catalina.startup.Bootstrap.init(Bootstrap.java:207)
at org.apache.catalina.startup.Bootstrap.main(Bootstrap.java:391)
Caused by: javax.management.NotCompliantMBeanException: MBean is not compliant
at mx4j.server.MX4JMBeanServer.registerImpl(MX4JMBeanServer.java:818)
at mx4j.server.MX4JMBeanServer.registerMBeanImpl(MX4JMBeanServer.java:769)
at mx4j.server.MX4JMBeanServer.registerMBean(MX4JMBeanServer.java:753)
at java.lang.management.ManagementFactory.getPlatformMBeanServer(libgcj.so.90)
...4 more


ce qui est assez ennuyeux vous en conviendrez...
Le problème est lié au fait que l'installation du jre effectuée est indépendante de la distribution Debian Lenny. Donc lorsque certaines mise à jour sont effectuées, le jre quant à lui n'est pas remis à jour ce qui peut poser rapidement des problèmes de compatibilités.

Le plus simple est donc d'installer un jre plus récent et plus ... standard.
Le jre par défaut sur Lenny est le gcj (du gnu) qui a l'avantage d'être complètement libre de license, mais qui n'est pas le plus efficient.

Le plus simple est donc d'installer le jre de java.
J'ai opté pour la version: jre-6u22-linux-i586.bin
Le plus simple est de la télécharger dans le répertoire utilisateur.
Ensuite en mode utilisateur:
$chmod a+x jre-6u22-linux-i586.bin
Puis en mode root
#mkdir /usr/java
#cd /usr/java
#/home//jre-6u22-linux-i586.bin
Le fichier va s'extraire tout seul.
Une fois le nouveau JRE installé il faut le faire reconnaître par Eclipse.
Window/Preference/Java/Installed JREs
Cliquer sur ajouter. Donnez le nom jre-6u22-linux-i586 et indiqué le path d'origine /usr/java/jrel-6.0_22
Déselectionnez le JRE gcj et sélectionnez le nouveau.

Aller dans
Window/Preference/Tomcat/JVM setting
dans la combo box, choisir le nouveau JRE jre-6u22-linux-i586
Ajouter un nouveau ClassPath
/usr/java/jrel-6.0_22
Et supprimer l'ancien

Voilà maintenant le démon Tomcat devrait pouvoir être à nouveau piloté par Eclipse et le debugging à nouveau rendu possible...

vendredi 17 septembre 2010

Creational Pattern (2)

2) Builder

Le builder est utilisé pour créer un objet complex indépendamment de sa représentation. Ainsi on pourra créer le même processus de construction pour générer des représentations différentes.

Cas d'utilisation:
Le ReaderRTF : le reader RTF parse un fichier RTF et traduit les formattages, soit il n'y a pas de formattage (plain-text), soit il faut les afficher en Html, Latex, ...

Dans ce cas de figure deux operations apparaissent : le parsing et l'affichage.
Nous allons donc créer deux classes:
La classe Director qui contient l'algorithme de parsing du fichier RTF
Et une classe Builder (abstraite) qui contient les méthodes d'affichage (ex: ConvertCharacter, ConvertFont, ConvertParagra...)
Les autres classes builder vont dériver de celle-là, exemple : ASCIBuilder, HtmlBuilder ou LatexBuilder (ConcreteBuilder). Les méthodes de la classe abstraites seront outrepassées et permettront d'afficher le document RTF, soit en text, soit en html ou soit en représentation graphique.

L'avantage du système est que le parsing peut générer plusieurs représentation, toutefois, il est aussi possible de changer l'algorithme de parsing et de ré-utiliser la même représentation.

Exemple du labyrinthe:
L'abstract builder contiendra les méthodes: BuildRoom, BuildDoor. Les concrete builder implémenteront concrètement la création des Room et des Door.
La classe MazeGame contiendra l'algorithme de création (Director) et définira quel room et quelles portes créer afin de générer un labyrinthe complexe.
L'abstract class builder contiendra une fonction GetMaze qui renverra le labyrinthe. La classe MazeBuilder sert donc uniquement à créer, elle renvoit ensuite l'instance de l'objet crée dans ce cas ci un objet de type Maze.

Application dans TrialXS:
Le pattern utilisé dans l'export crée en 2002, à l'époque, je ne savais pas quel nom leur donner c'est pourquoi la dénomination peut paraître étrange.
Le but de l'export est d'extraire des données relatives aux patients et de les exporter dans différents (text, SAS ou CDISC).

L'Export est composé d'une classe d'extraction des données appelée:
TPatientItemProvider (Director)
Cette classe contient toute la mécanique d'extraction des CRF relatifs aux patients, chaque fois qu'un CRF existant est complété elle appelle la méthode CollectData de la classe builder.
Si il s'agit d'un nouveau CRF non existant, on appellera la méthode : CollectStructure qui construira un nouveau CRF.

La méthode CollectData fait partie de la Classe TFormExport. Cette classe est l'abstract builder.
Différent builder en dérivent, tels que TFormSASExport, TFormFlat ou TCDISCExport. Cette classe n'est pas vraiment un abstract builder puisqu'elle est déjà le produit en tant que tel. Pour réaliser le pattern on aurait dû avoir une clasee TFormBuilder.getFormExport qui aurait renvoyé les TFormExport crées.

A la fin de la construction, le composite produite sera une liste de CRF défini dans une structure particulière (soit de simple fichier texte, soit des fichiers text avec un fichier de définition, soit une structure xml).

Autres exemples: TCVisitPlanBuilder: Cette classe construit un visit plan théorique en tenant compte des visites model et des visit instances. Encore une fois, il s'agit d'un cas particulier car le Director et le Builder sont aggrégés dans la même classe. Mais l'idée reste la même assurer la création d'un objet complexe à partir d'une autre classe.

Un pattern de type Builder génère en général un Composite.

mercredi 15 septembre 2010

Creational Patterns

1) Abstract factory :

Utilisé pour gérer de multiples look & feel ou source de données, on évite de créer des instances d’une classe particulière directement dans l’application. On passe par une classe intermédiaire qui se charge de créer les objets pour nous.

Nous allons appliquer ce pattern à TrialXS. Supposons que nous voulions rendre l’application indépendante du type de server de base de données (ce qui complique la tâche). Nous allons considérer deux server de base de données SQL Server et Oracle.

Exemple :
// Abstract Factory
TDBObjectFactory=class
function CreateProject :TProject;virtual;
function CreatePatient;TPatient;virtual;

End;
// Abstract Product
TDBObject=class
Procedure get;virtual;
Procedure Save;virtual;
Function createsql:string;virtual;
End;
TProject=class(TDBObject)
property BaseLine : integer read fBaseLine;
end;
TPatient=class(TDBObject)
end;
// Concrete Product
TSqlProject=class(TProject)
Function createsql:string;override //Contient le sql de creation de l’objet pour Sql server
Function Save ;override ;
Function get ; override ;
End ;
// Concrete product
TOraProject=class(TProject)
Function createsql:string;override //Contient le sql de creation de l’objet pour Oracle
Function Save ;override ;
Function get ; override ;
End ;
// Concrete Factory
TSqlObjectFactory =class(TDBObjectFactory)
Function CreateProject ;override; // Retourne un objet TSqlProject
function CreatePatient;override;
End;
// Concrete Factory
TOraObjectFactory=class(TDBObjectFactory)
function CreateProject ;override; // retourne un objet TOraProject
function CreatePatient;override;
End;

Dans l’initialisation de l’application

Var
ObjectFactory:TDBObjectFactory;
begin
If db_type=sql then
ObjectFactory:=TSqlObjectFactory.Create;
Else
ObjectFactory:=TOraObjectFactory.Create;
End;

Chaque fois que je crée un objet db dans l’application, je passerai par l’objet ObjectFactory. En faisant soit

Var
Project :TProject ;
begin
Project :=ObjectBuilder.CreateProject ;
Project.get;
end;

Ceci Va me donner un objet de type SqlProject ou OraProject suivant le type de serveur sql utilisé. Partout dans l’application j’utiliserai l’interface de TProject en me moquant de savoir si j’ai un OraProject ou un SqlProject puisqu’ils réalisent tous deux la même interface. Ce système, certe plus lourd à le mérite d’éviter les castings.

Cela signifie que le type de server sql est défini une fois, partout ailleurs dans l’application je me ficherais de savoir si j’utilise Oracle ou SQL Server puisque c’est l’instance de la classe TDBObjectFactory (ObjectFactory) qui appellera les bon constructeur de classe.
Mon application sera donc indépendante du server de base de donnée pour récupérer ses objets.
TDBObjectFactory ou TDBObject sont généralement considérées comme des classes abstraites ou des interfaces, elles ne peuvent pas être instanciée directement.

Avantage: permet de changer toute une famille de produit en une fois.
Inconvénient:Il n'est pas facile d'ajouter un nouveau produit, car il faut updater et créer plusieurs classes. (pas très flexible)

On utilise souvent l'abstract Factory avec le pattern Singleton, car on ne doit avoir qu'une instance de la Concrete Factory.

On appelle également ce pattern :Kit ou Usine (terme français pour Factory)

vendredi 10 septembre 2010

Validation des Systèmes Informatiques

Définition de la validation:

"Etablir des preuves documentées qui fournissent un haut degré d'assurance qu'un processus spécifique produira constamment a produit conforme à ses spécifications pré-déterminées et à ses attributs qualificatifs".

FDA, Guidelines on General Principle of Process Validation (1987)

D'une part, il faut distinguer validation d'un logiciel (Software Validation) et validation d'un système (System Validation). (Le logiciel n'étant qu'une composante du système).
On peut définir la validation d'un système comme étant une qualification qui vérifie les étapes du processus.
Dans le cas du développement on parle d'un processus comprenant: la collecte des requirements, la création des spécification fonctionnelles, les spécifications de Design, la construction du système.

IQ : Installation Qualification : vérifie que l'environnement est conforme aux system requirement définies dans la phase de Design.
(check list qui vérifie que l'équippement est conforme, que le logiciel est correctement installé et correctement configuré). Le système fonctionne.
OQ : Operational Qualification : vérifie que le système fait ce qu'il est censé faire (test scripts)
verifie la phase fonctionnelle (les spécifications fonctionnelles). Fait ce qu'il doit faire.
PQ :Performance qualification : vérfie que les system requirements (temps de réponse définit) sont corrects. L'utilisabilité pour le client. Ca fait ce que ça doit faire dans temps acceptables

Valider signifie produire ces trois documents décrivant l'IQ, l'OQ et le PQ. Ces documents sont bien sur les preuves énnoncées dans la définition.
La production et le contenu de ces documents est le noeud gordien du problème.

mercredi 8 septembre 2010

Cross Tab Queries en SQL Server 2005

Les cross tab queries sont souvent bien utiles pour présenter des synthèses de résultats par catégories par exemple (En statistique cela est très utile pour l'ANOVA ou les tests du khi carré sur des comptes variables binaires).

Certains informaticiens ont le réflexe de récupérer les données via un data set puis de les trier eux même sous formes de tableau, cette opération est souvent très couteuse alors que SQL Server 2005 nous offre la commande PIVOT.

Cette commande permet de créer un tableau de donnée en un clin d'oeil.

Voici un cas d'exemple

SELECT ID_Project, ID_FormModel, [1],[2],[3],[4],[5],[6] FROM
(select FM.ID_Project, FM.ID_FormModel, FI.FormStatus, 1 AS cnt from tblDMFormModel FM
LEFT OUTER JOIN tblDMForminstance FI on FI.ID_FormModel=FM.ID_FormModel) AS RawData
PIVOT (
SUM(cnt) FOR FormSTATUS IN ([1],[2],[3],[4],[5],[6])
) pvt
WHERE ID_Project=165

La première ligne formatte le tableau en affichant les deux colonnes non pivotables : l'id_project et le form model, les valeurs en gras indique les valeur du status des formes.

La seconde ligne construit le tableau des données brutes sur laquelle nous allons appliquer le pivot. (Le left outer join sert à récupérer tous les form modèles, même ceux qui n'ont pas de form instance). 1 indique que le form instance apparait une fois.
PIVOT fait pivoter les valeurs de rows autours de FormStatus en sommant les form instance pour obtenir les comptes.

Voilà, c'est peut-être pas super bien expliqué mais ça marche !