0 Abonnés · 130 Publications

SQL est un langage standard pour stocker, manipuler et récupérer des données dans des bases de données relationnelles.

Article Sylvain Guilbaud · Nov 6, 2025 5m read

Introduction

Dans mon article précédent, j'ai présenté le module IRIStool, qui intègre de manière transparente la bibliothèque pandas pour Python à la base de données IRIS. Je vais maintenant vous expliquer comment utiliser IRIStool pour exploiter InterSystems IRIS comme base pour une recherche sémantique intelligente dans les données de soins de santé au format FHIR.

Cet article décrit ce que j'ai fait pour créer une base de données pour mon autre projet, FHIR Data Explorer. Les deux projets sont candidats au concours InterSystems actuel, alors n'hésitez pas à voter pour eux si vous les trouvez utiles.

Ils sont disponibles sur Open Exchange:

Dans cet article, nous aborderons les sujets suivants:

  • Connexion à la base de données InterSystems IRIS via Python
  • Création d'un schéma de base de données compatible FHIR
  • Importation de données FHIR au moyen d'intégrations vectorielles pour la recherche sémantique

Conditions préalables

Installez IRIStool à partir de la page Github IRIStool et Data Manager.

1. Configuration de la connexion IRIS

Commencez par configurer votre connexion à l'aide des variables d'environnement dans un fichier .env:

IRIS_HOST=localhost
IRIS_PORT=9092
IRIS_NAMESPACE=USER
IRIS_USER=_SYSTEM
IRIS_PASSWORD=SYS

Connectez-vous à IRIS à l'aide du gestionnaire de contexte du module IRIStool:

from utils.iristool import IRIStool
import os
from dotenv import load_dotenv

load_dotenv()

with IRIStool( host=os.getenv('IRIS_HOST'), port=os.getenv('IRIS_PORT'), namespace=os.getenv('IRIS_NAMESPACE'), username=os.getenv('IRIS_USER'), password=os.getenv('IRIS_PASSWORD') ) as iris: # IRIStool manages the connection automatically pass

2. Création du schéma FHIR

Commencez par créer une table pour stocker les données FHIR, puis, tout en extrayant les données des paquets FHIR, créez des tables avec des capacités de recherche vectorielle pour chacune des ressources FHIR extraites (comme Patient, Osservability, etc.). 

Le module IRIStool simplifie la création de tables et d'index!

Table de référentiel FHIR

# Créer une table de référentiel principal pour les paquets FHIR brutsifnot iris.table_exists("FHIRrepository", "SQLUser"):
    iris.create_table(
        table_name="FHIRrepository",
        columns={
            "patient_id": "VARCHAR(200)",
            "fhir_bundle": "CLOB"
        }
    )
    iris.quick_create_index(
        table_name="FHIRrepository",
        column_name="patient_id"
    )

Table de patients avec support vectoriel

# Création d'une table de patients au moyen de la colonne vectorielle pour la recherche sémantiqueifnot iris.table_exists("Patient", "SQLUser"):
    iris.create_table(
        table_name="Patient",
        columns={
            "patient_row_id": "INT AUTO_INCREMENT PRIMARY KEY",
            "patient_id": "VARCHAR(200)",
            "description": "CLOB",
            "description_vector": "VECTOR(FLOAT, 384)",
            "full_name": "VARCHAR(200)",
            "gender": "VARCHAR(30)",
            "age": "INTEGER",
            "birthdate": "TIMESTAMP"
        }
    )
<span class="hljs-comment"># Création d'index standards</span>
iris.quick_create_index(table_name=<span class="hljs-string">"Patient"</span>, column_name=<span class="hljs-string">"patient_id"</span>)
iris.quick_create_index(table_name=<span class="hljs-string">"Patient"</span>, column_name=<span class="hljs-string">"age"</span>)

<span class="hljs-comment"># Création d'un index vectoriel HNSW pour la recherche par similarité</span>
iris.create_hnsw_index(
    index_name=<span class="hljs-string">"patient_vector_idx"</span>,
    table_name=<span class="hljs-string">"Patient"</span>,
    column_name=<span class="hljs-string">"description_vector"</span>,
    distance=<span class="hljs-string">"Cosine"</span>
)</code></pre>

3. Importation de données FHIR à l'aide de vecteurs

Générez facilement des intégrations vectorielles à partir des descriptions des patients FHIR et insérez-les dans IRIS:

from sentence_transformers import SentenceTransformer

# Initialisation du modèle de convertisseur model = SentenceTransformer('sentence-transformers/all-MiniLM-L6-v2')

# Exemple : Traitement des données du patient patient_description = "45-year-old male with hypertension and type 2 diabetes" patient_id = "patient-123"# Création d'un encodage vectoriel vector = model.encode(patient_description, normalize_embeddings=True).tolist()

# Insertion des données du patient à l'aide du vecteur iris.insert( table_name="Patient", patient_id=patient_id, description=patient_description, description_vector=str(vector), full_name="John Doe", gender="male", age=45, birthdate="1979-03-15" )

4. Recherche sémantique

Lorsque vos données sont téléchargées, vous pouvez effectuer des recherches par similarité:

# Requête de recherche
search_text = "patients with diabetes"
query_vector = model.encode(search_text, normalize_embeddings=True).tolist()

# définition de requête SQL query = f""" SELECT TOP 5 patient_id, full_name, description, VECTOR_COSINE(description_vector, TO_VECTOR(?)) as similarity FROM Patient ORDER BY similarity DESC """# définition des paramètres de requête parameters = [str(query_vector)]

# Recherche de patients similaires à l'aide de la recherche vectorielle results = iris.query(query, parameters)

# Impression des données du DataFrameifnot results.empty: print(f"{results['full_name']}: {results['similarity']:.3f}")

Conclusion

  • Le module IRIStool simplifie l'intégration d'IRIS avec des méthodes Python intuitives pour la création de tables et d'index
  • IRIS prend en charge le stockage hybride SQL + vecteur de manière native, ce qui permet de réaliser les deux requêtes traditionnelles et la recherche sémantique
  • Les intégrations vectorielles permettent une recherche intelligente dans les données de soins de santé FHIR à l'aide du langage naturel
  • Les index HNSW fournissent une recherche par similarité efficace à grande échelle

Cette approche prouve qu'InterSystems IRIS peut servir de base solide pour créer des applications de santé intelligentes avec des capacités de recherche sémantique sur les données FHIR.

0
0 8
InterSystems officiel Adeline Icard · Oct 24, 2025

Les versions de maintenance 2025.1.2 et 2024.1.5 de la plateforme de données InterSystems IRIS, d'InterSystems IRIS for Health et d'HealthShare Health Connect sont désormais disponibles en disponibilité générale (GA). Ces versions incluent les correctifs pour plusieurs alertes et avis publiés récemment, notamment :

0
0 15
Article Corentin Blondeau · Oct 13, 2025 4m read

Bonjour

Je vous soumets cet article en tant qu’état de l’art enrichi.
L’objectif est de réunir les différentes façons d’importer et d’exporter des CSV en un seul endroit.
Cet article est basé sur InterSystems 2024.1 .
N'hésitez pas à commenter pour rajouter des précisions.
Si vous voulez une deuxième partie sur l'export de CSV, faites le moi savoir.

Contexte

3
2 63
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 Iryna Mykhailova · Oct 14, 2025 9m read

Dans mon article précédent, « Utilisation de LIKE avec des variables et des modèles dans SQL », nous avons exploré le comportement du prédicat LIKE dans différents scénarios, de l'Embedded SQL au Dynamic SQL, et l'impact sur les performances lorsque des caractères génériques et des variables entrent en jeu. Cet article visait à se familiariser avec l'écriture d'une requête LIKE fonctionnelle. Mais écrire du SQL efficace n'est que le point de départ. Pour créer des applications fiables, évolutives et sécurisées, vous devez comprendre les bonnes pratiques qui sous-tendent tout SQL, y compris les requêtes utilisant LIKE.

Cet article franchit une nouvelle étape. Nous aborderons quelques points clés pour renforcer votre code SQL, éviter les pièges courants et garantir l'exécution correcte, efficace et sécurisée de vos instructions SELECT. J'utiliserai les instructions SELECT avec le prédicat LIKE comme exemple, montrant comment ces principes généraux affectent directement vos requêtes et leurs résultats.

0
0 21
Article Lorenzo Scalese · Oct 6, 2025 5m read

Commençons par une question simple et motivante : au cours des 14 derniers jours, quelles sont les erreurs les plus courantes dans le Journal des erreurs d'application?

Répondre à cette question via le portail de gestion ou le terminal est un processus manuel fastidieux. Nous devrions pouvoir simplement utiliser SQL. Heureusement, quelques requêtes de classe sont disponibles  pour vous aider dans la classe SYS.ApplicationError de l'espace de noms %SYS. Vous pouvez répondre à cette question pour une seule date à l'aide d'une commande telle que:

select"Error message",count(*)
from SYS.ApplicationError_ErrorList('CCR','12/16/2024')
groupby"Error message"orderby2desc

Malheureusement, la structure des requêtes de classe est soumise aux mêmes contraintes structurelles générales que les pages du portail de gestion ; la requête ErrorList nécessite un espace de noms et une date. Il existe sûrement une meilleure approche que de faire 14 appels conjoints à cette requête de classe pour différentes dates, n'est-ce pas ? D'une certaine manière, c'est un véritable problème. S'il existe une bonne façon de procéder avec du SQL classique et que je l'ai simplement manquée, merci de me le faire savoir!

Logiquement, il convient de rédiger notre propre requête de classe personnalisée. Cela implique d'ajouter un membre de classe Query (par exemple <QueryName>) et d'implémenter des méthodes nommées <QueryName>Execute, <QueryName>Fetch et <QueryName>Close. De manière générale, la méthode Execute configure le contexte de la requête de classe et effectue toutes les tâches initiales, en conservant l'état dans qHandle. La méthode Fetch récupère une seule ligne et indique si toutes les lignes ont été trouvées ou non. Enfin, la méthode Close effectue le nettoyage final. Par exemple, si l'implémentation des méthodes Execute/Fetch utilise une variable globale privée au processus, la méthode Close peut la supprimer.

N'oubliez pas d'ajouter un indicateur [ SqlProc ] magique au membre Query afin qu'il puisse être appelé en tant que TVF (fonction table) à partir d'autres requêtes SQL!

Ci-dessous, vous trouverez un exemple complet fonctionnel:

