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 Guillaume Rongier · Mai 6, 2022 4m read

Voici le troisième article de notre courte série sur les innovations d'IRIS SQL qui offrent une expérience plus adaptative et plus performante aux analystes et aux applications requérant des données relationnelles sur IRIS. Il s'agit peut-être du dernier article de cette série pour 2021.2, mais nous prévoyons plusieurs autres améliorations dans ce domaine. Dans cet article, nous allons approfondir un peu plus les statistiques de tableaux supplémentaires que nous commençons à rassembler dans cette version : Histogrammes

0
0 118
Article Guillaume Rongier · Mai 3, 2022 5m read

Voici le deuxième article de notre série sur les améliorations apportées à la version 2021.2 de SQL, qui offre une expérience SQL adaptative et performante. Dans cet article, nous allons examiner les innovations en matière de collecte Table Statistics, qui sont bien sûr le principal élément d'entrée pour la capacité de Run Time Plan Choice que nous avons décrite dans l'article précédent.

0
0 66
Article Guillaume Rongier · Mai 2, 2022 5m read

La version 2021.2 de la plate-forme de données InterSystems IRIS Data Platform comprend de nombreuses nouvelles fonctionnalités intéressantes pour le développement rapide, flexible et sécurisé de vos applications critiques. Embedded Python est certainement la vedette (et pour une bonne raison !), mais en SQL, nous avons également fait un grand pas en avant vers un moteur plus adaptatif qui recueille des informations statistiques détaillées sur les données de votre tableau et les exploite pour fournir les meilleurs plans de requête. Dans cette brève série d'articles, nous allons examiner de plus près trois éléments qui sont nouveaux dans 2021.2 et qui travaillent ensemble vers cet objectif, en commençant par Run Time Plan Choice.

Il est difficile de trouver le bon ordre pour en parler (vous ne pouvez pas imaginer le nombre de fois où je les ai remaniés en rédigeant cet article), car ils s'emboîtent si bien les uns dans les autres. Vous pouvez donc les lire dans un ordre aléatoire smiley.

0
0 56
Article Lorenzo Scalese · Avr 28, 2022 4m read

Alors je sais que ça fait un peu longtemps, et je déteste laisser tomber mes fans adorateurs... mais pas assez pour recommencer à écrire.  Mais l'attente est terminée et je suis de retour !  Maintenant, profitez de mes mots vraiment magnifiques !

Pour cette série, je vais examiner certains problèmes courants que nous rencontrons au WRC et discuter de certaines solutions communes.  Bien sûr, même si vous trouvez une solution ici, vous êtes toujours le bienvenu pour me contacter et exprimer votre gratitude, ou simplement entendre ma voix !

Le problème courant de cette semaine : "Ma requête ne renvoie aucune donnée."

Maintenant, je suppose que vous avez vérifié que votre requête DEVRAIT retourner des données.  En d'autres termes, si vous effectuez un "SELECT * FROM MyTable" et que vous n'obtenez aucune donnée, je ne pense pas que votre requête plus compliquée avec des JOINs, une clause WHERE et un GROUP BY le fera également.  Donc, si vous avez déterminé que les données se trouvent dans votre tableau, qu'est-ce qui peut bien se passer d'autre ?

1) Vous êtes dans un mauvais espace de noms.

Vous pouvez vous moquer si vous le souhaitez, mais c'est le problème le plus courant.  En général, les gens ne signalent pas ce problème, mais il arrive qu'il se présente.  Si vous n'obtenez pas de données, la première chose à faire est de vérifier votre espace de noms et, tant que vous y êtes, de vous assurer que vous vous connectez à la bonne instance.  Débarrassez-vous d'abord du problème le plus simple.

2) Vous devez construire des indices.

