#ObjectScript

0 Abonnés · 105 Publications

InterSystems ObjectScript est un langage de script permettant d'opérer avec des données en utilisant n'importe quel modèle de données de la plateforme de données InterSystems (objets, relationnel, clé-valeur, document, globales) et de développer une logique métier pour les applications côté serveur sur la plateforme de données InterSystems.

Documentation.

Article Sarah Schlegel · Sept 6, 2022 8m read

Bonjour la communauté !

Cet article vise à donner un aperçu des webservices REST JSON développés pour TrakCare.

Ces webservices ont été développés dans le but de permettre aux utilisateurs d’accéder aux données de TrakCare depuis l’extérieur, notamment via des applications externes.

Ils sont développés en REST avec ObjectScript, et permettent d’accéder aux données via quatre modes :

0
0 414
Article Danny Wijnschenk · Juil 19, 2022 4m read

Utiliser des méthodes avec syntax objet et SQL est l'une des caractéristiques les plus intéressantes dans Object Script. Mais dans un cas précis, ça m'a donné des résultats inattendus, donc j'ai essayé d'isoler le cas et le décrire ici.

Disons que vous devez écrire une méthode de classe qui met à jour une seule propriété. Habituellement, j'écrirais cela en utilisant SQL comme ceci :

0
0 60
Article Guillaume Rongier · Juin 10, 2022 8m read

Cette publication est le résultat direct d'une collaboration avec un client d'InterSystems qui est venu me consulter pour le problème suivant :

SELECT COUNT(*) FROM MyCustomTable

Cela prend 0,005 secondes, pour un total de 2300 lignes.  Cependant :

SELECT * FROM MyCustomTable

Prenait des minutes.  La raison en est subtile et suffisamment intéressante pour que j'écrive un article à ce sujet.  Cet article est long, mais si vous faites défiler la page jusqu'en bas, je vous donnerai un résumé rapide. Si vous êtes arrivé jusqu'ici et que vous pensez en avoir lu assez, faites défiler la page jusqu'à la fin pour connaître l'essentiel.  Vérifiez la phrase en gras.


Lors de la création de vos classes, il faut tenir compte de la question du stockage.  Comme beaucoup d'entre vous le savent, toutes les données dans Caché sont stockées dans des Globales.  

<Digression> 

Si vous ne le savez pas, je pense que cet article sera un peu trop long.  Je vous recommande de consulter un excellent tutoriel dans notre documentation :

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

Si vous n'avez jamais utilisé Caché/Ensemble/HealthShare, le tutoriel ci-dessus est très utile, et même si vous l'avez fait, il vaut la peine de le consulter !  

Maintenant, comme toutes les données sont stockées dans des globales, il est important de comprendre comment les définitions de vos classes correspondent aux globales.  Construisons une application ensemble !  Nous allons examiner certains pièges courants et discuter de la façon dont le développement de vos classes affecte vos stratégies de stockage, avec un regard particulier sur les performances SQL.  

Imaginons que nous soyons le Bureau du recensement des États-Unis et que nous voulions disposer d'une base de données pour stocker les informations concernant tous les habitants des États-Unis.  Nous construisons donc une classe de la manière suivante :

Class USA.Person extends %Persistent
{
 Property Name as %String;
 Property SSN as %String;
 Property Address as %String;
 Property DateOfBirth as %Date;
}

SSN est l'abréviation de "Social Security Number" (numéro de sécurité sociale) qui, bien qu'il n'ait pas été conçu à l'origine pour être un numéro d'identification des personnes, est leur numéro d'identification de facto.  Cependant, comme nous sommes traditionalistes, nous ne l'utiliserons pas pour l'identification.  Cela dit, nous tenons à ce que cet élément soit indexé, car c'est un excellent moyen de rechercher une personne.  Nous savons que nous devrons parfois rechercher des personnes par le nom, c'est pourquoi nous voulons également un index des noms.  Et parce que notre patron aime ses rapports basés sur des tranches d'âge, nous pensons qu'un index des dates de naissance pourrait également être utile.  Ajoutons-les donc à notre classe

Class USA.Person extends %Persistent
{
 Property Name as %String;
 Property SSN as %String;
 Property Address as %String;
 Property DateOfBirth as %Date;

 Index NameIDX On Name;
 Index SSNIDX On SSN [Unique];
 Index DOBIDX on DateOfBirth;

}

Très bien.  Alors ajoutons une ligne et voyons à quoi ressemblent nos globales.  Notre instruction INSERT est la suivante :

INSERT INTO USA.Person (Name,SSN,Address,DateOfBirth) VALUES
   ('Baxter, Kyle','111-11-1111','1 Memorial Drive, Cambridge, MA 02142','1985-07-20')

Et la globale:

USER>zw ^USA.PersonD
^USA.PersonD=1
^USA.PersonD(1)=$lb("","Baxter, Kyle","111-11-1111","1 Memorial Drive, Cambridge, MA 02142",52796)

Le stockage par défaut d'une classe stocke vos données dans ^Package.ClassD.  Si le nom de la classe est trop long, il peut être haché, et vous pouvez le trouver dans la définition de stockage au bas de votre définition de classe.  Les index, à quoi ressemblent-ils ?

USER>zw ^USA.PersonI                      
^USA.PersonI("DOBIDX",52796,1)=""
^USA.PersonI("NameIDX"," BAXTER, KYLE",1)=""
^USA.PersonI("SSNIDX"," 111-11-1111",1)=""

Excellent, notre stockage est plutôt bon pour l'instant.  Donc on ajoute nos 320 millions de personnes et on peut trouver des gens assez rapidement.  Mais maintenant nous avons un problème, car nous voulons traiter le président et tous les ex-présidents avec une considération spéciale.  Nous ajoutons donc une classe spéciale pour le président :

Class USA.President extends USA.Person
{
Property PresNumber as %Integer;

Index PresNumberIDX on PresNumber;
}

Bien.  En raison de l'héritage, nous récupérons toutes les propriétés de USA.Person, et nous en ajoutons une pour nous permettre de savoir quel numéro de président il était.  Puisque je veux faire un peu de politique, je vais INSÉRER notre PROCHAIN président.  Voici l'instruction :

INSERT INTO USA.President (Name,SSN,DateOfBirth,Address,PresNumber) VALUES ('McDonald,Ronald','221-18-7518','01-01-1963','1600 Pennsylvania Ave NW, Washington, DC 20006',45)

Note : Son numéro de sécurité sociale s'écrit 'Burger'.  Désolé si c'est le vôtre.

Alors c'est génial !  Regardons votre Globale du Président :

USER>zw ^USA.PresidentD