/// Requêtes utilitaires pour aider à accéder au journal des erreurs de l'application à partir de SQLClass AppS.Util.ApplicationErrorLog
{

/// Renvoi de toutes les erreurs d'application (toutes dates confondues) à partir du journal des erreurs d'application Query All() As%Query(ROWSPEC = "Date:%Date,ErrorNumber:%Integer,ErrorMessage:%String,Username:%String") [ SqlProc ] { }

/// Récupèration d'une liste de dates comportant des erreurs et la stocke dans qHandleClassMethod AllExecute(ByRef qHandle As%Binary) As%Status { Set ns = $NamespaceNew$NamespaceSet$Namespace = "%SYS"Set stmt = ##class(%SQL.Statement).%New() Set stmt.%SelectMode = 0Set result = ##class(%SQL.Statement).%ExecDirect(stmt,"select %DLIST(""Date"") ""Dates"" from SYS.ApplicationError_DateList(?)",ns) $$$ThrowSQLIfError(result.%SQLCODE,result.%Message) If 'result.%Next(.sc) { Return sc } Set qHandle("list") = result.%Get("Dates") Set qHandle("pointer") = 0Quit$$$OK }

/// Récupèreation de la ligne suivante, en passant à la date suivante si nécessaireClassMethod AllFetch(ByRef qHandle As%Binary, ByRef Row As%List, ByRef AtEnd As%Integer = 0) As%Status [ PlaceAfter = AllExecute ] { Set sc = $$$OKSet ns = $NamespaceNew$NamespaceSet$Namespace = "%SYS"If$Get(qHandle("dateResult")) = "" { // Passage à la date suivanteSet pointer = qHandle("pointer") If '$ListNext(qHandle("list"),pointer,oneDate) { Set AtEnd = 1Quit$$$OK } Set qHandle("pointer") = pointer Set qHandle("currentDate") = oneDate Set qHandle("dateResult") = ##class(%SQL.Statement).%ExecDirect(,"select * from SYS.ApplicationError_ErrorList(?,?)",ns,oneDate) $$$ThrowSQLIfError(qHandle("dateResult").%SQLCODE,qHandle("dateResult").%Message) } If qHandle("dateResult").%Next(.sc) { // Si nous avons une ligne pour la date actuelle, ajoutons-laSet Row = $ListBuild(qHandle("currentDate"),qHandle("dateResult").%GetData(1),qHandle("dateResult").%GetData(2),qHandle("dateResult").%GetData(6)) } ElseIf$$$ISOK(sc) { // Sinon, il faut vider le jeu de résultats et appeler AllFetch pour avancerSet qHandle("dateResult") = ""Set$Namespace = ns Set sc = ..AllFetch(.qHandle,.Row,.AtEnd) } Quit sc }

ClassMethod AllClose(ByRef qHandle As%Binary) As%Status [ PlaceAfter = AllExecute ] { New$NamespaceSet$Namespace = "%SYS"// Il semble parfois nécessaire pour que %OnClose s'exécute correctementKill qHandle("dateResult") Quit$$$OK }

}

Dans cet exemple, nous commençons dans un espace de noms utilisateur, mais toutes les requêtes s'exécutent en réalité dans %SYS. Execute obtient une liste des dates d'erreur pour l'espace de noms actuel et la stocke dans qHandle. Fetch passe à la date suivante lorsque cela est approprié, puis renvoie l'erreur suivante pour la date actuelle. Et Close s'assure que la requête de classe sort de la portée dans %SYS, car j'obtenais parfois des erreurs si ce n'était pas le cas. C'était un peu surprenant, mais cela semble logique, car la requête de classe que nous appelons n'existe que dans %SYS.

La réutilisabilité des fonctions table offre de nombreuses possibilités. Par exemple, nous pouvons en ajouter une autre dans la même classe:

/// Obtenir le nombre d'erreurs survenues au cours des derniers <var>Days</var> jours
Query ErrorCounts(Days As%Integer) As%SQLQuery(ROWSPEC = "Occurrences:%Integer,ErrorMessage:%String") [ SqlProc ]
{
    SELECT COUNT(*) AS Occurrences, ErrorMessage
    FROM AppS_Util.ApplicationErrorLog_All()
    WHERE DATEDIFF(D,"Date",$h) <= :Days
    GROUP BY ErrorMessage
    ORDER BY Occurrences DESC
}

Et maintenant, pour obtenir les erreurs d'application les plus courantes au cours des 14 derniers jours, il suffit de:

call AppS_Util.ApplicationErrorLog_ErrorCounts(14)

Maintenant, il ne nous reste plus qu'à les corriger! 😅

0
0 18
Article Sylvain Guilbaud · Août 29, 2025 1m read

Rubrique FAQ InterSystems

Par défaut, l'ordre des colonnes d'une table est déterminé automatiquement par le système. Pour modifier cet ordre, définissez explicitement l'ordre de chaque propriété à l'aide du mot-clé SqlColumnNumber lors de la définition de la classe.

Exemple :

Property Name As %String [SqlColumnNumber = 2];

Veuillez consulter la documentation ci-dessous.

SqlColumnNumber

Si vous souhaitez modifier le nom de la table SQL, spécifiez SqlTableName. Si vous souhaitez modifier le nom de la colonne (nom du champ), spécifiez SqlFieldName.

0
0 16
Article Iryna Mykhailova · Août 18, 2025 4m read

Récompense d’août pour les articles sur Global Masters a retenu mon attention, et l'un des sujets proposés m'a semblé très intéressant quant à son utilisation future dans mon enseignement. Voici donc ce que j'aimerais dire à mes étudiants à propos des tables dans IRIS et de leur corrélation avec le modèle objet.

Tout d'abord, InterSystems IRIS dispose d'un modèle de données unifié. Cela signifie que lorsque vous travaillez avec des données, vous n'êtes pas enfermé dans un paradigme unique. Les mêmes données sont accessibles et manipulables comme une table SQL traditionnelle, comme un objet natif, ou même comme un tableau multidimensionnel (global). Cela signifie que lorsque vous créez une table SQL, IRIS crée automatiquement une classe d'objet correspondante. Lorsque vous définissez une classe d'objet, IRIS la rend automatiquement disponible sous forme de table SQL. Les données elles-mêmes ne sont stockées qu'une seule fois dans le moteur de stockage multidimensionnel performant d'IRIS. Le moteur SQL et le moteur objet sont simplement des « optiques » différentes pour visualiser et travailler avec les mêmes données.

Commençons par examiner la corrélation entre le modèle relationnel et le modèle objet :

Relationnel Objet
Table Classe
Colonne Propriété
Ligne Objet
Clé primaire Identifiant d'objet

La corrélation n'est pas toujours exacte, car plusieurs tables peuvent représenter une même classe, par exemple. Mais c'est une règle générale.

0
0 23
Article Iryna Mykhailova · Août 13, 2025 8m read

Au fil des ans, j'ai constaté que certaines questions SQL revenaient régulièrement au sein de la Communauté des développeurs InterSystems, notamment concernant l'utilisation du prédicat LIKE dans différents contextes. Parmi les variantes courantes, on peut citer :

et bien d'autres dérivés. J'ai donc décidé d'écrire un article consacré au fonctionnement de LIKE dans InterSystems IRIS SQL, notamment lorsqu'il est utilisé avec des variables dans Embedded SQL, Dynamic SQL et les requêtes de classes, tout en abordant l'échappement de motifs et les recherches de caractères spéciaux.

Tout d'abord, je tiens à préciser qu'InterSystems IRIS SQL offre la plupart des fonctionnalités disponibles dans d'autres bases de données relationnelles implémentant une version ultérieure de la norme SQL. Il est toutefois important de préciser qu'outre l'accès relationnel, IRIS permet également d'utiliser d'autres modèles pour obtenir les mêmes données, par exemple des modèles objet ou document.

À ce propos, examinons le prédicat LIKE et son utilisation en SQL pour la recherche de motifs.

0
0 33
Article Benjamin De Boe · Juil 30, 2025 13m read

Cet article décrit une amélioration significative apportée dans la version 2025.2 à la manière dont InterSystems IRIS traite les statistiques de table, un élément crucial pour le traitement SQL IRIS. Nous commencerons par un bref rappel sur ce que sont les statistiques de table, comment elles sont utilisées et pourquoi cette amélioration était nécessaire. Nous nous intéresserons ensuite en détail à la nouvelle infrastructure de collecte et d'enregistrement des statistiques de table, puis nous examinerons ce que ce changement signifie en pratique pour vos applications. Nous terminerons par quelques remarques supplémentaires sur les modèles rendus possibles par le nouveau modèle et nous attendons avec impatience les étapes suivantes de cette première livraison.

Je n'utilise pas SQL, pourquoi suis-je ici? – C'est une question existentielle pertinente (wink) , mais cet article peut néanmoins contenir des informations précieuses pour vous. Certaines fonctionnalités avancées d'IRIS, notamment Interopérabilité, utilisent IRIS SQL en arrière-plan, et certains changements de comportement peuvent vous inciter à activer une nouvelle tâche de maintenance. Reportez-vous à la section "Collecte automatique des statistiques des tables" ci-dessous.

Statistiques de table 101

SQL signifie Structured Query Language (langage de requête structuré). SQL est un langage dans lequel vous exprimez ce que vous voulez (la requête), plutôt que comment vous le voulez (le code permettant d'obtenir le résultat de la requête). C'est au moteur de requête d'une base de données qu'il revient d'examiner toutes les options et de choisir le plan vraisemblablement optimal pour récupérer le résultat que vous demandez. Pour déterminer le meilleur plan, l'optimiseur de requêtes a essentiellement besoin de deux choses: des informations sur la structure de votre table, telles que l'emplacement de stockage de vos données et les index disponibles, et des informations sur les données de la table elle-même: les statistiques de table. Ces statistiques de table couvrent des informations telles que la taille estimée de la table et la sélectivité de chaque champ, qui exprime la proportion moyenne de la table correspondant à une valeur de champ particulière. Ces informations sont essentielles pour prendre les bonnes décisions concernant les plans de requête. Supposons que vous deviez trouver le nombre d'utilisateurs féminins dans un code postal particulier et que vous puissiez choisir entre partir d'un index sur le sexe ou sur le code postal. La sélectivité de l'index sur le sexe sera d'environ 50 %, tandis que celle sur le code postal pourra être aussi faible que 1 % ou moins, ce qui signifie que vous filtrerez beaucoup plus rapidement l'ensemble des lignes à rechercher en partant de ce dernier. 

Si la structure des tables fait naturellement partie de votre application et peut être intégrée au code de celle-ci, ce n'est pas nécessairement le cas des statistiques de table. Si l'exemple précédent est tiré d'une petite application librairie, il est probable que les valeurs de sélectivité des champs soient valables pour toutes les librairies dans lesquelles l'application est déployée, mais s'il s'agit d'un système de dossiers médicaux électroniques, la sélectivité du champ sexe sera différente entre une maternité et un cabinet médical généraliste, et le nombre de codes postaux (et donc leur sélectivité) dépendra de la taille de la zone couverte par l'hôpital. Il est évident que vous souhaitez que vos statistiques, et donc vos plans de requête, soient basés sur les données réelles de l'environnement dans lequel l'application est déployée.

Statistiques de table dans IRIS - avant 2025.2

Sur IRIS, les statistiques de table ont toujours été stockées dans la définition d'une classe. Cela présente plusieurs inconvénients

  • Comme décrit précédemment, les distributions de données peuvent varier considérablement entre les environnements dans lesquels vous déployez votre application. Ceci n'est pas seulement vrai pour la taille estimée des tables et la sélectivité, mais encore plus pour d'autres types de statistiques plus avancées telles que les informations sur les valeurs aberrantes et les histogrammes.
  • Lorsque vous déployez une version mise à jour de votre application, vous souhaitez généralement conserver toutes les statistiques existantes, en supposant qu'elles sont basées sur les données réelles de cet environnement. Cela s'avère plus délicat lorsqu'elles sont stockées en tant que partie intégrante du code, car vous ne souhaitez évidemment pas déployer accidentellement les statistiques de table de votre environnement de développement ou de test sur le système d'un utilisateur.
  • Lorsque le code d'application est déployé sous la forme d'une base de données en lecture seule ou lorsqu'il fait partie d'une bibliothèque système IRIS (comme les messages d'interopérabilité), il n'existe aucun moyen efficace de mettre à jour les statistiques de ces tables.

Consultez également cet article précédent qui fournit davantage de détails à ce sujet. Voici certaines raisons qui nous ont poussés à repenser la gestion des statistiques de table dans IRIS. La nouvelle infrastructure est désormais incluse dans InterSystems IRIS 2025.2.

Quel est le changement?

Statistiques collectées vs statistiques fixes

Comme décrit dans les exemples précédents, le stockage des statistiques dans le code de l'application n'a de sens que dans des environnements très spécifiques et contrôlés. Dans la plupart des cas, il est préférable de conserver les statistiques avec les données à partir desquelles elles ont été collectées, plutôt que de les fixer dans le code. C'est donc ce que nous faisons dans le nouveau modèle. Nous faisons la distinction entre les statistiques collectées qui sont toujours basées sur des données réelles et stockées dans l'index d'extension (une variable globale contenant d'autres détails au format registre sur les données de votre table, également appelées extensions), et les statistiques fixes qui sont codées en dur dans la définition de classe par le développeur de l'application et peuvent être basées sur des données réelles ou sur des hypothèses raisonnables. Comme vous pouvez le deviner, les statistiques fixes correspondent au modèle antérieur à la version 2025.2 pour la gestion des statistiques de table. 

Utilisez la nouvelle commande COLLECT STATISTICS FOR TABLE t   (qui est 100 % synonyme de la commande existante TUNE TABLE , (qui est 100 % synonyme de la commande existante TUNE TABLE, et qui sert simplement à normaliser la terminologie) pour remplir un nouvel ensemble de statistiques basé sur les données actuelles de votre table. Les statistiques de table sont très petites, nous n'écrasons donc pas les statistiques précédentes, mais stockons plutôt un nouvel ensemble et conservons les anciennes statistiques à des fins d'information jusqu'à ce qu'elles répondent aux critères de purge par défaut ou soient explicitement purgées. Si vous souhaitez passer au modèle fixe, vous pouvez enregistrer les statistiques collectées dans la définition de classe à l'aide de ALTER TABLE t FIX STATISTICS. Cela peut faire partie de la procédure de package de votre application, si vous êtes certain que ces statistiques correspondent à votre environnement cible et que vous préférez le modèle statique.

Par défaut, les statistiques fixes prévalent sur les statistiques collectées. Cela garantit la rétrocompatibilité et signifie qu'en tant que développeur, c'est toujours vous qui avez le dernier mot. En fait, si vous avez des statistiques fixes, même pour un seul champ, les statistiques collectées seront ignorées et nous reviendrons à des estimations sûres basées sur le type de données du champ pour tous les champs qui n'ont pas de statistiques fixes. Si vous souhaitez tester les statistiques collectées sans supprimer vos statistiques fixes, vous pouvez inclure l'indication %NOFIXEDSTATS hint dans la clause FROM de votre texte de requête (par table) afin d'obtenir le plan de requête basé uniquement sur les statistiques collectées, ou utiliser ALTER TABLE t SET RUNTIME IGNOREFIXEDSTATS = TRUE pour définir ce comportement au niveau de la table. 

Collecte automatique des statistiques de table

L'introduction des statistiques collectées résout déjà bon nombre des inconvénients liés au packagage et au déploiement mentionnés dans l'introduction. Cependant, elle ne résout pas vraiment l'un des problèmes les plus courants rencontrés par les utilisateurs: le manque de statistiques à jour . Comme décrit dans l'introduction, des statistiques précises sont essentielles pour obtenir les meilleurs plans de requête pour votre distribution de données spécifique. Si vous ne vous êtes jamais soucié de les collecter, l'optimiseur utilisera des valeurs par défaut approximatives basées sur le type de données de vos champs, mais celles-ci sont peu susceptibles de tenir compte correctement des spécificités de votre application dans tous les cas. À partir de la version 2021.2, IRIS collectera automatiquement les statistiques pour les tables qui n'en ont pas et qui sont éligibles à une technique d'échantillonnage rapide au moment de la requête, mais cela ne se produira qu'une seule fois, et peut-être à un moment où votre table n'aura pas encore été remplie avec des données représentatives. Mais même lorsque les statistiques ont été collectées à un moment opportun, les distributions de données évoluent avec le temps (notamment les histogrammes) et nous constatons très souvent que les utilisateurs obtiennent des plans de requête sous-optimaux simplement parce qu'ils sont basés sur des statistiques obsolètes.

Avec la version 2025.2, nous introduisons une nouvelle tâche système qui s'exécute pendant une fenêtre de maintenance de nuit et qui, pour chaque espace de noms, prend en compte toutes les tables qui n'ont aucune statistique, pour lesquelles les statistiques les plus récentes ont été invalidées (par exemple après avoir modifié la définition de stockage de la table et recompilé) ou pour lesquelles au moins 25 % des données de la table ont changé depuis la dernière collecte de statistiques (environ, ceci est mesuré sur la base du ROWCOUNT combiné pour les opérations DML sur cette table). Les statistiques sont ensuite collectées pour ces tables une par une, ou jusqu'à ce que la durée maximale de la tâche de maintenance (configurable) soit atteinte. D'autres options de configuration permettent de choisir de prendre en compte les tables avec des statistiques fixes (ON par défaut), les tables mappées vers un stockage distant (OFF par défaut) et les tables qui ne sont pas éligibles pour une technique d'échantillonnage rapide (OFF par défaut). Si nécessaire, vous pouvez marquer des tables individuelles à l'aide d'un paramètre de classe SKIPAUTOMATICSTATSCOLLECTION  (ou d'un indicateur d'exécution équivalent) afin qu'elles soient ignorées par cette tâche système (notez que ce nouvel indicateur affecte également le comportement AutoTune préexistant).

Dans la version 2025.2, cette tâche système est désactivée par défaut, car nous souhaitons évaluer avec les utilisateurs l'impact de cette fonctionnalité sur les modèles d'E/S des systèmes réels. Nous encourageons les utilisateurs à activer la tâche système et à nous communiquer leur expérience en matière de charge système afin que nous puissions procéder aux ajustements nécessaires avant de l'activer par défaut dans une version ultérieure. À ce moment-là, nous prévoyons de supprimer le fonctionnement précédent d' AutoTune qui pouvait se déclencher pendant le traitement des requêtes.

La collecte manuelle des statistiques de table est bien sûr toujours prise en charge, et l'exécution de COLLECT STATISTICS après des chargements ou des purges de données importants reste une bonne pratique.

Qu'est-ce que cela signifie pour votre application?

Nous avons pris soin de concevoir le nouveau modèle afin que les applications SQL mises à niveau vers IRIS 2025.2 ne subissent aucun changement. Les statistiques préexistantes seront désormais traitées comme des statistiques fixes, qui auront priorité sur toutes les statistiques collectées automatiquement en arrière-plan. Nous conseillons aux utilisateurs qui passent au nouveau modèle de tenir compte des trois recommandations suivantes:

Suppression des statistiques fixes après la mise à niveau

Si votre application ou, en fait, n'importe quelle table avait des statistiques avant la mise à niveau (ce qui serait le cas pour toutes les tables si vous avez suivi les meilleures pratiques), envisagez de les supprimer purement et simplement. Après la mise à niveau, elles seront considérées comme des statistiques fixes et auront priorité sur toutes les statistiques que vous collecterez explicitement (peut-être via des scripts personnalisés appelant TUNE TABLE) ou implicitement. Les statistiques fixes des tables peuvent être supprimées à l'aide d'une nouvelle commande ALTER TABLE t DROP FIXED STATISTICS ou de la commande équivalente ALTER SCHEMA s DROP FIXED STATISTICS. Vous pouvez utiliser l'indication %NOFIXEDSTATS pour vérifier à l'avance l'impact sur les requêtes individuelles, selon vos préférences.

On ne conservera les statistiques fixes que si l'on estime que les statistiques prédéfinies doivent rester inchangées pour maintenir les plans de requête actuels et qu'aucune modification de la distribution des données n'est prévue.

Prise en compte des statistiques pour les classes système

Il convient de noter que le nouveau modèle de statistiques collectées s'applique aux tables dont la définition réside dans une base de données en lecture seule, y compris les tables système IRIS telles que  Ens.MessageHeader. Pour ces tables, les statistiques de table étaient auparavant peu pratiques, voire impossibles à maintenir, mais elles deviendront désormais pertinentes et, lorsque vous aurez activé la tâche du système de collecte, elles seront maintenues automatiquement. Lorsque vous utilisez des productions d'interopérabilité ou d'autres éléments de l'infrastructure IRIS pouvant impliquer SQL en arrière-plan, nous vous recommandons de surveiller les performances des opérations de recherche et autres opérations similaires après la mise à niveau, et de collecter les statistiques manuellement ou d'activer la tâche système. 

Lors de l'utilisation de la recherche de messages, nous avons constaté des améliorations significatives des performances des requêtes après l'adoption du nouveau modèle, car celui-ci pouvait désormais utiliser une sélectivité et d'autres statistiques beaucoup plus réalistes, alors qu'auparavant, il s'appuyait sur des valeurs par défaut approximatives. Nous avons donc supprimé les statistiques fixes fournies avec ces tables système.

Soyez vigilant lors de l'importation et de l'exportation entre différentes versions

Lorsque vous exportez des définitions de table avec l'indicateur /exportselectivity=1 (par défaut), les statistiques sont incluses dans un nouveau format qui prend en charge à la fois les statistiques fixes et les statistiques les plus récentes collectées collected mais qui est incompatible avec les versions antérieures. Pour prendre en charge l'importation dans des instances IRIS exécutant des versions antérieures, utilisez /exportversion=2025.1 ou /exportselectivity=0 selon le cas. Veuillez noter que les statistiques collectées et exportées à l'aide de ce format seront toujours importées en tant que statistiques collectées et ne deviendront pas fixes de manière silencieuse parce qu'elles ont été incluses dans le fichier de définition de classe export . Compte tenu de ces nuances, vous pouvez revoir votre stratégie de contrôle de source afin de vous assurer que les informations correctes sont suivies pour votre modèle de déploiement. Notez également l'indicateur symétrique /importselectivity qui peut être utilisé lors de l'importation de définitions de classe. 

Les méthodes Import() et Export() de la classe $SYSTEM.SQL.Stats.Table ont également été étendues avec un argument de type supplémentaire afin de différencier correctement les deux types de statistiques. Pour plus d'informations, consultez la référence de classe .

Travaux futurs

Cette version comprend toute l'infrastructure nécessaire pour tirer parti du nouveau modèle. Outre quelques améliorations mineures visant à faciliter l'utilisation, nous prévoyons les deux mises à jour suivantes dans une prochaine version (probablement 2025.3)

Activation de la tâche de collecte automatique

Comme indiqué précédemment, nous avons introduit une nouvelle tâche système pour la collecte automatique des statistiques des tables pendant une fenêtre de traitement par lots de nuit, mais nous l'avons désactivée afin d'éviter toute charge E/S inattendue après la mise à niveau. Dans une prochaine version, nous l'activerons par défaut, si elle n'est pas déjà activée par l'utilisateur à ce moment-là.

Taille d'échantillon plus intelligente

Nous avons remarqué que notre algorithme d'échantillonnage rapide par blocs pouvait dans certains cas surestimer la sélectivité des champs hautement sélectifs dans les grandes tables, ce qui signifie que l'optimiseur pouvait ne pas utiliser les index correspondants alors que cela aurait été optimal. Nous mettons en place un algorithme qui affinera de manière dynamique la taille de l'échantillon si nous détectons trop de variabilité entre les échantillonnages. Ce changement n'a pratiquement aucun impact sur la modification globale de l'infrastructure décrite plus haut dans cet article.

Partagez votre expérience

Cela fait longtemps que nous attendions avec impatience le déploiement de cette modification, et nous sommes ravis qu'elle soit désormais incluse dans IRIS 2025.2. Nous avons pris soin de tester cette modification dans de nombreuses configurations différentes et nous sommes convaincus que la nouvelle infrastructure est robuste, mais nous sommes conscients que les modifications peuvent affecter des pratiques de déploiement hautement personnalisées, notamment le mappage des espaces de noms et le packagage du code. Nous vous invitons donc à nous faire part de vos commentaires et espérons que la nouvelle infrastructure vous facilitera la vie.

0
0 21
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
Article Sylvain Guilbaud · Mai 9, 2025 9m read

L'indication de requête parallèle augmente les performances de certaines requêtes sur les systèmes multiprocesseurs par le biais du traitement parallèle. L'optimiseur SQL détermine les cas où cela est bénéfique. Sur les systèmes à un seul processeur, cette indication n'a aucun effet.

Le traitement parallèle peut être géré par:

  1.  Définition de l'option auto parallel pour l'ensemble du système.
    
  2. L'utilisation du mot-clé %PARALLEL dans la clause FROM de certaines requêtes.
    

Le mot clé %PARALLEL est ignoré lorsqu'il est appliqué aux requêtes suivantes:

  1. Les requêtes INSERT, UPDATE et DELETE (cette fonctionnalité ne s'applique qu'aux requêtes SELECT)
  2. Les requêtes impliquant des fonctions ou des variables spécifiques au processus
  3. Une sous-requête corrélée à une requête englobante.
  4. Une sous-requête contenant des prédicats complexes, tels que les prédicats FOR SOME et FOR SOME %ELEMENT.

En plus des raisons mentionnées précédemment, voici quelques raisons pour lesquelles le traitement parallèle des requêtes peut être ignoré:

  • Certaines requêtes complexes ne bénéficient pas d'un traitement parallèle, même si elles semblent en bénéficier au départ.
  • Certaines configurations et paramètres de base de données ne supportent pas le traitement %PARALLEL.
  • Les dépendances et les relations au sein de la structure des données peuvent empêcher une parallélisation efficace.

Dans des scénarios suivants, %PARALLEL n'effectuera pas de traitement parallèle:

  1.  La requête inclut à la fois les clauses TOP et ORDER BY, en optimisant le temps le plus rapide pour atteindre la première ligne.
    
  2.  La requête fait référence à une vue et renvoie un identifiant de vue.
    
  3.  La requête utilise des formats de stockage personnalisés ou des tables GLOBAL TEMPORARY ainsi que des tables avec un stockage de référence global étendu.
    
  4.  La requête accède à une table avec une sécurité au niveau de la ligne.
    
  5.  Les données sont stockées dans une base de données distante.
    
  6.  La collation NLS au niveau du processus ne correspond pas à la collation NLS de tous les globaux impliqués.
    

Pour plus de détails sur les options, les considérations et les restrictions, reportez-vous à Configure Parallel Query Processing (Interystems Documentation) et Specify Optimization Hints in Queries (Configuration du traitement parallèle des requêtes (Documentation Interystems) et Spécifier les conseils d'optimisation dans les requêtes). Ce sujet a été récemment exploré dans le cadre d'une discussion au sein de la Communauté de développeurs d'InterSystems (DC), qui a inspiré cet article sur IRIS, Cache et Ensemble.

InterSystems IRIS supporte le traitement parallèle à la fois pour le SQL intégré, le SQL dynamique et le SQL dans les QueryMethods. Lorsque le mot-clé %PARALLEL est utilisé dans la clause FROM d'une requête pour suggérer un traitement parallèle. L'optimiseur SQL déterminera si la requête peut bénéficier d'un traitement parallèle et l'appliquera le cas échéant.

Pour utiliser efficacement le traitement %PARALLEL dans InterSystems IRIS, plusieurs paramètres et restrictions doivent être pris en compte, tant au niveau du système qu'au niveau de la requête, afin d'en tirer tous les avantages.

Dans le cas où vous essayez d'obtenir le traitement %PARALLEL à l'aide du traitement parallèle des requêtes à l'échelle du système et que le mode adaptatif est désactivé, vous pouvez activer le traitement parallèle des requêtes à l'échelle du système via le Portail de gestion ou $SYSTEM.SQL.Util.SetOption() Exemple

USER>w ##class(%SYSTEM.SQL.Util).GetOption("AutoParallel")
0
USER>d ##class(%SYSTEM.SQL.Util).SetOption("AutoParallel",1,.oldParVal)
 
USER>w ##class(%SYSTEM.SQL.Util).GetOption("AutoParallel")
1
USER>zw oldParVal
oldParVal=0

Autres aspects importants à prendre en compte lors de la mise en œuvre de la fonctionnalité %PARALLEL.

  •   Lorsque le [AdaptiveMode](https://docs.intersystems.com/iris20242/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_AdaptiveMode) est activé, le traitement parallèle automatique est appliqué à toutes les requêtes SELECT, en les accompagnant de la mention %PARALLEL. Cependant, toutes les requêtes ne peuvent pas utiliser le traitement parallèle, car l'optimiseur SQL peut en décider autrement.
    
  •   Lorsque nous essayons d'utiliser cette fonctionnalité %PARALLEL, nous devons également prendre en compte le paramètre  [AutoParallelThreshold](https://docs.intersystems.com/iris20242/csp/docbook/Doc.View.cls?KEY=RACS_AutoParallelThreshold) (la valeur par défaut est 3200) et ce paramètre n'est pas utile dans le cas où [AutoParallel](https://docs.intersystems.com/iris20242/csp/docbook/DocBook.UI.Page.cls?KEY=RACS_AutoParallel) est désactivé.
    
  •   Le paramètre AutoParallelThreshold détermine si une requête est exécutée en parallèle, les valeurs les plus élevées réduisant les chances de traitement en parallèle. La valeur par défaut est 3200, elle peut être ajustée via $SYSTEM.SQL.Util.SetOption("AutoParallelThreshold",n,.oldval).
    
  •   Dans les environnements partagés, le traitement parallèle est utilisé pour toutes les requêtes, quel que soit le seuil, lorsque le mode adaptatif AdaptiveMode est activé.
    
  •   Lorsque le mode AdaptiveMode est activé (défini à 1) et que la fonctionnalité AutoParallel est désactivée, le Mode adaptatif remplace le paramètre AutoParallel et active le traitement parallèle.
    

Exemple: Exemple de classe avec 100 000 enregistrements remplis

 Class SQLClass.MyTest Extends (%Persistent, %Populate)
  {
    
    Property Name As %String(MAXLEN = 255);
    
    Property Age As %Integer(MAXVAL = 100, MINVAL = 1);
    
    Property Address As %String(MAXLEN = 255);
    
    Property City As %String(MAXLEN = 255);
    
    Property State As %String(MAXLEN = 255);
    
    Property Zip As %String(MAXLEN = 255);
    
    Property Country As %String(MAXLEN = 255);
    
    Property Comment As %String(MAXLEN = 255);
    
    Property Hobby As %String(MAXLEN = 255);
    
    Property JobTitle As %String(MAXLEN = 255);
    
    Property Company As %String(MAXLEN = 255);
    
    Property PhoneNumber As %String(MAXLEN = 255);
    
    Property Email As %String(MAXLEN = 255);
    
    Property Gender As %String(MAXLEN = 1);
    
    Property Ethnicity As %String(MAXLEN = 255);
    
    Property Race As %String(MAXLEN = 255);
    
    Property Religion As %String(MAXLEN = 255);
    
    Property MaritalStatus As %String(MAXLEN = 255);
    
    Property Children As %Integer(MAXVAL = 10, MINVAL = 0);
    
    Property Income As %Integer(MAXVAL = 100000, MINVAL = 0);
    
    Property Occupation As %String(MAXLEN = 255);
    
    Property Education As %String(MAXLEN = 255);
    
    Property HomePhone As %String(MAXLEN = 255);
    
    Property MobilePhone As %String(MAXLEN = 255);
    
    Property WorkPhone As %String(MAXLEN = 255);
    
    Property WorkEmail As %String(MAXLEN = 255);
    
    Property HomeEmail As %String(MAXLEN = 255);
    
    Property HomeAddress As %String(MAXLEN = 255);
    
    Property HomeCity As %String(MAXLEN = 255);
    
    Property HomeState As %String(MAXLEN = 255);
    
    Property HomeZip As %String(MAXLEN = 255);
    
    Property HomeCountry As %String(MAXLEN = 255);
    
    Property WorkAddress As %String(MAXLEN = 255);
    
    Property WorkCity As %String(MAXLEN = 255);
    
    Property WorkState As %String(MAXLEN = 255);
    
    Property WorkZip As %String(MAXLEN = 255);
    
    Property WorkCountry As %String(MAXLEN = 255);
    
    Property WorkPhoneNumber As %String(MAXLEN = 255);
    
    Property WorkMobilePhone As %String(MAXLEN = 255);
    
    Property WorkFax As %String(MAXLEN = 255);
    
    Property WorkWebsite As %String(MAXLEN = 255);
    
    Property WorkComments As %String(MAXLEN = 255);
    
    
    Index IdxAge On Age;
}

Test n° 1Exemple d'exécution sans % PARALLEL (pour afficher 10 000 enregistrements en SMP)

select * from SQLClass.MyTest where age>40
  • 3.2069 secondes
  • 10404 références globales
  • 3325407 commandes exécutées

Exemple d'exécution avec %PARALLEL(pour afficher 10 000 enregistrements dans SMP)

select * from %PARALLEL SQLClass.MyTest where age>40
  • 2.8681 secondes
  • 10404 références globales
  • 3325407 commandes exécutées

Test n° 2 :Exemple d'exécution sans % PARALLEL (pour afficher 1 enregistrement en SMP)

select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10
  • 0.4037 secondes
  • 46559 références globales
  • 1459936 commandes exécutées

Exemple d'exécution avec %PARALLEL (pour afficher 1 enregistrement en SMP)

select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from %PARALLEL SQLClass.MyTest where age>10
  • 0.0845 secondes
  • 46560 références globales
  • 1460418 commandes exécutées

Exemple avec SQL intégré

ClassMethod embeddedSQL() As %Status
{
    // w ##Class(SQLClass.MyTest).embeddedSQL()
    Set sc = $$$OK
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2)
    &sql(select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10)
    w:'SQLCODE "Without %Parallel : ",($p($zts,",",2)-stime),!
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2)
    &sql(select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from %PARALLEL SQLClass.MyTest where age>10)
    w:'SQLCODE "With %Parallel : ",($p($zts,",",2)-stime),!
    Return sc
}

Résultats (SQL intégré) : USER> D ##Class(SQLClass.MyTest).embeddedSQL() 5466 blocs supprimés Sans %Parallel : .355737 5217 blocs supprimés Avec %Parallel : .3407056

USER>

Exemple avec SQL dynamique

ClassMethod dynamicSQL() As %Status
{
     // w ##Class(SQLClass.MyTest).dynamicSQL()
    Set sc = $$$OK
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2), recCnt=0
    Set rs=##class(%ResultSet).%New()
    Set sc=rs.Prepare("select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10")
    Set sc=rs.Execute()
    While(rs.Next()) {
	 	w "COUNT(Children) : ",rs.GetData(1),"; MAX(Children) : ",rs.GetData(2),"; MIN(Children) : ",rs.GetData(3),"; AVG(Children) : ",rs.GetData(4),!
    }
    w "Without %Parallel : ",($p($zts,",",2)-stime),!!!
    DO ClearBuffers^|"%SYS"|GLOBUFF()
    set stime=$p($zts,",",2), recCnt=0
    Set sc=rs.Prepare("select COUNT(Children),MAX(Children),MIN(Children),AVG(Children) from SQLClass.MyTest where age>10")
    Set sc=rs.Execute()
    While(rs.Next()) {
	 	w "COUNT(Children) : ",rs.GetData(1),"; MAX(Children) : ",rs.GetData(2),"; MIN(Children) : ",rs.GetData(3),"; AVG(Children) : ",rs.GetData(4),!
	}
    w "With %Parallel : ",($p($zts,",",2)-stime),!
    Return sc
}

Résultats (SQL dynamique): USER>d ##Class(SQLClass.MyTest).dynamicSQL() 22163 blocs supprimés NOMBRE(Enfants) : 89908; MAX(Enfants) : 10; MIN(Enfants) : 0; AVG(Enfants) : 5.021989144458780086 Sans %Parallel : .4036913

5721 blocs supprimés NOMBRE(Enfants) : 89908; MAX(Enfants) : 10; MIN(Enfants) : 0; AVG(Enfants) : 5.021989144458780086 Avec %Parallel : .3693442

0
0 37
Article Lorenzo Scalese · Avr 30, 2025 5m read

Après tant d'années d'attente, nous avons enfin un pilote officiel disponible sur Pypi

De plus, j'ai découvert que le pilote JDBC était enfin disponible sur Maven depuis déjà 3 mois,  et le pilote .Net driver - surNuget depuis plus d'un mois.

 La mise en œuvre de la DB-API et que les fonctions devraient au moins être définies par cette norme. La seule différence devrait se situer au niveau de SQL.

Et ce qui est intéressant dans l'utilisation de bibliothèques existantes, c'est qu'elles ont déjà mis en œuvre d'autres bases de données en utilisant le standard DB-API, et que ces bibliothèques s'attendent déjà à ce que le pilote fonctionne.

J'ai décidé de tester le pilote officiel d'InterSystems en mettant en œuvre son support dans la bibliothèque SQLAlchemy-iris.

executemany

Préparez une opération de base de données (requête ou commande) et exécutez-la en fonction de toutes les séquences de paramètres ou de mappages trouvées dans la séquence seq_of_parameters.

Cette fonction très utile permet d'insérer plusieurs lignes à la fois. Commençons par un exemple simple

import iris

host = "localhost" port = 1972 namespace = "USER" username = "_SYSTEM" password = "SYS" conn = iris.connect( host, port, namespace, username, password, )

with conn.cursor() as cursor: cursor = conn.cursor()

res = cursor.execute(<span class="hljs-string">"DROP TABLE IF EXISTS test"</span>)
res = cursor.execute(
    <span class="hljs-string">"""
CREATE TABLE test (
        id IDENTITY NOT NULL,
        value VARCHAR(50)
) WITH %CLASSPARAMETER ALLOWIDENTITYINSERT = 1
"""</span>
)

cursor = conn.cursor()
res = cursor.executemany(
    <span class="hljs-string">"INSERT INTO test (id, value) VALUES (?, ?)"</span>, [
        (<span class="hljs-number">1</span>, <span class="hljs-string">'val1'</span>),
        (<span class="hljs-number">2</span>, <span class="hljs-string">'val2'</span>),
        (<span class="hljs-number">3</span>, <span class="hljs-string">'val3'</span>),
        (<span class="hljs-number">4</span>, <span class="hljs-string">'val4'</span>),
    ]
)</code></pre>

Cela fonctionne bien, mais que se passe-t-il s'il faut insérer une seule valeur par ligne.

    res = cursor.executemany(
        "INSERT INTO test (value) VALUES (?)", [
            ('val1', ),
            ('val2', ),
            ('val3', ),
            ('val4', ),
        ]
    )

Cela conduit malheureusement à une exception inattendue

RuntimeError: Cannot use list/tuple for single values (Impossible d'utiliser une liste/tuple pour des valeurs uniques)

Pour certaines raisons, une seule valeur par ligne est autorisée, et InterSystems demande d'utiliser une méthode différente

    res = cursor.executemany(
        "INSERT INTO test (value) VALUES (?)", [
            'val1',
            'val2',
            'val3',
            'val4',
        ]
    )

De cette façon, cela fonctionne bien

fetchone

Récupère la ligne suivante d'un ensemble de résultats de requête, en renvoyant une seule séquence, ou None lorsqu'il n'y a plus de données disponibles.

Un exemple simple sur sqlite

import sqlite3
con = sqlite3.connect(":memory:")

cur = con.cursor() cur.execute("SELECT 1 one, 2 two") onerow = cur.fetchone() print('onerow', type(onerow), onerow) cur.execute("SELECT 1 one, 2 two union all select '01' as one, '02' as two") allrows = cur.fetchall() print('allrows', type(allrows), allrows)

fournit

onerow <class 'tuple'> (1, 2)
allrows <class 'list'> [(1, 2), ('01', '02')]

Et avec le pilote InterSystems

import iris

con = iris.connect( hostname="localhost", port=1972, namespace="USER", username="_SYSTEM", password="SYS", )

cur = con.cursor() cur.execute("SELECT 1 one, 2 two") onerow = cur.fetchone() print("onerow", type(onerow), onerow) cur.execute("SELECT 1 one, 2 two union all select '01' as one, '02' as two") allrows = cur.fetchall() print("allrows", type(allrows), allrows)

par certaines raisons fournit

onerow <class 'iris.dbapi.DataRow'> <iris.dbapi.DataRow object at 0x104ca4e10>
allrows <class 'tuple'> ((1, 2), ('01', '02'))

Qu'est-ce que DataRow, et pourquoi ne pas utiliser un tuple ou au moins une liste

Exceptions

SLa norme décrit une variété de classes d'exceptions que le pilote est censé utiliser, au cas où quelque chose ne fonctionnerait pas. Or, le pilote InterSystems ne les utilise pas du tout, se contentant de déclencher une erreur RunTime pour toute raison, ce qui, de toute façon, est contraire à la norme.

L'application peut s'appuyer sur le type d'exception qui se produit et se comporter en conséquence. Mais le pilote InterSystems ne fournit aucune différence. Par ailleurs, SQLCODE serait utile, mais il doit être extrait du message d'erreur

Conclusion

Au cours des tests, j'ai donc trouvé plusieurs bogues

  • Erreurs aléatoires survenant à tout moment <LIST ERROR> Format de liste incorrect, type non supporté pour IRISList; Détails : type détecté : 32
    • fonctionnent correctement, si vous réessayez juste après l'erreur
  • Des erreurs de segmentation ont été détectées, je ne sais même pas comment cela se produit
  • Résultat inattendu de la fonction fetchone
  • Fonctionnement inattendu de la fonction executemany, pour une seule ligne de valeur
  • Les exceptions ne sont pas du tout implémentées, des exceptions différentes devraient être générées en cas d'erreurs différentes, et les applications s'appuient sur ces exceptions
  • Python intégré peut être interrompu en cas d'installation à côté d'IRIS
    • en raison du même nom utilisé par Python intégré et ce pilote, il remplace ce qui est déjà installé avec IRIS et peut l'interrompre

SQLAlchemy-iris supporte maintenant le pilote officiel d'InterSystems, mais ceci en raison d'une incompatibilité avec Python intégré et de plusieurs bogues découverts lors des tests. Installation à l'aide de cette commande, avec l'option définie

pip install sqlalchemy-iris[intersystems]

Et pour une utilisation simple, l'URL devrait être iris+intersystems://

from sqlalchemy import Column, MetaData, Table
from sqlalchemy.sql.sqltypes import Integer, VARCHAR
from sqlalchemy import create_engine
from sqlalchemy.orm import DeclarativeBase

DATABASE_URL = "iris+intersystems://_SYSTEM:SYS@localhost:1972/USER" engine = create_engine(DATABASE_URL, echo=True)

# Create a table metadata metadata = MetaData()

classBase(DeclarativeBase):passdefmain(): demo_table = Table( "demo_table", metadata, Column("id", Integer, primary_key=True, autoincrement=True), Column("value", VARCHAR(50)), )

demo_table.drop(engine, checkfirst=<span class="hljs-keyword">True</span>)
demo_table.create(engine, checkfirst=<span class="hljs-keyword">True</span>)
<span class="hljs-keyword">with</span> engine.connect() <span class="hljs-keyword">as</span> conn:
    conn.execute(
        demo_table.insert(),
        [
            {<span class="hljs-string">"id"</span>: <span class="hljs-number">1</span>, <span class="hljs-string">"value"</span>: <span class="hljs-string">"Test"</span>},
            {<span class="hljs-string">"id"</span>: <span class="hljs-number">2</span>, <span class="hljs-string">"value"</span>: <span class="hljs-string">"More"</span>},
        ],
    )
    conn.commit()
    result = conn.execute(demo_table.select()).fetchall()
    print(<span class="hljs-string">"result"</span>, result)

main()

En raison de bogues dans le pilote InterSystems, certaines fonctionnalités peuvent ne pas fonctionner comme prévu. J'espère que cela sera corrigé à l'avenir

0
0 27
InterSystems officiel Adeline Icard · Avr 23, 2025

Les versions de maintenance 2024.1.4 et 2023.1.6 de la plateforme de données InterSystems IRIS®, d'InterSystems IRIS® for HealthTMet de HealthShare® Health Connect sont désormais disponibles en disponibilité générale (GA). Ces versions incluent les correctifs pour l'alerte suivante récemment émise : Alerte : Requêtes SQL renvoyant des résultats erronés | InterSystems. N'hésitez pas à partager vos commentaires via la Communauté des développeurs afin que nous puissions développer ensemble un meilleur produit.

Documentation

0
0 52
Article Iryna Mykhailova · Avr 18, 2025 9m read

IRIS propose une fonctionnalité dédiée à la gestion des documents JSON, appelée DocDB.

Plateforme de données DocDB d'InterSystems IRIS® est une fonctionnalité permettant de stocker et de récupérer des données de base de données. Elle est compatible avec le stockage et la récupération de données de tables et de champs SQL traditionnels (classe et propriété), mais en est distincte. Elle est basée sur JSON (JavaScript Object Notation) qui prend en charge l'échange de données sur le Web. InterSystems IRIS prend en charge le développement de bases de données et d'applications DocDB en REST et en ObjectScript, ainsi que le support SQL pour la création ou l'interrogation de données DocDB.

De par sa nature, la base de données documentaire InterSystems IRIS est une structure de données sans schéma. Cela signifie que chaque document a sa propre structure, qui peut différer de celle des autres documents de la même base de données. Cela présente plusieurs avantages par rapport au SQL, qui nécessite une structure de données prédéfinie.

Le mot « document » est utilisé ici comme un terme technique spécifique à l'industrie, en tant que structure de stockage de données dynamique. Le « document », tel qu'utilisé dans DocDB, ne doit pas être confondu avec un document textuel ou avec la documentation.

Voyons comment DocDB peut permettre de stocker JSON dans la base de données et de l'intégrer dans des projets qui reposent uniquement sur des protocoles xDBC.

Commençons!

DocDB définit deux composants clés:

  • %DocDB.Database - Bien qu'il s'attende à la création d'une "base de données", ce qui peut prêter à confusion puisque nous avons déjà une base de données en termes SQL, il s'agit essentiellement d'une classe en ObjectScript. Pour ceux qui sont plus familiers avec SQL, elle fonctionne comme une table.
  • %DocDB.Document - Classe de base pour une 'base de données' qui étend la classe %Persistent et introduit des propriétés spécifiques à DocDB:
    • %DocumentId - IdKey
    • %Doc As %DynamicAbstractObject - Le stockage actuel du document JSON
    • %LastModified - Un horodatage mis à jour automatiquement pour chaque insertion et mise à jour

Création d'une table (base de données)

Passons maintenant à la création de notre première table, ou plutôt de notre première « base de données ». Il semble que l'on ne s'attendait pas à la création d'une base de données DocDB.Database uniquement à l'aide de SQL. Par conséquent, il n'est pas possible de créer une nouvelle « base de données » en utilisant uniquement SQL. Pour le vérifier, nous allons utiliser une approche ObjectScript classique. Voici un exemple de définition d'une classe qui étend %DocDB.Document:

Class User.docdb Extends%DocDB.Document [ DdlAllowed ]
{

}

La vérification de la table nouvellement créée à l'aide de SQL permet de s'assurer de son bon fonctionnement.

Il est temps de faire un premier essai et d'insérer quelques données

Nous pouvons insérer n'importe quelles données sans validation, ce qui signifie qu'il n'y a aucune restriction sur ce qui peut être inséré dans %Doc. La mise en place d'une validation serait bénéfique.

Extraction de valeurs d'un document

La base de données %DocDB.Database permet d'extraire des propriétés des documents et de les rendre disponibles sous forme de colonnes dédiées. Cela permet également d'effectuer une indexation sur ces propriétés.

Il faudrait d'abord obtenir la base de données.

USER>set docdb=##class(%DocDB.Database).%GetDatabase("User.docdb")

<THROW>%GetDatabase+5^%DocDB.Database.1 *%Exception.StatusException ERROR #25351: DocDB Database 'User.docdb' does not exist.

USER 2e1>w $SYSTEM.DocDB.Exists("User.docdb") 0

Euh, la base de données "n'existe pas", d'accord, créons-la alors

USER>set docdb=##class(%DocDB.Database).%CreateDatabase("User.docdb")

<THROW>%CreateDatabase+13^%DocDB.Database.1 *%Exception.StatusException ERROR #25070: The generated class name for the database 'User.docdb' conflicts with another class: User.docdb USER 2e1>

Ainsi, une simple définition de classe ne suffit pas. Il faut utiliser %DocDB.Database dès le début, ce qui n'est pas pratique, surtout lors de l'utilisation du contrôle de code source.

Pour résoudre ce problème, nous supprimons la classe existante et créons correctement la base de données:

USER>do $system.OBJ.Delete("User.docdb")

Deleting class User.docdb USER>set docdb=##class(%DocDB.Database).%CreateDatabase("User.docdb")

USER>zwrite docdb docdb=6@%DocDB.Database ; <OREF,refs=1> +----------------- general information --------------- | oref value: 6 | class name: %DocDB.Database | %%OID: $lb("3","%DocDB.Database") | reference count: 1 +----------------- attribute values ------------------ | %Concurrency = 1 <Set> | ClassName = "User.docdb" | DocumentType = "" | Name = "User.docdb" | Resource = "" | SqlNameQualified = "SQLUser.docdb" +-----------------------------------------------------

Cette fois, cela fonctionne et les données précédemment insérées restent intactes.

Supposons que nous ayons un document comme celui-ci

{"name":"test", "some_value":12345}

Extrayons ces deux champs à l'aide de la méthode %CreateProperty

USER>do docdb.%CreateProperty("name","%String","$.name",0)

USER>do docdb.%CreateProperty("someValue","%String","$.some_value",0)

Et vérifions la table

En vérifiant cette table, nous constatons que deux nouvelles colonnes ont été ajoutées, mais que celles-ci contiennent des valeurs nulles. Il semble que ces propriétés ne s'appliquent pas rétroactivement aux données existantes. Si un développeur ajoute ultérieurement des propriétés et des index à des fins d'optimisation, les données existantes ne refléteront pas automatiquement ces modifications.

Mettez à jour en utilisant la même valeur et vérifiez si %doc est json. Et nous obtenons notre valeur.

Jetons maintenant un coup d'œil à la classe, qui est entièrement créée et mise à jour par %DocDB.Database

Class User.docdb Extends%DocDB.Document [ Owner = {irisowner}, ProcedureBlock ]
{

Property name As%String [ SqlComputeCode = { set {}=$$%EvaluatePathOne^%DocDB.Document({%Doc},"$.name") }, SqlComputed, SqlComputeOnChange = %Doc ];Property someValue As%String [ SqlComputeCode = { set {}=$$%EvaluatePathOne^%DocDB.Document({%Doc},"$.some_value") }, SqlComputed, SqlComputeOnChange = %Doc ]; Index name On name; Index someValue On someValue; }

Ainsi, les propriétés créées contiennent un code pour extraire la valeur de %Doc, et oui, elles ne sont remplies que lorsque %Doc est modifié. Et des index ont été créés pour les deux champs, sans que personne ne le demande. Le fait d'avoir de nombreuses valeurs extraites augmentera l'utilisation des variables globales simplement par le nombre d'index.

Il sera possible de mettre à jour ces propriétés créées, sans nuire au %Doc original, mais les valeurs deviendront inutiles.

 
Insertion de données non valides
Insert NULL

Chaîne vide ou tout texte non-json.

Réponse franchement moche, rien à ajouter, la validation des tentatives d'insertion de quelque chose d'illégal dans une table semble raisonnable, donc, le message d'erreur serait au moins quelque chose de significatif. 

La base de données %DocDB.Database avec une méthode %GetProperty

USER>zw docdb.%GetPropertyDefinition("someValue")

{"Name":"someValue","Type":"%Library.String"}  ; <DYNAMIC OBJECT> USER>zw docdb.%GetPropertyDefinition("name")

{"Name":"name","Type":"%Library.String"}  ; <DYNAMIC OBJECT>

Le chemin d'accès à la valeur qui a été utilisé dans %CreateProperty a disparu, il n'y a aucun moyen de le valider. Si le chemin d'accès est incorrect, pour le mettre à jour, il faut d'abord appeler %DropProperty puis à nouveau %CreateProperty.

%FindDocuments

%%DocDB.Database vous permet de rechercher des documents

Pour trouver un ou plusieurs documents dans une base de données et renvoyer ceux-ci au format JSON, appelez la méthode %FindDocuments(). Cette méthode accepte n'importe quelle combinaison de trois prédicats positionnels facultatifs : une matrice de restriction, une matrice de projection et une paire clé/valeur limite.

Plus important encore, %FindDocuments ne se soucie pas de %Doc lui-même, il ne fonctionne que sur les propriétés. Assez fragile, il lève des exceptions sur tout ce qui ne correspond pas à ce qui est attendu. En fait, il construit simplement une requête SQL et l'exécute.

USER>do docdb.%FindDocuments(["firstName","B","%STARTSWITH"]).%ToJSON() 

<THROW>%FindDocuments+37^%DocDB.Database.1 *%Exception.StatusException ERROR #25541: DocDB Property 'firstName' does not exist in 'User.docdb'

USER>do docdb.%FindDocuments(["name","test","in"],["name"]).%ToJSON()

{"sqlcode":100,"message":null,"content":[{"name":"test"}]} USER>do docdb.%FindDocuments(["name","","in"],["name"]).%ToJSON()

<THROW>%FindDocuments+37^%DocDB.Database.1 *%Exception.SQL -12 -12   A term expected, beginning with either of:  identifier, constant, aggregate, $$, (, :, +, -, %ALPHAUP, %EXACT, %MVR %SQLSTRING, %SQLUPPER, %STRING, %TRUNCATE, or %UPPER^ SELECT name FROM SQLUser . docdb WHERE name IN ( )

USER>do docdb.%FindDocuments(["name","test","="]).%ToJSON()

{"sqlcode":100,"message":null,"content":[{"%Doc":"{"name":"test", "some_value":12345}","%DocumentId":"1","%LastModified":"2025-02-05 12:25:02.405"}]} USER 2e1>do docdb.%FindDocuments(["Name","test","="]).%ToJSON()

<THROW>%FindDocuments+37^%DocDB.Database.1 *%Exception.StatusException ERROR #25541: DocDB Property 'Name' does not exist in 'User.docdb'

USER>do docdb.%FindDocuments(["%Doc","JSON","IS"]).%ToJSON()

<THROW>%FindDocuments+37^%DocDB.Database.1 *%Exception.StatusException ERROR #25540: DocDB Comparison operator is not valid: 'IS' USER 2e1>do docdb.%FindDocuments(["%Doc","","IS JSON"]).%ToJSON()

<THROW>%FindDocuments+37^%DocDB.Database.1 *%Exception.StatusException ERROR #25540: DocDB Comparison operator is not valid: 'IS JSON'

L'utilisation de SQL simple serait bien plus fiable 

Enregistrement

Un autre aspect très intéressant est l'efficacité avec laquelle JSON est enregistré dans la base de données.

^poCN.bvx3.1(1)=$lb("","2025-02-05 12:25:02.405","test",12345)
^poCN.bvx3.1(1,"%Doc")="{""name"":""test"", ""some_value"":12345}"
^poCN.bvx3.1(2)=$lb("","2025-02-05 12:25:02.405")
^poCN.bvx3.1(2,"%Doc")="[1,2,3]"
^poCN.bvx3.1(3)=$lb("","2025-02-05 12:01:18.542")
^poCN.bvx3.1(3,"%Doc")="test"
^poCN.bvx3.1(4)=$lb("","2025-02-05 12:01:19.445")
^poCN.bvx3.1(4,"%Doc")=$c(0)
^poCN.bvx3.1(5)=$lb("","2025-02-05 12:01:20.794")

Le JSON est stocké sous forme de texte brut, tandis que d'autres bases de données utilisent des formats binaires pour un enregistrement et une recherche plus efficaces. La base de données DocDB d'IRIS ne prend pas en charge la recherche directe dans le contenu des documents, sauf si JSON_TABLE est utilisé, ce qui nécessite tout de même l'analyse du JSON dans un format binaire interne.

Dans la version 2025.1, %DynamicAbstractObject introduit les méthodes %ToPVA et %FromPVA, ce qui semble enregistrer le JSON dans un format binaire.

USER>do ({"name":"value"}).%ToPVA($name(^JSON.Data(1))) 

USER>zw ^JSON.Data ^JSON.Data(1,0,0)="PVA1"$c(134,0,6,0,2,0,0,0,0,0,14,0,15,0,2,0,21,9,6,136,0,1,6,0,1,0,2,1,137,0,1,5,8,1,6)"value"$c(6,0,6)"name"_$c(5)

USER>zw {}.%FromPVA($name(^JSON.Data(1)))

{"name":"value"}  ; <DYNAMIC OBJECT,refs=1>

Cependant, le traitement de certaines structures présente des incohérences.

USER>do ({}).%ToPVA($name(^JSON.Data(1)))

<SYSTEM>%ToPVA+1^%Library.DynamicAbstractObject.1

USER>do ({"name":{}}).%ToPVA($name(^JSON.Data(1)))

<SYSTEM>%ToPVA+1^%Library.DynamicAbstractObject.1

Conclusion

Actuellement, %DocDB n'est vraiment pratique qu'avec ObjectScript et a des limites en SQL. Des problèmes de performances apparaissent lorsqu'il s'agit de traiter de grands ensembles de données. Tout ce que %DocDB offre peut être réalisé en utilisant du SQL de base tout en conservant un support SQL complet. Compte tenu de l'implémentation actuelle, il y a peu d'intérêt à utiliser DocDB plutôt que des approches SQL de base.

0
0 46
InterSystems officiel Adeline Icard · Mars 27, 2025

InterSystems annonce la disponibilité générale d'InterSystems IRIS, InterSystems IRIS for Health et HealthShare Health Connect 2025.1.

La version 2025.1 de la plateforme de données InterSystems IRIS®, InterSystems IRIS® for HealthTM et HealthShare® Health Connect est désormais disponible. Il s'agit d'une version en maintenance prolongée.

Points forts de la version

Cette nouvelle version propose plusieurs nouvelles fonctionnalités et améliorations, notamment :

1. Fonctionnalités avancées de recherche vectorielle

0
0 28
Article Iryna Mykhailova · Mars 17, 2025 3m read

   

Contrairement au film mentionné dans l'image (pour ceux qui ne connaissent pas, Matrix, 1999), le choix entre Dynamic SQL et Embedded SQL n'est pas un choix entre réalité et fantaisie, mais une décision à prendre. Ci-dessous, je vais essayer de vous faciliter la tâche.

Si votre besoin concerne les interactions entre le client et l'application (et par conséquent la base de données), le Dynamic SQL peut être plus approprié, car il s'adapte très facilement à ces changements de requête. Cependant, ce dynamisme a un coût : à chaque nouvelle requête, elle est remodelée, ce qui peut entraîner un coût d'exécution plus élevé. Voici un exemple simple d'extrait de code Python.

0
0 38
InterSystems officiel Benjamin De Boe · Mars 12, 2025

Bonjour,

Nous lançons un programme d'accès anticipé pour une nouvelle fonctionnalité de partitionnement de table. Cette fonctionnalité aidera les clients IRIS à gérer des tables très volumineuses et à répartir les données de ligne et les index associés entre les bases de données et les niveaux de stockage. Le partitionnement de table est au cœur de la gestion des données relationnelles d'IRIS. Nous souhaitons donc nous assurer de la réussite de nos projets en collaborant avec quelques clients impliqués, capables de nous faire part de leurs commentaires sur les livrables initiaux et de les peaufiner si nécessaire.

Si vous travaillez avec de très grands ensembles de données relationnelles, recherchez une efficacité opérationnelle accrue et êtes prêt à tester cette nouvelle fonctionnalité, inscrivez-vous sur https://www.intersystems.com/early-access-program/. Vous recevrez un e-mail de bienvenue vous redirigeant vers le portail d'évaluation, où vous trouverez une licence de développement temporaire, des kits et des images de conteneurs récents contenant la nouvelle fonctionnalité, ainsi qu'un tutoriel pour vous aider à démarrer.

0
0 39
Question Sylvain Guilbaud · Mars 7, 2025

En utilisant IRIS 2024.1.2 lorsque je monte la base de données de routine en lecture seule, j'obtiens une erreur lors de l'exécution de requêtes SQL dynamique.

Quelles globales doivent être mappées dans une base de données en lecture-écriture afin de permettre l'écriture des plans d'exécutions ?

0
0 40
Article Rahul Singhal · Mars 3, 2025 6m read

Introduction

Pour atteindre des performances optimisées en matière d'IA, une explicabilité robuste, une adaptabilité et une efficacité dans les solutions de santé, InterSystems IRIS sert de fondation centrale pour un projet au sein du cadre multi-agent x-rAI. Cet article offre une analyse approfondie de la manière dont InterSystems IRIS permet le développement d'une plateforme d'analyse de données de santé en temps réel, permettant des analyses avancées et des informations exploitables. La solution exploite les points forts d'InterSystems IRIS, notamment le SQL dynamique, les capacités natives de recherche vectorielle, la mise en cache distribuée (ECP) et l'interopérabilité FHIR. Cette approche innovante s'aligne directement sur les thèmes du concours « Utilisation du SQL dynamique et SQL intégré », « GenAI, recherche vectorielle » et « FHIR, DME », démontrant une application pratique d'InterSystems IRIS dans un contexte critique de santé.

Architecture du système

L'Agent Santé dans x-rAI repose sur une architecture modulaire intégrant plusieurs composants :

Couche d'ingestion des données : Récupère les données de santé en temps réel à partir de dispositifs portables via l'API Terra.

Couche de stockage des données : Utilise InterSystems IRIS pour stocker et gérer les données de santé structurées.

Moteur analytique : Exploite les capacités de recherche vectorielle d'InterSystems IRIS pour l'analyse de similarité et la génération d'informations.

Couche de mise en cache : Implémente la mise en cache distribuée via le protocole Enterprise Cache Protocol (ECP) d'InterSystems IRIS pour améliorer l'évolutivité.

Couche d'interopérabilité : Utilise les normes FHIR pour s'intégrer aux systèmes externes de santé comme les DME.

Voici un diagramme d'architecture à haut niveau :

text [Dispositifs portables] --> [API Terra] --> [Ingestion des données] --> [InterSystems IRIS] --> [Moteur analytique] ------[Couche de mise en cache]------ ----[Intégration FHIR]----- Mise en œuvre technique

  1. Intégration des données en temps réel avec SQL dynamique

L'Agent Santé ingère des métriques de santé en temps réel (par exemple, fréquence cardiaque, pas effectués, heures de sommeil) depuis des dispositifs portables via l'API Terra. Ces données sont stockées dans InterSystems IRIS à l'aide du SQL dynamique pour une flexibilité dans la génération des requêtes.

Implémentation du SQL dynamique

Le SQL dynamique permet au système de construire adaptativement des requêtes basées sur les structures des données entrantes.

text def index_health_data_to_iris(data): conn = iris_connect() if conn is None: raise ConnectionError("Échec de la connexion à InterSystems IRIS.") try: with conn.cursor() as cursor: query = """ INSERT INTO HealthData (user_id, heart_rate, steps, sleep_hours) VALUES (?, ?, ?, ?) """ cursor.execute(query, ( data['user_id'], data['heart_rate'], data['steps'], data['sleep_hours'] )) conn.commit() print("Données indexées avec succès dans IRIS.") except Exception as e: print(f"Erreur lors de l'indexation des données : {e}") finally: conn.close() Avantages du SQL dynamique

Permet une construction flexible des requêtes basée sur les schémas des données entrantes.

Réduit la charge de développement en évitant les requêtes codées en dur.

Facilite l'intégration transparente de nouvelles métriques sans modifier le schéma de la base.

  1. Analytique avancée avec recherche vectorielle

Le type natif vector et les fonctions de similarité d'InterSystems IRIS ont été utilisés pour effectuer une recherche vectorielle sur les données de santé. Cela permet au système d’identifier les dossiers historiques similaires aux métriques actuelles d’un utilisateur.

Flux de travail pour la recherche vectorielle

Convertir les métriques de santé (par exemple, fréquence cardiaque, pas effectués, heures de sommeil) en une représentation vectorielle.

Stocker ces vecteurs dans une colonne dédiée dans la table HealthData.

Effectuer des recherches basées sur la similarité à l'aide de VECTOR_SIMILARITY().

Requête SQL pour la recherche vectorielle

text SELECT TOP 3 user_id, heart_rate, steps, sleep_hours, VECTOR_SIMILARITY(vec_data, ?) AS similarity FROM HealthData ORDER BY similarity DESC; Intégration Python

text def iris_vector_search(query_vector): conn = iris_connect() if conn is None: raise ConnectionError("Échec de la connexion à InterSystems IRIS.") try: with conn.cursor() as cursor: query_vector_str = ",".join(map(str, query_vector)) sql = """ SELECT TOP 3 user_id, heart_rate, steps, sleep_hours, VECTOR_SIMILARITY(vec_data, ?) AS similarity FROM HealthData ORDER BY similarity DESC; """ cursor.execute(sql, (query_vector_str,)) results = cursor.fetchall() return results except Exception as e: print(f"Erreur lors de la recherche vectorielle : {e}") return [] finally: conn.close() Avantages de la recherche vectorielle

Permet des recommandations personnalisées grâce à l’identification des tendances historiques.

Améliore l’explicabilité en reliant les métriques actuelles à des cas passés similaires.

Optimisé pour des analyses rapides grâce aux opérations SIMD (Single Instruction Multiple Data).

  1. Mise en cache distribuée pour l’évolutivité

Pour gérer efficacement le volume croissant des données, l’Agent Santé utilise le protocole Enterprise Cache Protocol (ECP) d’InterSystems IRIS. Ce mécanisme réduit la latence et améliore l’évolutivité.

Caractéristiques clés

Mise en cache locale sur les serveurs applicatifs pour minimiser les requêtes vers la base centrale.

Synchronisation automatique garantissant la cohérence entre tous les nœuds du cache.

Évolutivité horizontale permettant l’ajout dynamique de serveurs applicatifs.

Avantages du caching

Réduction des temps de réponse grâce à la mise en cache locale.

Amélioration de l’évolutivité grâce à la répartition des charges entre plusieurs nœuds.

Réduction des coûts d’infrastructure grâce à une moindre sollicitation du serveur central.

  1. Intégration FHIR pour l’interopérabilité

Le support d’InterSystems IRIS pour FHIR (Fast Healthcare Interoperability Resources) garantit une intégration fluide avec les systèmes externes comme les DME.

Flux FHIR

Les données issues des dispositifs portables sont transformées en ressources compatibles FHIR (par exemple Observation, Patient).

Ces ressources sont stockées dans InterSystems IRIS et accessibles via des API RESTful.

Les systèmes externes peuvent interroger ou mettre à jour ces ressources via des points d’accès standard FHIR.

Avantages

Assure la conformité avec les normes d’interopérabilité en santé.

Facilite un échange sécurisé des données entre systèmes.

Permet une intégration avec les flux et applications existants dans le domaine médical.

IA explicable grâce aux informations en temps réel

En combinant les capacités analytiques d’InterSystems IRIS avec le cadre multi-agent x-rAI, l’Agent Santé génère des informations exploitables et explicables. Par exemple :

« L’utilisateur 123 avait des métriques similaires (Fréquence cardiaque : 70 bpm ; Pas : 9 800 ; Sommeil : 7 h). Sur la base des tendances historiques, il est recommandé de maintenir vos niveaux actuels d’activité. »

Cette transparence renforce la confiance dans les applications IA dédiées à la santé en offrant un raisonnement clair derrière chaque recommandation.

Conclusion

L’intégration d’InterSystems IRIS dans l’Agent Santé du cadre x-rAI illustre son potentiel comme plateforme robuste pour construire des systèmes IA intelligents et explicables dans le domaine médical. En exploitant le SQL dynamique, la recherche vectorielle, la mise en cache distribuée et l’interopérabilité FHIR, ce projet fournit des informations exploitables et transparentes—ouvrant ainsi la voie à des applications IA plus fiables dans des domaines critiques comme celui de la santé.

0
0 45
InterSystems officiel Adeline Icard · Fév 20, 2025

19 février 2025 – Alerte : les requêtes SQL renvoient des résultats erronés

InterSystems a corrigé deux problèmes pouvant entraîner le renvoi de résultats erronés par un petit nombre de requêtes SQL. De plus, InterSystems a corrigé une incohérence dans la gestion des types de données date/heure qui peut entraîner des résultats différents, inattendus, mais corrects, pour les applications existantes qui s'appuient sur le comportement antérieur et incohérent.

DP-436825 : les requêtes SQL avec jointure latérale peuvent renvoyer des résultats erronés

0
0 31
Article Guillaume Rongier · Fév 7, 2025 9m read

L'essor des projets Big Data, des analyses en libre-service en temps réel, des services de recherche en ligne et des réseaux sociaux, entre autres, a donné naissance à des scénarios de requête de données massives et très performantes. En réponse à ce défi, la technologie MPP (base de données de traitement hautement parallèle) a été créée et s'est rapidement imposée. Parmi les options MPP open-source, Presto (https://prestodb.io/) est la plus connue. Cette solution a vu le jour au sein de Facebook et a été utilisée pour l'analyse de données, avant d'être mise à disposition en libre accès. Cependant, depuis que Teradata a rejoint la communauté Presto, elle offre désormais un support.

Presto se connecte aux sources de données transactionnelles (Oracle, DB2, MySQL, PostgreSQL, MongoDB et autres bases de données SQL et NoSQL) et fournit un traitement de données SQL distribué et en mémoire, combiné à des optimisations automatiques des plans d'exécution. Son objectif est avant tout d'exécuter des requêtes rapides, que vous traitiez des gigaoctets ou des téraoctets de données, en mettant à l'échelle et en parallélisant les charges de travail.

Presto n'avait pas à l'origine de connecteur natif pour la base de données IRIS, mais heureusement, ce problème a été résolu avec un projet communautaire d'InterSystems "presto-iris"(https://openexchange.intersystems.com/package/presto-iris). C'est pourquoi nous pouvons maintenant exposer une couche MPP devant les référentiels IRIS d'InterSystems pour permettre des requêtes, des rapports et des tableaux de bord de haute performance à partir de données transactionnelles dans IRIS.

Dans cet article, nous suivrons un guide étape par étape pour configurer Presto, le connecter à IRIS et établir une couche MPP pour vos clients. Nous démontrerons également les principales fonctionnalités de Presto, ses commandes et outils principaux, toujours avec IRIS en tant que base de données source.

Caractéristiques de Presto

Les caractéristiques de Presto comprennent les fonctionnalités suivantes:

  1. Architecture simple mais extensible.
  2. Connecteurs enfichables (Presto prend en charge les connecteurs enfichables pour fournir des métadonnées et des données pour les requêtes).
  3. Exécutions en pipeline (cela évite les surcharges de latence d'E/S (I/O) inutiles).
  4. Fonctions définies par l'utilisateur (les analystes peuvent créer des fonctions personnalisées définies par l'utilisateur pour faciliter la migration).
  5. Traitement en colonne vectorisé.


Avantages de Presto

Vous trouverez ci-dessous une liste des avantages offerts par Apache Presto:

Opérations SQL spécialisées;
Installation et débogage faciles;
Abstraction de stockage simple;
Évolutivité rapide des données en pétaoctets avec une faible latence.

Architecture de Presto


  • Clients: Ils sont les utilisateurs de PrestoDB. Les clients utilisent le protocole JDBC/ODBC/REST pour communiquer avec les coordinateurs.
  • Les coordinateurs: Ils sont responsables de la gestion des nœuds de travail associés, de l'analyse, du traitement des requêtes et de la génération des plans d'exécution. Ils sont également chargés de la livraison des données pour le traitement entre les opérateurs, ce qui crée des plans logiques composés d'étapes, où chaque étape est exécutée de manière distribuée à l'aide de tâches entre les opérateurs.
  • Opérateurs: Il s'agit de nœuds de calcul pour l'exécution de tâches et le traitement de données, permettant le traitement et la consommation de données à grande échelle./li>
  • Communication: Chaque travailleur Presto communique avec le coordinateur à l'aide d'un serveur de découverte pour se préparer au travail.
  • Connecteurs: Chaque type de source de données possible possède un connecteur utilisé par Presto pour consommer les données. Le projet https://openexchange.intersystems.com/package/presto-iris permet l'utilisation d'InterSystems IRIS par Presto.
  • Catalogue: Il contient des informations sur l'emplacement des données, y compris les schémas et la source de données. Lorsque les utilisateurs exécutent une instruction SQL dans Presto, ils l'exécutent contre un ou plusieurs catalogues

Cas d'utilisation de Presto

InterSystems IRIS et Presto offrent ensemble les possibilités d'utilisation suivantes:

  • Requêtes ad hoc: Vous pouvez exécuter des requêtes ad hoc très performantes sur des téraoctets de données.
  • Rapports et tableaux de bord: Il existe un moteur permettant de réaliser des requêtes de données très performantes pour les rapports, l'informatique décisionnelle en libre-service et les outils d'analyse, par exemple Apache Superset (découvrez l'exemple dans cet article).
  • Mode lac de données (Open lakehouse): Presto has the connectors and catalogs to unify required data sources and deliver scalable queries and data using SQL between workers.

InterSystems IRIS est un partenaire idéal pour Presto. Comme il s'agit d'un référentiel de données à haute performance qui supporte le traitement distribué à l'aide de shards et associé aux opérateurs Presto, n'importe quel volume de données peut être interrogé en seulement quelques millisecondes.

Installation et lancement de PrestoDB

Il y a plusieurs options (Docker et Java JAR) pour l'installation de Presto. Vous pouvez trouver plus de détails à ce sujet sur https://prestodb.io/docs/current/installation/deployment.html. Dans cet article, nous utiliserons Docker. Pour faciliter la connaissance et permettre un démarrage rapide, nous avons mis à disposition un exemple d'application sur Open Exchange (il a été dérivé d'un autre logiciel https://openexchange.intersystems.com/package/presto-iris). Suivez les étapes suivantes pour vous en faire une idée:

  1. Accédez à https://openexchange.intersystems.com/package/iris-presto-sample pour télécharger l'échantillon utilisé dans ce tutoriel.
  2. Lancer l'environnement de démonstration avec docker-compose:
    docker-compose up -d --build
    Note : Pour les besoins de la démo, il utilise Apache Superset avec superset-iris et les exemples qui l'accompagnent. Il faut donc un certain temps pour que la démonstration se télécharge.
  3. L'interface utilisateur Presto sera disponible via ce lien: http://localhost:8080/ui/#.
  4. Attendez 15 à 20 minutes (il y a beaucoup d'échantillons de données à télécharger). Lorsque SuperSet aura fini de télécharger les échantillons après 10 à 15 minutes, il devrait être disponible sur le lien http://localhost:8088/databaseview/list (saisissez admin/admin comme nom d'utilisateur/mot de passe sur la page de connexion).
  5. Accédez maintenant à la rubrique Tableaux de bord:
  6. Si nous visitons http://localhost:8080/ui, nous pouvons remarquer que Presto a exécuté des requêtes et affiche des statistiques:

Ci-dessus, vous pouvez voir l'interface web de Presto pour surveiller et gérer les requêtes. On peut y accéder à partir du numéro de port spécifié dans les propriétés de configuration du coordinateur (pour cet article, le numéro de port est 8080).

Détails sur l'exemple de code

Fichier Dockerfile

Le fichier Dockerfile est utilisé pour créer une image Docker PrestoDB avec le plugin presto-iris et le fichier JDBC InterSystems IRIS inclus:

# Image officielle de PrestoDB sur Docker HubFROM prestodb/presto

# À partir de https://github.com/caretdev/presto-iris/releases# Ajout du plugin presto-iris dans l'image DockerADDhttps://github.com/caretdev/presto-iris/releases/download/0.1/presto-iris-0.1-plugin.tar.gz /tmp/presto-iris/presto-iris-0.1-plugin.tar.gz # À partir de https://github.com/intersystems-community/iris-driver-distribution# Ajout du pilote IRIS JDBC dans l'image DockerADDhttps://raw.githubusercontent.com/intersystems-community/iris-driver-distribution/refs/heads/main/JDBC/JDK18/com/intersystems/intersystems-jdbc/3.8.4/intersystems-jdbc-3.8.4.jar /opt/presto-server/plugin/iris/intersystems-jdbc-3.8.4.jar RUN --mount=type=bind,src=.,dst=/tmp/presto-iris
tar -zxvf /tmp/presto-iris/presto-iris-0.1-plugin.tar.gz -C /opt/presto-server/plugin/iris/ --strip-components=1

Fichier Docker-compose.yml

Ce fichier crée 3 instances de conteneurs : une instance pour InterSystems IRIS (service IRIS), une instance pour PrestoDB (service Presto) et une instance pour Superset (service Superset). Le Superset est un outil de visualisation analytique utilisé pour afficher des données dans des tableaux de bord.

# à partir du projet https://github.com/caretdev/presto-irisservices:# création d'une instance de conteneur InterSystems IRIS  iris:    image:intersystemsdc/iris-community    ports:      -1972      -52773    environment:      IRIS_USERNAME:_SYSTEM      IRIS_PASSWORD:SYS# création d'une instance de conteneur PrestoDB qui utilise la base de données IRIS  presto:    build:.    volumes:# PrestoDB utilisera iris.properties pour obtenir des informations sur la connexion       -./iris.properties:/opt/presto-server/etc/catalog/iris.properties    ports:      -8080:8080# création d'une instance de conteneur Superset (Outil d'analyse du tableau de bord)  superset:    image:apache/superset:3.0.2    platform:linux/amd64    environment:      SUPERSET_SECRET_KEY:supersecret# création d'une connexion InterSystems IRIS pour le chargement des échantillons de données      SUPERSET_SQLALCHEMY_EXAMPLES_URI:iris://_SYSTEM:SYS@iris:1972/USER    volumes:      -./superset_entrypoint.sh:/superset_entrypoint.sh      -./superset_config.py:/app/pythonpath/superset_config.py    ports:      -8088:8088    entrypoint:/superset_entrypoint.sh

Fichier iris.properties

Ce fichier contient les informations nécessaires pour connecter PrestoDB à InterSystems IRIS DB et créer une couche MPP pour des requêtes performantes et évolutives à partir des tableaux de bord Superset.

# from the project https://github.com/caretdev/presto-iris
connector.name=iris
connection-url=jdbc:IRIS://iris:1972/USER
connection-user=_SYSTEM
connection-password=SYS

Fichier superset_entrypoint.sh

Ce script installe la bibliothèque superset-iris (pour le support d'IRIS par Superset), démarre l'instance Superset et charge des échantillons de données dans la base de données InterSystems IRIS. Au moment de l'exécution, les données consommées par Superset proviendront de PrestoDB, qui sera une couche MPP pour IRIS DB.

#!/bin/bash
# Installation de l'extension InterSystems IRIS Superset
pip install superset-iris

superset db upgrade

superset fab create-admin
--username admin
--firstname Superset
--lastname Admin
--email admin@superset.com
--password ${ADMIN_PASSWORD:-admin} superset init

# Téléchargement d'échantillons dans IRIS superset load-examples

# Modification de l'URI de la base de données d'échantillons en Presto superset set-database-uri -d examples -u presto://presto:8080/iris

/usr/bin/run-server.sh

Au sujet de Superset

Il s'agit d'une plateforme moderne d'exploration et de visualisation des données qui peut remplacer ou renforcer les outils propriétaires de veille stratégique pour de nombreuses équipes.Superset s'intègre parfaitement à une grande variété de sources de données.
Superset offre les avantages suivants:

  • Une interface sans code pour construire rapidement des graphiques
  • Un éditeur SQL puissant basé sur le web pour des requêtes avancées
  • Une couche sémantique légère pour définir rapidement des dimensions et des métriques personnalisées
  • Un support prêt à l'emploi pour presque toutes les bases de données SQL ou moteurs de données
  • Un large éventail de visualisations magnifiques pour présenter vos données, allant de simples diagrammes à barres à des visualisations géospatiales
  • Une couche de mise en cache légère et configurable pour alléger la charge de la base de données
  • Des options d'authentification et des rôles de sécurité très extensibles
  • Une API pour la personnalisation programmatique
  • Une architecture cloud-native conçue à partir de zéro pour s'adapter à l'échelle

Sources et supports d'apprentissage supplémentaires

  1. Tutoriel complet sur PrestoDB: https://www.tutorialspoint.com/apache_presto/apache_presto_quick_guide.htm
  2. Documentation sur PrestoDB: https://prestodb.io/docs/current/overview.html
  3. Plugin Presto-iris: https://openexchange.intersystems.com/package/presto-iris
  4. Échantillon Iris-presto: https://openexchange.intersystems.com/package/iris-presto-sample
  5. Au sujet de Superset: https://github.com/apache/superset
  6. Superset et InterSystems IRIS: https://openexchange.intersystems.com/package/superset-iris  
0
0 38
Article Iryna Mykhailova · Fév 3, 2025 2m read

Bonjour ! J'ai étendu mon référentiel de démonstration, andreas5588/demo-dbs-iris, pour faciliter le test des fonctionnalités FOREIGN SERVER et FOREIGN TABLE dans IRIS.

Pour y parvenir, j'ai créé un espace de noms appelé FEDERATION. L'idée est la suivante :

  1. Configurez des connexions JDBC pour chaque espace de noms.
  2. Créez un FOREIGN SERVER dans l'espace de noms FEDERATION pour chaque connexion.
  3. Définissez une FOREIGN TABLE au moins pour une table basée sur chaque serveur étranger.

Le script :  demo-dbs-iris/src/sql/02_create_foreign_server.sql

0
0 30
Article Iryna Mykhailova · Déc 13, 2024 3m read

Comme beaucoup d'autres se retrouvent probablement, nous étions obligés de faire un mappage de données en direct dans notre moteur d'interface, ce que nous ne voulions vraiment pas faire, mais nous n'avions pas de bon choix alternatif. Nous voulons uniquement conserver les mappages aussi longtemps que nécessaire, puis purger les lignes expirées en fonction d'une valeur TTL. Nous avions en fait 4 cas d'utilisation pour cela nous-mêmes avant de créer cela. Cas d'utilisation :

0
0 37
Article Iryna Mykhailova · Déc 3, 2024 2m read

L'audit est une fonctionnalité essentielle pour garantir la sécurité de votre serveur. Depuis un certain temps déjà, nous avons inclus la possibilité d'auditer les instructions SQL exécutées sur le serveur.

Avec la version 2024.3 déjà disponible, nous proposons des options plus précises pour définir ces événements à auditer.

0
0 30
Article Iryna Mykhailova · Oct 28, 2024 2m read

Les fonctions d'agrégation définies par l'utilisateur sont prises en charge dans IRIS depuis 2021.1.0. J'avais souhaité une étoile pour celle-ci il y a des années avant de trouver un moyen secret et astucieux de remplacer MAX et MIN dans un type de données personnalisé, mais je n'ai pas eu l'occasion d'en essayer un jusqu'à aujourd'hui. J'ai pensé que c'était une expérience/un exemple intéressant - la question de savoir comment obtenir une médiane dans IRIS SQL s'est déjà posée une fois - donc je la partage ici sans trop de commentaires supplémentaires.

0
0 37
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
Article Iryna Mykhailova · Sept 21, 2024 4m read

Selon le rapport OWASP Top Ten de 2021, un document de référence reconnu dans le domaine de la sécurité des applications web, les injections SQL arrivent en troisième position des risques les plus critiques. Ce rapport, disponible sur OWASP Top 10: Injection, souligne la gravité de cette menace et la nécessité de mettre en place des mesures de protection efficaces.

Une injection SQL se produit lorsqu'un attaquant malveillant parvient à insérer du code SQL non autorisé dans une requête envoyée à une base de données. Ce code, dissimulé au sein des entrées utilisateur, peut alors être exécuté par la base de données, provoquant des actions indésirables comme le vol de données confidentielles, la modification ou la suppression d'informations sensibles, ou encore la perturbation du fonctionnement de l'application.

0
0 119