C'est le problème qui perturbe la plupart des gens.  Si vous ouvrez votre définition de classe et ajoutez un index, il n'est pas automatiquement construit pour vous.  Par conséquent, lorsque vous ajoutez un index, il est disponible pour être utilisé par le compilateur SQL Compiler, mais il ne contient pas de données.  Donc quand on regarde, pas de données, boom, requête faite, pas de résultats !  Vous devez appeler ##class().%BuildIndices($LB("<New Index>")) afin que ce nouvel index soit rempli avec les données appropriées.  Attention toutefois à ne pas faire cela sur un système actif !  Pour obtenir des conseils sur la construction d'un index sur un système actif, veuillez contacter le WRC et nous faire part de votre version !

3) Vous êtes dans un mauvais SELECTMODE.

Lorsque vous exécutez une requête, vous pouvez utiliser plusieurs modes : Logical, Display et ODBC.  La façon typique de montrer la différence est avec les propriétés %Date.  Par exemple, la date d'aujourd'hui est 64295, 01/12/2017, 2017-01-12 dans les modes Logical, Display, et ODBC respectivement.  Si vous utilisez le mauvais SELECTMODE, vos requêtes de date ne renverront parfois aucune donnée, même si elles le devraient.  Une bonne façon de tester cela est d'exécuter une requête dans le Shell SQL et de définir votre mode de sélection, comme suit :

SAMPLES>d $SYSTEM.SQL.Shell()

SAMPLES>>selectmode = odbc

De cette façon, vous avez un contrôle absolu (et simple) sur le selectmode de votre requête.  Vous pouvez également exécuter des requêtes xDBC (ODBC/JDBC) en sachant que nous sommes en mode ODBC.  

4) Problèmes de collation

Si vous avez suivi l'un des excellents articles de Brendan concernant la création de votre propre stockage, sachez qu'il existe un problème supplémentaire qui peut vous rattraper.  Vous devez vous assurer que la collation que vous avez défini pour votre champ est correctement placé dans votre index.  Par conséquent, si votre champ %String a été classé en SQLUPPER, l'index doit être classé en SQLUPPER.  Si ce n'est pas le cas, vous pouvez constater que vous n'avez pas de données. 

5) NLS sur CacheTemp

C'est plutôt rare, mais si vous utilisez une collation NLS différent, vous devez vous assurer que la collation NLS de CACHTEMP correspond à la collation NLS de votre base de données.  Le texte officiel est disponible dans les documents ici:

http://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=GSQL_basics#GSQL_basics_collation_sqlnls

6) Bug d'InterSystems

Hé, je ne suis pas trop fier de l'admettre.  De temps en temps, nous avons des bugs et ça pourrait être le cas.  Si vous le pensez, contactez le WRC !

7) Votre requête comporte un bug.

Quoi, vous pensiez que vous aviez décroché ! ?  Si notre code peut avoir un bug, votre requête peut aussi en avoir un !  Vérifiez encore une fois votre requête !

Ce sont les plus importants.  Essayez d'examiner certains des éléments mentionnés ici, et si vous avez des difficultés à les résoudre, n'hésitez pas à contacter le service d'assistance et nous serons heureux de les analyser avec vous !

-----------------------------------------------------------------------------

Vous avez besoin d'un TL;DR pour ça !?  C'était si court !

7 raisons expliquant pourquoi votre requête peut ne pas renvoyer de données :

  1. Vous êtes dans un mauvais espace de noms.

  2. Vous devez construire des indices.

  3. Vous êtes dans un mauvais SELECTMODE.

  4. Problèmes de collation

  5. NLS sur CacheTemp

  6. Bug d'InterSystems

  7. Votre requête comporte un bug.

Correction : Dernière remarque : avez-vous rencontré un problème où une requête ne renvoyant aucune donnée ?  Ajoutez votre expérience dans les commentaires !

0
0 47
Article Irène Mykhailova · Avr 25, 2022 7m read

