#Bases de données

0 Abonnés · 34 Publications

La base de données InterSystems Caché est un fichier où sont stockés l'ensemble des données, des scripts d'application, des utilisateurs, des rôles et des configurations de sécurité. En général, le nom du fichier est cache.dat.

Documentation.

Article Guillaume Rongier · Oct 15, 2025 9m read

Vous êtes familier avec les bases de données SQL, mais vous ne connaissez pas IRIS ? Alors lisez la suite...

Il y a environ un an, j'ai rejoint InterSystems, et c'est ainsi que j'ai découvert IRIS.  J'utilise des bases de données depuis plus de 40 ans, la plupart du temps pour des fournisseurs de bases de données, et je pensais qu'IRIS serait similaire aux autres bases de données connues.  Cependant, j'ai été surpris de constater qu'IRIS est très différente par rapport aux autres bases de données, et souvent bien meilleure.  Avec mon premier article dans la communauté Dev, je vais présenter IRIS de manière générale aux personnes qui connaissent déjà d'autres bases de données telles qu'Oracle, SQL Server, Snowflake, PostgeSQL, etc.   J'espère vous rendre les choses plus claires et plus simples et vous faire gagner du temps pour vous lancer.

Tout d'abord, IRIS prend en charge les commandes et la syntaxe SQL de la norme ANSI. Il dispose de tables, de colonnes, de types de données, de procédures stockées, de fonctions...   bref, tout ce qui concerne les relations.  Et vous pouvez utiliser ODBC, JDBC, DBeaver ou tout autre navigateur de base de données que vous préférez.  Donc, oui, la plupart des connaissances et des opérations que vous maîtrisez avec d'autres bases de données fonctionneront très bien avec IRIS.  Youpi!  

Mais qu'en est-il de ces différences que j'ai mentionnées?  Bien, attachez vos ceintures:

Multi-Model: IRIS est une base de données relationnelle, mais c'est aussi une base de données orientée objet, un magasin de documents, et elle prend en charge les vecteurs, les cubes/MDX, et... vous comprenez où je veux en venir.  Ce qui est incroyable, c'est que vous pouvez profiter de tous ces modèles... dans la même instruction SQL!  Et bien souvent, les données peuvent être stockées sous plusieurs de ces structures de données — sans avoir à les sauvegarder à deux reprises — ni à utiliser plusieurs types de bases de données!  Lorsque vous accédez à des données identiques comme s'il s'agissait de modèles différents, InterSystems parle de CDP (Common Data Plane, ou plan de données commun).  C'est pour le moins rare, voire unique, dans le secteur des bases de données.  Personne ne s'intéressait vraiment au CDP jusqu'à ce que la révolution de l'IA rende tout à coup indispensable la prise en charge du multimodèle. Il ne s'agit pas d'une fonctionnalité que d'autres bases de données sont susceptibles d'implémenter, car elle est intégrée au cœur même du noyau.  IRIS facilite l'utilisation des modèles multiples ainsi que des technologies NoSQL et NewSQL pour les utilisateurs SQL:

Pour la base de données Object, vous extrayez une clé-valeur de l'arborescence JSON, qui correspond simplement à la valeur d'une table classique. 

-- exemple de requête dans une base de données ObjectSELECT JSON_OBJECT('product':Product,'sizes':PopularSizes) FROM Sample.Clothing

-- Cela renvoie une liste de paires clé-valeur. Si une paire manque, -- IRIS crée par défaut une paire avec une valeur nulle.

En ce qui concerne Vector, considérez-le simplement comme un autre type de données, mais avec certaines fonctions spéciales qui ne fonctionnent qu'avec le type de données en question 

-- exemple de création d'une table avec une colonne vectorielleCREATETABLE Sample.CustEventV1 (
  CustID INTEGER,
  EventDt DATE,
  Vtest VECTOR(integer,4),
  EventValue NUMERIC(12,2),  
  EventCD VARCHAR(8)) 

-- Vous pouvez utiliser des fonctions telles que VECTOR_DOT_PRODUCT ou VECTOR_COSINE sur Vtest