Pas de données !  Et c'est là que nous arrivons à l'essentiel de cet article.  Parce que nous avons décidé d'hériter de USA.Person FIRST, nous avons hérité non seulement de ses propriétés et index, mais aussi de son stockage !  Donc pour localiser le président McDonald, nous devons regarder dans ^USA.PersonD.  Et nous pouvons voir ce qui suit :

^USA.PersonD(2)=$lb("~USA.President~","McDonald,Ronald","221-18-7518","1600 Pennsylvania Ave NW, Washington, DC 20006",44560)
^USA.PersonD(2,"President")=$lb(45&)

Deux choses à noter ici.  La première est que nous pouvons voir que le nœud (2) possède toutes les informations déjà stockées dans USA.Person.  Alors que le noeud (2, "President") ne contient que les informations spécifiques à la classe USA.President.  

Qu'est-ce que cela signifie en pratique ?  Eh bien, si nous voulons faire une opération de type : SELECT * FROM USA.President, nous aurons BESOIN de parcourir l'ensemble du tableau des personnes.  Si nous pensons que le tableau des personnes contient 320 000 000 lignes et que le tableau des présidents en contient 45, alors nous devons faire plus de 320 000 045 références globales pour extraire 45 lignes !  En effet, si l'on regarde le plan de requête :

  • Lire la carte maîtresse USA.President.IDKEY, en bouclant sur ID.
  • Pour chaque ligne:
  •  Résultat de la ligne.

Nous observons ce que nous attendons.  Cependant, nous avons déjà vu que cela signifie qu'il faut nécessairement regarder dans la globale ^USA.PersonD.  Donc, cela va être une référence globale de 320 000 000+ car nous devons tester CHAQUE ^USA.PersonD pour vérifier s'il y a des données dans ^USA.PersonD(i, "Président") puisque nous ne savons pas quelles personnes seront présidents.  Eh bien, c'est mauvais ! Ce n'est pas du tout ce que nous voulions !  Que pouvons-nous faire ?  Eh bien, nous avons deux options :

Option 1

Ajouter un index d'éxtent.  Si nous faisons cela, nous obtenons une liste d'identifiants qui nous permet de savoir quelles personnes sont des présidents et nous pouvons utiliser cette information pour lire des nœuds spécifiques de la globale ^USA.Person.  Comme je dispose d'un stockage par défaut, je peux utiliser un index bitmap, ce qui rendra l'opération encore plus rapide.  Nous ajoutons l'index comme suit :

Index Extent [Type=Bitmap, Extent];

Et quand nous regardons notre plan de requête pour SELECT * FROM USA.President nous pouvons voir :

  • Lecture de l'extent du bitmap USA.President.Extent, en bouclant sur l'ID.

  • Pour chaque ligne :

  •  Lecture de la carte maîtresse USA.President.IDKEY, en utilisant la valeur idkey donnée.
     Résultat de la ligne.

Ah, maintenant ça va être sympa et rapide.  Une référence globale pour lire l'Extent et ensuite 45 autres pour les présidents.  C'est plutôt efficace.  

Les inconvénients ?  La connexion à ce tableau devient un peu plus compliquée et peut impliquer un plus grand nombre de tableaux temporaires que vous ne le souhaiteriez.  

Option 2

Changement de la définition de la classe en ::

Class USA.President extends (%Persistent, USA.Person)

En faisant de %Persistent la première classe étendue, USA.President aura sa propre définition de stockage.  Ainsi, les présidents seront stockés de la manière suivante :

USER>zw ^USA.PresidentD
^USA.PresidentD=1
^USA.PresidentD(1)=$lb("","McDonald,Ronald","221-18-7518","1600 Pennsylvania Ave NW, Washington, DC 20006",44560,45)

C'est donc une bonne chose, car choisir USA.President signifie simplement lire les 45 membres de cette globale.  C'est facile et agréable, et le design est clair.

Les inconvénients ?  Eh bien maintenant, les présidents ne sont PAS dans le tableau des personnes Person.  Donc si vous voulez des informations sur les présidents ET les non-présidents, vous devez faire SELECT ... FROM USA.Person UNION ALL SELECT ... FROM USA.President


Si vous avez arrêté de lire au début, recommencez ici !

Lors de la création d'un héritage, nous avons deux options

Option 1: L'héritage de la superclasse est le premier.  Cela permet de stocker les données dans la même globale que la superclasse.  Utile si vous voulez avoir toutes les informations ensemble, et vous pouvez atténuer les problèmes de performance dans la sous-classe en ayant un index extent.

Option 2: Héritage de %Persistent first.  Cela permet de stocker les données dans une nouvelle globale.  C'est utile si vous interrogez beaucoup la sous-classe, mais si vous voulez voir les données de la super-classe et de la sous-classe, vous devez utiliser une requête UNION.

Laquelle de ces solutions est la meilleure ?  Cela dépend de la façon dont vous allez utiliser votre application.  Si vous souhaitez effectuer un grand nombre de requêtes sur l'ensemble des données, vous opterez probablement pour la première approche.  En revanche, si vous ne pensez pas interroger les données dans leur ensemble, vous opterez probablement pour la seconde approche.  Les deux approches sont tout à fait acceptables, à condition de ne pas oublier l'index extent de l'option 1.

Des questions ? Des commentaires ? De longues pensées contradictoires ?  Laissez-les ci-dessous !

0
1 100
Article Guillaume Rongier · Juin 3, 2022 13m read

Class Query dans InterSystems IRIS (et Cache, Ensemble, HealthShare) est un outil utile qui sépare les requêtes SQL du code Object Script. En principe, cela fonctionne comme suit : supposons que vous souhaitiez utiliser la même requête SQL avec différents arguments à plusieurs endroits différents. Dans ce cas, vous pouvez éviter la duplication du code en déclarant le corps de la requête comme une Class Query, puis en appelant cette requête par son nom. Cette approche est également pratique pour les requêtes personnalisées, dans lesquelles la tâche consistant à obtenir la ligne suivante est définie par un développeur. Cela vous intéresse ? Alors lisez la suite !

Class queries de base

