InterSystems est fier d'offrir 50 % de réduction sur les examens de certification InterSystems à tous les participants inscrits au UK&I Summit. Des bons seront disponibles au bureau d'inscription.

Examens disponibles
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.
InterSystems est fier d'offrir 50 % de réduction sur les examens de certification InterSystems à tous les participants inscrits au UK&I Summit. Des bons seront disponibles au bureau d'inscription.

Examens disponibles
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 :
Dans la newsletter actuelle des Learning Services, découvrez comment vous pouvez vous familiariser avec les outils d'analyse des soins de santé, Embedded Python et InterSystems Package Manager. Si vous débutez avec InterSystems ObjectScript, essayez un parcours d'apprentissage pour commencer !
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 :
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.PresidentDPas 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 :
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 !
Class Query dans InterSystems IRIS (et Cache, Ensemble, HealthShare) est un outil utile qui sépare les requêtes SQL du code . 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 !
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 . 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 :
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 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
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 :
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 :
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
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ù:
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:
En général, les opérations suivantes sont effectuées dans le cadre de cette méthode :
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ù :
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.
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 :
SQL statique
L'approche est basée sur les curseurs et le SQL statique. Elle est utilisée pour :
Remarque:
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
}Vous pouvez également créer une requête en sous-classant la classe %SQL.CustomResultSet. Les avantages de cette approche sont les suivants :
Pour créer une requête à partir de la sous-classe de la classe %SQL.CustomResultSet, assurez-vous d'effectuer les étapes suivantes :
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 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.
Les requêtes personnalisées vous aideront à séparer les expressions SQL du code et à mettre en œuvre un comportement sophistiqué qui peut être trop difficile pour le SQL pur.
Itération à travers une globale
L'auteur tient à remercier [Alexander Koblov] (https://community.intersystems.com/user/alexander-koblov) pour son aide à la composition de cet article.
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é.
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().
$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.
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
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
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 :
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 : CST6CDTUtilisez $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" }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)
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.
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)
WednesdayIl 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.
Liens vers la documentation en ligne d'InterSystems pour les éléments abordés dans le présent article.
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.
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
(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.
(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
Voici un exemple de définition.
Query NewQuery1() As %SQLQuery(SELECTMODE = "ODBC")<br>{<br>select ID,Name,DOB from Sample.Person where ID<=5<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.
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
※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
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】
【InterSystems ObjectScript】
(2) Chaîne vide ('')
【SQL】
【InterSystems ObjectScript】
Pour plus de détails, veuillez consulter les documents suivants.
NULL and the Empty String (IRIS)
NULL and the Empty String (Caché)
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.
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.
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(
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 :
Vous êtes dans un mauvais espace de noms.
Vous devez construire des indices.
Vous êtes dans un mauvais SELECTMODE.
Problèmes de collation
NLS sur CacheTemp
Bug d'InterSystems
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 !
Alors que la solution classique suivait d'assez près les concepts et le design de ses ancêtres, Caché / IRIS permet une approche plus moderne des propriétés flexibles/multidimensionnelles.
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.
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 :
il y en a d'autres Je vais vous montrer comment surmonter ces limites.
La carte de stockage montre déjà le problème n°1 : pas de place pour "Multi"
donc nous ajoutons 2 méthodes :
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 :
et c'est le stockage associé, une jolie globale multidimensionnelle :
Jusqu'à présent, tout va bien.

donc nous ajoutons une propriété calculée SQL et un style approprié.
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.