Taxonomie:  les différents fournisseurs de bases de données n'utilisent pas tels termes comme base de données, schéma, déploiement, instance, etc. exactement de la même manière. 

  • Instance: lorsque vous installez le logiciel de base de données, généralement appelé 'instance' par les éditeurs de bases de données. J'entends parfois ce terme chez InterSystems, mais plus souvent, j'entends le terme 'déploiement'.  Cela s'explique probablement par le fait que le terme 'instance' est déjà utilisé dans le monde orienté objet.  Quel que soit le terme utilisé, la hiérarchie pour les autres bases de données est généralement la suivante:
    • instance/déploiement
      • base de deonnées
        • schéma
          • tables, vues, etc.

            .. ou bien:

  • instance/déploiement (il *s'agit* de la base de données)
    • schéma
      • tables, vues, etc.

            .. mais IRIS est un peu différent dans la mesure où il comporte une couche supplémentaire appelée 'espace de nom':

  • instance/déploiement
    • espace de nom
      • base de données
        • schéma
          • tables, viues, etc.

Un espace de noms est une entité logique qui contient des bases de données. Cependant, plusieurs espaces de noms peuvent contenir la même base de données, il ne s'agit donc peut-être pas d'une hiérarchie.  Il est principalement utilisé pour le contrôle d'accès. Et il peut contenir des bases de données provenant d'autres instances/déploiements!

HA: La haute disponibilité (High Availability) est obtenue grâce à une technique appelée mise en miroir ( mirroring ).  Il s'agit d'un type de réplication où l'intégralité de la base de données est répliquée, y compris le code.  Vous pensez peut-être que vous ne souhaitez pas répliquer l'intégralité de la base de données.  Mais grâce aux espaces de noms, vous pouvez considérer une base de données comme une sorte de schéma et diviser vos données de manière à ce que celles que vous souhaitez mettre en miroir et celles que vous ne souhaitez pas mettre en miroir se trouvent dans des bases de données distinctes. 

Stockage du code: Oui, vous avez parfaitement compris: lorsque vous mettez une base de données en miroir, le code est également transféré!  Il s'agit d'une fonctionnalité très récente pour certaines bases de données à la mode, mais IRIS l'offre depuis toujours. Vous pouvez stocker à la fois le code et les données dans la même base de données, mais le plus souvent, les utilisateurs préfèrent les séparer.

ECP: Bien; c'est le protocole Enterprise Cache Protocol qui rend IRIS vraiment intéressant.  Je ne savais même pas que cela était possible, mais j'ai récemment découvert qu'il existe quelques bases de données NoSQL peu connues qui le permettent.  Avec le protocole ECP vous pouvez configurer le système de manière à ce que différents déploiements puissent partager leurs caches!  Oui, je veux bien dire leurs caches memory réels.. et non pas le partage des données des tables. Pour ce faire, le cache d'un déploiement est automatiquement synchronisé avec celui d'un autre déploiement.  C'est ce qu'on appelle être synchronisé!  C'est très facile à configurer, même si cela doit être compliqué en coulisses. Il s'agit d'un tout autre type de mise à l'échelle horizontale qui peut rendre les applications ultra-rapides.

Translytique: Ce terme, translytique, est utilisé pour décrire une base de données qui est à la fois OLTP et OLAP. Il peut également être appelé HTAP ou HOAP. Parfois, on emploie le terme hybride mais ce terme est trop utilisé dans le monde technologique, je vais donc m'en tenir au terme commençant par T.  Au début, toutes les bases de données étaient translytiques.  Mais avec l'avènement des structures en colonnes et d'autres structures, ainsi que de nouveaux types de stockage (par exemple le stockage en blocs par opposition au stockage en blobs) elles ont été séparées en OLTP et OLAP. Aujourd'hui, les fournisseurs tentent de les réunir à nouveau.   Il est beaucoup plus facile d'ajouter OLAP à un noyau OLTP que de faire l'inverse. Bien sûr, les fournisseurs de solutions DW peuvent ajouter quelques indexations pour les recherches sur une seule ligne, mais je doute qu'ils ajoutent rapidement la prise en charge de fonctionnalités complexes telles que les déclencheurs et les insertions/mises à jour rapides. Le fait est que l'OLTP rapide est plus compliqué à construire que l'OLAP... c'est une technologie beaucoup plus mature. IRIS est une excellente base de données translytique (voir les évaluations des analystes pour comprendre pourquoi). Par exemple, certaines bases de données prennent en charge à la fois le stockage en lignes et en colonnes, mais dans des tables différentes.  IRIS peut disposer de colonnes de stockage en lignes dans la même table que les colonnes de stockage en colonnes.

/* Exemple de combinaison entre stockage en lignes et stockage en colonnes. 
   Toutes les colonnes sont stockées en lignes (par défaut), à l'exception de EventValue.
   EventValue est explicitement définie comme stockage en colonnes. 
   Si vous interrogiez la valeur moyenne de EventValue pour l'ensemble de la table, la réponse serait RAPIDE! */CREATETABLE Sample.CustEvent (
  CustID INTEGER,
  EventDt DATE,
  EventValue NUMERIC(12,2) WITH STORAGETYPE = COLUMNAR,
  EventCD VARCHAR(8))

Installation: Avec d'autres bases de données, vous devez généralement les installer quelque part (sur site ou dans le cloud), comme vous le faites avec Postgres ou SQL Server, ou bien recourir à un SAAS cloud tel que RedShift ou Snowflake. Avec IRIS, cela dépend. Il y a trois moyens d'obtenir IRIS : via une licence, via un service géré ou via Cloud SQL. 

  1. Grâce à une licence, vous pouvez l'installer, le configurer et le maintenir de manière indépendante. Cela peut se faire sur site ou sur le cloud de votre choix. J'ai surtout entendu parler de son utilisation sur AWS, Azure, GCP et TenCent.
  2. Grâce à un service géré, InterSystems installe, configure et assure la maintenance d'IRIS pour vous via un cloud public. 
  3. Grâce à Cloud SQL, il est possible de bénéficier d'un service SAAS (ou devrais-je dire PAAS ? DBAAS ?).  Vous n'avez rien à installer.  Il est conçu pour s'intégrer dans des systèmes plus vastes en tant que module composable, n'offrant qu'un sous-ensemble des fonctionnalités IRIS, telles que SQL et les fonctions d'apprentissage automatique (ML).  La suite de cet article concerne IRIS sous licence ou IRIS géré, et ne concerne pas Cloud SQL.

Langages intégrés: Outre SQL, IRIS a toujours pris en charge un langage orienté objet appelé ObjectScript, qui est un dérivé du langage médical MUMPS. Il s'agit d'un langage très puissant, mais peu connu. Ne vous inquiétez pas, IRIS prend également en charge Python intégré. 

Documentation: Comme IRIS a toujours été étroitement lié à ObjectScript, la documentation a tendance à utiliser une terminologie orientée objet.  Vous trouverez donc des termes simples tels que tables désignés par 'classes persistentes'.  Mais cela semble disparaître de la documentation au fil du temps, et vous pouvez tout simplement ignorer ces termes, sauf si vous souhaitez devenir programmeur IRIS.

IRIS prend donc en charge le langage SQL que vous connaissez et appréciez, ainsi que Python, il est translytique, fonctionne sur site ou dans le cloud, est multimodèle et dispose de fonctionnalités futuristes telles que l'ECP.  Il y a bien d'autres choses encore mais ce sont celles-ci qui m'ont paru les plus importantes et interessantes.  Je pense qu'elles pourraient être utiles à d'autres développeurs SQL et administrateurs de bases de données provenant d'autres produits.   Si c'est votre cas et que vous essayez IRIS, je souhaiterais connaître votre avis sur votre expérience.

0
0 27
Article Sylvain Guilbaud · Juil 8, 2025 3m read

Si vous migrez d'Oracle vers InterSystems IRIS, comme beaucoup de mes clients, vous risquez de rencontrer des modèles SQL spécifiques à Oracle nécessitant une conversion.

Prenons l'exemple suivant:

SELECT (TO_DATE('2023-05-12','YYYY-MM-DD') - LEVEL + 1) AS gap_date
FROM dual
CONNECT BY LEVEL <= (TO_DATE('2023-05-12','YYYY-MM-DD') - TO_DATE('2023-05-02','YYYY-MM-DD') + 1);

Dans Oracle:

  • LEVEL est une pseudo-colonne utilisée dans les requêtes hiérarchiques (CONNECT BY). Elle commence à 1 et s'incrémente de 1.
  • CONNECT BY LEVEL <= (...) détermine le nombre de lignes à générer.
  • La différence entre les deux dates plus un donne 11, donc la requête génère 11 lignes, en comptant à rebours à partir du 12 mai 2023 jusqu'au 2 mai 2023.

Répartition du résultat:

LEVEL = 1  → 2023-05-12
LEVEL = 2  → 2023-05-11
...
LEVEL = 11 → 2023-05-02

La question est maintenant de savoir comment obtenir ce résultat dans InterSystems IRIS, qui ne prend pas en charge CONNECT BY?

Une solution consiste à implémenter une requête de type SQL à l'aide d'ObjectScript qui imite ce comportement. Vous trouverez ci-dessous un exemple de définition CREATE QUERY qui accepte une date de début STARTDATE et un nombre de jours DAYS, et renvoie la liste des dates par ordre descendant.


✅ InterSystems IRIS: mise en œuvre d'une requête de l'intervalle de date

CREATE QUERY GET_GAP_DATE(IN STARTDATE DATE, IN DAYS INT)
  RESULTS (GAP_DATE DATE)
  PROCEDURE
  LANGUAGE OBJECTSCRIPT

Execute(INOUT QHandle BINARY(255), IN STARTDATE DATE, IN DAYS INT) { SET QHandle("start") = STARTDATE SET QHandle("days") = DAYS SET QHandle("level") = 1 RETURN $$$OK }

Fetch(INOUT QHandle BINARY(255), INOUT Row %List, INOUT AtEnd INT) { IF (QHandle("level") > QHandle("days")) { SET Row = "" SET AtEnd = 1 } ELSE { SET Row = $ListBuild(QHandle("start") - QHandle("level") + 1) SET QHandle("level") = QHandle("level") + 1 } RETURN $$$OK }

Close(INOUT QHandle BINARY(255)) { KILL QHandle QUIT $$$OK }

Vous pouvez exécuter la commande CREATE QUERY Vous pouvez exécuter la commande CREATE QUERY ci-dessus dans le portail IRIS System Management, ou via un outil tel que DBeaver ou un éditeur Python/Jupyter Notebook utilisant JDBC/ODBC.


🧪 Exemple d'utilisation:

Pour générer le même résultat que la requête Oracle ci-dessus, utilisez:

SELECT * FROM GET_GAP_DATE(
  TO_DATE('2023-05-12', 'YYYY-MM-DD'),
  TO_DATE('2023-05-12', 'YYYY-MM-DD') - TO_DATE('2023-05-02', 'YYYY-MM-DD') + 1
);

Cela donnera le résultat suivant:

GAP_DATE
----------
2023-05-12
2023-05-11
...
2023-05-02
(11 rows)

🔁 Utilisation avancée: Jointure avec d'autres tables

Vous pouvez également utiliser cette requête comme sous-requête ou dans des jointures:

SELECT * 
FROM GET_GAP_DATE(TO_DATE('2023-05-12', 'YYYY-MM-DD'), 11) 
CROSS JOIN dual;

Cela vous permet d'intégrer des plages de dates dans des flux SQL plus importants.


J'espère que cela sera utile à tous ceux qui sont confrontés à des scénarios de migration d'Oracle vers IRIS ! Si vous avez mis au point des solutions alternatives ou si vous proposez des améliorations, n'hésitez pas à me faire part de vos commentaires.

0
0 31
InterSystems officiel Adeline Icard · Juin 10, 2025

InterSystems a publié de nouvelles mises à jour afin de corriger un défaut affectant les versions antérieures les plus récentes (2025.1.0, 2024.1.4, 2023.1.6 et 2022.1.7), pour les gammes de produits prises en charge suivantes :

  • InterSystems IRIS
  • InterSystems IRIS for Health
  • HealthShare Health Connect

Ce problème pouvait entraîner des erreurs <PROTECT> inattendues ou des anomalies d'accès lors de l'utilisation de fonctionnalités telles que :

0
0 33
InterSystems officiel Adeline Icard · Avr 8, 2025

Résumé des alertes

Alert ID Produit et versions concernés Exigences explicites
DP-439207 Plateforme de données InterSystems IRIS® 2024.3 (AIX) Installations AIX utilisant le traitement JSON et les caractères Unicode non-Latin-1
DP-439280 InterSystems IRIS 2024.3 (conteneurs avec IntegratedML) Conteneurs IntegratedML utilisant TensorFlow

Détail des alertes

DP-439207 - Problème d'analyse JSON Unicode AIX

Un bug a été identifié dans InterSystems IRIS 2024.3.0 sur les instances AIX. Il affecte l'analyse des chaînes JSON Unicode. Ce problème survient lorsque la méthode %FromJSON() ou %FromJSONFile() analyse des chaînes contenant des caractères dont la valeur est inférieure à $CHAR(256) suivis de caractères Unicode dont la valeur est supérieure à $CHAR(255). Le processus de conversion transforme incorrectement les premiers caractères en $CHAR(0), ce qui entraîne une corruption silencieuse des données. Ce problème concerne uniquement la version AIX 2024.3 des produits suivants :

  • InterSystems IRIS
  • InterSystems IRIS for Health
  • HealthShare® Health Connect
0
0 24
Article Iryna Mykhailova · Oct 15, 2024 8m read

Problèmes de chaînes

J'utilise Python pour accéder aux bases de données IRIS avec JDBC (ou ODBC). Je veux récupérer les données dans pandas dataframe pour manipuler les données et créer des graphiques à partir de celles-ci. Lors de l'utilisation de JDBC, j'ai rencontré un problème avec la gestion des chaînes. Cet article est destiné à aider les personnes qui ont les mêmes problèmes. S'il existe un moyen plus simple de résoudre ce problème, faites-le moi savoir dans les commentaires !

J'utilise OSX, donc je ne sais pas à quel point mon problème est unique. J'utilise Jupyter Notebooks, mais le code serait généralement le même si vous utilisiez n'importe quel autre programme ou cadre en Python.

Le problème JDBC

Lorsque je récupère des données de la base de données, les descriptions de colonnes et toutes les données de type chaîne sont renvoyées en tant que données de type java.lang.String. Si vous imprimez une chaîne de données, elle se présentera sous la forme suivante : "(p,a,i,n,i,n,t,h,e,r,e,a,r)" au lieu de l'attendu "painintherear".

Ceci est probablement dû au fait que les chaînes de caractères de type java.lang.String se présentent sous la forme d'un itérable ou d'un tableau lorsqu'elles sont récupérées à l'aide de JDBC. Cela peut arriver si le pont Python-Java que vous utilisez (par exemple, JayDeBeApi, JDBC) ne convertit pas automatiquement java.lang.String en une chaîne Python en une seule étape.

La représentation de la chaîne str de Python, en revanche, dispose de la chaîne entière en tant qu'unité unique. Lorsque Python récupère une chaîne normale (par exemple via ODBC), elle n'est pas divisée en caractères individuels.

La solution JDBC

Pour résoudre ce problème, vous devez vous assurer que le type java.lang.String est correctement converti en type str de Python. Vous pouvez explicitement gérer cette conversion lors du traitement des données récupérées afin qu'elles ne soient pas interprétées comme un itérable ou une liste de caractères.

Il existe de nombreuses façons de manipuler les chaînes de caractères ; c'est ce que j'ai fait.

import pandas as pd

import pyodbc

import jaydebeapi
import jpype

def my_function(jdbc_used)

    # Un autre code pour créer la connexion se trouve ici

    cursor.execute(query_string)

    if jdbc_used:
        # Récupération des résultats, conversion des données java.lang.String en str Python
        # (java.lang.String est retourné comme suit : "(p,a,i,n,i,n,t,h,e,r,e,a,r)" Conversion en type str "painintherear"
        results = []
        for row in cursor.fetchall():

            converted_row = [str(item) if isinstance(item, jpype.java.lang.String) else item for item in row]

            results.append(converted_row)


        # Obtention des noms des colonnes et vérification qu'il s'agit bien de chaînes Python

        column_names = [str(col[0]) for col in cursor.description]


        # Création du cadre de données

        df = pd.DataFrame.from_records(results, columns=column_names)


        # Vérification des résultats

        print(df.head().to_string())


    else:

        # Je testais aussi ODBC
        # Dans le cas d'ensembles de résultats très volumineux, obtenez les résultats par blocs en utilisant cursor.fetchmany() ou fetchall()
        results = cursor.fetchall()
        # Obtention des noms des colonnes
        column_names = [column[0] for column in cursor.description]
        # Création du cadre de données
        df = pd.DataFrame.from_records(results, columns=column_names)

    # Faites des choses avec votre cadre de données

Le problème ODBC

Lors d'une connexion ODBC, les chaînes de caractères ne sont pas renvoyées ou sont S.O.

Si vous vous connectez à une base de données qui contient des données Unicode (par exemple, des noms dans des langues différentes) ou si votre application doit stocker ou récupérer des caractères qui ne sont pas ASCII, vous devez vous assurer que les données restent correctement encodées lorsqu'elles sont transmises entre la base de données et votre application Python.

La solution ODBC

Ce code garantit que les données de type chaîne sont encodées et décodées en utilisant UTF-8 lors de l'envoi et de la récupération de données dans la base de données. C'est particulièrement important lorsqu'il s'agit de caractères non ASCII ou d'assurer la compatibilité avec les données Unicode.

def create_connection(connection_string, password):
    connection = None

try:
        # print(f"Connecting to {connection_string}")
        connection = pyodbc.connect(connection_string + ";PWD=" + password)

        # Veiller à ce que les chaînes de caractères soient lues correctement
        connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
        connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
        connection.setencoding(encoding="utf8")

    except pyodbc.Error as e:
        print(f"The error '{e}' occurred")

    return connection

connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")

Désigné ci-dessus indique à pyodbc comment décoder les données de caractères de la base de données lors de la récupération des types SQL_CHAR (typiquement, les champs de caractères de longueur fixe).

connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")

Désigné ci-dessus définit le décodage pour SQL_WCHAR, les types de caractères larges (c'est-à-dire les chaînes Unicode, telles que NVARCHAR ou NCHAR dans SQL Server).

connection.setencoding(encoding="utf8")

Désigné ci-dessus garantit que toutes les chaînes ou données de caractères envoyées de Python à la base de données seront encodées en UTF-8, cela signifie que Python traduira son type str interne (qui est Unicode) en octets UTF-8 lors de la communication avec la base de données.


La mise en place de l'ensemble

Installation de JDBC

Installation de JAVA-utiliser dmg

https://www.oracle.com/middleeast/java/technologies/downloads/#jdk23-mac

Mise à jour du shell pour définir la version par défaut

$ /usr/libexec/java_home -V
Matching Java Virtual Machines (2):
    23 (arm64) "Oracle Corporation" - "Java SE 23" /Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
    1.8.421.09 (arm64) "Oracle Corporation" - "Java" /Library/Internet Plug-Ins/JavaAppletPlugin.plugin/Contents/Home
/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home
$ echo $SHELL
/opt/homebrew/bin/bash
$ vi ~/.bash_profile

Ajoutez JAVA_HOME à votre chemin

export JAVA_HOME=$(/usr/libexec/java_home -v 23)
export PATH=$JAVA_HOME/bin:$PATH

Obtention du pilote JDBC

https://intersystems-community.github.io/iris-driver-distribution/

Placement du fichier jar quelque part... Je l'ai placé dans $HOME

$ ls $HOME/*.jar
/Users/myname/intersystems-jdbc-3.8.4.jar

Exemple de code

Cela suppose que vous avez configuré ODBC (un exemple à suivre un autre jour, le chien a mangé mes notes...).

Remarque : il s'agit d'un piratage de mon propre code réel. Notez les noms des variables.

import os

import datetime
from datetime import date, time, datetime, timedelta

import pandas as pd
import pyodbc

import jaydebeapi
import jpype

def jdbc_create_connection(jdbc_url, jdbc_username, jdbc_password):

    # Chemin d'accès au pilote JDBC
    jdbc_driver_path = '/Users/yourname/intersystems-jdbc-3.8.4.jar'

    # Il faut s'assurer que JAVA_HOME est défini 
    os.environ['JAVA_HOME']='/Library/Java/JavaVirtualMachines/jdk-23.jdk/Contents/Home'
    os.environ['CLASSPATH'] = jdbc_driver_path

    # Démarrage de la JVM (si elle n'est pas déjà en cours d'exécution)
    if not jpype.isJVMStarted():
        jpype.startJVM(jpype.getDefaultJVMPath(), classpath=[jdbc_driver_path])

    # Connexion aux bases de données:
    connection = None

    try:
        connection = jaydebeapi.connect("com.intersystems.jdbc.IRISDriver",
                                  jdbc_url,
                                  [jdbc_username, jdbc_password],
                                  jdbc_driver_path)
        print("Connection successful")
    except Exception as e:
        print(f"An error occurred: {e}")

    return connection


def odbc_create_connection(connection_string):
    connection = None

    try:
        # print(f"Connecting to {connection_string}")
        connection = pyodbc.connect(connection_string)

        # Veiller à ce que les chaînes de caractères soient lues correctement
        connection.setdecoding(pyodbc.SQL_CHAR, encoding="utf8")
        connection.setdecoding(pyodbc.SQL_WCHAR, encoding="utf8")
        connection.setencoding(encoding="utf8")

    except pyodbc.Error as e:
        print(f"The error '{e}' occurred")

    return connection

# Paramètres

odbc_driver = "InterSystems ODBC"
odbc_host = "your_host"
odbc_port = "51773"
odbc_namespace = "your_namespace"
odbc_username = "username"
odbc_password = "password"

jdbc_host = "your_host"
jdbc_port = "51773"
jdbc_namespace = "your_namespace"
jdbc_username = "username"
jdbc_password = "password"

# Création d'une connexion et des graphiques

jdbc_used = True

if jdbc_used:
    print("Using JDBC")
    jdbc_url = f"jdbc:IRIS://{jdbc_host}:{jdbc_port}/{jdbc_namespace}?useUnicode=true&characterEncoding=UTF-8"
    connection = jdbc_create_connection(jdbc_url, jdbc_username, jdbc_password)
else:
    print("Using ODBC")
    connection_string = f"Driver={odbc_driver};Host={odbc_host};Port={odbc_port};Database={odbc_namespace};UID={odbc_username};PWD={odbc_password}"
    connection = odbc_create_connection(connection_string)


if connection is None:
    print("Unable to connect to IRIS")
    exit()

cursor = connection.cursor()

site = "SAMPLE"
table_name = "your.TableNAME"

desired_columns = [
    "RunDate",
    "ActiveUsersCount",
    "EpisodeCountEmergency",
    "EpisodeCountInpatient",
    "EpisodeCountOutpatient",
    "EpisodeCountTotal",
    "AppointmentCount",
    "PrintCountTotal",
    "site",
]

# Construction de la partie de la requête relative à la sélection des colonnes
column_selection = ", ".join(desired_columns)

query_string = f"SELECT {column_selection} FROM {table_name} WHERE Site = '{site}'"

print(query_string)
cursor.execute(query_string)

if jdbc_used:
    # Récupération des résultats
    results = []
    for row in cursor.fetchall():
        converted_row = [str(item) if isinstance(item, jpype.java.lang.String) else item for item in row]
        results.append(converted_row)

    #  Il faut récupérer les noms des colonnes et s'assurer qu'il s'agit bien de chaînes Python (java.lang.String is returned "(p,a,i,n,i,n,t,h,e,a,r,s,e)" 
    column_names = [str(col[0]) for col in cursor.description]

    # Création du cadre de données
    df = pd.DataFrame.from_records(results, columns=column_names)
    print(df.head().to_string())
else:
    # Dans le cas d'ensembles de résultats très volumineux, obtenez les résultats par blocs en utilisant cursor.fetchmany() ou fetchall()
    results = cursor.fetchall()
    # Obtention des noms des colonnes
    column_names = [column[0] for column in cursor.description]
    # Création du cadre de données
    df = pd.DataFrame.from_records(results, columns=column_names)

    print(df.head().to_string())

# # Construction des graphiques pour un site
# cf.build_7_day_rolling_average_chart(site, cursor, jdbc_used)

cursor.close()
connection.close()

# Arrêt de la JVM (si vous l'avez démarrée)
# jpype.shutdownJVM()
0
0 74
Article Lorenzo Scalese · Oct 10, 2024 30m read

Une expérience sur la manière d'utiliser le cadre LangChain, la recherche vectorielle IRIS et les LLM pour générer une base de données SQL compatible IRIS à partir des invites utilisateur.

Cet article a été rédigé à partir du carnet suivant. Vous pouvez l'utiliser dans un environnement prêt à l'emploi avec l'application suivante dans OpenExchange.

Configuration

Tout d'abord, nous devons installer les bibliothèques nécessaires:

!pip install --upgrade --quiet langchain langchain-openai langchain-iris pandas

Ensuite, nous importons les modules requis et configurons l'environnement:

import os
import datetime
import hashlib
from copy import deepcopy
from sqlalchemy import create_engine
import getpass
import pandas as pd
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.example_selectors import SemanticSimilarityExampleSelector
from langchain_openai import OpenAIEmbeddings, ChatOpenAI
from langchain.docstore.document import Document
from langchain_community.document_loaders import DataFrameLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_core.output_parsers import StrOutputParser
from langchain.globals import set_llm_cache
from langchain.cache import SQLiteCache
from langchain_iris import IRISVector

Nous utiliserons SQLiteCache pour mettre en cache les appels LLM:

# Cache pour les appels LLM
set_llm_cache(SQLiteCache(database_path=".langchain.db"))

Configurez les paramètres de connexion à la base de données IRIS:

# Paramètres de connexion à la base de données IRIS
os.environ["ISC_LOCAL_SQL_HOSTNAME"] = "localhost"
os.environ["ISC_LOCAL_SQL_PORT"] = "1972"
os.environ["ISC_LOCAL_SQL_NAMESPACE"] = "IRISAPP"
os.environ["ISC_LOCAL_SQL_USER"] = "_system"
os.environ["ISC_LOCAL_SQL_PWD"] = "SYS"

Si la clé API OpenAI n'est pas déjà configurée dans l'environnement, demandez à l'utilisateur de la saisir:

if not "OPENAI_API_KEY" in os.environ:
    os.environ["OPENAI_API_KEY"] = getpass.getpass()

Créez la chaîne de connexion pour la base de données IRIS:

# Chaîne de connexion à la base de données IRIS
args = {
    'hostname': os.getenv("ISC_LOCAL_SQL_HOSTNAME"), 
    'port': os.getenv("ISC_LOCAL_SQL_PORT"), 
    'namespace': os.getenv("ISC_LOCAL_SQL_NAMESPACE"), 
    'username': os.getenv("ISC_LOCAL_SQL_USER"), 
    'password': os.getenv("ISC_LOCAL_SQL_PWD")
}
iris_conn_str = f"iris://{args['username']}:{args['password']}@{args['hostname']}:{args['port']}/{args['namespace']}"

Etablissez la connexion avec la base de données IRIS:

# Connexion à la base de données IRIS
engine = create_engine(iris_conn_str)
cnx = engine.connect().connection

Préparez un dictionnaire contenant les renseignements contextuels pour l'invite du système:

# Dict pour les renseignements contextuels de l'invite système
context = {}
context["top_k"] = 3

Création de l'invite

Pour transformer les données de l'utilisateur en requêtes SQL compatibles avec la base de données IRIS, nous devons créer une invite efficace pour le modèle linguistique. Nous commençons par une invite initiale qui fournit des instructions de base pour générer des requêtes SQL. Ce modèle est dérivé des Invites par défaut de LangChain pour MSSQL et personnalisé pour la base de données IRIS.

# Modèle d'invite de base avec instructions SQL pour la base de données IRIS
iris_sql_template = """
Vous êtes un expert InterSystems IRIS.  Compte tenu d'une question d'entrée, créez d'abord une requête InterSystems IRIS syntaxiquement correcte pour exécuter et renvoyer la réponse à la question saisie.
Si l'utilisateur ne spécifie pas dans la question un nombre spécifique d'exemples à obtenir, demandez au maximum {top_k} résultats en utilisant la clause TOP conformément à InterSystems IRIS. Vous pouvez classer les résultats de manière à obtenir les renseignements les plus pertinents de la base de données.
Ne faites jamais de requête pour toutes les colonnes d'une table. Vous ne devez requérir que les colonnes nécessaires pour répondre à la question.. Mettez chaque nom de colonne entre guillemets simples ('') pour indiquer qu'il s'agit d'identifiants délimités.
Veillez à n'utiliser que les noms de colonnes que vous pouvez voir dans les tables ci-dessous. Veillez à ne pas requérir les colonnes qui n'existent pas. Faites également attention à ce que les colonnes se trouvent dans les différents tables.
Veillez à utiliser la fonction CAST(CURRENT_DATE as date) pour obtenir la date du jour, si la question porte sur "aujourd'hui".
Utilisez des guillemets doubles pour délimiter les identifiants des colonnes.
Renvoyez des données SQL simples ; n'appliquez aucune forme de formatage.
"""

Cette invite de base configure le modèle linguistique (LLM) pour qu'il fonctionne comme un expert SQL avec des conseils spécifiques pour la base de données IRIS. Ensuite, nous fournissons une invite auxiliaire avec des renseignements sur le schéma de la base de données pour éviter les hallucinations.

# Extension des modèles SQL pour inclure les renseignements sur le contexte des tables
tables_prompt_template = """
N'utilisez que les tables suivantes:
{table_info}
"""

Afin d'améliorer la précision des réponses du LLM, nous utilisons une technique appelée "incitation en quelques coups" ("few-shot prompting"). Il s'agit de présenter quelques exemples au LLM.

# Extension du modèle SQL pour l'inclusion de quelques exemples
prompt_sql_few_shots_template = """
Vous trouverez ci-dessous un certain nombre d'exemples de questions et de requêtes SQL correspondantes.

{examples_value}
"""

Nous définissons le modèle pour des exemples en quelques coups:

# Modèle d'invite à quelques coups
example_prompt_template = "User input: {input}\nSQL query: {query}"
example_prompt = PromptTemplate.from_template(example_prompt_template)

Nous construisons l'invite utilisateur en utilisant le modèle en quelques coups:

# Modèle d'invite utilisateur
user_prompt = "\n" + example_prompt.invoke({"input": "{input}", "query": ""}).to_string()

Enfin, nous composons toutes les invites pour créer l'invite finale:

# Modèle d'invite complet
prompt = (
    ChatPromptTemplate.from_messages([("system", iris_sql_template)])
    + ChatPromptTemplate.from_messages([("system", tables_prompt_template)])
    + ChatPromptTemplate.from_messages([("system", prompt_sql_few_shots_template)])
    + ChatPromptTemplate.from_messages([("human", user_prompt)])
)
prompt

Cette invite attend les variables examples_value, input, table_info, et top_k.

Voici comment l'invite est structurée:

ChatPromptTemplate(
    input_variables=['examples_value', 'input', 'table_info', 'top_k'], 
    messages=[
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['top_k'], 
                template=iris_sql_template
            )
        ), 
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['table_info'], 
                template=tables_prompt_template
            )
        ), 
        SystemMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['examples_value'], 
                template=prompt_sql_few_shots_template
            )
        ), 
        HumanMessagePromptTemplate(
            prompt=PromptTemplate(
                input_variables=['input'], 
                template=user_prompt
            )
        )
    ]
)

Pour visualiser la manière dont l'invite sera envoyée au LLM, nous pouvons utiliser des valeurs de remplacement pour les variables requises:

prompt_value = prompt.invoke({
    "top_k": "<top_k>",
    "table_info": "<table_info>",
    "examples_value": "<examples_value>",
    "input": "<input>"
})
print(prompt_value.to_string())
Système: 
Vous êtes un expert d'InterSystems IRIS. Compte tenu d'une question d'entrée, créez d'abord une requête InterSystems IRIS syntaxiquement correcte pour exécuter et renvoyer la réponse à la question saisie.
Si l'utilisateur ne spécifie pas dans la question un nombre spécifique d'exemples à obtenir, demandez au maximum <top_k> résultats en utilisant la clause TOP conformément à InterSystems IRIS. Vous pouvez classer les résultats de manière à obtenir les renseignements les plus pertinents de la base de données.
Ne faites jamais de requête pour toutes les colonnes d'une table. Vous ne devez requérir que les colonnes nécessaires pour répondre à la question.. Mettez chaque nom de colonne entre guillemets simples ('') pour indiquer qu'il s'agit d'identifiants délimités.
Veillez à n'utiliser que les noms de colonnes que vous pouvez voir dans les tables ci-dessous. Veillez à ne pas requérir les colonnes qui n'existent pas. Faites également attention à ce que les colonnes se trouvent dans les différents tables.
Veillez à utiliser la fonction CAST(CURRENT_DATE as date) pour obtenir la date du jour, si la question porte sur "aujourd'hui".
Utilisez des guillemets doubles pour délimiter les identifiants des colonnes.
Renvoyez des données SQL simples; n'appliquez aucune forme de formatage.

Système: 
N'utilisez que les tables suivantes:
<table_info>

Système: 
Vous trouverez ci-dessous un certain nombre d'exemples de questions et de requêtes SQL correspondantes.

<examples_value>

Human: 
User input: <input>
SQL query: 

Maintenant, nous sommes prêts à envoyer cette invite au LLM en fournissant les variables nécessaires. Passons à l'étape suivante lorsque vous êtes prêt.

Fourniture des renseignements sur la table

Pour créer des requêtes SQL précises, nous devons fournir au modèle linguistique (LLM) des renseignements détaillés sur les tables de la base de données. Sans ces renseignements, le LLM pourrait générer des requêtes qui semblent plausibles mais qui sont incorrectes en raison d'hallucinations. Par conséquent, notre première étape consiste à créer une fonction qui récupère les définitions des tables de la base de données IRIS.

Fonction de récupération des définitions de tables

La fonction suivante interroge INFORMATION_SCHEMA pour obtenir les définitions de tables pour un schéma donné. Si une table spécifique est fournie, elle récupère la définition de cette table ; sinon, elle récupère les définitions de toutes les tables du schéma.

def get_table_definitions_array(cnx, schema, table=None):
    cursor = cnx.cursor()

    # Requête de base pour obtenir les renseignements sur les colonnes
    query = """
    SELECT TABLE_SCHEMA, TABLE_NAME, COLUMN_NAME, DATA_TYPE, IS_NULLABLE, COLUMN_DEFAULT, PRIMARY_KEY, null EXTRA
    FROM INFORMATION_SCHEMA.COLUMNS
    WHERE TABLE_SCHEMA = %s
    """
    
    # Paramètres de la requête
    params = [schema]

    # Ajout de filtres optionnels
    if table:
        query += " AND TABLE_NAME = %s"
        params.append(table)
    
    # Exécution de la requête
    cursor.execute(query, params)

    # Récupération des résultats
    rows = cursor.fetchall()
    
    # Traitement des résultats pour générer la (les) définition(s) de table(s)
    table_definitions = {}
    for row in rows:
        table_schema, table_name, column_name, column_type, is_nullable, column_default, column_key, extra = row
        if table_name not in table_definitions:
            table_definitions[table_name] = []
        table_definitions[table_name].append({
            "column_name": column_name,
            "column_type": column_type,
            "is_nullable": is_nullable,
            "column_default": column_default,
            "column_key": column_key,
            "extra": extra
        })

    primary_keys = {}
    
    # Construire la chaîne de sortie
    result = []
    for table_name, columns in table_definitions.items():
        table_def = f"CREATE TABLE {schema}.{table_name} (\n"
        column_definitions = []
        for column in columns:
            column_def = f"  {column['column_name']} {column['column_type']}"
            if column['is_nullable'] == "NO":
                column_def += " NOT NULL"
            if column['column_default'] is not None:
                column_def += f" DEFAULT {column['column_default']}"
            if column['extra']:
                column_def += f" {column['extra']}"
            column_definitions.append(column_def)
        if table_name in primary_keys:
            pk_def = f"  PRIMARY KEY ({', '.join(primary_keys[table_name])})"
            column_definitions.append(pk_def)
        table_def += ",\n".join(column_definitions)
        table_def += "\n);"
        result.append(table_def)

    return result

Récupération des définitions de tables pour un schéma

Pour cet exemple, nous utilisons le schéma "Aviation", qui est disponible ici.

# Récupération des définitions de tables pour un schéma "Aviation"
tables = get_table_definitions_array(cnx, "Aviation")
print(tables)

Cette fonction renvoie les instructions CREATE TABLE (creer une table) pour toutes les tables du schéma "Aviation":

[
    'CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  AccidentExplosion varchar,\n  AccidentFire varchar,\n  AirFrameHours varchar,\n  AirFrameHoursSince varchar,\n  AirFrameHoursSinceLastInspection varchar,\n  AircraftCategory varchar,\n  AircraftCertMaxGrossWeight integer,\n  AircraftHomeBuilt varchar,\n  AircraftKey integer NOT NULL,\n  AircraftManufacturer varchar,\n  AircraftModel varchar,\n  AircraftRegistrationClass varchar,\n  AircraftSerialNo varchar,\n  AircraftSeries varchar,\n  Damage varchar,\n  DepartureAirportId varchar,\n  DepartureCity varchar,\n  DepartureCountry varchar,\n  DepartureSameAsEvent varchar,\n  DepartureState varchar,\n  DepartureTime integer,\n  DepartureTimeZone varchar,\n  DestinationAirportId varchar,\n  DestinationCity varchar,\n  DestinationCountry varchar,\n  DestinationSameAsLocal varchar,\n  DestinationState varchar,\n  EngineCount integer,\n  EvacuationOccurred varchar,\n  EventId varchar NOT NULL,\n  FlightMedical varchar,\n  FlightMedicalType varchar,\n  FlightPhase integer,\n  FlightPlan varchar,\n  FlightPlanActivated varchar,\n  FlightSiteSeeing varchar,\n  FlightType varchar,\n  GearType varchar,\n  LastInspectionDate timestamp,\n  LastInspectionType varchar,\n  Missing varchar,\n  OperationDomestic varchar,\n  OperationScheduled varchar,\n  OperationType varchar,\n  OperatorCertificate varchar,\n  OperatorCertificateNum varchar,\n  OperatorCode varchar,\n  OperatorCountry varchar,\n  OperatorIndividual varchar,\n  OperatorName varchar,\n  OperatorState varchar,\n  Owner varchar,\n  OwnerCertified varchar,\n  OwnerCountry varchar,\n  OwnerState varchar,\n  RegistrationNumber varchar,\n  ReportedToICAO varchar,\n  SeatsCabinCrew integer,\n  SeatsFlightCrew integer,\n  SeatsPassengers integer,\n  SeatsTotal integer,\n  SecondPilot varchar,\n  childsub bigint NOT NULL DEFAULT $i(^Aviation.EventC("Aircraft"))\n);',
    'CREATE TABLE Aviation.Crew (\n  Aircraft varchar NOT NULL,\n  ID varchar NOT NULL,\n  Age integer,\n  AircraftKey integer NOT NULL,\n  Category varchar,\n  CrewNumber integer NOT NULL,\n  EventId varchar NOT NULL,\n  Injury varchar,\n  MedicalCertification varchar,\n  MedicalCertificationDate timestamp,\n  MedicalCertificationValid varchar,\n  Seat varchar,\n  SeatbeltUsed varchar,\n  Sex varchar,\n  ShoulderHarnessUsed varchar,\n  ToxicologyTestPerformed varchar,\n  childsub bigint NOT NULL DEFAULT $i(^Aviation.AircraftC("Crew"))\n);',
    'CREATE TABLE Aviation.Event (\n  ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n  AirportDirection integer,\n  AirportDistance varchar,\n  AirportElevation integer,\n  AirportLocation varchar,\n  AirportName varchar,\n  Altimeter varchar,\n  EventDate timestamp,\n  EventId varchar NOT NULL,\n  EventTime integer,\n  FAADistrictOffice varchar,\n  InjuriesGroundFatal integer,\n  InjuriesGroundMinor integer,\n  InjuriesGroundSerious integer,\n  InjuriesHighest varchar,\n  InjuriesTotal integer,\n  InjuriesTotalFatal integer,\n  InjuriesTotalMinor integer,\n  InjuriesTotalNone integer,\n  InjuriesTotalSerious integer,\n  InvestigatingAgency varchar,\n  LightConditions varchar,\n  LocationCity varchar,\n  LocationCoordsLatitude double,\n  LocationCoordsLongitude double,\n  LocationCountry varchar,\n  LocationSiteZipCode varchar,\n  LocationState varchar,\n  MidAir varchar,\n  NTSBId varchar,\n  NarrativeCause varchar,\n  NarrativeFull varchar,\n  NarrativeSummary varchar,\n  OnGroundCollision varchar,\n  SkyConditionCeiling varchar,\n  SkyConditionCeilingHeight integer,\n  SkyConditionNonCeiling varchar,\n  SkyConditionNonCeilingHeight integer,\n  TimeZone varchar,\n  Type varchar,\n  Visibility varchar,\n  WeatherAirTemperature integer,\n  WeatherPrecipitation varchar,\n  WindDirection integer,\n  WindDirectionIndicator varchar,\n  WindGust integer,\n  WindGustIndicator varchar,\n  WindVelocity integer,\n  WindVelocityIndicator varchar\n);'
]

Avec ces définitions de tables, nous pouvons passer à l'étape suivante, qui consiste à les intégrer dans notre invite pour le LLM. Cela permet de s'assurer que le LLM a des renseignements précis et complets sur le schéma de la base de données lorsqu'il génère des requêtes SQL.

Sélection des tables les plus pertinentes

Lorsque vous travaillez avec des bases de données, en particulier les plus grandes, l'envoi du langage de définition des données (DDL) pour toutes les tables d'une invite peut s'avérer peu pratique. Si cette approche peut fonctionner pour les petites bases de données, les bases de données réelles contiennent souvent des centaines ou des milliers de tables, ce qui rend inefficace le traitement de chacune d'entre elles.

De plus, il est peu probable qu'un modèle linguistique ait besoin de connaître toutes les tables de la base de données pour générer efficacement des requêtes SQL. Pour relever ce défi, nous pouvons exploiter les capacités de recherche sémantique pour sélectionner uniquement les tables les plus pertinentes en fonction de la requête de l'utilisateu.

Approche

Nous y parvenons en utilisant la recherche sémantique avec IRIS Vector Search. Notez que cette méthode est plus efficace si les identifiants de vos éléments SQL (tels que les tables, les champs et les clés) ont des noms significatifs. Si vos identifiants sont des codes arbitraires, envisagez plutôt d'utiliser un dictionnaire de données.

Étapes

  1. Récupération des renseignements sur les tables

Commencez par extraire les définitions des tables dans d'un objet DataFrame pandas:

# Récupérer les définitions de tables dans un objet DataFrame pandas
table_def = get_table_definitions_array(cnx=cnx, schema='Aviation')
table_df = pd.DataFrame(data=table_def, columns=["col_def"])
table_df["id"] = table_df.index + 1
table_df

L'objet DataFrame (table_df) ressemblera à ceci:

col_defid
0CREATE TABLE Aviation.Aircraft (\n Event bigi...1
1CREATE TABLE Aviation.Crew (\n Aircraft varch...2
2CREATE TABLE Aviation.Event (\n ID bigint NOT...3
  1. Répartition des définitions dans des documents

Ensuite, répartissez les définitions des tables dans les documents Langchain. . Cette étape est cruciale pour gérer de gros fragment de texte et extraire des incorporations de texte:

loader = DataFrameLoader(table_df, page_content_column="col_def")
documents = loader.load()
text_splitter = CharacterTextSplitter(chunk_size=400, chunk_overlap=20, separator="\n")
tables_docs = text_splitter.split_documents(documents)
tables_docs

La liste tables_docs qui en résulte contient des documents fractionnés avec des métadonnées, comme suit:

[Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  ...'),
 Document(metadata={'id': 2}, page_content='CREATE TABLE Aviation.Crew (\n  Aircraft varchar NOT NULL,\n  ID varchar NOT NULL,\n  ...'),
 Document(metadata={'id': 3}, page_content='CREATE TABLE Aviation.Event (\n  ID bigint NOT NULL DEFAULT $i(^Aviation.EventD),\n  ...')]
  1. Extraction des incorporations et stockage dans IRIS

Utilisez maintenant la classe IRISVector de langchain-iris pour extraire les vecteurs d'intégration et les stocker:

tables_vector_store = IRISVector.from_documents(
    embedding=OpenAIEmbeddings(), 
    documents=tables_docs,
    connection_string=iris_conn_str,
    collection_name="sql_tables",
    pre_delete_collection=True
)

Remarque : l'indicateur pre_delete_collection est fixé à True (vrai) à des fins de démonstration, afin de garantir une nouvelle collection à chaque exécution du test. Dans un environnement de production, cet indicateur doit généralement être défini sur False (faux).

  1. Recherche de documents pertinents Avec les incorporations de table stockées, vous pouvez désormais interroger les tables pertinentes en fonction des données de l'utilisateur:
input_query = "List the first 2 manufacturers"
relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3)
relevant_tables_docs

Par exemple, une requête portant sur les fabricants peut aboutir à un résultat:

[Document(metadata={'id': 1}, page_content='GearType varchar,\n  LastInspectionDate timestamp,\n  ...'),
 Document(metadata={'id': 1}, page_content='AircraftModel varchar,\n  AircraftRegistrationClass varchar,\n  ...'),
 Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n  LocationState varchar,\n  ...')]

À partir des métadonnées, vous pouvez voir que seul la table ID 1 (Aviation.Avion) est pertinente, ce qui correspond à la requête.

  1. Gestion des cas limites

Bien que cette approche soit généralement efficace, elle n'est pas toujours parfaite. Par exemple, la recherche de sites d'accidents peut également renvoyer des tables moins pertinentes:

input_query = "List the top 10 most crash sites"
relevant_tables_docs = tables_vector_store.similarity_search(input_query, k=3)
relevant_tables_docs

Les résultats peuvent inclure ce qui suit:

[Document(metadata={'id': 3}, page_content='LocationSiteZipCode varchar,\n  LocationState varchar,\n  ...'),
 Document(metadata={'id': 3}, page_content='InjuriesGroundSerious integer,\n  InjuriesHighest varchar,\n  ...'),
 Document(metadata={'id': 1}, page_content='CREATE TABLE Aviation.Aircraft (\n  Event bigint NOT NULL,\n  ID varchar NOT NULL,\n  ...')]

Bien que la table Aviation.Event ait été récupérée deux fois, la table Aviation.Aircraft peut également apparaître, ce qui pourrait être amélioré par un filtrage ou un seuillage supplémentaire. Cela dépasse le cadre de cet exemple et sera laissé à l'appréciation de futures implémentations.

  1. Définition d'une fonction pour récupérer les tables pertinentes

Pour automatiser ce processus, définissez une fonction qui filtre et renvoie les tableaux pertinents en fonction des données de l'utilisateur:

def get_relevant_tables(user_input, tables_vector_store, table_df):
    relevant_tables_docs = tables_vector_store.similarity_search(user_input)
    relevant_tables_docs_indices = [x.metadata["id"] for x in relevant_tables_docs]
    indices = table_df["id"].isin(relevant_tables_docs_indices)
    relevant_tables_array = [x for x in table_df[indices]["col_def"]]
    return relevant_tables_array

Cette fonction permet d'extraire efficacement les tables pertinentes à envoyer au LLM, de réduire la longueur de l'invite et d'améliorer les performances globales de la requête.

Sélection des exemples les plus pertinents ("invitation en quelques coups")

Lorsque vous travaillez avec des modèles linguistiques (LLM), le fait de leur fournir des exemples pertinents permet d'obtenir des réponses précises et adaptées au contexte. Ces exemples, appelés "quelques exemples", guident le LLM dans la compréhension de la structure et du contexte des requêtes qu'il doit traiter.

Dans notre cas, nous devons remplir la variable examples_value avec un ensemble varié de requêtes SQL qui couvrent un large spectre de la syntaxe SQL d'IRIS et des tables disponibles dans la base de données. Cela permet d'éviter que le LLM ne génère des requêtes incorrectes ou non pertinentes.

Définition de requêtes d'exemple

Vous trouverez ci-dessous une liste d'exemples de requêtes conçues pour illustrer diverses opérations SQL:

examples = [
    {"input": "List all aircrafts.", "query": "SELECT * FROM Aviation.Aircraft"},
    {"input": "Find all incidents for the aircraft with ID 'N12345'.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"},
    {"input": "List all incidents in the 'Commercial' operation type.", "query": "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE OperationType = 'Commercial')"},
    {"input": "Find the total number of incidents.", "query": "SELECT COUNT(*) FROM Aviation.Event"},
    {"input": "List all incidents that occurred in 'Canada'.", "query": "SELECT * FROM Aviation.Event WHERE LocationCountry = 'Canada'"},
    {"input": "How many incidents are associated with the aircraft with AircraftKey 5?", "query": "SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5"},
    {"input": "Find the total number of distinct aircrafts involved in incidents.", "query": "SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft"},
    {"input": "List all incidents that occurred after 5 PM.", "query": "SELECT * FROM Aviation.Event WHERE EventTime > 1700"},
    {"input": "Who are the top 5 operators by the number of incidents?", "query": "SELECT TOP 5 OperatorName, COUNT(*) AS IncidentCount FROM Aviation.Aircraft GROUP BY OperatorName ORDER BY IncidentCount DESC"},
    {"input": "Which incidents occurred in the year 2020?", "query": "SELECT * FROM Aviation.Event WHERE YEAR(EventDate) = '2020'"},
    {"input": "What was the month with most events in the year 2020?", "query": "SELECT TOP 1 MONTH(EventDate) EventMonth, COUNT(*) EventCount FROM Aviation.Event WHERE YEAR(EventDate) = '2020' GROUP BY MONTH(EventDate) ORDER BY EventCount DESC"},
    {"input": "How many crew members were involved in incidents?", "query": "SELECT COUNT(*) FROM Aviation.Crew"},
    {"input": "List all incidents with detailed aircraft information for incidents that occurred in the year 2012.", "query": "SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012"},
    {"input": "Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.", "query": "SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5"},
    {"input": "List all crew members involved in incidents with serious injuries, along with the incident date and location.", "query": "SELECT c.CrewNumber AS 'Crew Number', c.Age, c.Sex AS Gender, e.EventDate AS 'Event Date', e.LocationCity AS 'Location City', e.LocationState AS 'Location State' FROM Aviation.Crew c JOIN Aviation.Event e ON c.EventId = e.EventId WHERE c.Injury = 'Serious'"}
]

Sélection d'exemples pertinents

Compte tenu de la liste d'exemples qui ne cesse de s'allonger, il n'est pas pratique de fournir tous les exemples au LLM. Au lieu de cela, nous utilisons la recherche vectorielle d'IRIS et la classe SemanticSimilarityExampleSelector pour identifier les exemples les plus pertinents sur la base des invites de l'utilisateur.

Définition du Sélecteur d'exemples:

example_selector = SemanticSimilarityExampleSelector.from_examples(
    examples,
    OpenAIEmbeddings(),
    IRISVector,
    k=5,
    input_keys=["input"],
    connection_string=iris_conn_str,
    collection_name="sql_samples",
    pre_delete_collection=True
)

Remarque : l'indicateur pre_delete_collection est fixé ici à des fins de démonstration, afin de garantir une nouvelle collection à chaque exécution du test. Dans un environnement de production, cet indicateur doit être défini sur Faux (false) pour éviter les suppressions inutiles.

Requéte du sélecteur:

Pour trouver les exemples les plus pertinents pour une saisie donnée, utilisez le sélecteur comme suit:

input_query = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
relevant_examples = example_selector.select_examples({"input": input_query})

Les résultats pourraient ressembler à ceci:

[{'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'},
 {'input': "Find all incidents for the aircraft with ID 'N12345'.", 'query': "SELECT * FROM Aviation.Event WHERE EventId IN (SELECT EventId FROM Aviation.Aircraft WHERE ID = 'N12345')"},
 {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'},
 {'input': 'List all aircrafts.', 'query': 'SELECT * FROM Aviation.Aircraft'},
 {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'}]

Si vous avez spécifiquement besoin d'exemples liés aux quantités, vous pouvez interroger le sélecteur en conséquence:

input_query = "What is the number of incidents involving Boeing aircraft."
quantity_examples = example_selector.select_examples({"input": input_query})

Le résultat peut être comme suit:

[{'input': 'How many incidents are associated with the aircraft with AircraftKey 5?', 'query': 'SELECT COUNT(*) FROM Aviation.Aircraft WHERE AircraftKey = 5'},
 {'input': 'Find the total number of distinct aircrafts involved in incidents.', 'query': 'SELECT COUNT(DISTINCT AircraftKey) FROM Aviation.Aircraft'},
 {'input': 'How many crew members were involved in incidents?', 'query': 'SELECT COUNT(*) FROM Aviation.Crew'},
 {'input': 'Find all incidents where there were more than 5 injuries and include the aircraft manufacturer and model.', 'query': 'SELECT e.EventId, e.InjuriesTotal, a.AircraftManufacturer, a.AircraftModel FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE e.InjuriesTotal > 5'},
 {'input': 'List all incidents with detailed aircraft information for incidents that occurred in the year 2012.', 'query': 'SELECT e.EventId, e.EventDate, a.AircraftManufacturer, a.AircraftModel, a.AircraftCategory FROM Aviation.Event e JOIN Aviation.Aircraft a ON e.EventId = a.EventId WHERE Year(e.EventDate) = 2012'}]

Ce résultat comprend des exemples qui traitent spécifiquement du comptage et des quantités.

Considérations futures

Bien que le sélecteur SemanticSimilarityExampleSelector soit puissant, il est important de noter que tous les exemples sélectionnés ne sont pas forcément parfaits. Les améliorations futures peuvent impliquer l'ajout de filtres ou de seuils pour exclure les résultats moins pertinents, garantissant que seuls les exemples les plus appropriés sont fournis au LLM.

Test de précision

Pour évaluer les performances de l'invite et de la génération de requêtes SQL, nous devons mettre en place et exécuter une série de tests. L'objectif est d'évaluer dans quelle mesure le LLM génère des requêtes SQL basées sur les données de l'utilisateur, avec et sans l'utilisation de quelques coups basés sur des exemples.

Fonction de génération de requêtes SQL

Nous commençons par définir une fonction qui utilise le LLM pour générer des requêtes SQL en fonction du contexte fourni, de l'invite, de la saisie de l'utilisateur et d'autres paramètres:

def get_sql_from_text(context, prompt, user_input, use_few_shots, tables_vector_store, table_df, example_selector=None, example_prompt=None):
    relevant_tables = get_relevant_tables(user_input, tables_vector_store, table_df)
    context["table_info"] = "\n\n".join(relevant_tables)

    examples = example_selector.select_examples({"input": user_input}) if example_selector else []
    context["examples_value"] = "\n\n".join([
        example_prompt.invoke(x).to_string() for x in examples
    ])
    
    model = ChatOpenAI(model="gpt-3.5-turbo", temperature=0)
    output_parser = StrOutputParser()
    chain_model = prompt | model | output_parser
    
    response = chain_model.invoke({
        "top_k": context["top_k"],
        "table_info": context["table_info"],
        "examples_value": context["examples_value"],
        "input": user_input
    })
    return response

Nous commençons par définir une fonction qui utilise le LLM pour générer des requêtes SQL en fonction du contexte fourni, de l'invite, de la saisie de l'utilisateur et d'autres paramètres

Testez l'invite avec et sans exemples:

# Exécution de l'invite **avec** quelques coups
input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
response_with_few_shots = get_sql_from_text(
    context, 
    prompt, 
    user_input=input, 
    use_few_shots=True, 
    tables_vector_store=tables_vector_store, 
    table_df=table_df,
    example_selector=example_selector, 
    example_prompt=example_prompt,
)
print(response_with_few_shots)
SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel
FROM Aviation.Event e
JOIN Aviation.Aircraft a ON e.EventId = a.EventId
WHERE Year(e.EventDate) = 2010
# Exécution de l'invite **sans** quelques coups
input = "Find all events in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model."
response_with_no_few_shots = get_sql_from_text(
    context, 
    prompt, 
    user_input=input, 
    use_few_shots=False, 
    tables_vector_store=tables_vector_store, 
    table_df=table_df,
)
print(response_with_no_few_shots)
SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel"
FROM Aviation.Event e
JOIN Aviation.Aircraft a ON e.ID = a.Event
WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01'
Utility Functions for Testing

Pour tester les requêtes SQL générées, nous définissons quelques fonctions utilitaires:

def execute_sql_query(cnx, query):
    try:
        cursor = cnx.cursor()
        cursor.execute(query)
        rows = cursor.fetchall()
        return rows
    except:
        print('Error running query:')
        print(query)
        print('-'*80)
    return None

def sql_result_equals(cnx, query, expected):
    rows = execute_sql_query(cnx, query)
    result = [set(row._asdict().values()) for row in rows or []]
    if result != expected and rows is not None:
        print('Result not as expected for query:')
        print(query)
        print('-'*80)
    return result == expected
# Test SQL pour l'invite **avec** quelques coups
print("SQL is OK" if not execute_sql_query(cnx, response_with_few_shots) is None else "SQL is not OK")
    SQL is OK
# Test SQL pour l'invite **sans** quelques coups
print("SQL is OK" if not execute_sql_query(cnx, response_with_no_few_shots) is None else "SQL is not OK")
    error on running query: 
    SELECT TOP 3 "EventId", "EventDate", "LocationCity", "LocationState", "AircraftManufacturer", "AircraftModel"
    FROM Aviation.Event e
    JOIN Aviation.Aircraft a ON e.ID = a.Event
    WHERE e.EventDate >= '2010-01-01' AND e.EventDate < '2011-01-01'
    --------------------------------------------------------------------------------
    SQL is not OK

Définition et exécution des tests

Définissez un ensemble de scénarios de test et les exécutez:

tests = [{
    "input": "What were the top 3 years with the most recorded events?",
    "expected": [{128, 2003}, {122, 2007}, {117, 2005}]
},{
    "input": "How many incidents involving Boeing aircraft.",
    "expected": [{5}]
},{
    "input": "How many incidents that resulted in fatalities.",
    "expected": [{237}]
},{
    "input": "List event Id and date and, crew number, age and gender for incidents that occurred in 2013.",
    "expected": [{1, datetime.datetime(2013, 3, 4, 11, 6), '20130305X71252', 59, 'M'},
                 {1, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 32, 'M'},
                 {2, datetime.datetime(2013, 1, 1, 15, 0), '20130101X94035', 35, 'M'},
                 {1, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 25, 'M'},
                 {2, datetime.datetime(2013, 1, 12, 15, 0), '20130113X42535', 34, 'M'},
                 {1, datetime.datetime(2013, 2, 1, 15, 0), '20130203X53401', 29, 'M'},
                 {1, datetime.datetime(2013, 2, 15, 15, 0), '20130218X70747', 27, 'M'},
                 {1, datetime.datetime(2013, 3, 2, 15, 0), '20130303X21011', 49, 'M'},
                 {1, datetime.datetime(2013, 3, 23, 13, 52), '20130326X85150', 'M', None}]
},{
    "input": "Find the total number of incidents that occurred in the United States.",
    "expected": [{1178}]
},{
    "input": "List all incidents latitude and longitude coordinates with more than 5 injuries that occurred in 2010.",
    "expected": [{-78.76833333333333, 43.25277777777778}]
},{
    "input": "Find all incidents in 2010 informing the Event Id and date, location city and state, aircraft manufacturer and model.",
    "expected": [
        {datetime.datetime(2010, 5, 20, 13, 43), '20100520X60222', 'CIRRUS DESIGN CORP', 'Farmingdale', 'New York', 'SR22'},
        {datetime.datetime(2010, 4, 11, 15, 0), '20100411X73253', 'CZECH AIRCRAFT WORKS SPOL SRO', 'Millbrook', 'New York', 'SPORTCRUISER'},
        {'108', datetime.datetime(2010, 1, 9, 12, 55), '20100111X41106', 'Bayport', 'New York', 'STINSON'},
        {datetime.datetime(2010, 8, 1, 14, 20), '20100801X85218', 'A185F', 'CESSNA', 'New York', 'Newfane'}
    ]
}]

Évaluation de la précision

Exécutez les tests et calculez la précision:

def execute_tests(cnx, context, prompt, use_few_shots, tables_vector_store, table_df, example_selector, example_prompt):
    tests_generated_sql = [(x, get_sql_from_text(
            context, 
            prompt, 
            user_input=x['input'], 
            use_few_shots=use_few_shots, 
            tables_vector_store=tables_vector_store, 
            table_df=table_df,
            example_selector=example_selector if use_few_shots else None, 
            example_prompt=example_prompt if use_few_shots else None,
        )) for x in deepcopy(tests)]
    
    tests_sql_executions = [(x[0], sql_result_equals(cnx, x[1], x[0]['expected'])) 
                            for x in tests_generated_sql]
    
    accuracy = sum(1 for i in tests_sql_executions if i[1] == True) / len(tests_sql_executions)
    print(f'Accuracy: {accuracy}')
    print('-'*80)

Résultats

# Tests de précision pour les invites exécutées **sans** quelques coups
use_few_shots = False
execute_tests(
    cnx,
    context, 
    prompt, 
    use_few_shots, 
    tables_vector_store, 
    table_df, 
    example_selector, 
    example_prompt
)
    error on running query: 
    SELECT "EventDate", COUNT("EventId") as "TotalEvents"
    FROM Aviation.Event
    GROUP BY "EventDate"
    ORDER BY "TotalEvents" DESC
    TOP 3;
    --------------------------------------------------------------------------------
    error on running query: 
    SELECT "EventId", "EventDate", "C"."CrewNumber", "C"."Age", "C"."Sex"
    FROM "Aviation.Event" AS "E"
    JOIN "Aviation.Crew" AS "C" ON "E"."ID" = "C"."EventId"
    WHERE "E"."EventDate" >= '2013-01-01' AND "E"."EventDate" < '2014-01-01'
    --------------------------------------------------------------------------------
    result not expected for query: 
    SELECT TOP 3 "e"."EventId", "e"."EventDate", "e"."LocationCity", "e"."LocationState", "a"."AircraftManufacturer", "a"."AircraftModel"
    FROM "Aviation"."Event" AS "e"
    JOIN "Aviation"."Aircraft" AS "a" ON "e"."ID" = "a"."Event"
    WHERE "e"."EventDate" >= '2010-01-01' AND "e"."EventDate" < '2011-01-01'
    --------------------------------------------------------------------------------
    accuracy: 0.5714285714285714
    --------------------------------------------------------------------------------
# Tests de précision pour les invites exécutées **avec** quelques coups
use_few_shots = True
execute_tests(
    cnx,
    context, 
    prompt, 
    use_few_shots, 
    tables_vector_store, 
    table_df, 
    example_selector, 
    example_prompt
)
    error on running query: 
    SELECT e.EventId, e.EventDate, e.LocationCity, e.LocationState, a.AircraftManufacturer, a.AircraftModel
    FROM Aviation.Event e
    JOIN Aviation.Aircraft a ON e.EventId = a.EventId
    WHERE Year(e.EventDate) = 2010 TOP 3
    --------------------------------------------------------------------------------
    accuracy: 0.8571428571428571
    --------------------------------------------------------------------------------

Conclusion

La précision des requêtes SQL générées avec des exemples (quelques coups) est environ 49% plus élevée que celles générées sans exemples (85% contre 57%).

Références

0
0 52
Annonce Irène Mykhailova · Sept 17, 2024

Bonjour la Communauté,

L'équipe de certification d'InterSystems Learning Services est ravie d'annoncer la sortie de notre nouvel examen InterSystems IRIS SQL Specialist. Il est désormais disponible à l'achat et à la planification dans le catalogue d'examens InterSystems. Les candidats potentiels peuvent consulter les sujets d'examen et les questions de pratique pour les aider à s'orienter vers les approches et le contenu des questions d'examen. Les candidats qui réussissent l'examen recevront un badge de certification numérique qui peut être partagé sur des comptes de réseaux sociaux comme LinkedIn.

0
0 34
Article Matthieu LAURENT · Sept 11, 2024 4m read

Bonjour,

je vous soumets cet article sous forme d'ADR (Architecture decision record) que nous avons rédigé dans nos équipes. L'objectif était d'être pertinent dans le choix de nos types de stream dans un contexte mirroré. A noter que des éléments peuvent être utiles même dans un environnement sans miroir.

Stockage des "Character Stream" dans un environnement mirroré

  • Auteur : Matthieu LAURENT et Vincent DHEILLY
  • Date : 2024-09-11
  • Version : IRIS 2024.1

Contexte

Les streams sont très utilisées au sein d'InterSystems notamment dans 2 contextes très différents :

  • Stocker des fichiers contenant des caractères (PDF par exemple)
  • Stocker des chaines de caractères très volumineuses

Nos analyses ont été menées avec des CharacterStream. Cependant, les éléments de conclusion et de bonnes pratiques sont transposables pour les BinaryStream.

Il existe 3 classes permettant de manipuler les streams de caractère.

  • %Stream.GlobalCharacter : Avec cette classe, les streams sont stockées dans une globale dédiée.
  • %Stream.FileCharacter : Avec cette classe, les streams sont stockées dans un fichier externe. Seul un pointeur vers le fichier est stocké dans une globale.
  • %Stream.TmpCharacter : Permet de stocker une stream temporairement mais ne peut être sauvegardée.

Solutions étudiées

Le but étant d'étudier le stockage pérenne, la classe TmpCharacter est totalement exclue des solutions viables et donc étudiées.

Utilisation des GlobalCharacter

Les GlobalCharacter sont stockés dans une globale dédiée. Elles sont donc par défaut stockées dans la base de données "DATA" du namespace dans laquelle elle se trouve.

AvantagesInconvénients
Les streams sont stockées dans la base et donc mirroréesLa taille conséquente des streams peuvent faire grossir la base
Aucune date de création des fichiers. Difficile d'isoler les "vieux" fichiers

Utilisation des FileCharacter

Les FileCharacter sont stockés dans des fichiers externes. Elles sont donc par défaut stockées dans un sous dossier stream de la base de données "DATA" du namespace dans laquelle elle se trouve. Seul un pointeur vers le fichier est stocké en base de données.

AvantagesInconvénients
Les fichiers sont stockés sur le serveur directement et ne font pas grossir la baseLes fichiers ne sont pas mirrorés. En cas de document métier important, il faut une solution tierce pour prévoir une copie temps réel de ces fichiers
Les fichiers étant datés, une purge est facilement envisageable

Décision

L'utilisation des FileCharacter stream n'est pas une solution viable de façon autonome dans un environnement mirroré. Il est donc impossible d'utiliser ce type dans le cas d'un document de gestion persisté.

Les streams stockés dans les messages n'ont par contre aucune utilité d'être stockées de façon pérenne.

La décision est d'utiliser dans tous les cas la classe GlobalCharacter lorsque l'attribut se trouve dans une classe persistante. Elle est aussi d'utiliser dans tous les cas un FileCharacter lorsque l'attribut fait partie d'une classe sérialisable.

A noter que dans les deux cas, il est impératif de définir une localisation de stockage.

Exemple avec un GlobalCharacter pour stocker un fichier Justificatif
Toujours préfixer avec le mot clef STREAM pour identifier les globales

Property Justificatif As %Stream.GlobalCharacter(LOCATION = "^STREAMJustificatif");

Exemple avec un FileCharacter pour stocker un fichier Justificatif.
Mettre dans un dossier indiquant que c'est des streams et que c'est temporaire

Property Justificatif As %Stream.FileCharacter(LOCATION = "/stream/tmp/justificatif");

Dans le cas des FileCharacter, prévoir dès l'implémentation une stratégie de purge des fichiers sur base de date.

Conséquences

En choisissant cette stratégie, voici les conséquences :

  1. Mirroring automatique : Toutes les streams persistantes sont automatiquement mirrorées

  2. Stockage maîtrisé : Les streams non pérennes peuvent être facilement purgées. Attention, ce n'est pas automatique, purge manuelle via le système de fichier nécessaire. Il est également possible de créer des tâches de purge directement via IRIS.

  3. Base de données dédiées aux fichiers : Les streams étant stockés dans des globales dédiées, il est possible de prévoir une base de données locales dédiées aux streams par namespace et de gérer avec une transposition de package.

Références

1
3 130
Article Guillaume Rongier · Août 29, 2024 4m read

   

📜 Sainte Thècle, verset  8: "Étendez votre souris sur l'écran, et l'océan de données ouvrira un chemin devant vous!!"

Bonjour la communauté, tout d'abord, toutes mes excuses si quelqu'un a été offensé par le blasphème 😔

Avez-vous déjà pensé d'avoir séparé le code source et les données de la base de données, ce qui serait intéressant? Peut-être aimeriez-vous pouvoir sauvegarder votre code sans avoir à copier des giga-octets d'informations de vos données clients.

Je vais vous expliquer les étapes à suivre pour séparer votre océan de code source et de données d'espace de noms dans deux bases de données différentes.

Pour cet exemple, je vais partir d'un nouvel espace de noms que je vais créer pour l'exemple.

Tout d'abord, nous allons créer deux nouvelles bases de données:

1. Accédez au portail et à la section Bases de données locales:

Cliquerons sur le bouton "Créer une nouvelle base de données":

Nous le nommerons et indiquerons le dossier dans lequel il sera stocké (je l'ai préfixé "Tutoriel" car j'ai l'intention d'appeler l'espace de noms "Tutoriel"):

Il nous permet maintenant de choisir la taille que nous voulons attribuer et de déterminer si nous voulons activer la journalisation pour cela:

Sélectionnons "Créer une nouvelle ressource":

Nous attribuons le nom de la nouvelle ressource, nous mettons la description et, si nous le jugeons approprié, nous marquons les autorisations d'accès public:

Suivons donc ensuite les mêmes étapes pour l'autre base de données:

Sélectionnons également "Créer une nouvelle ressource":

Maintenant, nous avons créé nos deux bases de données.

Maintenant, créons le nouvel espace de noms ("Namespace") et attribuons-lui les deux bases de données que nous venons de créer.

Nous accédons à la section "Namespaces"::

Nous cliquons sur le bouton "Créer un nouveau Namespace".

Attribuons-lui un nom et choisissons la base de données pour les données et pour le code source, puis cliquons sur le bouton "Enregistrer" (Save).

Et voilà, nous avons créé notre tout nouveau Namespace avec deux bases de données distinctes, l'une pour les données et l'autre pour le code source.

Cela pourrait également être très utile dans des situations telles que... Imaginez que vous ayez une base de données contenant une grande quantité de données partagées par tous les membres de l'équipe de développement.

Ces membres peuvent avoir des ordinateurs portables qu'ils transportent et vous ne voudriez pas que ces ordinateurs portables aient une copie des données de la base localement, soit pour des raisons de sécurité, soit parce que vous pourriez avoir à créer des données de test sur chacun d'entre eux.

Dans ce cas, il peut être judicieux de créer le code source du Namespace localement et les routines dans une base de données distante. "Quoi? Comment connecter une base de données distante?" C'est simple comme bonjour!

Connexion à une base de données distante. **Vous devez préalablement configurer le serveur distant comme ECP (je l'explique à la fin du tutoriel).

Nous accédons à la section des serveurs distants pour le configurer (si nous ne l'avons pas déjà configuré).

Nous cliquons sur le bouton "Serveurs de données":

Et nous ajoutons le nouveau serveur.

Nous remplissons les informations:

Par défaut, il est désactivé. Cliquons sur le bouton "Modifier l'état" pour l'activer:

Nous passons maintenant à la section "Bases de données distantes".

Et nous cliquons sur le bouton "Créer une base de données distante":

Sélectionnons le serveur qui possède la base de données distante et choisissons-le dans le menu déroulant:

Ensuite, nous allons créer un nouveau Namespace "hHybride" avec les informations du code source sur la machine locale et les données sur un serveur distant.

Nous accédons à la section Namespaces et cliquons sur le bouton "Créer un Namespace". Pourtant, cette fois dans la section Database pour les globales nous allons sélectionner l'option "Remote Database" et choisir la base de données distante que nous avons créée:

Et maintenant, nous avons configuré notre incroyable Namespace hybride!

**Configuration du serveur comme ECP (pour pouvoir servir des bases de données distantes, cela nécessite une licence payante):

Définissons le nombre maximum de serveurs de données, SSL, etc. et cliquons sur les boutons "Sauvegarder" et "Activer":

Maintenant nous pouvons avoir nos données séparées sur le même serveur ou même sur des serveurs différents.

Voici une vidéo qui explique comment créer les bases de données et comment créer et configurer le Namespace:

<iframe allowfullscreen="" frameborder="0" height="360" src="https://www.youtube.com/embed/uHT0d_qEPUc" width="640"></iframe>

J'espère que ce tutoriel vous rendra plus agréable la longue traversée du désert 🌴🐪 pour obtenir le pardon du CTO / CIO jusqu'à ce que vous atteigniez la retraite promise.

Rendez-vous dans la prochaine publication! Faites-moi savoir si vous avez trouvé cet article intéressant; tous vos commentaires ou questions sont toujours les bienvenus. 🙌

0
0 59
Article Guillaume Rongier · Juin 24, 2024 10m read

InterSystems IRIS dispose d'une série de dispositifs facilitant la capture, la persistance, l'interopérabilité et la génération d'informations analytiques à partir de données au format XML. Cet article vous montrera comment procéder:

  1. Capture du XML (via un fichier dans notre exemple);
  2. Traitement des données capturées en interopérabilité;
  3. Persistance du XML dans les entités/tables persistantes;
  4. Création des vues analytiques pour les données XML capturées.

Capture des données XML

L'InterSystems IRIS dispose de nombreux adaptateurs intégrés pour capturer des données, notamment les suivants:

  1. Adaptateur de fichiers : utilisé pour obtenir des fichiers à partir de dossiers de réseau.
  2. Adaptateur FTP : utilisé pour obtenir des fichiers à partir de serveurs FTP/SFTP. 
  3. Adaptateur SOAP/Services Web : utilisé pour recevoir des données XML à partir d'opérations de services Web.
  4. Adaptateurs HTTP: utilisés pour acquérir des données et des fichiers XML à partir de points de terminaison HTTP.
  5. D'autres adaptateurs peu courants pour obtenir des données XML, par exemple des adaptateurs de messagerie. Vous pouvez les trouver à l'adresse suivante http://docs.intersystems.com/irislatest/csp/docbook/DocBook.UI.Page.cls?KEY=PAGE_interop_protocols.

Pour capturer des données à l'aide de productions d'interopérabilité, il faut configurer une production et utiliser un service métier Business Service associé à un adaptateur approprié. Les services métier les plus courants sont énumérés ci-dessous:

  1. EnsLib.XML.Object.Service.FileService. C'est l'option que je préfère car elle utilise un adaptateur de fichier pour obtenir un fichier XML à partir du réseau et le charger sur des objets mappés en schéma XML. C'est l'approche que nous emploierons dans cet article.
  2. EnsLib.XML.Object.Service.FTPService. Il utilise un adaptateur FTP pour acquérir un fichier XML à partir d'un serveur FTP et le charger sur des objets mappés en schéma XML.
  3. EnsLib.XML.FileService. Il utilise un adaptateur de fichier pour obtenir un fichier XML à partir du réseau et le charger sur un flux de fichiers File Stream.
  4. EnsLib.XML.FTPService. Il utilise un adaptateur FTP pour obtenir un fichier XML à partir du serveur FTP et le charger sur un flux de fichiers File Stream.
  5. EnsLib.HTTP.GenericService et EnsLib.HTTP.Service. Ils utilisent un adaptateur HTTP pour consommer un point de terminaison HTTP et acquérir un contenu du fichier XML.
  6. EnsLib.SOAP.GenericService et EnsLib.SOAP.Service. Ils utilisent un adaptateur SOAP pour consommer un point de terminaison SOAP et recevoir un contenu du fichier XML.

Persistance et/ou interopérabilité du XML capturé

Pour rendre persistantes ou envoyer (interopérer) les données XML, vous pouvez utiliser tous les adaptateurs mentionnés ci-dessus, mais vous devez également inclure l'adaptateur SQL. Il est utilisé pour conserver les données dans les bases de données SGBDR, par exemple la base de données IRIS. Vous trouverez ci-dessous la liste des adaptateurs couramment utilisés pour l'interopérabilité:

  1. Adaptateur SQL : pour interopérer les données vers les bases de données cibles.
  2. Adaptateur Kafka: pour interopérer les données de manière asynchrone dans des rubriques Kafka.
  3. Adaptateur REST: pour interagir avec les API REST.
  4. Adaptateurs PEX: pour interagir avec les composants natifs Java, dotNet ou Python.  

Pour interopérer ou conserver des données à l'aide de productions d'interopérabilité, il faut configurer une production et utiliser une opération métier associée à un adaptateur approprié. Les opérations métier les plus couramment utilisées sont les suivantes:

  1. EnsLib.SQL.Operation.GenericOperation: utilisée pour conserver les données capturées dans les bases de données SQL et IRIS.
  2. EnsLib.Kafka.Operation: utilisée pour publier les données capturées/traitées dans les rubriques Kafka. 
  3. EnsLib.REPOS.GenericOperation: utilisée pour envoyer des données capturées/traitées aux méthodes des API REST, permettant des intégrations via des API REST.
  4. EnsLib.SAVON.GenericOperation: utilisée pour envoyer des données capturées/traitées aux méthodes de service Web SOAP, permettant des intégrations via des services Web SOAP.
  5. EnsLib.PEX.BusinessOperation: utilisée pour envoyer des données capturées/traitées aux composants Java, dotNet et Python natifs.
  6. EnsLib.XML.Objet.Opération.Ftoperation et EnsLib.XML.Objet.Opération.FileOperation: utilisées pour enregistrer des données XML sous forme de fichier à partir de données d'objet dans des serveurs FTP et des emplacements de système de fichiers Filesystem.

Entre les données capturées à l'aide des services métier et les données envoyées par les opérations métier, il est possible d'utiliser un processus métier pour mettre en œuvre des règles/logiques métier ou pour mapper et traduire le format et la structure des données du service métier à l'opération métier. Les types de processus métier les plus courants sont les suivants:

  1. Ens.BusinessProcessBPL. Il implémente la logique métier à l'aide du langage visuel BPL.
  2. EnsLib.MsgRouter.RoutingEngine. Il est utilisé pour mettre en œuvre la logique de mappage et de traduction afin de convertir les données capturées dans le protocole/la structure utilisé(e) par les opérations métier.
  3. EnsLib.PEX.BusinessProcess. Il est utilisé pour mettre en œuvre la logique métier via Java, DotNet ou Python.

En reprenant le système d'interopérabilité InterSystems IRIS, nous avons ce qui suit:


Pour illustrer et matérialiser l'interopérabilité des données XML, nous allons utiliser un exemple d'application Open Exchange.

Interopérabilité XML: un échantillon

Installer l'échantillon

1. Clone/git extrait le référentiel dans n'importe quel répertoire local.

$ git clone https://github.com/yurimarx/iris-xml-sample.git

2. Ouvrez le terminal dans ce répertoire et lancez:

$ docker-compose build

3. Lancez le conteneur IRIS avec votre projet:

$ docker-compose up -d

 

Lancement de la production d'échantillons

1. Ouvrez le lien http://localhost:52796/csp/user/EnsPortal.ProductionConfig.zen?$NAMESPACE=USER&$NAMESPACE=USER& 
2. Cliquez sur le bouton Start et voyez tous les rubriques en vert:


CConfiguration du VSCode pour ce projet

1. Ouvrez le code source dans VSCode:


2. Dans le pied de page, recherchez le bouton ObjectScript (uniquement s'il n'est pas encore connecté):

3. Cliquez dessus et sélectionnez "Toggle Connection" en haut de la page:

4. Vous avez maintenant le VSCode et le serveur IRIS connectés à l'espace de noms de l'utilisateur USER:

Traitement d'un échantillon XML

1. Accédez à l'onglet Explorateur:

2. Sélectionnez le fichier books.xml et copiez-le dans le dossier xml_input:

3. Une ou deux secondes plus tard, le fichier books.xml sera traité et supprimé du dossier (VSCode affiche le fichier comme étant effacé et le supprime).
4. Vous pouvez maintenant voir les résultats dans l'éditeur de production: http://localhost:52796/csp/user/EnsPortal.ProductionConfig.zen?$NAMESPACE=USER&$NAMESPACE=USER&
5. Accédez à l'onglet Messages et cliquez sur la première session de la liste:

6. Consultez l'onglet Contenu:

7. Accédez maintenant à l'éditeur SQL: http://localhost:52796/csp/sys/exp/%25CSP.UI.Portal.SQL.Home.zen?$NAMESPACE=USER&$NAMESPACE=USER 

8. Exécutez le SQL suivant et regardez les résultats:

SELECTID, author, description, genre, price, publish, title, totalDays
FROM dc_XmlDemo.Catalog


À ce stade, nous allons découvrir le code source de cet échantillon.

Derrière les coulisses : le code source

Création d'un mappage de classe pour le schéma XML

1. Ouvrez le fichier books.xml et copiez son contenu:

2. Nous devons créer un schéma XML pour ce fichier XML au cas où il n'existerait pas encore. J'ai utilisé ce site : https://www.liquid-technologies.com/online-xml-to-xsd-converter. Cependant, il existe de nombreuses autres possibilités en ligne. Il faut donc insérer le contenu XML et cliquer sur Générer un schéma.

3. Copiez le schéma XML généré et créez le fichier books.xsd avec son contenu.:

4. Accédez au terminal IRIS. Pour ce faire, cliquez sur le bouton docker:iris:52796[USER] dans le pied de page et sélectionnez l'option Ouvrir le terminal dans Docker:


5. Écrivez les commandes comme indiqué ci-dessous:

USER>set reader = ##class(%XML.Utils.SchemaReader).%New()              
USER>do reader.Process("/home/irisowner/dev/xml_input/books.xsd","dc.XmlDemo.Schemas")


6. Cette méthode de classe IRIS crée pour vous des classes qui correspondent à la structure XML, ce qui vous permet d'accéder au XML par le biais de classes, de propriétés et de méthodes:


Création de la classe persistante pour stocker les livres ingérés à partir de données XML

1. Créez le fichier Catalog.cls dans le dossier src/dc/XmlDemo avec le contenu suivant:

Class dc.XmlDemo.Catalog Extends%Persistent
{

Property author As%String(MAXLEN = 200);Property title As%String;Property genre As%String;Property price As%Double;Property publish As%Date;Property totalDays As%Integer;Property description As%String(MAXLEN = ""); }


2. Je ne comprends pas bien cette phrase. Peut-être que l'auteur voulait dire : "Cette classe élargit la classe %Persistent, ce qui nous permet de stocker des données persistantes dans la base de données".
3. Ces propriétés reflètent les données XML. Cependant, l'une d'entre elles, totalDays, n'est pas présente dans les données XML. Elle sera calculée en fonction du nombre de jours écoulés entre la publication et le jour actuel. Vous devez enregistrer cette classe pour la créer sur le serveur IRIS.

Création des informations d'identification pour accéder à la base de données IRIS à partir de la production

1. Accéder à Interopérabilité > Configuration > Informations d'identification et créer une référence IrisCreds:

  • Identifiant: IrisCreds
  • Nom de l'utilisateur: _SYSTEM
  • Mot de passe: SYS

2. Cliquez sur Enregistrer (Save).


Création de la Production d'Interopérabilité

1. Accéder à Interopérabilité &gt ; Liste &gt ; Productions:


2. Créez une nouvelle production en cliquant sur le bouton Nouveau (New).
3. Saisissez les valeurs conformément à l'image ci-dessous et cliquez sur le bouton OK:

4. Cliquez sur le bouton + à côté des Services:


5. Définissez les options comme indiqué ci-dessous:

6. Remplissez les chemins d'accès au fichier XML avec les données suivantes:

  • Chemin d'accès au fichier: /home/irisowner/dev/xml_input/
  • Spécification du fichier: *.xml
  • Chemin d'accès à l'Archive: /home/irisowner/dev/xml_output/

7. Saisissez le nom de la classe et le nom du composant pour obtenir les données relatives aux éléments du livre au moment de l'exécution:

8. Cliquez sur le bouton Appliquer (Apply).
9. À ce stade, il est préférable d'utiliser Java Gateway car nous allons établir une connexion JDBC avec la base de données du serveur IRIS pour envoyer des commandes d'insertion SQL et sauvegarder les données. Cette approche est valable pour tous les fournisseurs de bases de données, y compris Oracle, SQL Server, DB2, MySQL et PostgreSQL, lorsque vous devez rendre persistantes des données XML dans une table SQL.
10. Cliquez à nouveau sur le bouton + à côté des Services. Saisissez les champs comme illustré ci-dessous et cliquez sur le bouton OK:


11. Choisissez la passerelle JavaGateway et saisissez les données suivantes dans le champ de chemin d'acces à la classe:

  • Chemin d'acces à la classe: /usr/irissys/dev/java/lib/1.8/*.jar


12. Ce chemin contient le pilote JDBC de la base de données IRIS.
13. Cliquez sur le bouton Appliquer (Apply).
14. Cliquez sur le bouton + à côté des Opérations.
15. Saisissez les valeurs et cliquez sur le bouton OK:


16. Choisissez SqlOperation et saisissez le DSN avec le nom de l'utilisateur (espace de noms dans lequel la table SQL du livre est disponible).
17. Sélectionnez IrisCreds dans le champ des Informations d'identification.
18. Écrivez la commande SQL suivante dans le champ de requête:

insertinto dc_XmlDemo.Catalog(author, description, genre, price, publish, title,totalDays) values (?,?,?,?,DATE(?),?,DATEDIFF('d',?,CURRENT_DATE))


19. Saisissez la valeur dc.XmlDemo.Schemas.book dans RequestClass:

20. Cliquez sur le bouton + à côté de Ajouter (Add) et cliquez sur le bouton Liste à côté du bouton X pour choisir le champ du premier paramètre ‘?’:

21. Sélectionnez *author et cliquez sur le bouton OK:

22. Incluez plus de paramètres pour obtenir les résultats affichés ci-dessous:

23. Il est essentiel de vérifier si vos valeurs ont * avant le champ.
24. Cliquez sur le bouton Appliquer (Apply).
25. Maintenant, choisissez à nouveau XmlFileService et définissez le champ des noms de configuration cible (Target Config Names) sur SqlOperation. Cela permettra au service de recevoir les données XML et d'envoyer les valeurs à l'opération SqlOperation enregistrée dans la base de données.

26. Arrêtez la production créée dans l'exemple avant de lancer celle que vous avez développée:

27. Cliquez sur Ouvrir (Open) et l'arrêter (Stop):

28. Sélectionnez la production dont vous avez besoin: 
29. Sélectionnez votre nouvelle production, ouvrez-la et démarrez-la.
30. Dans VSCode, copiez le fichier books.xml dans le dossier xml_input pour tester votre production.
31. Observez les résultats comme indiqué dans la section d'échantillon XML de processus (Process XML Sample) et profitez-en!

0
1 100
Article Guillaume Rongier · Mai 22, 2024 6m read

Il me semble que c'était hier, lorsque nous avons réalisé un petit projet en Java pour tester les performances d'IRIS, PostgreSQL et MySQL (vous pouvez consulter l'article que nous avons écrit en juin à la fin de cet article). Si vous vous souvenez bien, IRIS était supérieur à PostgreSQL et dépassait considérablement MySQL en termes d'insertions, sans grande différence au niveau des requêtes.

Peu de temps après , @Dmitry Maslennikov m'a demandé "Pourquoi ne le testez-vous pas à partir d'un projet Python ?". Voici donc la version Python des tests que nous avons effectués précédemment en utilisant les connexions JDBC.

Tout d'abord, sachez que je ne suis pas un expert en Python, donc si vous remarquez des choses qui pourraient être améliorées, n'hésitez pas à me contacter.

Pour cet exemple, j'ai utilisé Jupyter Notebook, qui simplifie considérablement le développement en Python et nous permet de voir étape par étape ce que nous faisons. En annexe de cet article vous trouverez l'application qui vous permettra d'effectuer vos propres tests.

Avertissement pour les utilisateurs de Windows

Si vous clonez le projet GitHub dans Visual Studio Code, pour déployer correctement les conteneurs, il vous faudra peut-être changer la configuration de fin de ligne par défaut de CRLF à LF:

Si vous souhaitez reproduire les tests sur vos ordinateurs, vous devez tenir compte des éléments suivants : Docker Desktop demandera des autorisations d'accès aux dossiers de vos ordinateurs dont il a besoin pour déployer le projet. Si vous n'avez pas configuré l'autorisation d'accès à ces dossiers avant de lancer les conteneurs Docker, la création initiale de la table de test dans PostgreSQL échouera, donc avant de lancer le projet, vous devez configurer l'accès partagé aux dossiers du projet dans votre DockerDesktop.

Pour ce faire, vous devez accéder à Paramètres -> Ressources -> Partage de fichiers ("Settings -> Resources -> File sharing") et ajouter à la liste le dossier dans lequel vous avez cloné le projet:

Vous êtes prévenus!

 

Test de performance

Pour ces tests, nous utiliserons une table relativement simple contenant les informations les plus basiques possibles sur un patient. Ici vous pouvez voir la commande pour créer la table en SQL:

CREATETABLE Test.Patient (
    NameVARCHAR(225),
    Lastname VARCHAR(225),
    Photo VARCHAR(5000),
    Phone VARCHAR(14),
    Address VARCHAR(225)    
)

Comme vous pouvez le voir, nous avons défini la photo du patient comme un VARCHAR(5000), puisque nous allons inclure (théoriquement) les informations vectorisées de la photo. Il y a quelques mois, j'ai publié un article expliquant comment utiliser Embedded Python pour implémenter IRIS, un système de reconnaissance faciale (ici) où vous pouvez voir comment les images sont transformées en vecteurs pour une comparaison ultérieure. La question de la vectorisation vient du fait que ce format vectoriel est la norme dans de nombreux modèles d'apprentissage automatique et qu'il est toujours utile de tester avec quelque chose de similaire à la réalité (juste quelque chose)./p>

 

Paramètres de Jupyter Notebook

Pour simplifier au maximum le développement du projet en Python, j'ai utilisé un outil magnifique, le Jupyter Notebook, qui permet de développer pas à pas chacune des fonctionnalités dont nous aurons besoin.

Voici un aperçu de notre Jupyter:

Jetons un coup d'œil aux points les plus intéressants de celui-ci:

Importation de bibliothèques:

import iris
import names
import numpy as np
from datetime import datetime
import psycopg2
import mysql.connector
import matplotlib.pyplot as plt
import random_address
from phone_gen import PhoneNumber

Connexion aux bases de données:

IRIS:

connection_string = "iris:1972/TEST"
username = "superuser"
password = "SYS"
connectionIRIS = iris.connect(connection_string, username, password)
cursorIRIS = connectionIRIS.cursor()
print("Connected")

PostgreSQL:

connectionPostgres = psycopg2.connect(database="testuser",
                        host="postgres",
                        user="testuser",
                        password="testpassword",
                        port="5432")
cursorPostgres = connectionPostgres.cursor()
print("Connected")

MySQL:

connectionMySQL = mysql.connector.connect(
  host="mysql",
  user="testuser",
  password="testpassword"
)
cursorMySQL = connectionMySQL.cursor()
print("Connected")

Génération des valeurs à insérer

phone_number = PhoneNumber("USA")
resultsIRIS = []
resultsPostgres = []
resultsMySQL = []
parameters =  []
for x in range(1000):
    rng = np.random.default_rng()
    parameter = []
    parameter.append(names.get_first_name())
    parameter.append(names.get_last_name())
    parameter.append(str(rng.standard_normal(50)))
    parameter.append(phone_number.get_number())
    parameter.append(random_address.real_random_address_by_state('CA')['address1'])
    parameters.append(parameter)

print("Parameters built")

Insertion dans IRIS

date_before = datetime.now()

cursorIRIS.executemany("INSERT INTO Test.Patient (Name, Lastname, Photo, Phone, Address) VALUES (?, ?, ?, ?, ?)", parameters) connectionIRIS.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsIRIS.append(difference.total_seconds())

Insertion dans PostgreSQL

date_before = datetime.now()

cursorPostgres.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters) connectionPostgres.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsPostgres.append(difference.total_seconds())

Insertion dans MySQL

date_before = datetime.now()

cursorMySQL.executemany("INSERT INTO test.patient (name, lastname, photo, phone, address) VALUES (%s,%s,%s,%s,%s)", parameters) connectionMySQL.commit() difference = datetime.now() - date_before print(difference.total_seconds()) resultsMySQL.append(difference.total_seconds())

Pour notre test, j'ai décidé d'insérer les valeurs suivantes dans chaque base de données:

  • 1 insertion de 1000 patients.
  • 1 insertion de 5000 patients.
  • 1 insertion de 20 000 patients.
  • 1 insertion de 50 000 patients.

Gardez à l'esprit, lors de l'exécution des tests, que le processus le plus long est la création des valeurs à insérer par Python. Pour se rapprocher de la réalité, j'ai lancé plusieurs tests à l'avance afin que les bases de données disposent déjà d'un ensemble significatif d'enregistrements (environ 200 000 enregistrements)./p>

Résultats des tests

Insertion de 1000 patients:

  • InterSystems IRIS: 0,037949 seconde.
  • PostgreSQL: 0,106508 seconde.
  • MySQL: 0,053338 seconde.

Insertion de 5,000 patients:

  • InterSystems IRIS: 0,162791 seconde.
  • PostgreSQL: 0,432642 seconde.
  • MySQL: 0,18925 seconde.

Insertion de 20,000 patients:

  • InterSystems IRIS: 0,601944 seconde.
  • PostgreSQL: 1,803113 seconde.
  • MySQL: 0,594396 seconde.

Insertion de 50,000 patients:

  • InterSystems IRIS: 1.482824 seconde.
  • PostgreSQL: 4,581251 secondes.
  • MySQL: 2,162996 secondes.

Bien qu'il s'agisse d'un test assez simple, il est très significatif car il nous permet de voir la tendance de chaque base de données en ce qui concerne les performances d'insertion.

Conclusions

Si nous comparons les performances des tests effectués avec le projet Java et le projet actuel en Python, nous constatons qu'à cette occasion, PostgreSQL est clairement inférieur au projet Python, étant 4 fois plus lent qu'InterSystems IRIS, tandis que MySQL s'est amélioré par rapport à la version Java.

InterSystems IRIS reste incontestablement le meilleur des trois, présentant un comportement plus linéaire et une meilleure performance d'insertion, quelle que soit la technologie utilisée.

Caractéristiques techniques de l'ordinateur portable utilisé pour les tests:

  • Système d'exploitation: Microsoft Windows 11 Pro.
  • Processeur: 13th Gen Intel(R) Core(TM) i9-13900H, 2600 Mhz.
  • Mémoire RAM: 64 Go.
0
0 79
Article Guillaume Rongier · Mai 20, 2024 11m read

En tant qu'ancien développeur de JAVA, j'ai toujours eu du mal à décider quelle base de données était la plus appropriée pour le projet à développer. L'un des principaux critères que j'utilisais était la performance, ainsi que les capacités de configuration de HA (haute disponibilité). Eh bien, il est maintenant temps de mettre IRIS à l'épreuve en ce qui concerne certaines bases de données les plus couramment utilisées, j'ai donc décidé de créer un petit projet Java basé sur SpringBoot qui se connecte via JDBC avec une base de données MySQL, avec une autre base de données PostgreSQL et enfin avec une base de données IRIS.

Nous allons profiter du fait de disposer d'images Docker de ces bases de données pour les utiliser dans notre projet et vous permettre de l'essayer vous-même sans avoir à procéder à une quelconque installation. Nous pouvons vérifier la configuration du docker dans notre fichier docker-compose.yml

version:"2.2"services:# mysql  mysql:    build:      context:mysql    container_name:mysql    restart:always    command:--default-authentication-plugin=mysql_native_password    environment:      MYSQL_ROOT_PASSWORD:SYS      MYSQL_USER:testuser      MYSQL_PASSWORD:testpassword      MYSQL_DATABASE:test    volumes:    -./mysql/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql    ports:      -3306:3306# postgres  postgres:    build:      context:postgres    container_name:postgres    restart:always    environment:      POSTGRES_USER:testuser      POSTGRES_PASSWORD:testpassword    volumes:    -./postgres/sql/dump.sql:/docker-entrypoint-initdb.d/dump.sql    ports:      -5432:5432  adminer:    container_name:adminer    image:adminer    restart:always    depends_on:      -mysql      -postgres    ports:      -8081:8080# iris  iris:    init:true    container_name:iris    build:      context:.      dockerfile:iris/Dockerfile    ports:      -52773:52773      -1972:1972    command:--check-capsfalse# tomcat  tomcat:    init:true    container_name:tomcat    build:      context:.      dockerfile:tomcat/Dockerfile    volumes:      -./tomcat/performance.war:/usr/local/tomcat/webapps/performance.war    ports:      -8080:8080

D'un coup d'œil rapide, nous constatons que nous utilisons les images suivantes :

  • IRIS: Instance de la Communauté IRIS à laquelle nous nous connecterons par JDBC.
  • Postgres: Image de base de données PostgreSQL sur le port 5432.
  • MySQL: Image de base de données MySQL sur le port 3306
  • Tomcat: Image Docker configurée avec un serveur d'application Apache Tomcat sur lequel nous allons déployer le fichier WAR de notre application.
  • Adminer: Administrateur de base de données qui nous permettra de consulter les bases de données Postgres et MySQL.

Comme vous pouvez le voir, nous avons configuré les ports de listening de manière à ce qu'ils soient également mappés sur notre ordinateur, et pas seulement au sein de Docker. Pour les bases de données, ce n'est pas nécessaire, car la connexion se fait dans les conteneurs Docker, donc si vous avez des problèmes avec les ports, vous pouvez supprimer la ligne de prots de votre fichier docker-compose.yml.

Chaque image de base de données exécute un pré-script qui créera les tables nécessaires aux tests de performance. Examinons l'un des fichiers dump.sql

CREATESCHEMAtest;

DROPTABLEIFEXISTS test.patient;

CREATETABLE test.country ( idINT PRIMARY KEY, nameVARCHAR(225) );

CREATETABLE test.city ( idINT PRIMARY KEY, nameVARCHAR(225), lastname VARCHAR(225), photo BYTEA, phone VARCHAR(14), address VARCHAR(225), country INT, CONSTRAINT fk_country FOREIGN KEY(country) REFERENCES test.country(id) );

CREATETABLE test.patient ( idINTGENERATEDBYDEFAULTASIDENTITY PRIMARY KEY, nameVARCHAR(225), lastname VARCHAR(225), photo BYTEA, phone VARCHAR(14), address VARCHAR(225), city INT, CONSTRAINT fk_city FOREIGN KEY(city) REFERENCES test.city(id) );

INSERTINTO test.country VALUES (1,'Spain'), (2,'France'), (3,'Portugal'), (4,'Germany');

INSERTINTO test.city VALUES (1,'Madrid',1), (2,'Valencia',1), (3,'Paris',2), (4,'Bordeaux',2), (5,'Lisbon',3), (6,'Porto',3), (7,'Berlin',4), (8,'Frankfurt',4);

Nous allons créer 3 tables pour nos tests: patient, ville et pqys, ces deux dernières vont avoir des données préchargées de villes et de pays.

Parfait, maintenant nous allons étudier comment établir les connexions avec la base de données.

Pour ce faire, nous avons créé notre projet Java à partir d'un projet Spring Boot préconfiguré disponible dans Visual Studio Code qui nous fournit la structure de base.

Ne vous inquiétez pas si vous ne comprenez pas immédiatement la structure du projet, le but n'est pas d'apprendre Java, mais nous allons tout de même expliquer un peu plus en détail les documents principaux.

MyDataSourceFactory.java

Cette classe Java permet d'ouvrir les connexions aux différentes bases de données.

PerformancerController.java

Contrôleur chargé de publier les endpoints que nous appellerons depuis Postman.

application.properties

Fichier de configuration avec les différentes connexions aux bases de données déployées dans notre Docker.

Comme vous pouvez le constater, les URL de connexion utilisent le nom du conteneur puisque, lorsqu'elles sont déployées dans un conteneur Tomcat, les bases de données ne seront accessibles par notre application Java qu'avec le nom du conteneur correspondant. Nous pouvons également vérifier la manière dont l'URL établit une connexion via JDBC à nos bases de données. Les bibliothèques Java utilisées dans le projet sont définies dans le fichier pom.xml.

Si vous modifiez le code source, il vous suffit d'exécuter la commande suivante :

mvn package

Cela va générer un fichier performance-0.0.1-SNAPSHOT.war, renommez-le en performance.war et déplacez-le dans le répertoire /tomcat, en remplaçant le fichier existant.

Puisque le projet est sur GitHub, il suffit de le cloner sur notre ordinateur depuis Visual Studio et d'exécuter les commandes suivantes dans le terminal:

docker-compose build
docker-compose up -d

Vérifions le portail Docker:

Génial! Les conteneurs Docker fonctionnent. Vérifions maintenant depuis notre Adminer et le portail de gestion IRIS que nos tables sont correctement créées.

 Accédons d'abord à la base de données MySQL. Si vous consultez le fichier docker-compose.yml vous verrez que le nom d'utilisateur et le mot de passe définis pour MySQL et PostgreSQL sont les mêmes: testuser/testpassword

Les trois tables se trouvent dans notre base de données Test, regardons notre base de données PostgreSQL:

Sélectionnons la base de données testuser et le schéma test:

Nous avons ici nos tables parfaitement créées dans PostgreSQL. Vérifions enfin si tout est bien configuré dans IRIS:

Tout est correct, nous avons créé nos tables dans l'espace de noms USER Namespace sous le schéma Test.

Très bien, une fois les vérifications effectuées, c'est parti ! Pour ce faire, nous utiliserons Postman, dans lequel nous chargerons le fichier attaché au projet: performance.postman_collection.json

Voici les différents tests que nous allons lancer, nous commencerons par des insertions et nous continuerons par des requêtes sur la base de données. Je n'ai inclus aucun type d'index autre que ceux qui sont créés automatiquement avec la définition des clés primaires dans les différentes bases de données.

Insertion

Appel REST: GET http://localhost:8080/performance/tests/insert/{database}?total=1000

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

Et l'attribut total sera celui que nous modifierons pour indiquer le nombre total d'insertions que nous voulons faire.

La méthode qui sera invoquée s'appelle insertRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, vous pouvez voir qu'il s'agit d'une insertion extrêmement simple:

INSERTINTO test.patient VALUES (null, ?, ?, null, ?, ?, ?)

La première valeur est nulle car il s'agit de la clé primaire autogénérée et la deuxième valeur nulle correspond à un champ de type BLOB/BYTEA/LONGVARBINARY dans lequel nous enregistrerons une photo ultérieurement.

Nous allons lancer les lots de commandes type push suivants : 100, 1000, 10000, 20000 et nous allons vérifier les temps de réponse que nous recevons dans Postman. Pour chaque mesure, nous effectuerons 3 tests et nous calculerons la moyenne des 3 valeurs obtenues.

 10010001000020000
MySQL0.7548.91 s88 s192 s
PostgreSQL0.23 s2.24 s20.92 s40.35 s
IRIS0.07 s0.33 s2.6 s5 s

Représentons-le graphiquement.

Insertion avec un fichier binaire

Dans l'exemple précédent, nous avons fait des insertions simples, allons donc accélérer les choses en incluant dans notre insertion une image de 50 kB qui servira de photo pour nos patients.

Appel REST: GET http://localhost:8080/performance/tests/insertBlob/{database}?total=1000

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

Et l'attribut total sera celui que nous modifierons pour indiquer le nombre total d'insertions que nous voulons faire.

La méthode qui sera invoquée s'appelle insertBlobRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, vous pouvez vérifier qu'il s'agit d'une insertion similaire à la précédente à l'exception du fait d'introduire le fichier dans l'insertion:

INSERTINTO test.patient (Name, Lastname, Photo, Phone, Address, City) VALUES (?, ?, ?, ?, ?, ?)

Modifions un peu le nombre d'insertions ci-dessus pour éviter que le test ne prenne une éternité, puis nettoyons le Docker des images pour recommencer à zéro avec une égalité totale des chances.

 1001000500010000
MySQL1.87 s17 s149 s234 s
PostgreSQL0.6 s5.22 s23.93 s60.43 s
IRIS0.13 s0.88 s4.58 s12.57 s

Examinons le graphe:

 

Selection

Testons les performances avec une simple requête qui récupère tous les enregistrements de la table Patient.

Appel REST: GET http://localhost:8080/performance/tests/select/{database}

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle selectRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, la requête est extrêmement simple:

SELECT * FROM test.patient

Nous allons tester la requête avec le même ensemble d'éléments que nous avons utilisé pour notre premier test d'insertion.

 10010001000020000
MySQL0.03 s0,02 s0.03 s0.04 s
PostgreSQL0.03 s0.02 s0.04 s0.03 s
IRIS0.02 s0.02 s0.04 s0.05 s

Et graphiquement:

Sélection d'un groupe par

Testons les performances avec une requête incluant une jointure gauche ainsi que des fonctions d'agrégation. 

Appel REST: GET http://localhost:8080/performance/tests/selectGroupBy/{database}

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle selectGroupBy et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, examinons donc la requête:

SELECTcount(p.Name), c.Name FROM test.patient p leftjoin test.city c on p.City = c.Id GROUPBY c.Name

Nous allons tester la requête encore une fois avec le même ensemble d'éléments que nous avons utilisé pour notre premier test d'insertion.

 10010001000020000
MySQL0.02 s0.02 s0.03 s0.03 s
PostgreSQL0.02 s0.02 s0.02 s0.02 s
IRIS0.02 s0.020.03 s0.04 s

Et graphiquement:

Mise à jour

Pour la mise à jour, nous allons lancer une requête associée à une sous-requête dans le cadre de ses conditions.

Appel REST: GET http://localhost:8080/performance/tests/update/{database}

La variable {database} peut avoir les valeurs suivantes:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle UpdateRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, examinons donc la requête:

UPDATE test.patient SET Phone = '+15553535301'WHERENamein (SELECTNameFROM test.patient whereNamelike'%12')

Lançons la requête et examinons les résultats.

 10010001000020000
MySQLXXXX
PostgreSQL0.02 s0.02 s0.02 s0.03 s
IRIS0.02 s0.02 s0.02 s0.04 s

Nous constatons que MySQL ne permet pas ce type de sous-requêtes dans la même table que celle que nous allons mettre à jour, de sorte que nous ne pouvons pas mesurer leurs durées dans des conditions égales. Ici, nous n'utiliserons pas le graphe, car il est très simple.

Suppression

Pour la suppression, nous allons lancer une requête associée à une sous-requête dans le cadre de ses conditions.

Appel REST: GET http://localhost:8080/performance/tests/delete/{database}

The variable {database} may have the following values:

  • postgres
  • mysql
  • iris

La méthode qui sera invoquée s'appelle DeleteRecords et se trouve dans le fichier PerformanceController.java situé dans /src/main/java/com/performance/controller/, examinons donc la requête:

DELETE test.patient WHERENamein (SELECTNameFROM test.patient whereNamelike'%12')

Lançons la requête et examinons les résultats.

 10010001000020000
MySQLXXXX
PostgreSQL0.01 s0.02 s0.02 s0.03 s
IRIS0.02 s0.02 s0.02 s0.04 s

Nous constatons encore une fois que MySQL ne permet pas ce type de sous-requêtes dans la même table que nous allons supprimer, de sorte que nous ne pouvons pas mesurer leurs durées dans des conditions égales.

Conclusions

Nous pouvons affirmer qu'ils sont tous très au point en ce qui concerne les requêtes de données, ainsi que la mise à jour et la suppression d'enregistrements (à l'exception de MySQL). La plus grande différence se trouve dans la gestion des insertions. IRIS est en effet le meilleur parmi les trois, étant 6 fois plus rapide que PostgreSQL et jusqu'à 20 fois plus rapide que MySQL lors de l'ingestion de données.

Pour travailler avec de grands ensembles de données, c'est IRIS qui est sans aucun doute la meilleure option dans les tests effectués.

Alors... nous avons déjà un champion! IRIS A GAGNÉ!

PS: Il ne s'agit que de quelques exemples de tests que vous pouvez effectuer, n'hésitez pas à modifier le code comme vous le souhaitez.

0
0 68
Article Sylvain Guilbaud · Avr 10, 2024 9m read

Dans cet article, nous aborderons les sujets ci-dessous :

  • Qu’est-ce que Kubernetes ?
  • Principaux composants Kubernetes (K8s)


Qu’est-ce que Kubernetes?

Kubernetes est un framework d'orchestration de conteneurs open source développé par Google. Essentiellement, il contrôle la vitesse des conteneurs et vous aide à gérer des applications composées de plusieurs conteneurs. De plus, il vous permet de les exploiter dans différents environnements, par exemple des machines physiques, des machines virtuelles, des environnements Cloud ou même des environnements de déploiement hybrides.

0
0 52
Article Pierre LaFay · Fév 28, 2024 5m read

Qu'est-ce que les données non structurées ?

Les données non structurées sont des informations qui n'ont pas de modèle de données ou d'organisation prédéfinis. Contrairement aux données structurées que l'on trouve dans les bases de données avec des structures claires (par exemple, des tables et des champs), les données non structurées n'ont pas de schéma fixe. Ce type de données comprend le texte, les images, les vidéos, les fichiers audio, les messages sur les médias sociaux, les courriels, etc.

0
0 63
Article Pierre LaFay · Déc 30, 2023 6m read

Cela semble être hier lorsque nous avons réalisé un petit projet en Java pour tester les performances d'IRIS, PostgreSQL et MySQL (vous pouvez consulter l'article que nous avons écrit en juin à la fin de cet article). Si vous vous en souvenez, IRIS était supérieur à PostgreSQL et clairement supérieur à MySQL en termes d'insertions, sans grande différence en termes de requêtes.

0
0 125
Article Iryna Mykhailova · Oct 13, 2023 12m read

La série d'articles relatifs à l'application QuinielaML se poursuit. Dans cet article, nous verrons comment préparer les données brutes que nous avons capturées à l'aide de la fonctionnalité Embedded Python.

Bienvenue à toutes et à tous !

Happy Artist GIF by VIRTUTE - Find & Share on GIPHY

Introduction

Si vous vous souvenez de l'article précédent, nous avons capturé les données des résultats des matches de football de première et deuxième division pour les 23 dernières années en utilisant Embedded Python à partir d'un site web externe. Maintenant que nous disposons des données brutes, nous allons les transformer et les préparer pour faciliter la maintenance de l'application et l'utilisation de notre modèle de prédiction.

QUINIELA_Object.RawMatch

Voyons sous quelle forme se présentent les données que nous avons saisies dans notre base de données IRIS :

Comme vous le voyez dans la capture d'écran suivante, elles sont peu différentes des informations présentes sur le site web de BDFutbol :

 

Tableaux principaux :

Pour faciliter la maintenance ultérieure des données communes et améliorer les performances du modèle de prédiction, nous avons défini deux tableaux principaux pour stocker les équipes et les arbitres. Ces tableaux ne comprendront que la colonne contenant l'identifiant de l'enregistrement et le nom. Examinons les deux tableaux :

QUINIELA_Object.Referee:

Сorrespondance avec le tableau principal d'arbitres.

QUINIELA_Object.Team

Сorrespondance avec le tableau principal d'arbitres.

 

Préparation de données

Avec nos tableaux principaux et nos données brutes, nous pouvons maintenant entreprendre le processus de préparation de ces données en vue de la formation de notre modèle. Nous avons ici l'opération métier qui sera chargée de la préparation :

Class QUINIELA.BO.PrepareBO Extends Ens.BusinessOperation
{

Parameter INVOCATION = "Queue";
Method PrepareData(pRequest As QUINIELA.Message.PrepareRequest, pResponse As QUINIELA.Message.PrepareResponse) As%Status
{
    Set sc = $$$OKset pResponse = ##class(QUINIELA.Message.PrepareResponse).%New()
    set pResponse.Operation = pRequest.Operation
    
    set sqlTruncateTrain = "TRUNCATE TABLE QUINIELA_Object.MatchTrain"set statementTruncateTrain = ##class(%SQL.Statement).%New()
    set statusTruncateTrain = statementTruncateTrain.%Prepare(sqlTruncateTrain)
    if ($$$ISOK(statusTruncateTrain)) {
        set resultSetTruncateTrain = statementTruncateTrain.%Execute()
        if (resultSetTruncateTrain.%SQLCODE = 0) {
            set sqlMatchTrain = "INSERT INTO QUINIELA_Object.MatchTrain (Day, Division, Journey, LocalTeam, Referee, Result, VisitorTeam, IntDay) "_
                "SELECT "_
                "TO_DATE(RM.Day,'DD/MM/YYYY') AS DayTransformed, "_
                "RM.Division, "_
                "RM.Journey, "_
                "LT.ID as LocalTeam, "_
                "R.ID as Referee, "_
                "CASE WHEN CAST(RM.GoalsLocal As INTEGER) > CAST(RM.GoalsVisitor As INTEGER) THEN 1 WHEN CAST(RM.GoalsLocal As INTEGER) < CAST(RM.GoalsVisitor As INTEGER) THEN 2 ELSE 0 END as Result, "_
                "VT.ID as VisitorTeam, "_
                "CAST({fn CONCAT({fn CONCAT(SUBSTR(RM.Day,7,4),SUBSTR(RM.Day,4,2))},SUBSTR(RM.Day,1,2))} As INTEGER) as IntDay "_
                "FROM "_
                "QUINIELA_Object.RawMatch RM "_
                "LEFT JOIN QUINIELA_Object.Team LT ON UPPER(RM.LocalTeam) = UPPER(LT.Name) "_
                "LEFT JOIN QUINIELA_Object.Team VT ON UPPER(RM.VisitorTeam) = UPPER(VT.Name) "_
                "LEFT JOIN QUINIELA_Object.Referee R ON UPPER(RM.Referee) = UPPER(R.Name)"set statementMatchTrain = ##class(%SQL.Statement).%New()
            set statusMatchTrain = statementMatchTrain.%Prepare(sqlMatchTrain)
            if ($$$ISOK(statusMatchTrain)) {
                set resultSetMatchTrain = statementMatchTrain.%Execute()
                if (resultSetMatchTrain.%SQLCODE = 0) {
                    set sqlUpdateLocalStreak = "UPDATE QUINIELA_Object.MatchTrain SET QUINIELA_Object.MatchTrain.LocalStreak = "_
                        "(SELECT SUM(CASE WHEN IsVictory = 1 THEN 4-%VID ELSE 0 END) FROM "_
                        "(SELECT TOP 3 SubMatch.IntDay, "_
                        "CASE WHEN Result = 1 THEN 1 ELSE 0 END AS IsVictory "_
                        "FROM QUINIELA_Object.MatchTrain AS SubMatch "_
                        "WHERE "_
                        "UPPER(SubMatch.LocalTeam) = UPPER(QUINIELA_Object.MatchTrain.LocalTeam) "_
                        "AND SubMatch.IntDay < QUINIELA_Object.MatchTrain.IntDay "_
                        "ORDER BY SubMatch.IntDay DESC)) "set statementUpdateLocalStreak = ##class(%SQL.Statement).%New()
                    set statusUpdateLocalStreak = statementUpdateLocalStreak.%Prepare(sqlUpdateLocalStreak)
                    if ($$$ISOK(statusUpdateLocalStreak)) {
                        set resultSetUpdateLocalStreak = statementUpdateLocalStreak.%Execute()
                        if (resultSetUpdateLocalStreak.%SQLCODE = 0) {
                            set sqlUpdateVisitorStreak = "UPDATE QUINIELA_Object.MatchTrain SET QUINIELA_Object.MatchTrain.VisitorStreak = "_
                                "(SELECT SUM(CASE WHEN IsVictory = 1 THEN 4-%VID ELSE 0 END) FROM "_
                                "(SELECT TOP 3 SubMatch.IntDay, "_
                                "CASE WHEN Result = 2 THEN 1 ELSE 0 END AS IsVictory "_
                                "FROM QUINIELA_Object.MatchTrain AS SubMatch "_
                                "WHERE "_
                                "UPPER(SubMatch.VisitorTeam) = UPPER(QUINIELA_Object.MatchTrain.VisitorTeam) "_
                                "AND SubMatch.IntDay < QUINIELA_Object.MatchTrain.IntDay "_
                                "ORDER BY SubMatch.IntDay DESC)) "set statementUpdateVisitorStreak = ##class(%SQL.Statement).%New()
                            set statusUpdateVisitorStreak = statementUpdateVisitorStreak.%Prepare(sqlUpdateVisitorStreak)
                            if ($$$ISOK(statusUpdateVisitorStreak)) {
                                set resultSetUpdateVisitorStreak = statementUpdateVisitorStreak.%Execute()
                                set sc = statusUpdateVisitorStreak
                            }
                            else {
                                set sc = statusUpdateVisitorStreak
                            }
                        }
                    }
                    else {
                        set sc = statusUpdateLocalStreak
                    }
                }
            }
            else {
                set sc = statusMatchTrain
            }
        }
    }
    
    set pResponse.Status = "Finished"Return sc
}

XData MessageMap
{
"QUINIELA.Message.PrepareRequest">
    PrepareData
}

}

Examinons maintenant en détail chacune des instructions SQL que nous exécutons :

  1. Nous quittons le tableau de données d'apprentissage :

    TRUNCATETABLE QUINIELA_Object.MatchTrain
  2. Nous lançons une insertion massive dans notre tableau d'apprentissage QUINIELA_Object.MatchTrain

    INSERTINTO QUINIELA_Object.MatchTrain (Day, Division, Journey, LocalTeam, Referee, Result, VisitorTeam, IntDay)
    SELECTTO_DATE(RM.Day,'DD/MM/YYYY') AS DayTransformed,
    RM.Division,
    RM.Journey,
    LT.ID as LocalTeam,
    R.ID as Referee,
    CASEWHENCAST(RM.GoalsLocal AsINTEGER) > CAST(RM.GoalsVisitor AsINTEGER) THEN1WHENCAST(RM.GoalsLocal AsINTEGER) < CAST(RM.GoalsVisitor AsINTEGER) THEN2ELSE0ENDasResult,
    VT.ID as VisitorTeam,
    CAST({fn CONCAT({fn CONCAT(SUBSTR(RM.Day,7,4),SUBSTR(RM.Day,4,2))},SUBSTR(RM.Day,1,2))} AsINTEGER) as IntDay
    FROM
    QUINIELA_Object.RawMatch RM
    LEFTJOIN QUINIELA_Object.Team LT ONUPPER(RM.LocalTeam) = UPPER(LT.Name)
    LEFTJOIN QUINIELA_Object.Team VT ONUPPER(RM.VisitorTeam) = UPPER(VT.Name)
    LEFTJOIN QUINIELA_Object.Referee R ONUPPER(RM.Referee) = UPPER(R.Name)
    Pour y parvenir, nous remplaçons le libellé par le nom de l'arbitre et les équipes par leur référence dans les tableaux principaux. Nous obtenons également le résultat à partir des scores du match, 0 pour le match nul, 1 pour la victoire à domicile et 2 pour la victoire à l'extérieur. La colonne
**Résultat** est celle qui définit notre modèle de prédiction comme un modèle de classification, c'est-à-dire que chaque correspondance est classée dans l'une des trois classes (1, X ou 2).  
  1. Nous calculons les séries pour chaque équipe selon qu'elle joue à domicile ou à l'extérieur. Nous avons ajouté ces colonnes pour améliorer, autant que possible, la performance du modèle prédictif. Nous avons supposé qu'une équipe qui a plusieurs victoires consécutives à domicile ou à l'extérieur a plus de facilité à poursuivre les victoires en étant "sur une série". Le calcul se fait de la manière suivante : on obtient les 3 derniers matchs (à domicile pour l'équipe qui joue à domicile ou à l'extérieur pour celle qui joue à l'extérieur), si elle a gagné le dernier match on lui attribue 3 points, si elle a gagné le match avant-dernier on lui attribue 2 points et si elle a gagné le match précédent avant-dernier on lui attribue 1 point, enfin on additionne les points obtenus ce qui donne une valeur numérique à la série. Série à domicile :
UPDATE QUINIELA_Object.MatchTrain SET QUINIELA_Object.MatchTrain.LocalStreak = 
    (SELECTSUM(CASEWHEN IsVictory = 1THEN4-%VID ELSE0END) FROM
        (SELECT TOP 3 SubMatch.IntDay, 
            CASEWHENResult = 1THEN1ELSE0ENDAS IsVictory 
        FROM QUINIELA_Object.MatchTrain AS SubMatch 
        WHEREUPPER(SubMatch.LocalTeam) = UPPER(QUINIELA_Object.MatchTrain.LocalTeam) 
            AND SubMatch.IntDay < QUINIELA_Object.MatchTrain.IntDay 
        ORDERBY SubMatch.IntDay DESC
        )
    )

Série à l'extérieur:

UPDATE QUINIELA_Object.MatchTrain SET QUINIELA_Object.MatchTrain.VisitorStreak = 
    (SELECTSUM(CASEWHEN IsVictory = 1THEN4-%VID ELSE0END) 
    FROM
        (SELECT TOP 3 SubMatch.IntDay, 
            CASEWHENResult = 2THEN1ELSE0ENDAS IsVictory 
        FROM QUINIELA_Object.MatchTrain AS SubMatch 
        WHEREUPPER(SubMatch.VisitorTeam) = UPPER(QUINIELA_Object.MatchTrain.VisitorTeam) 
            AND SubMatch.IntDay < QUINIELA_Object.MatchTrain.IntDay 
            ORDERBY SubMatch.IntDay DESC
        )
    )

Voyons quel est le résultat de cette série d'insertions et de mises à jour consultant le tableau QUINIELA_Object.MatchTrain :

Comme vous voyez, nous avons transformé les champs de texte en valeurs numériques... ou non ? Examinons la définition de la classe :

Class QUINIELA.Object.MatchTrain Extends (%Persistent, %JSON.Adaptor) [ DdlAllowed ]
{

/// Jour du matchProperty Day As%Date;/// Équipe localeProperty LocalTeam As%String;/// Équipe jouant à l'extérieurProperty VisitorTeam As%String/// Série localeProperty LocalStreak As%Integer;/// Série de victoires de l'équipe jouant à l'extérieurProperty VisitorStreak As%Integer/// ArbitreProperty Referee As%String;/// RésultatProperty Result &As%String/// DivisionProperty Division As%String;/// ItinéraireProperty Journey As%String;/// Nombre entier de joursProperty IntDay As%Integer;
}

Comme vous voyez, les références aux tableaux principaux sont toujours de type %String. Pourquoi ? Sur cette page vous trouverez l'explication de la documentation, mais en résumé, c'est parce que, bien qu'elles soient réellement numériques, elles ne correspondent pas à des valeurs quantifiables, mais à des identifiants.

Parfait, nous avons maintenant tout ce qu'il faut pour créer et former notre modèle prédictif.

Création et formation du modèle prédictif

Grâce à la fonctionnalité d'IntegratedML, cette étape est très simple pour nous, puisqu'il nous suffit d'exécuter deux commandes simples dans notre base de données. Examinons la transaction métier que nous avons créée à cette fin :

Class QUINIELA.BO.TrainBO Extends Ens.BusinessOperation
{

Parameter INVOCATION = "Queue";/// Description
Method CreateAndTrainModel(pRequest As QUINIELA.Message.TrainRequest, pResponse As QUINIELA.Message.TrainResponse) As%Status
{
        Set tSC = $$$OKset pResponse = ##class(QUINIELA.Message.TrainResponse).%New()
        set pResponse.Operation = pRequest.Operation
        set pResponse.Status = "In Process"set sql = "SELECT MODEL_NAME FROM INFORMATION_SCHEMA.ML_MODELS WHERE MODEL_NAME = 'QuinielaModel'"set statement = ##class(%SQL.Statement).%New()
        set status = statement.%Prepare(sql)
        $$$TRACE(status)
        if ($$$ISOK(status)) {
            set resultSet = statement.%Execute()
            $$$TRACE(resultSet.%SQLCODE)
            if (resultSet.%SQLCODE = 0) {
                while (resultSet.%Next() '= 0) {
                    if (resultSet.%GetData(1) '= "") {
                        set sqlDrop = "DROP MODEL QuinielaModel"set statementDrop = ##class(%SQL.Statement).%New()
                        set statusDrop = statementDrop.%Prepare(sqlDrop)
                        if ($$$ISOK(statusDrop)) {
                            set resultSetDrop = statementDrop.%Execute()
                            if (resultSetDrop.%SQLCODE = 0) {
                                set tSC = statusDrop                                                                
                            }
                        }
                    }
                }
            }            
        }
        $$$TRACE("Creating model")
        set sqlCreate = "CREATE MODEL QuinielaModel PREDICTING (Result) FROM QUINIELA_Object.MatchTrain"set statementCreate = ##class(%SQL.Statement).%New()
        set statusCreate = statementCreate.%Prepare(sqlCreate)
        if ($$$ISOK(statusCreate)) {
            $$$TRACE("Model created")
            set resultSetCreate = statementCreate.%Execute()
            if (resultSetCreate.%SQLCODE = 0) {
                set tSC = statusCreate                                
            }
        }
        else
        {
            set tSC = statusDrop
        }

        $$$TRACE("Training model")
        set sqlTrain = "TRAIN MODEL QuinielaModel"set statementTrain = ##class(%SQL.Statement).%New()
        set statusTrain = statementTrain.%Prepare(sqlTrain)
        if ($$$ISOK(statusTrain)) {
            set resultSetTrain = statementTrain.%Execute()
            if (resultSetTrain.%SQLCODE = 0) {
                // VALIDATION OF THE MODEL WITH THE PRE-LOADED MATCHESset sqlValidate = "VALIDATE MODEL QuinielaModel FROM QUINIELA_Object.MatchTrain"set statementValidate = ##class(%SQL.Statement).%New()
                set statusValidate = statementValidate.%Prepare(sqlValidate)
                set resultSetValidate = statementValidate.%Execute()
                set tSC = statusValidate                                    
            }
        }
        else {
            set tSC = statusTrain
        }
        
        set pResponse.Status = "Finished"Return tSC
}

XData MessageMap
{
"QUINIELA.Message.TrainRequest">
    CreateAndTrainModel
}

}

Analysons ce que fait notre BO :

  1. Création du modèle prédictif et définition de la valeur à prédire en indiquant la colonne de notre tableau d'entraînement correspondant.

    CREATEMODEL QuinielaModel PREDICTING (Result) FROM QUINIELA_Object.MatchTrain
  2. Formation de notre modèle nouvellement créé.

    TRAIN MODEL QuinielaModel
  3. Validation du modèle créé sur la base du tableau d'entraînement utilisé.

    VALIDATE MODEL QuinielaModel FROM QUINIELA_Object.MatchTrain

Avec ces trois étapes très simples, notre modèle est prêt à générer des prédictions. Examinons la qualité de notre modèle. Pour ce faire, exécutons la requête suivante :

SELECT * FROM INFORMATION_SCHEMA.ML_VALIDATION_METRICS

Avec cette requête, nous obtiendrons les mesures suivantes :

Eh bien, pas si mal, pour la victoire au niveau local, le taux de réussite est de 52%, pour la victoire au niveau des visiteurs de 41% et pour les matchs nuls de 37%, nous dépassons le taux de réussite de 33% dû au pur hasard !

<img alt="Boxing Memes on X: "Le compatriote de Golovkin réagit à sa victoire... #GolovkinGeale http://t.co/MDW6F5eJlz" / X" src="https://pbs.twimg.com/media/BthSZmaIcAAsIXU.jpg">

0
0 91
Article Sylvain Guilbaud · Sept 6, 2023 3m read

pour démonter/monter une base de données, utilisez les méthodes Dismount() et Mount() dans la classe SYS.Database disponible dans l'espace de noms %SYS.
NB : l'ID de la base de données est le nom du chemin absolu de son répertoire.

Vous trouverez quelques exemples de démontage/montage et de méthodes contrôlant si une base de données est montée (Mounted=1) ou non (Mounted=0), ou affichant rapidement tous les attributs d'une base de données (via zwrite)

0
0 120
Article Iryna Mykhailova · Juil 19, 2022 9m read

Pour parler des différentes bases de données et des différents modèles de données qui existent, on doit premièrement comprendre ce qui est une base de données et comment les utiliser.

Une base de données est une collection organisée de données stockées et accessibles par voie électronique. Elle permet de stocker et de retrouver des données structurées, semi-structurées ou des données brutes souvent en rapport avec un thème ou une activité.

Au cœur de chaque base de données se trouve au moins un modèle utilisé pour décrire ses données. Et selon le modèle sur lequel elle est basée, elle peut avoir des caractéristiques un peu différentes et stocker différents types de données.

Pour inscrire, retrouver, modifier, trier, transformer ou imprimer les informations de la base de données on utilise un logiciel qui s’appelle système de gestion de base de données (SGBD, en anglais DBMS pour Database management system).

La taille, les capacités et les performances des bases de données et de leurs SGBD respectifs ont augmenté de plusieurs ordres de grandeur. Ces augmentations de performances ont été rendues possibles par les progrès technologiques dans différents domaines, tels que les domaines des processeurs, de la mémoire informatique, du stockage informatique et des réseaux informatiques. Le développement ultérieur de la technologie des bases de données peut être divisé en quatre générations basées sur le modèle ou la structure des données : navigation, relationnel, objet et post-relationnel.

4
0 642
Article Sylvain Guilbaud · Août 25, 2023 1m read

Rubrique FAQ InterSystems

Vous pouvez voir l'espace disponible libre pour la base de données à l'aide du bouton radio « Vue de l'espace libre » dans le portail de gestion : Exploitation du système > Bases de données.

Et il peut être obtenu par programmation par la requête FreeSpace de la classe système SYS.Database.

0
0 70
Article Irène Mykhailova · Juil 21, 2023 2m read

Titre:  Le projet PostgreSQL étudie un changement majeur qui pourrait sacrifier des fonctionnalités importantes

Résumé:  L'équipe de développement de PostgreSQL réévalue actuellement son modèle basé sur les processus et explore la possibilité de passer à un modèle basé sur les threads. Le modèle actuel, basé sur les processus, offre une isolation, une robustesse et une sécurité importantes, mais présente des limitations en termes de performances, de consommation de ressources et de complexité du développement.

0
0 53
Article Lucas Enard · Sept 30, 2022 9m read

Dans ce GitHub nous recueillons des informations à partir d'un csv, nous utilisons une DataTransformation pour les transformer en un objet FHIR, puis nous sauvegardons ces informations sur un serveur FHIR, et tout cela en utilisant uniquement Python.

The objective is to show how easy it is to manipulate data into the output we want, here a FHIR Bundle, in the IRIS full Python framework.

1. Fhir-orga-dt

2. Préalables

Assurez-vous que git et Docker desktop sont installé.

Si vous travaillez à l'intérieur du conteneur, comme il est montré dans 3.3., vous n'avez pas besoin d'installer fhirpy et fhir.resources.

Si vous n'êtes pas dans le conteneur, vous pouvez utiliser pip pour installer fhirpy et fhir.resources.
Vérifiez fhirpy et fhir.resources pour plus d'information.

3. Installation

3.1. Installation pour le développement

Clone/git tire le repo dans n'importe quel répertoire local, par exemple comme indiqué ci-dessous :

git clone https://github.com/LucasEnard/fhir-client-python.git

Ouvrez le terminal dans ce répertoire et lancez :

docker build .

3.2. Portail de gestion et VSCode

Ce référentiel est prêt pour VS Code.

Ouvrez le dossier fhir-client-python cloné localement dans VS Code.

Si vous y êtes invité (coin inférieur droit), installez les extensions recommandées.

3.3. Avoir le dossier ouvert à l'intérieur du conteneur

Vous pouvez être à l'intérieur du conteneur avant de coder si vous le souhaitez.
Pour cela, il faut que docker soit activé avant d'ouvrir VSCode.
Ensuite, dans VSCode, lorsque vous y êtes invité ( coin inférieur droit ), rouvrez le dossier à l'intérieur du conteneur afin de pouvoir utiliser les composants python qu'il contient.
La première fois que vous effectuez cette opération, cela peut prendre plusieurs minutes, le temps que le conteneur soit préparé.

Si vous n'avez pas cette option, vous pouvez cliquer dans le coin inférieur gauche et cliquer sur Ouvrir à nouveau dans le conteneur puis sélectionner De Dockerfile

Plus d'informations ici

Architecture


En ouvrant le dossier à distance, vous permettez à VS Code et à tous les terminaux que vous ouvrez dans ce dossier d'utiliser les composants python dans le conteneur.

4. Serveur FHIR

Pour compléter cette présentation, nous allons utiliser un serveur fhir.
Ce serveur fhir était déjà intégré lorsque vous avez cloné et construit le conteneur.

L'url est localhost:52773/fhir/r4

5. Présentation pas à pas

Présentation complète de la réalisation d'IRIS en Python.

5.1. Messages and objects

Les objets et les messages contiennent les informations entre nos services, nos processus et nos opérations.

Dans le fichier obj.py nous devons créer une classe de données qui correspond au csv, ceci sera utilisé pour garder l'information avant de faire la DataTransformation.
Dans notre exemple, le csv organisation.csv ressemble à ceci,

active;name;city;country;system;value
true;Name1;city1;country1;phone;050678504
false;Name2;city2;country2;phone;123456789

Par conséquent, l'objet ressemblera à ceci,

@dataclass
# > Cette classe représente une organisation simple
class BaseOrganization:
    active:bool = None
    name:str = None
    city:str = None
    country:str = None
    system:str = None
    value:str = None

Dans le fichier msg.py, nous aurons deux types de requêtes, la première contenant les informations d'une organisation avant la DataTransformation et la seconde contenant les informations de l'organisation après la DataTransformation.

5.2. Service aux entreprises

Dans le fichier bs.py, nous avons le code qui nous permet de lire le csv et pour chaque ligne du csv (donc pour chaque organisation), le mapper dans un objet que nous avons créé plus tôt. Ensuite, pour chacune de ces lignes (organisation), nous créons une requête et l'envoyons à notre processus pour effectuer la DataTransformation.

# Nous ouvrons le fichier
with open(self.path + self.filename,encoding="utf-8") as csv:
    # Nous le lisons et le mappons en utilisant l'objet BaseOrganization précédemment utilisé
    reader = DataclassReader(csv, self.fhir_type ,delimiter=";")
    # Pour chacune de ces organisations, nous pouvons créer une requête et l'envoyer au processus
    for row in reader:
        msg = OrgaRequest()
        msg.organization = row
        self.send_request_sync('Python.ProcessCSV',msg)

5.3. Service aux entreprises

Dans le fichier bp.py, nous avons la DataTransformation, qui convertit un simple objet python contenant peu d'informations en un objet FHIR R4

Voici les étapes pour effectuer une DataTransformation en utilisant du python embarqué sur notre organisation simple,

# Création de l'objet Organisation
organisation = Organisation()

# Mappage des informations de la demande à l'objet Organisation
organization.name = base_orga.name

organization.active = base_orga.active

## Création de l'objet Adresse et mappage des informations
## de la demande à l'objet Addresse
adresse = Addresse()
adress.country = base_orga.country
adress.city = base_orga.city

### Configuration de l'adresse de notre organisation à celle que nous avons créée
organization.address = [adress]

## Création de l'objet ContactPoint et mise en correspondance
## des informations de la demande avec l'objet ContactPoint
telecom = ContactPoint()
telecom.value = base_orga.value
telecom.system = base_orga.system

### Configuration de la télécommunication de notre organisation à celle que nous avons créée
organization.telecom = [telecom]

# Maintenant, notre DT est achevé, nous avons une organisation d'objets qui est
# un objet FHIR R4 et qui contient toutes nos informations csv.

Après cela, notre mappage est terminé et notre DT fonctionne.
Maintenant, nous pouvons envoyer cette ressource FHIR R4 nouvellement créée à notre FhirClient qui est notre opération.

5.4. Opération d'un entreprise

Dans le fichier bo.py nous avons le FhirClient, ce client crée une connexion à un serveur fhir qui contiendra les informations recueillies par le csv.

Dans cet exemple, nous utilisons un serveur fhir local qui n'a pas besoin d'une clé api pour se connecter.
Pour s'y connecter, nous devons utiliser la fonction on_init,

if not hasattr(self,'url'):
    self.url = 'localhost:52773/fhir/r4'

self.client = SyncFHIRClient(url=self.url)

Maintenant, lorsque nous recevons un message/demande, nous pouvons, en trouvant le type de ressource de la ressource que nous envoyons avec notre demande au client, créer un objet lisible par le client, puis le sauvegarder sur le serveur fhir.

# Obtenez le type de ressource de la demande ( ici "Organisation" )
resource_type = request.resource["resource_type"]

# Créer une ressource de ce type en utilisant les données de la demande
resource = construct_fhir_element(resource_type, request.resource)

# Sauvegarder la ressource sur le serveur FHIR en utilisant le client
self.client.resource(resource_type,**json.loads(resource.json())).save()

Il est à noter que le client fhir fonctionne avec n'importe quelle ressource de FHIR R4 et pour utiliser et modifier notre exemple, nous devons seulement changer la DataTransformation et l'objet qui contient les informations csv.

5.5. Conclusion de la présentation

Si vous avez suivi ce parcours, vous savez maintenant exactement comment lire un csv d'une représentation d'une ressource FHIR R4, utiliser une DataTransformation pour le transformer en un véritable objet FHIR R4 et le sauvegarder sur un serveur.

6. Création d'une nouvelle DataTransformation

Ce référentiel est prêt à être codé en VSCode avec les plugins InterSystems. Ouvrez /src/python pour commencer à coder ou utiliser l'autocomplétion.

Étapes pour créer une nouvelle transformation
Pour ajouter une nouvelle transformation et l'utiliser, la seule chose que vous devez faire est d'ajouter votre csv nommé Patient.csv (par exemple) dans le dossier src/python/csv.
Ensuite, créez un objet dans src/python/obj.py appelé BasePatient qui met en correspondance votre csv.
Maintenant, créez une requête dans src/python/msg.py appelée PatientRequest qui a une variable resource de type BasePatient.
L'étape finale est la DataTransformation, pour cela, allez dans src/python/bp.py et ajoutez votre DT. Ajoutez d'abord if isinstance(request, PatientRequest): puis mappez votre ressource de requête à une fhir.resource Patient.
Maintenant si vous allez dans le portail de gestion et changez le paramètre du ServiceCSV pour ajouter filename=Patient.csv vous pouvez juste démarrer la réalisation et voir votre transformation se dérouler et votre client envoyer les informations au serveur.

Étapes détaillées pour créer une nouvelle transformation
Si vous n'êtes pas sûr de ce qu'il faut faire ou de la manière de le faire, voici une création étape par étape d'une nouvelle transformation :

Créez le fichier Patient.csv and le dossier src/python/csv pour les remplir avec le suivant:

family;given;system;value
FamilyName1;GivenName1;phone;555789675
FamilyName2;GivenName2;phone;023020202

Notre CSV contient un nom de famille, un prénom et un numéro de téléphone pour deux patients.


Dans src/python/obj.py écrivez :

@dataclass
class BasePatient:
    family:str = None
    given:str = None
    system:str = None
    value:str = None


Dans src/python/msg.py écrivez :

from obj import BasePatient
@dataclass
class PatientRequest(Message):
    resource:BasePatient = None


Dans src/python/bp.py écrivez :

from msg import PatientRequest
from fhir.resources.patient import Patient
from fhir.resources.humanname import HumanName

Dans src/python/bp.py dans la fonction on_request écrivez :

if isinstance(request,PatientRequest):
    base_patient = request.resource

    patient = Patient()

    name = HumanName()
    name.family = base_patient.family
    name.given = [base_patient.given]
    patient.name = [name]

    telecom = ContactPoint()
    telecom.value = base_patient.value
    telecom.system = base_patient.system
    patient.telecom = [telecom]

    msg = FhirRequest()
    msg.resource = patient

    self.send_request_sync("Python.FhirClient", msg)


Maintenant si vous allez dans le portail de gestion et changez le paramètre du ServiceCSV pour ajouter filename=Patient.csv vous pouvez juste arrêter et redémarrer la production et voir votre transformation se dérouler et votre client envoyer les informations au serveur.

Settings

7. Ce qu'il y a dans le référentiel

7.1. Dockerfile

Le dockerfile le plus simple pour démarrer un conteneur Python.
Utilisez docker build . pour construire et rouvrir votre fichier dans le conteneur pour travailler à l'intérieur de celui-ci.

7.2. .vscode/settings.json

Fichier de paramètres.

7.3. .vscode/launch.json

Fichier de configuration si vous voulez déboguer.

0
0 126
Article Lorenzo Scalese · Juin 1, 2022 9m read

Un système de stockage global d'aspect plus industriel

Dans le premier article de cette série, nous avons étudié le modèle entité-attribut-valeur (EAV) dans les bases de données relationnelles, et nous avons examiné les avantages et les inconvénients du stockage de ces entités, attributs et valeurs dans des tables. Nous avons appris que, malgré les avantages de cette approche en termes de flexibilité, elle présente de réels inconvénients, notamment une inadéquation fondamentale entre la structure logique des données et leur stockage physique, qui entraîne diverses difficultés.

Pour résoudre ces problèmes, nous avons décidé de voir si l'utilisation de globales - qui sont optimisées pour le stockage d'informations hiérarchiques - serait efficace pour les tâches que l'approche EAV traite habituellement.

Dans la Partie 1, nous avons créé un catalogue pour une boutique en ligne, d'abord en utilisant des tables, puis en utilisant une seule globale. Maintenant, essayons d'implémenter la même structure pour quelques globales.

Dans la première globale, ^catalog, nous allons stocker la structure du répertoire. Dans la deuxième globale, ^good, nous allons stocker les marchandises. Et dans la globale ^index, nous allons stocker les index. Puisque nos propriétés sont liées à un catalogue hiérarchique, nous ne créerons pas de globale séparée pour elles.

Avec cette approche, pour chaque entité (à l'exception des propriétés), nous avons une globale séparée, ce qui est bon du point de vue de la logique. Voici la structure du catalogue global :

 
Set ^сatalog(root_id, "Properties", "capacity", "name") = "Capacity, GB"
Set ^сatalog(root_id, "Properties", "capacity", "sort") = 1

Set ^сatalog(root_id, sub1_id, "Properties", "endurance", "name") = "Endurance, TBW"
Set ^сatalog(root_id, sub1_id, "Properties", "endurance", "sort") = 2

Set ^сatalog(root_id, sub1_id, "goods", id_good1) = 1
Set ^сatalog(root_id, sub1_id, "goods", id_good2) = 1

Set ^сatalog(root_id, sub2_id, "Properties", "avg_seek_time", "name") = "Rotate speed, ms"
Set ^сatalog(root_id, sub2_id, "Properties", "avg_seek_time", "sort") = 3

Set ^сatalog(root_id, sub2_id, "goods", id_good3) = 1
Set ^сatalog(root_id, sub2_id, "goods", id_good4) = 1

 

Une globale avec des marchandises ressemblera à quelque chose comme ceci :

Set ^good(id_good, property1) = value1
Set ^good(id_good, property2) = value2
Set ^good(id_good, property3) = value3
Set ^good(id_good, "catalog") = catalog_id

 

Bien sûr, nous avons besoin d'index afin que pour toute section du catalogue contenant des marchandises, nous puissions trier par les propriétés dont nous avons besoin. Une globale d'index aura une structure semblable à quelque chose comme ceci :

Configurer ^index(id_catalog, property1, id_good) = 1
; Pour obtenir rapidement le chemin complet du sous-catalogue concret
Configurer ^index("path", id_catalog) = "^catalog(root_id, sub1_id)"

 

Ainsi, dans n'importe quelle section du catalogue, on peut obtenir une liste triée. Une globale d'index est facultative. Il n'est utile que si le nombre de produits dans cette section du catalogue est important.

Code ObjectScript pour travailler avec des données de démonstration Demo Data

Maintenant, nous allons utiliser ObjectScript pour travailler avec nos données. Pour commencer, nous allons obtenir les propriétés d'une marchandise spécifique. Nous avons l'ID d'une marchandise particulière et nous devons afficher ses propriétés dans l'ordre donné par la valeur de tri. Voici le code pour cela :

get_sorted_properties(path, boolTable)
{
  ; mémoriser toutes les propriétés dans la globale temporaire
  While $QLENGTH(@path) > 0 {
    if ($DATA(@path("Properties"))) {
      set ln=""
      for {
	    Set ln = $order(@path("Properties", ln))
	    Quit: ln = ""

        IF boolTable & @path("Properties", ln, "table_view") = 1 {
  	      Set ^tmp(@path("Properties", ln, "sort"), ln) = @path("Properties", ln, "name")
	    }
	  ELSE {
  	    Set ^tmp(@path("Properties", ln, "sort"), ln) = @path("Properties", ln, "name")
	  }
    }
  }
}

print_sorted_properties_of_good(id_good)
{
  Set id_catalog = ^good(id_good, "catalog")
  Set path = ^index("path", id_catalog)

  Do get_sorted_properties(path, 0)

  set ln =""
  for {
   Set ln = $order(^tmp(ln))
   Quit: ln = ""
   Set fn = ""
   for {
 	Set fn = $order(^tmp(ln, fn))
 	Quit: fn = ""
 	Write ^tmp(ln, fn), " ", ^good(id_good, fn),!
   }
  }
}

 

Ensuite, nous voulons récupérer les produits de la section catalogue sous la forme de la table, basé sur id_catalog :

print_goods_table_of_catalog(id_catalog)
{
  Set path = ^index("path", id_catalog)
  Do get_sorted_properties(path, 1)

  set id=""
  for {
    Set id = $order(@path("goods"), id)
    Quit: id = ""

    Write id," ", ^good(id, "price"), " "

    set ln =""
    for {
      Set ln = $order(^tmp(ln))
      Quit: ln = ""
      Set fn = ""
      for {
 	    Set fn = $order(^tmp(ln, fn))
 	    Quit: fn = ""
 	    Write ^tmp(ln, fn), " ", ^good(id, fn)
      }
      Write !
    }
  }
}

 

Lisibilité : EAV SQL contre les globales

Comparons maintenant l'utilisation d'EAV et de SQL par rapport à l'utilisation de globales. En ce qui concerne la clarté du code, il est évident qu'il s'agit d'un paramètre subjectif. Mais regardons, par exemple, la création d'un nouveau produit.

Nous allons commencer par l'approche EAV, en utilisant SQL. Tout d'abord, nous devons obtenir une liste des propriétés de l'objet. Il s'agit d'une tâche distincte qui prend beaucoup de temps. Supposons que nous connaissions déjà les IDs de ces trois propriétés : capacité, poids, et endurance.

START TRANSACTION
INSERT INTO good (name, price, item_count, catalog_id) VALUES ('F320 3.2TB AIC SSD', 700, 10, 15);

SET @last_id = LAST_INSERT_ID ();

INSERT INTO NumberValues ​​Values​​(@last_id, @id_capacity, 3200);
INSERT INTO NumberValues ​​Values​​(@last_id, @id_weight, 0.4);
INSERT INTO NumberValues ​​Values​​(@last_id, @id_endurance, 29000);
COMMIT

 

Dans cet exemple, nous n'avons que trois propriétés, et l'exemple ne semble donc pas si inquiétant. Dans le cas général, nous aurions toujours quelques insertions dans la table de texte à l'intérieur de la transaction :

INSERT INTO TextValues ​​Values​​(@last_id, @ id_text_prop1, 'Text value of property 1');
INSERT INTO TextValues ​​Values​​(@last_id, @ id_text_prop2, 'Text value of property 2');
...
INSERT INTO TextValues Values (@last_id, @id_text_propN, 'Text value of property N');

 

Bien sûr, nous pourrions simplifier un peu la version SQL si nous utilisions la notation textuelle à la place des propriétés ID, par exemple "capacité" au lieu d'un nombre. Mais dans le monde SQL, ce n'est pas acceptable. Il est plutôt d'usage d'utiliser un ID numérique pour énumérer les instances d'entités. Cela permet d'obtenir des index plus rapides (il faut indexer moins d'octets), il est plus facile de suivre l'unicité et il est plus facile de créer automatiquement un nouvel ID. Dans ce cas, le fragment d'insertion aurait l'apparence suivante :

INSERT INTO NumberValues ​​Values​​(@last_id, 'capacity', 3200);
INSERT INTO NumberValues ​​Values​​(@last_id, 'weight', 0.4);
INSERT INTO NumberValues ​​Values​​(@last_id, 'endurance', 29000);

 

Voici le même exemple en utilisant des globales :

TSTART
Set ^good(id, "name") = "F320 3.2TB AIC SSD"
Set ^("price") = 700, ^("item_count") = 10, ^("reserved_count") = 0, ^("catalog") = id_catalog
Set ^("capacity") = 3200, ^("weight") = 0.4, ^("endurance") = 29000
TCOMMIT

 

Supprimons maintenant une marchandise en utilisant l'approche EAV :

START TRANSACTION
DELETE FROM good WHERE id = @ good_id;
DELETE FROM NumberValues ​​WHERE good_id = @ good_id;
DELETE FROM TextValues ​​WHERE good_id = @ good_id;
COMMIT

 

Et ensuite, faisons la même chose avec les globales :

Kill ^good(id_good)

Nous pouvons également comparer les deux approches en termes de longueur de code. Comme vous pouvez le constater dans les exemples précédents, lorsque vous utilisez des globales, le code est plus court. C'est une bonne chose. Plus le code est court, moins il y a d'erreurs et plus il est facile à comprendre et à gérer.

En général, un code plus court est aussi plus rapide. Et, dans ce cas, c'est certainement vrai, puisque les globales constituent une structure de données de niveau inférieur aux tables relationnelles.

Mise à l'échelle des données avec EAV et Globales

Ensuite, examinons la mise à l'échelle horizontale. Avec l'approche EAV, nous devons au moins distribuer les trois plus grandes tables sur les serveurs : Good, NumberValues, et TextValues. Les tables contenant des entités et des attributs peuvent simplement être entièrement copiés sur tous les serveurs, car ils contiennent peu d'informations.

Dans chaque serveur, avec une mise à l'échelle horizontale, des produits différents seraient stockés dans les tables Good, NumberValues et TextValues. Nous devrions allouer certains blocs d'identification pour les produits sur chaque serveur afin d'éviter la duplication des identifiants pour des produits différents.

Pour une mise à l'échelle horizontale avec des globales, il faudrait configurer des plages d'ID dans la globale et attribuer une plage de globale à chaque serveur.

La complexité est à peu près la même pour EAV et pour les globales, sauf que pour l'approche EAV, nous devrions configurer des plages d'ID pour trois tables. Avec les globales, nous configurons les ID pour une seule globale. C'est-à-dire qu'il est plus facile d'organiser la mise à l'échelle horizontale pour les globales.

Perte de données avec EAV et avec Globales

Enfin, considérons le risque de perte de données dû à des fichiers de base de données corrompus. Où est-il plus facile de sauvegarder toutes les données : dans cinq tables ou dans trois globales ( y compris une globale d'index ) ?

Je pense que c'est plus facile dans trois globales. Avec l'approche EAV, les données des marchandises différentes sont mélangées dans des tables, alors que pour les globales, les informations sont stockées de manière plus holistique. Les branches sous-jacentes sont stockées et triées séquentiellement. Par conséquent, la corruption d'une partie de la globale est moins susceptible d'entraîner des dommages que la corruption de l'une des tables dans l'approche EAV, où les données sont stockées comme des pâtes entremêlées.

Un autre casse-tête dans la récupération des données est l'affichage des informations. Avec l'approche EAV, les informations sont réparties entre plusieures tables et des scripts spéciaux sont nécessaires pour les assembler en un seul ensemble. Dans le cas des globales, vous pouvez simplement utiliser la commande ZWRITE pour afficher toutes les valeurs et les branches sous-jacentes du nœud.

Les Globales d'InterSystems IRIS : Une meilleure approche ?

L'approche EAV est apparue comme une astuce pour stocker des données hiérarchiques. Les tables n'ont pas été conçus à l'origine pour stocker des données imbriquées. L'approche EAV de facto est l'émulation des globales dans les tables. Étant donné que les tables représentent une structure de stockage de données de plus haut niveau et plus lente que les globales, l'approche EAV échoue par rapport aux globales.

À mon avis, pour les structures de données hiérarchiques, les globales sont plus pratiques et plus compréhensibles en termes de programmation, tout en étant plus rapides.

Si vous avez prévu une approche EAV pour votre projet, je vous suggère d'envisager d'utiliser les globales d'InterSystems IRIS pour stocker les données hiérarchiques.

0
0 407
Article Irène Mykhailova · Mai 31, 2022 1m read

La cause de cette erreur est que la ressource locked est déjà locked par un autre processus dans l'application et que le lock n'est pas libéré pour une raison quelconque.

S'il n'y a aucun signe que d'autres processus avec le lock, il est possible que la table de locks manque d'espace libre. Dans ce cas, le message LOCK TABLE FULL est envoyé au Message Log

Si vous effectuez un traitement transactionnel, il est possible que le report du lock ait un effet.
Veuillez vous référer aux documents suivants pour la transaction et le report de lock.

Using LOCK in Transactions【IRIS】

Using LOCK in Transactions

De plus, s'il existe un grand nombre d'enregistrements mis à jour par des instructions SQL dans la même table au cours d'une transaction, le seuil de lock (la valeur par défaut est 1000) est atteint et une escalade de lock se produit, entraînant un état de lock de table.

Comme vous pouvez le voir, il existe plusieurs causes possibles pour l'erreur de délai d'attente de lock. Tout d'abord, vérifiez l'état actuel du lock dans le menu de locks de Management Portal.

【Version 2011.1 ou ultérieure】
Management Portal : [System Operations]> [Lock]

【Version 2010.2 ou antérieure】
Management Portal :[Operations]> [Lock]

0
0 154