Plus simplement, les Class Queries de base vous permettent de représenter des requêtes SQL SELECT. L'optimiseur et le compilateur SQL les traitent comme des requêtes SQL standards, mais elles sont plus pratiques lorsqu'il s'agit de les exécuter à partir du contexte Caché Object Script. Ils sont déclarés en tant qu'éléments de requête Query dans les définitions de classe (similaires aux méthodes ou aux propriétés) de la manière suivante :

  • Type: %SQLQuery
  • Tous les arguments de votre requête SQL doivent être énumérés dans la liste des arguments
  • Type de requête: SELECT
  • Utiliser les deux-points pour accéder à chaque argument (similaire au SQL statique)
  • Définissez le paramètre ROWSPEC qui contient des informations sur les noms et les types de données des résultats de sortie ainsi que l'ordre des champs
  • (Facultatif) Définissez le paramètre CONTAINID qui correspond à l'ordre numérique si le champ contient l'ID. Si vous n'avez pas besoin de renvoyer l'ID, n'attribuez pas de valeur à CONTAINID
  • (Facultatif) Définissez le paramètre COMPILEMODE qui correspond au paramètre similaire en SQL statique et spécifie quand l'expression SQL doit être compilée. Lorsque ce paramètre est défini sur IMMEDIATE (par défaut), la requête sera compilée en même temps que la classe. Lorsque ce paramètre a la valeur DYNAMIC, la requête sera compilée avant sa première exécution (similaire au SQL dynamique)
  • (Facultatif) Définissez le paramètre SELECTMODE qui spécifie le format des résultats de la requête
  • Ajoutez la propriété SqlProc, si vous voulez appeler cette requête comme une procédure SQL.
  • Définissez la propriété SqlName, si vous souhaitez renommer la requête. Le nom par défaut d'une requête dans le contexte SQL est le suivant : PackageName.ClassName_QueryName
  • Caché Studio fournit l'assistant intégré pour la création de Class Query


Exemple de définition de la classe Sample.Person avec la requête ByName qui renvoie tous les noms d'utilisateur qui commencent par une lettre spécifiée

Class Sample.Person Extends %Persistent
{
Property Name As %String;
Property DOB As %Date;
Property SSN As %String;
Query ByName(name As %String = "") As %SQLQuery
    (ROWSPEC="ID:%Integer,Name:%String,DOB:%Date,SSN:%String",
     CONTAINID = 1, SELECTMODE = "RUNTIME",
     COMPILEMODE = "IMMEDIATE") [ SqlName = SP_Sample_By_Name, SqlProc ]
{
SELECT ID, Name, DOB, SSN
FROM Sample.Person
WHERE (Name %STARTSWITH :name)
ORDER BY Name
}
}

Vous pouvez appeler cette requête depuis Caché Object Script de la manière suivante : 

Set statement=##class(%SQL.Statement).%New()   
Set status=statement.%PrepareClassQuery("Sample.Person","ByName")   
If $$$ISERR(status) {
    Do $system.OBJ.DisplayError(status)
}   
Set resultset=statement.%Execute("A")   
While resultset.%Next() {
    Write !, resultset.%Get("Name")   
}

Vous pouvez également obtenir un ensemble de résultats en utilisant la méthode générée automatiquement queryNameFunc :

Set resultset = ##class(Sample.Person).ByNameFunc("A")    
While resultset.%Next() {
    Write !, resultset.%Get("Name")   
}

Cette requête peut également être appelée à partir du SQLcontext de ces deux manières :

Call Sample.SP_Sample_By_Name('A')
Select * from Sample.SP_Sample_By_Name('A')

Cette classe peut être trouvée dans l'espace de nom par défaut SAMPLES Caché. Et c'est tout pour les requêtes simples. Passons maintenant aux requêtes personnalisées

Class queries personnalisées

Bien que les Class Queries de base fonctionnent parfaitement dans la plupart des cas, il est parfois nécessaire d'exécuter un contrôle total sur le comportement des requêtes dans les applications, par exemple :

  • Des critères de sélection sophistiqués. Puisque dans les requêtes personnalisées vous implémentez une méthode Caché Object Script qui renvoie la ligne suivante de façon autonome, ces critères peuvent être aussi sophistiqués que vous le souhaitez.
  • Si les données sont accessibles uniquement via l'API dans un format que vous ne souhaitez pas utiliser
  • Si les données sont stockées dans des globales (sans classes)
  • Si vous avez besoin d'élever les droits afin d'accéder aux données
  • Si vous devez appeler une API externe afin d'accéder à des données
  • Si vous devez accéder au système de fichiers afin d'accéder aux données
  • Vous devez effectuer des opérations supplémentaires avant d'exécuter la requête (par exemple, établir une connexion, vérifier les autorisations, etc.)

Alors, comment créer des requêtes de classes personnalisées ? Tout d'abord, vous devez définir 4 méthodes qui mettent en œuvre l'ensemble du flux de travail de votre requête, de l'initialisation à la destruction :

  • queryName — fournit des informations sur une requête (similaire aux requêtes de classe de base)
  • queryNameExecute — construit une requête
  • queryNameFetch — obtient le résultat de la ligne suivante d'une requête
  • queryNameClose — détruit une requête

Analysons maintenant ces méthodes plus en détail.

La méthode queryName

La méthode queryName représente des informations sur une requête

  • Type: %Query
  • Laissez le corps vide
  • Définissez le paramètre ROWSPEC qui contient les informations sur les noms et les types de données des résultats de sortie ainsi que l'ordre des champs
  • (Facultatif) Définissez le paramètre CONTAINID qui correspond à l'ordre numérique si le champ contient l'ID. Si vous ne renvoyez pas d'ID, n'attribuez pas de valeur à CONTAINID

Par exemple, créons la requête AllRecords (queryName = AllRecords, et la méthode est simplement appelée AllRecords) qui produira toutes les instances de la nouvelle classe persistante Utils.CustomQuery, une par une. Tout d'abord, créons une nouvelle classe persistante Utils.CustomQuery :

Class Utils.CustomQuery Extends (%Persistent, %Populate){
Property Prop1 As %String;
Property Prop2 As %Integer;
}

Maintenant, écrivons la requête AllRecords :

Query AllRecords() As %Query(CONTAINID = 1, ROWSPEC = "Id:%String,Prop1:%String,Prop2:%Integer") [ SqlName = AllRecords, SqlProc ]
{
}

La méthode queryNameExecute
La méthode queryNameExecute initialise complètement une requête. La signature de cette méthode est la suivante :

ClassMethod queryNameExecute(ByRef qHandle As %Binary, args) As %Status

où:

  • qHandle est utilisé pour la communication avec les autres méthodes de l'implémentation de la requête
  • Cette méthode doit mettre qHandle dans l'état qui sera ensuite transmis à la méthode queryNameFetch
  • qHandle peut être défini comme OREF, une variable ou une variable multidimensionnelle
  • Les args sont des paramètres supplémentaires transmis à la requête. Vous pouvez ajouter autant d'args que vous le souhaitez (ou ne pas les utiliser du tout)
  • La méthode doit retourner le statut d'initialisation de la requête