@Benjamin De Boe a écrit un excellent article sur les Universal Cached Queries, mais qu'est-ce qu'une Universal Cached Query (UCQ) et pourquoi devrais-je m'en préoccuper si j'écris du bon vieux Embedded SQL?  Dans Caché et Ensemble, les Cached Queries seraient générées pour résoudre xDBC et Dynamic SQL.  Maintenant, l'Embedded SQL d'InterSystems IRIS a été mis à jour pour utiliser les Cached Queries, d'où l'ajout du mot Universal au nom.  Désormais, tout SQL exécuté sur IRIS le sera à partir d'une classe UCQ.

0
0 70
Article Lorenzo Scalese · Avr 15, 2022 26m read

Depuis Caché 2017, le moteur SQL comprend un nouvel ensemble de statistiques. Celles-ci enregistrent le nombre de fois qu'une requête est exécutée et le temps qu'elle prend pour s'exécuter.

C'est une mine d'or pour quiconque surveille et tente d'optimiser les performances d'une application qui comprend de nombreuses instructions SQL, mais il n'est pas aussi facile d'accéder aux données que certaines personnes le souhaitent.

0
0 59
Article Lorenzo Scalese · Mars 30, 2022 3m read

Comme vous le savez, dans Caché / IRIS, vous avez la possibilité de définir une propriété comme Multidimensionnelle, comme documenté ici et l'explication de la façon de l'utiliser est ici.

Bien que l'accès soit assez confortable (au sens traditionnel du COS), il y a 2 restrictions principales qui font mal :

  1. Il n'est pas sauvegardé sur le disque, sauf si votre application inclut du code pour le sauvegarder spécifiquement.
  2. Il ne peut pas être stocké dans des tableaux SQL ou exposé à travers ceux-ci.

il y en a d'autres Je vais vous montrer comment surmonter ces limites.

  1. Prenons cette classe simple comme exemple :