Revenons à notre exemple. Vous pouvez itérer dans l'étendue de plusieurs façons (je décrirai plus loin les approches de travail de base pour les requêtes personnalisées), mais pour cet exemple, itérons dans la globale en utilisant la fonction $Order. Dans ce cas, qHandle stockera l'ID actuel, et puisque nous n'avons pas besoin d'arguments supplémentaires, l'argument arg n'est pas nécessaire. Le résultat est le suivant :

ClassMethod AllRecordsExecute(ByRef qHandle As %Binary) As %Status {  
    Set qHandle = ""    Quit $$$OK
}

La méthode queryNameFetch
La méthode queryNameFetch renvoie un seul résultat sous la forme $List. La signature de cette méthode est la suivante :

ClassMethod queryNameFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = queryNameExecute ]

where:

  • qHandle est utilisé pour la communication avec les autres méthodes de l'implémentation de la requête
  • Lorsque la requête est exécutée, les valeurs spécifiées par queryNameExecute ou par un appel précédent de queryNameFetch sont attribuées à qHandle.
  • Le rang sera défini soit par une valeur de %List, soit par une chaîne vide, si toutes les données ont été traitées
  • AtEnd doit être mis à 1, une fois que la fin des données est atteinte.
  • La méthode "Fetch" doit être positionnée après la méthode "Execute", mais cela n'est important que pour SQL statique, c'est-à-dire les curseurs à l'intérieur des requêtes.

En général, les opérations suivantes sont effectuées dans le cadre de cette méthode :

  1. Vérifier si nous avons atteint la fin des données
  2. S'il reste encore des données : Créez une nouvelle %List et attribuez une valeur à la variable Row
  3. Sinon, mettez AtEnd à 1
  4. Préparer qHandle pour la prochaine récupération de résultat
  5. Retourner l'état

Voici comment cela se présente dans notre exemple :

ClassMethod AllRecordsFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status {
    #; itérer dans ^Utils.CustomQueryD    
    #; ecrire le prochain id dans qHandle et écriture de la valeur de la globale avec le nouvel id dans val
    Set qHandle = $Order(^Utils.CustomQueryD(qHandle),1,val)
    #; Vérifier s'il reste des données
       If qHandle = "" {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK    
    }
    #; Si ce n'est pas le cas, créer %List
    #; val = $Lb("", Prop1, Prop2) voir définition de Storage
    #; Row =$lb(Id,Prop1, Prop2)  voir ROWSPEC pour la demande AllRecords
    Set Row = $Lb(qHandle, $Lg(val,2), $Lg(val,3))
    Quit $$$OK
}

La méthode queryNameClose
La méthode queryNameClose met fin à la requête, une fois toutes les données obtenues. La signature de cette méthode est la suivante :

ClassMethod queryNameClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = queryNameFetch ]

où :

  • Caché exécute cette méthode après le dernier appel à la méthode queryNameFetch
  • En d'autres termes, il s'agit d'un destructeur de requête
  • Par conséquent, vous devez disposer de tous les curseurs SQL, des requêtes et des variables locales dans sa mise en œuvre
  • Les méthodes renvoient l'état actuel

Dans notre exemple, nous devons supprimer la variable locale qHandle :

ClassMethod AllRecordsClose(ByRef qHandle As %Binary) As %Status {
    Kill qHandle
    Quit $$$OK
  }

Et voilà ! Une fois que vous aurez compilé la classe, vous serez en mesure d'utiliser la requête AllRecords à partir de %SQL.Statement - tout comme les requêtes de la classe de base.

Approches de la logique d'itération pour les requêtes personnalisées

Alors, quelles approches peuvent être utilisées pour les requêtes personnalisées ? En général, il existe 3 approches de base :

Itération à travers une globale
Cette approche est basée sur l'utilisation de $Order et de fonctions similaires pour l'itération à travers une globale. Elle peut être utilisée dans les cas suivants :

  • Les données sont stockées dans des globales (sans classes)
  • Vous voulez réduire le nombre de glorefs dans le code
  • Les résultats doivent/peuvent être triés par l'indice de la globale


SQL statique
L'approche est basée sur les curseurs et le SQL statique. Elle est utilisée pour :

  • Rendre le code int plus lisible
  • Faciliter le travail avec les curseurs
  • Accélération du processus de compilation (le SQL statique est inclus dans la requête de la classe et n'est donc compilé qu'une seule fois).

Remarque:

  • Les curseurs générés à partir de requêtes du type %SQLQuery sont nommés automatiquement, par exemple Q14.
  • Tous les curseurs utilisés dans une classe doivent avoir des noms différents
  • Les messages d'erreur sont liés aux noms internes des curseurs qui comportent des caractères supplémentaires à la fin de leur nom. Par exemple, une erreur dans le curseur Q140 est en fait causée par le curseur Q14.
  • Utilisez PlaceAfter et assurez-vous que les curseurs sont utilisés dans la même routine int où ils ont été déclarés.
  • INTO doit être utilisé en conjonction avec FETCH, mais pas DECLARE.


Exemple de SQL statique pour Utils.CustomQuery :

Query AllStatic() As %Query(CONTAINID = 1, ROWSPEC = "Id:%String,Prop1:%String,Prop2:%Integer") [ SqlName = AllStatic, SqlProc ]
{
}

ClassMethod AllStaticExecute(ByRef qHandle As %Binary) As %Status
{
    &sql(DECLARE C CURSOR FOR
        SELECT Id, Prop1, Prop2
        FROM Utils.CustomQuery
     )
     &sql(OPEN C)
    Quit $$$OK
}

ClassMethod AllStaticFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status [ PlaceAfter = AllStaticExecute ]
{
    #; INTO doit être associé à FETCH
    &sql(FETCH C INTO :Id, :Prop1, :Prop2)
    #; Vérifier si la fin des données est atteinte
    If (SQLCODE'=0) {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK
    }
    Set Row = $Lb(Id, Prop1, Prop2)
    Quit $$$OK
}

ClassMethod AllStaticClose(ByRef qHandle As %Binary) As %Status [ PlaceAfter = AllStaticFetch ]
{
    &sql(CLOSE C)
    Quit $$$OK
}

SQL dynamique
L'approche est basée sur les requêtes d'autres classes et le SQL dynamique. Cette approche est raisonnable lorsqu'en plus d'une requête SQL proprement dite, vous devez également effectuer certaines opérations supplémentaires, par exemple exécuter une requête SQL dans plusieurs espaces de noms ou escalader les permissions avant d'exécuter la requête.

Exemple de SQL dynamique pour Utils.CustomQuery :

Query AllDynamic() As %Query(CONTAINID = 1, ROWSPEC = "Id:%String,Prop1:%String,Prop2:%Integer") [ SqlName = AllDynamic, SqlProc ]
{
}

ClassMethod AllDynamicExecute(ByRef qHandle As %Binary) As %Status
{
    Set qHandle = ##class(%SQL.Statement).%ExecDirect(,"SELECT * FROM Utils.CustomQuery")
    Quit $$$OK
}

ClassMethod AllDynamicFetch(ByRef qHandle As %Binary, ByRef Row As %List, ByRef AtEnd As %Integer = 0) As %Status
{
    If qHandle.%Next()=0 {
        Set AtEnd = 1
        Set Row = ""
        Quit $$$OK
    }
    Set Row = $Lb(qHandle.%Get("Id"), qHandle.%Get("Prop1"), qHandle.%Get("Prop2"))
    Quit $$$OK
}

ClassMethod AllDynamicClose(ByRef qHandle As %Binary) As %Status
{
    Kill qHandle
    Quit $$$OK
}

Approche alternative : %SQL.CustomResultSet

Vous pouvez également créer une requête en sous-classant la classe %SQL.CustomResultSet. Les avantages de cette approche sont les suivants :

  • Une légère augmentation de la vitesse
  • ROWSPEC est inutile, puisque toutes les métadonnées sont obtenues à partir de la définition de la classe
  • Respect des principes de conception orientée objet

Pour créer une requête à partir de la sous-classe de la classe %SQL.CustomResultSet, assurez-vous d'effectuer les étapes suivantes :

  1. Définir les propriétés correspondant aux champs résultants
  2. Définir les propriétés privées où le contexte de la requête sera stocké
  3. Remplacer la méthode %OpenCursor (similaire à queryNameExecute) qui initie le contexte. En cas d'erreur, définissez également %SQLCODE et %Message
  4. Remplacer la méthode %Next (similaire à queryNameFetch) qui obtient le résultat suivant. Remplacer les propriétés. La méthode renvoie 0 si toutes les données ont été traitées et 1 s'il reste des données
  5. Remplacer la méthode %CloseCursor (similaire à queryNameClose) si nécessaire


Exemple de %SQL.CustomResultSet pour Utils.CustomQuery :

Class Utils.CustomQueryRS Extends %SQL.CustomResultSet
{
Property Id As %String;
Property Prop1 As %String;
Property Prop2 As %Integer;
Method %OpenCursor() As %Library.Status
{
    Set ..Id = ""
    Quit $$$OK
}

Method %Next(ByRef sc As %Library.Status) As %Library.Integer [ PlaceAfter = %Execute ]
{
    Set sc = $$$OK
    Set ..Id = $Order(^Utils.CustomQueryD(..Id),1,val)
    Quit:..Id="" 0
    Set ..Prop1 = $Lg(val,2)
    Set ..Prop2 = $Lg(val,3)
    Quit $$$OK
}
}

Vous pouvez l'appeler à partir de Caché Object Script code de la manière suivante :

Set resultset= ##class(Utils.CustomQueryRS).%New()
       While resultset.%Next() {
        Write resultset.Id,!
 }

Un autre exemple est disponible dans l'espace de noms SAMPLES - il s'agit de la classe Sample.CustomResultSet qui implémente une requête pour Samples.Person.

Résumé

Les requêtes personnalisées vous aideront à séparer les expressions SQL du code Caché Object Script et à mettre en œuvre un comportement sophistiqué qui peut être trop difficile pour le SQL pur.

Références

Class Queries

Itération à travers une globale

SQL statique

Dynamic SQL

%SQL.CustomResultSet

Classe Utils.CustomQuery

Classe Utils.CustomQueryRS

L'auteur tient à remercier [Alexander Koblov] (https://community.intersystems.com/user/alexander-koblov) pour son aide à la composition de cet article.

0
0 150
Article Irène Mykhailova · Mai 25, 2022 9m read

Voici quelques exemples de conversions et d'opérations dont vous pourriez avoir besoin, ainsi que des liens vers la documentation où vous pourrez en apprendre davantage.

Au moment où j'ai écrit ces lignes, l'heure d'été était en vigueur pour mon système Caché.

Comment Caché conserve l'heure et la date

Caché a un format d'heure simple, avec une plus grande gamme de dates reconnues par rapport à certaines autres technologies.

L'heure actuelle est conservée dans une variable spéciale $HOROLOG ($H) :

USER>WRITE $H64146,54027USER>

Le premier nombre entier est le nombre de jours écoulés depuis le 31 décembre 1840. Le second nombre entier est le nombre de secondes écoulées depuis minuit le jour actuel.

Vous pouvez également obtenir l'heure et la date actuelles avec $SYSTEM.SYS.Horolog().

Comment établir un horodatage

$HOROLOG comptabilise le temps avec une précision de l'ordre de la seconde. $ZTIMESTAMP a une forme similaire à $HOROLOG, mais il suit les fractions de seconde dans la partie temps et conserve le temps universel coordonné (UTC), plutôt que l'heure locale. La précision des fractions de seconde dépend de votre plate-forme.

Par conséquent, $ZTIMESTAMP fournit un horodatage qui est uniforme dans tous les fuseaux horaires. L'horodatage que vous voyez à un moment donné peut avoir une date et une heure différentes de votre heure locale actuelle. Dans cet exemple, mon heure locale est l'heure avancée de l'Est, soit quatre heures de moins que l'heure UTC.

WRITE !,"$ZTIMESTAMP: "_$ZTIMESTAMP_" $HOROLOG: "_$HOROLOG

$ZTIMESTAMP: 64183,53760.475 $HOROLOG: 64183,39360

La différence (sans compter les secondes fractionnées) est de 14400 secondes. Mon $HOROLOG est donc quatre heures "derrière" $ZTIMESTAMP.

Comment convertir le format interne en format d'affichage

Vous pouvez utiliser $ZDATETIME. La conversion de la date et de l'heure actuelles à partir du format interne peut être aussi simple que suit

WRITE !, "Avec le format de date et d'heure par défaut : ",$ZDATETIME($HOROLOG)

Avec le format de date et d'heure par défaut : 09/22/2016 10:56:00

Cela prend les paramètres par défaut des paramètres locaux Caché et NLS.

Les deuxième et troisième arguments (facultatifs) servent à spécifier le format de la date et le format de l'heure.

WRITE !, "With dformat 5 and tformat 5: ", $ZDATETIME($HOROLOG,5,5)

Avec dformat 5 et tformat 5: Sep 22, 2016T10:56:00-04:00

Le format horaire 7, par exemple, affiche l'heure en temps universel coordonné comme vous le voyez ici.

WRITE !, "With dformat 5 and tformat 7: ", $ZDATETIME($HOROLOG,5,7)

Avec dformat 5 et tformat 7: Sep 22, 2016T14:56:00Z