Class DC.Multi Extends (%Persistent, %Populate) [ Final ]
{
    Property Name As %String;
    Property DOB As %Date;
    Property Multi As %String [ MultiDimensional 
];

La carte de stockage montre déjà le problème n°1 : pas de place pour "Multi"

Storage Default
{
  <Data name="MultiDefaultData">
    <Value name="1">
      <Value>Name</Value>
    </Value>
    <Value name="2">
      <Value>DOB</Value>
    </Value>
  </Data>
  <DataLocation>^DC.MultiD</DataLocation>
  <DefaultData>MultiDefaultData</DefaultData>
  <IdLocation>^DC.MultiD</IdLocation>
  <IndexLocation>^DC.MultiI</IndexLocation>
  <StreamLocation>^DC.MultiS</StreamLocation>
  <Type>%Storage.Persistent</Type>
}

donc nous ajoutons 2 méthodes :

/// save Multidimensional property
Method %OnAfterSave(insert As %Boolean) As %Status [ Private, ServerOnly = 1 ]
{    Merge ^(..%Id(),"Multi")=i%Multi quit $$$OK   }
/// load Multidimensional property
Method %OnOpen() As %Status [ Private, ServerOnly = 1 ]
{   Merge i%Multi=^(..%Id(),"Multi") quit $$$OK  }

nous attachons juste la structure orpheline à notre objet réel. Pour être franc, ce n'est pas mon invention, mais l'approche (simplifiée) qui était utilisée dans la classe %CSP.Session lorsqu'elle a été écrite vers le début du millénaire. Avec le simple ajout suivant, votre structure multidimensionnelle devient persistante.

L'objet en mémoire ressemble à ceci :

CACHE>zw o2
o2=3@DC.Multi  ; <OREF>
+----------------- general information ---------------
|      oref value: 3
|      class name: DC.Multi
|           %%OID: $lb("2","DC.Multi")
| reference count: 2
+----------------- attribute values ------------------
|       %Concurrency = 1  <Set>
|                DOB = 62459
|         Multi("a") = 1
|     Multi("rob",1) = "rcc"
|     Multi("rob",2) = 2222
|               Name = "Klingman,Uma C."
+-----------------------------------------------------

et c'est le stockage associé, une jolie globale multidimensionnelle :

CACHE>zw ^DC.MultiD(2)
^DC.MultiD(2)=$lb("Klingman,Uma C.",62459)
^DC.MultiD(2,"Multi","a")=1
^DC.MultiD(2,"Multi","rob",1)="rcc"
^DC.MultiD(2,"Multi","rob",2)=2222

Jusqu'à présent, tout va bien.

  1. SELECT * from DC.Multi n'a aucune idée de ce que peut être une colonne "Multi".

donc nous ajoutons une propriété calculée SQL et un style approprié.

Property SqlMulti As %String(MAXLEN = "") [ Calculated, SqlComputed,
SqlComputeCode 
= { set {*}= ##class(DC.Multi).ShowMulti({ID}) }];
ClassMethod ShowMulti(id) As %String(MAXLEN="")
{ set res="{"
      ,obj=..%OpenId(id)
   if $isobject(obj) {
      set qry=$query(obj.Multi(""),1,value)
      while qry'="" {
         set res=res_$piece(qry,".",2,99)_"="_value_","
            ,qry=$query(@qry,1,value)
         }
     set $extract(res,*)=""
   }
   quit res_"}"   
}

Et cela ressemble à ceci

Inutile de dire que la conception est totalement entre vos mains.

Comme il y a un progrès évident par rapport au passé, le prochain article vous montrera une solution plus appropriée au nouveau siècle.

0
0 94
Article Irène Mykhailova · Mars 28, 2022 5m read

Lorsque je décris InterSystems IRIS à des personnes plus orientées vers la technique, je commence toujours par dire qu'il s'agit d'un DBMS (système de gestion de base de données) multi-modèle.

À mon avis, c'est son principal avantage (du côté du DBMS). Et les données ne sont stockées qu'une seule fois. Vous choisissez simplement l'API d'accès que vous voulez utiliser.

  • Voulez-vous une sorte de résumé pour vos données ? Utilisez SQL !
  • Souhaitez-vous travailler en profondeur avec un seul enregistrement ? Utilisez des objets !
  • Voulez-vous accéder ou définir une valeur et vous connaissez la clé ? Utilisez les globales !

À première vue, c'est une belle histoire - courte et concrète, elle fait passer le message, mais lorsque les gens commencent vraiment à travailler avec InterSystems IRIS, les questions apparaissent. Comment les classes, les tables et les globales sont-ils liés ? Que sont-ils les uns pour les autres ? Comment les données sont-elles réellement stockées ?

Dans cet article, je vais essayer de répondre à ces questions et d'expliquer ce qui se passe réellement.

Première partie. Le biais des modèles.

Les personnes qui travaillent avec des données ont souvent un biais en faveur du modèle avec lequel elles travaillent.

Les développeurs pensent en objets. Pour eux, les bases de données et les tableaux sont des boîtes avec lesquelles vous interagissez via CRUD (Créer-Lire-Mettre à jour-Supprimer, de préférence via ORM), mais le modèle conceptuel sous-jacent est constitué d'objets (bien sûr, c'est surtout vrai pour les développeurs utilisant des langages orientés objet - donc la plupart d'entre nous).

D'autre part, pour avoir passé beaucoup de temps dans des DBMS relationnels, les DBA considèrent souvent les données comme des tables. Dans ce cas, les objets ne sont que des enveloppes sur les lignes.

Et avec InterSystems IRIS, une classe persistante est aussi un table, qui stocke les données en globale, donc une clarification est nécessaire.

Deuxième partie. Un exemple.

Disons que vous avez créé une classe Point :

Class try.Point Extends %Persistent [DDLAllowed]
{
    Property X;
    Property Y;
}

Vous pouvez également créer la même classe avec DDL/SQL :

CREATE Table try.Point (
    X VARCHAR(50),
    Y VARCHAR(50))

Après la compilation, notre nouvelle classe aurait généré automatiquement une structure de stockage qui fait correspondre les données qui sont nativement stockées dans les globaux aux colonnes (ou aux propriétés si vous êtes un penseur orienté objet) :

Storage Default
{
<Data name="PointDefaultData">
    <Value name="1">
        <Value>%%CLASSNAME</Value>
    </Value>
    <Value name="2">
        <Value>X</Value>
    </Value>
    <Value name="3">
        <Value>Y</Value>
    </Value>
</Data>
<DataLocation>^try.PointD</DataLocation>
<DefaultData>PointDefaultData</DefaultData>
<IdLocation>^try.PointD</IdLocation>
<IndexLocation>^try.PointI</IndexLocation>
<StreamLocation>^try.PointS</StreamLocation>
<Type>%Library.CacheStorage</Type>
}

Qu'est-ce qui se passe ici ?

De bas en haut (les mots en gras sont importants, ignorez le reste) :

  • Type : le type de stockage généré, dans notre cas le stockage par défaut pour les objets persistants
  • StreamLocation - l'endroit où nous stockons les flux de données séquencé
  • IndexLocation - la globale pour les indices
  • IdLocation - la globale où nous stockons l'ID compteur autoincrémental
  • DefaultData - l'élément XML de stockage qui fait correspondre la valeur globale aux colonnes/propriétés
  • DataLocation - la globale dans lequel les données sont stockées

Maintenant notre "DefaultData" est PointDefaultData alors regardons de plus près sa structure. Essentiellement, cela dit que le noeud global a cette structure :

  • 1 - %%CLASSNAME
  • 2 - X
  • 3 - Y

On peut donc s'attendre à ce que notre globale ressemble à ceci :

^try.PointD(id) = %%CLASSNAME, X, Y

Mais si nous imprimons notre globale, il sera vide car nous n'avons pas ajouté de données :

zw ^try.PointD

Ajoutons un objet :

set p = ##class(try.Point).%New()
set p.X = 1
set p.Y = 2
write p.%Save()

Et voici notre globale

zw ^try.PointD
^try.PointD=1
^try.PointD(1)=$lb("",1,2)

Comme vous le voyez, notre structure attendue %%CLASSNAME, X, Y est définie avec $lb("",1,2) qui correspond aux propriétés X et Y de notre objet (%%CLASSNAME est une propriété du système, ignorez-la).

Nous pouvons également ajouter une ligne via SQL :

INSERT INTO try.Point (X, Y) VALUES (3,4)

Maintenant, notre globale ressemble à ceci :

zw ^try.PointD
^try.PointD=2
^try.PointD(1)=$lb("",1,2)
^try.PointD(2)=$lb("",3,4)

Ainsi, les données que nous ajoutons par le biais d'objets ou de SQL sont stockées dans des globales en fonction des définitions de stockage (remarque : vous pouvez modifier manuellement la définition de stockage en remplaçant X et Y dans PointDefaultData - vérifiez ce qu'il arrive aux nouvelles données !)

Maintenant, que se passe-t-il lorsque nous voulons exécuter une requête SQL ?

SELECT * FROM try.Point

Elle est traduite en code ObjectScript qui itère sur la globale ^try.PointD et remplit les colonnes en fonction de la définition du stockage - la partie PointDefaultData précisément.

Maintenant pour les modifications. Supprimons toutes les données du table :

DELETE FROM try.Point

Et voyons notre globale à ce stade :

zw ^try.PointD
^try.PointD=2

Notez que seul le compteur d'ID est laissé, donc le nouvel objet/ligne aura un ID=3. De même, notre classe et notre table continuent d'exister. Mais que se passe-t-il quand on lance :

DROP TABLE try.Point

Il détruirait le table, la classe et supprimerait la globale.

zw ^try.PointD

Si vous avez suivi cet exemple, j'espère que vous comprenez maintenant mieux comment les globales, les classes et les tables s'intègrent et se complètent. L'utilisation de la bonne API pour le travail à effectuer permet un développement plus rapide, plus agile et moins bogué.

0
0 213