Outre les formats de date et d'heure, il existe de nombreux autres arguments facultatifs qui vous permettent de contrôler l'affichage. Par exemple, vous pouvez

  • Spécifier les limites inférieure et supérieure des dates valides si elles sont également autorisées par les paramètres régionaux actuels.
  • Contrôler si les années sont affichées avec deux ou quatre chiffres.
  • Contrôler l'affichage des erreurs.

Comment convertir un format d'affichage en un format interne à Caché

Utilisez $ZDATETIMEH pour passer du format d'affichage au format interne, comme $HOROLOG. Le H à la fin est un rappel que vous finirez avec le format $HOROLOG. De nombreux formats d'entrée différents peuvent être utilisés.

SET display = "09/19/2016 05:05 PM"
  WRITE !, display_" is "_$ZDATETIMEH(display)_" in internal format"
  WRITE !, "Suffixes AM, PM, NOON, MIDNIGHT can be used"
  SET startdate = "12/31/1840 12:00 MIDNIGHT"
  WRITE !, startdate_" is "_$ZDATETIMEH(startdate)_" in internal format"


09/19/2016 05:05 PM est 64180,61500 en format interne

Les suffixes AM, PM, NOON, MIDNIGHT peuvent être utilisés

12/31/1840 12:00 MIDNIGHT est 0,0 en format interne

Comment convertir l'heure UTC en heure locale et vice versa au format interne

Vous pouvez utiliser $ZDATETIME et $ZDATETIMEH avec un spécificateur spécial de format de date (-3) pour le deuxième argument.

La meilleure façon de convertir l'heure UTC en heure locale au format interne de Caché est d'utiliser la fonction $ZDATETIMEH(datetime, -3). Ici, le premier argument contient l'heure UTC au format interne.

SET utc1 = $ZTIMESTAMP
  SET loctime1 = $ZDATETIMEH(utc1, -3)
  WRITE !, "$ZTIMESTAMP returns a UTC time in internal format: ", utc1
  WRITE !, "$ZDATETIMEH( ts,-3) converts UTC to local time: ", loctime1
  WRITE !, "$ZDATETIME converts this to display formats: ", $ZDATETIME(utc1)
  WRITE !, "which is "_$ZDATETIME(loctime1)_" in local time"

$ZTIMESTAMP renvoie une heure UTC au format interne : 64183,53760.475

$ZDATETIMEH( ts,-3) convertit l'UTC en heure locale : 64183,39360.475

$ZDATETIME le convertit en format d'affichage : 09/22/2016 14:56:00

qui est 09/22/2016 10:56:00 en heure locale

Si vous avez besoin de passer de l'heure locale à UTC, toujours au format interne, utilisez $ZDATETIME(datetime, -3). Ici, le paramètre datetime contient l'heure reflétant votre fuseau horaire local au format interne.

SET loctime2 = $HOROLOG
  SET utc2 = $ZDATETIME(loctime2, -3)
  WRITE !, "$HOROLOG returns a local time in internal format: ", loctime2
  WRITE !, "$ZDATETIME(ts, -3) converts this to UTC: ", utc2
  WRITE !, "$ZDATETIME converts this to display formats:"
  WRITE !, "Local: ", $ZDATETIME(loctime2)
  WRITE !, "UTC: ", $ZDATETIME(utc2)

$HOROLOG renvoie une heure locale au format interne : 64183,39360

$ZDATETIME(ts, -3) le convertit en UTC : 64183,53760

$ZDATETIME le convertit en format d'affichage :

Local: 09/22/2016 10:56:00

UTC: 09/22/2016 14:56:00

Gardez ces points à l'esprit lorsque vous effectuez des conversions d'heure locale et UTC :

  • Les conversions entre l'heure locale et l'UTC doivent utiliser les règles de fuseau horaire en vigueur pour la date et le lieu spécifiés. Caché dépend du système d'exploitation pour suivre ces changements au cours du temps. Si le système d'exploitation ne le fait pas correctement, les conversions ne seront pas correctes.
  • Les conversions de dates et d'heures futures utilisent les règles actuelles gérées par le système d'exploitation. Cependant, les règles pour les années futures peuvent changer.

Comment déterminer le fuseau horaire du système

Vous pouvez obtenir le décalage du fuseau horaire actuel en examinant la valeur de $ZTIMEZONE ou de %SYSTEM.SYS.TimeZone(). La valeur par défaut est définie par le système d'exploitation.

WRITE !, "$ZTIMEZONE is set to "_$ZTIMEZONE
 WRITE !, "%SYSTEM.SYS.TimeZone() returns "_$System.SYS.TimeZone()

$ZTIMEZONE est réglé sur 300

%SYSTEM.SYS.TimeZone() renvoie 300

Vous ne devez pas modifier la valeur de $ZTIMEZONE. Si vous le faites, vous affecterez les résultats de IsDST(), $ZDATETIME, et $ZDATETIMEH, parmi de nombreux autres effets. L'heure pour le processus ne changera pas l'heure correctement pour l'heure d'été.  La modification de $ZTIMEZONE n'est pas un moyen cohérent de changer le fuseau horaire utilisé par Caché.

À partir de la version 2016.1, Caché fournit la méthode $System.Process.TimeZone() qui vous permet de définir et de récupérer le fuseau horaire pour un processus spécifique en utilisant la variable d'environnement TZ. Elle renvoie -1 si TZ n'est pas défini.

WRITE !,$System.Process.TimeZone()
WRITE !, "Current Time: "_$ZDT($H)
WRITE !, "Set Central Time"
DO $System.Process.TimeZone("CST6CDT")
WRITE !, "New current time: "_$ZDT($H)
WRITE !, "Current Time Zone: "_$System.Process.TimeZone()


-1

L'heure actuelle : 10/03/2016 15:46:04

Réglage de l'heure centrale

Nouvelle heure actuelle : 10/03/2016 14:46:04

Le fuseau horaire actuel : CST6CDT

Comment déterminer si l'heure d'été est en vigueur

Utilisez $SYSTEM.Util.IsDST(). Ici aussi, Caché s'appuie sur le système d'exploitation pour appliquer les règles correctes permettant de déterminer si l'heure d'été est en vigueur.

SET dst = $System.Util.IsDST()
  IF (dst = 1) {WRITE !, "DST is in effect"}
  ELSEIF (dst = 0) { WRITE !, "DST is not in effect" }
  ELSE { WRITE !, "DST cannot be determined" }

Comment effectuer l'arithmétique des dates

Puisque le format interne de Caché maintient un compte des jours et un compte des secondes dans chaque jour, vous pouvez faire de l'arithmétique de date d'une manière directe. La fonction $PIECE vous permet de séparer les parties date et heure du format interne.

Voici une courte routine qui utilise $ZDATE et $ZDATEH pour déterminer le dernier jour de l'année dernière afin de pouvoir compter le jour de l'année d'aujourd'hui. Cette routine utilise les méthodes de la classe %SYS.NLS pour définir le format de date que nous voulons, obtenir le séparateur de date et rétablir les valeurs par défaut.

DATECALC ; Exemple d'arithmétique de date.
  W !, "Extracting date and time from $H using $PIECE"
  W !, "---------------------------------------------"
  set curtime = $H
  set today = $PIECE(curtime,",",1)
  set now = $PIECE(curtime,",",2)
  W !, "Curtime: "_curtime_" Today: "_today_" Now: "_now

  W !, "Counting the days of the year"
  W !, "-----------------------------"
  ; set to US format
  SET rtn = ##class(%SYS.NLS.Format).SetFormatItem("DateFormat",1)
  set sep = ##class(%SYS.NLS.Format).GetFormatItem("DateSeparator")
  SET lastyear = ($PIECE($ZDATE($H),sep,3) - 1)
  SET start = $ZDATEH("12/31/"_lastyear)
  W !, "Today is day "_(today - start)_" of the year"
  ; put back the original date format
  SET rtn=##class(%SYS.NLS.Format).SetFormatItem("DateFormat",rtn)

Comment obtenir et définir d'autres paramètres NLS

Utilisez la classe %SYS.NLS.Format pour des paramètres tels que le format de la date, les dates maximum et minimum et d'autres paramètres. Les paramètres initiaux proviennent des paramètres régionaux actuels et les modifications que vous apportez à cette classe n'affectent que le processus actuel.

Heure et date en SQL

Caché fournit une variété de fonctions SQL pour travailler avec les dates et les heures. Celles-ci sont également disponibles en ObjectScript via la classe $System.SQL.

TO_DATE : Convertit une date au format CHAR ou VARCHAR2 en une date. Ceci est disponible en ObjectScript en utilisant $System.SQL.TODATE("string", "format")

DAYOFYEAR : Renvoie le jour de l'année pour une expression d'année donnée, qui peut être dans plusieurs formats, comme un entier de date de $HOROLOG.

DAYNAME : renvoie le nom du jour qui correspond à une date spécifiée.

W $ZDT($H)

10/12/2016 11:39:19


w $System.SQL.TODATE("2016-10-12","YYYY-MM-DD")

64203


W $System.SQL.DAYOFYEAR(+$H)

286


W $System.SQL.DAYNAME(+$H)

Wednesday

Il y a beaucoup plus d'informations dans la documentation de Cache' sur la façon d'utiliser ces fonctions (et bien d'autres). Référez-vous aux références de la section suivante.

Références

Liens vers la documentation en ligne d'InterSystems pour les éléments abordés dans le présent article.

$HOROLOG

$PIECE

SQL Functions reference

%SYS.NLS.Format

%SYSTEM.Process.TimeZone()

%SYSTEM.SQL

%SYSTEM.SYS.Horolog

%SYSTEM.Util.IsDST()

$ZDATE

$ZDATEH

$ZDATETIME

$ZDATETIMEH

1
0 254
Article Irène Mykhailova · Mai 23, 2022 6m read

Le type DATE correspond au type de données du produit InterSystems %Date et le type TIME correspond à %Time.

%Date enregistre une date interne (premier élément séparé par une virgule de la variable spéciale $Horolog), et %Time enregistre l'heure interne (deuxième élément séparé par une virgule de la variable spéciale $Horolog). La logique côté serveur utilise donc la valeur au format (logique) interne, sauf si vous changez le mode d'affichage.
La méthode permettant de modifier le format d'affichage de la date et de l'heure internes dans la logique côté serveur dépend de la méthode d'exploitation.

Dans les exemples suivants, nous utiliserons le tableau Sample.Person.
(L'exemple d'exécution de la commande est présenté pour une instruction SELECT, mais il peut également être écrit pour une instruction de mise à jour.)

Pour essayer IRIS/IRIS for Health, téléchargez la documentation à partir de (Télécharger des échantillons à utiliser avec InterSystems IRIS),
ou à partir de Articles connexes (téléchargement de la définition de classe de l'échantillon (Sample.Person) et création de données d'échantillon), veuillez commencer par importer la classe Sample.Person et créer les données d'exemple.

Si vous essayez Caché/Ensemble, utilisez Sample.Person dans l'espace de noms SAMPLES.


(1) Si vous utilisez l'Embedded SQL

Pour changer le format d'affichage à l'aide d'Embedded SQL, utilisez #sqlcomple select.
Les valeurs suivantes peuvent être spécifiées.

  • Logical (par défaut)
  • Display
  • ODBC
  • Runtime

Documentation (IRIS) : Compilation du SQL intégré et du préprocesseur de macros【IRIS】
Documentation : Compilation du SQL intégré et du préprocesseur de macros

#sqlcompile select=ODBC
&sql(declare C1 Cursor for select ID,Name,DOB into :pid,:name,:dob from Sample.Person where ID<=5)
&sql(open C1)
for {
    &sql(fetch C1)
    if SQLCODE'=0 { quit }
   //Exemple d'affichage)1-Mastrolito,Susan T.-2013-01-01
    write pid,"-",name,"-",dob,!
}
&sql(close C1)

(2) Si vous utilisez le Dynamic SQL 

Pour changer le format d'affichage en SQL dynamique à l'aide de %SQL.Statement, utilisez la propriété %SelectMode.
Cette propriété doit être définie avant l'exécution de %Execute().

Les valeurs qui peuvent être définies sont les suivantes.

  • 0: mode logique
  • 1: mode ODBC
  • 2: mode d'affichage
SAMPLES>set sql="select ID,Name,DOB from Sample.Person where ID <= 5" SAMPLES>set stmt=##class(%SQL.Statement).%New() SAMPLES>set st=stmt.%Prepare(sql) SAMPLES>set rset=stmt.%Execute() SAMPLES>do rset.%Display()
ID      Name    DOB
1       Gallant,Yan N.  42146
2       Waal,Umberto G. 45359
3       Jenkins,Sam A.  37404
4       Marks,Milhouse B.       52043
5       Hernandez,Phyllis W.    64590 5 Rows(s) Affected
SAMPLES>

(3) Lorsque vous utilisez une requête de classe 

Pour changer le format d'affichage dans une requête de classe, utilisez le paramètre de définition de la requête : SELECTMODE.
Les valeurs qui peuvent être spécifiées sont les suivantes

  • RUNTIME (par défaut)
  • LOGICAL
  • DISPLAY
  • ODBC

Voici un exemple de définition.

Query NewQuery1() As %SQLQuery(SELECTMODE = "ODBC")&lt;br>{&lt;br>select ID,Name,DOB from Sample.Person where ID&lt;=5&lt;br>}

(4) Comment changer le format d'affichage des processus en cours

L'objet système $SYSTEM.SQL.SetSelectMode() peut être utilisé pour modifier le format d'affichage du processus en cours.
Les arguments et les valeurs de retour sont les suivants.

  • Spécifiez 0 (logique), 1 (ODBC) ou 2 (affichage) comme premier argument.
  • Le second argument est un argument de type pass-by-reference dont le résultat d'exécution est défini par %Status.
  • La valeur de retour est le numéro du mode d'affichage en cours.

Veuillez vous référer à la page du document ci-dessous pour plus de détails.

Les bases d'InterSystems SQL - Options d'affichage des données【IRIS】
Options d'affichage de CachéSQL Basics_Data

// Changement du format par défaut au format ODBC
SAMPLES>set cm=$system.SQL.SetSelectMode(1,.st)
SAMPLES>set sql="select ID,Name,DOB from Sample.Person where ID <= 5"
SAMPLES>set stmt=##class(%SQL.Statement).%New()
SAMPLES>set st=stmt.%Prepare(sql)
SAMPLES>set rset=stmt.%Execute()
SAMPLES>do rset.%Display()
ID      Name    DOB
1       Gallant,Yan N.  1956-05-23
2       Waal,Umberto G. 1965-03-10
3       Jenkins,Sam A.  1943-05-30
4       Marks,Milhouse B.       1983-06-28
5       Hernandez,Phyllis W.    2017-11-03 5 Rows(s) Affected
SAMPLES>

※Après avoir changé le format d'affichage d'un processus, si le format d'affichage est modifié pour chaque méthode d'exécution SQL, le dernier format d'affichage spécifié sera utilisé.

(5) Comment convertir le format d'affichage à l'aide de fonctions ObjectScript

Une autre méthode consiste à utiliser les fonctions de conversion d'affichage d'ObjectScript pour convertir le format interne en format d'affichage.

Pour les fonctions de datation,
 Affichage -> Format interne $ZDATEH(yyyymmdd,8) ou $ZDATE(yyyy-mm-dd,3)
 Interne -> Pour obtenir le résultat du format d'affichage YYYYYMMDD : $ZDATE(+$Horolog,8) Si vous voulez obtenir le résultat de YYYYY-MM-DD : $ZDATEH(+$H,3), dans la fonction horaire
 Affichage -> format interne $ZTIMEH("HH:MM:SS")
 Interne -> Si vous voulez obtenir le résultat au format d'affichage HH:MM:SS : $ZTIMEH($piece($Horolog,"",2)), il existe également les fonctions $ZDATETIME() et $ZDATETIMEH() pour manipuler la date et l'heure.

Vous trouverez plus de détails sur les fonctions de date dans des documents suivants.
ObjectScript fonction【IRIS】
ObjectScript fonction

SAMPLES>write $horolog
63895,34979
SAMPLES>write $ZDATE(+$horolog,8)  /Conversion au format yyyymmdd>63895
SAMPLES>write $ZDATEH("2015-12-09",3)  // Conversion du format yyyy-mm-dd en format interne
63895
SAMPLES>write $ZTIME($piece($horolog,",",2))  // Conversion du format interne en format horaire
09:44:16
SAMPLES>write $ZTIMEH("10:01:11")  // Conversion de l'heure d'affichage au format interne
36071
SAMPLES>write $ZDATETIME($horolog,8)  // Conversion date/heure avec $horolog
20151209 09:45:15
SAMPLES>write $ZDATETIME($horolog,3)
2015-12-09 09:45:16
SAMPLES>
0
0 88
Article Irène Mykhailova · Mai 19, 2022 1m read

Pour SQL, null et la chaîne vide ('') sont distinguées. Chaque méthode de définition/réception est la suivante.

(1) NULL

【SQL】

insert into test (a) values (NULL)
select * from test where a IS NULL

【InterSystems ObjectScript】

set x=##class(User.test).%New()
set x.a=""

(2) Chaîne vide ('')

【SQL】

insert into test (a) values ('')
select * from test where a = ''

【InterSystems ObjectScript】

set x=##class(User.test).%New()
set x.a=$C(0)

Pour plus de détails, veuillez consulter les documents suivants.

NULL and the Empty String (IRIS)
NULL and the Empty String (Caché)

0
0 100
InterSystems officiel Robert Bira · Mai 10, 2022

Ce mois, j'annonce la sortie de la version 1.8.0 de l'extension VS Code qui contient les améliorations et corrections de bogues suivantes.

La grande nouveauté est la prise en charge des fichiers de projet côté serveur, comme certains d'entre vous se souviendront de Studio. Si vous travaillez côté client, VS Code possède déjà d'excellentes fonctionnalités de gestion de projet. Vous pouvez simplement utiliser un dossier en tant que projet ou utiliser des multi-root workspaces. Mais si vous travaillez côté serveur, vous apprécierez peut-être de meilleures capacités de gestion des artefacts, et c'est de cela qu'il s'agit. Pour en savoir plus, consultez ce nouveau chapitre Projects de la documentation.

Changelog pour 1.8.0

  • Améliorations
    • Ajout de la prise en charge des projets côté serveur (#851)
    • Implémenter le changement de nom et la suppression du dossier isfs (#923, #922)
    • Prise en charge de l'indicateur "mapped" pour les isfs et les filtres d'exportation, pour exclure les packages mappés à partir d'autres bases de données (#931)

Le changelog complet pour toutes les versions est ici.

Comme toujours, si vous avez déjà installé l'extension, VS Code devrait automatiquement mettre à jour votre extension. Si vous êtes un nouvel utilisateur, suivez ces instructions pour commencer.

0
0 72
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
Annonce Irène Mykhailova · Avr 2, 2022

InterSystems a le plaisir d'annoncer la version 2.0.0 du Language Server pour VS Code. Le serveur de langage augmente l'extension VS Code ObjectScript pour fournir une meilleure coloration de la syntaxe, une documentation intégrée, la complétion de code et plus encore. Des informations détaillées sont disponibles dans le fichier README du GitHub. La version 2.0.0 ajoute la prise en charge d'un certain nombre de nouvelles architectures de plate-forme, y compris les Mac M1 ! Il réduit également la taille du package, améliore la coloration SQL et corrige un certain nombre d'autres problèmes détaillés dans le CHANGELOG.

Comme toujours, si vous avez déjà installé l'extension, VS Code devrait automatiquement mettre à jour votre extension lorsque vous redémarrez ou rechargez. Si vous êtes un nouvel utilisateur, suivez ces instructions pour commencer.

0
0 110
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