Vitaliy Serdtsev · Oct 7, 2020 go to post
 

Source code

Class mp.test Extends %Persistent
{

Property prop1;

Property prop2;

Property prop3;

Index idx3 On (prop3, prop1, prop2) [ Type = bitmap ];

ClassMethod Fill(total 5000000)
{
  
..%KillExtent()

  

i=1:1:total ^mp.testD(i)=$lb("","name","prop2",$r(30))
  
^mp.testD=total
  
  
..%BuildIndices()
}

ClassMethod Query()
{
  
N=5
  
  
&sql(SELECT count(*into :count FROM mp.test WHERE prop1='name' AND prop2='prop2' AND prop3>='1' AND prop3<='30')
  
  
"count=",count,!!
  
  
sql=##class(%SQL.Statement).%New()

  

sql.%Prepare("SELECT %ID,prop3 FROM mp.test WHERE %ID IN ("_
  
"  SELECT * FROM ("_
  
"    SELECT TOP ALL %ID FROM mp.test WHERE prop1='name' AND prop2='prop2' AND prop3>='1' AND prop3<='30' ORDER BY prop3 DESC"_
  
"  ) WHERE %VID BETWEEN ? AND ?"_
  
") ORDER BY prop3 DESC")
  
  
"first 5 [1:5]",!

  

rset=sql.%Execute(1,5)
  
time=$zh
  d 
rset.%Display()
  
!,$zh-time," secs",!!

  

w $$$FormatText("last 5 [%1:%2]",count-N+1,count),!

  

rset=sql.%Execute(count-N+1,count)
  
time=$zh
  d 
rset.%Display()
  
!,$zh-time," secs"
}

}

USER>##class(mp.test).Fill(5000000)

USER>##class(mp.test).Query()
count=3833346

first 5 [1:5]
ID prop3
3 3
4 3
24 3
30 3
97 3

5 Rows(s) Affected
.000328 secs

last 5 [3833342:3833346]
ID prop3
4999798 1
4999817 1
4999836 1
4999866 1
4999947 1

5 Rows(s) Affected
2.884304 secs

PS: for those who put a minus. May I ask why?

Vitaliy Serdtsev · Sep 11, 2020 go to post

There is at least one case where this encoding style is acceptable - it is a generator method. Just look at the code generated by &sql() or CSP, for example. No wonder there are two versions for javascript libraries: for the developer and for production (processed by the minimizer).

Vitaliy Serdtsev · Aug 5, 2020 go to post

My EAV implementation is the same as your Approach 3, so it will work fine even with fully filled 4.000.000 attributes. Since the string has a limit of 3,641,144, approaches with serial and %List are dropped.

All other things being equal, everything depends on the specific technical task: speed, support for Objects/SQL, the ability to name each attribute, the number of attributes, and so on.

Vitaliy Serdtsev · Jul 9, 2020 go to post

select *,
case when is JSON then else end aa,
case when is JSON then else end bb,
case when is JSON then else end cc

from (SELECT aJSON_OBJECT('id':1) b'{"id":1}' c)

Vitaliy Serdtsev · Jun 10, 2020 go to post

Here is a ready-made example (works even in last Caché):

Class dc.test Extends %Persistent
{

Property title As %VarString;

Property author As %VarString;

/// do ##class(dc.test).test()
ClassMethod test()
{
  &sql(truncate table dc.test)
  
  &sql(insert into dc.test(title,author)
    select 'For Whom the Bell Tolls','Hemmingway' union
    select 'The Invisible Man','Ellison' union
    select 'Tender is the Night','Fitzgerald')
  
  set provider=##class(%ZEN.Auxiliary.altJSONSQLProvider).%New(),
      provider.sql="select title,author from dc.test",
      provider.arrayName="items",
      provider.maxRows = 0,
      provider.%Format "tw"
  
  do provider.%WriteJSONStreamFromSQL(.stream,,,,,provider)
  
  set json={}.%FromJSON(stream),
      json.results=json.items.%Size()

  write json.%ToJSON()
}

}

Result:

USER>do ##class(dc.test).test()
{"items":[{"title":"For Whom the Bell Tolls","author":"Hemmingway"},{"title":"The Invisible Man","author":"Ellison"},{"title":"Tender is the Night","author":"Fitzgerald"}],"results":3}
Vitaliy Serdtsev · May 22, 2020 go to post
 

Added several more types:

Class dc.test Abstract ]
{

ClassMethod ValType(ByRef varAs %String
{
  a(-1)="undefined",a(0)="empty",a(1)="string",a(2)="integer",a(3)="float",a(4)="double",a(5)="object",a(6)="list",a(7)="array",a(8)="bitstring"
  r=$s('$d(var):-1, $isobject(var):5, $d(var)>9:7, var="":0, $lv(var):6, $l(var)>254:1, 1:$tr($a($lb(var),2),1245678,1122334))
  r=1 {
    s=##class(%Utility).FormatString(var)
    s:($f(s,"$c(")=4)||($f(s,"$zwc(")=6) r=8
  }
  a(r)
}

/// d ##class(dc.test).test()
ClassMethod test()
{
  old=$system.Process.Undefined(2)
  try{

    ##class(%Utility).FormatString(undef)," => ",..ValType(.undef),!

    i="","1",1,1.1,$double(1.1),##class(%ListOfDataTypes).%New(),$lb(),$factor(1),$c(7) {
      ##class(%Utility).FormatString(i)," => ",..ValType(.i),!
    }
    
    (i,i(1))=""
    ##class(%Utility).FormatString(i)," => ",..ValType(.i),!

  }catch{
  }
  
  d $system.Process.Undefined(old)
}

}

Result:

USER>##class(dc.test).test()
"" => undefined
"" => empty
1 => string
1 => integer
1.1 => float
$double(1.1000000000000000888) => double
"1@%Library.ListOfDataTypes" => object
$lb() => list
$zwc(128,4)_$c(1,0,0,0)/*$bit(1)*/ => bitstring
$c(7) => bitstring
"" => array
Vitaliy Serdtsev · May 20, 2020 go to post

Extending the reply of @Robert Cemper

  • The following query compiles without errors, even though Studio highlights the error
    &sql(select :fieldname into :fieldvar from :tablename)

    Everything is fine in Caché: the error occurs at the compilation-time.

    In order for an error to occur in IRIS Studio too you need to use the flag /compileembedded=1

  • It is strange that the documentation mentions field name, but there is no mention of table name:
    A host variable cannot be used to pass a field name or keyword into an SQL statement. proof
Vitaliy Serdtsev · May 20, 2020 go to post

In MUMPS, not everything is so simple, so specify technical task ;)

Example:

  1. USER>set v1=1,v2="1" zzdump v1,v2
     
    0000: 31                                                      1
    0000: 31                                                      1
  2. what to do with dates:
    30000 is a number or date 20.02.1923?
    or
    "20.02.1923" - is it a date or a string?
  3. what about numbers: 0 is %TinyInt, %SmallInt, or %Integer?
  4. what about boolean: 1 is %Boolean or %Integer?

In addition, you mix MUMPS (variable) and InterSystems ObjectScript (%Library.XXX): see Variable Types

Vitaliy Serdtsev · May 20, 2020 go to post

It can be simpler:

Property HowManyRecords As %Integer CalculatedReadOnlyRequiredSqlComputeCode = {r,SQLCODE &sql(select count(*into :r from schema_package.table{*}=$s(SQLCODE:-1,1:r)}, SqlComputed ];
Vitaliy Serdtsev · May 18, 2020 go to post

Absolutely true.

InterSystems SQL uses this index to improve performance of COUNT(*), which returns the number of records (rows) in the table. proof
Vitaliy Serdtsev · Apr 30, 2020 go to post

Unfortunately, there are errors in the methods GenExportBinaryStream/GenImportBinaryStream in the class %JSON.Generator.

Vitaliy Serdtsev · Apr 30, 2020 go to post

Thank you for asking.
It seems that no one has tried my code in all this time, or it's just that no one is interested in it.

There are two ways to solve the issue:

  1. configure Undefined=2. I have historically set this value. This can be done programmatically or via SMP
  2. fix the code:
    ClassMethod NewQuery1Fetch(
      ByRef qHandle As %Binary,
      ByRef Row As %List,
      ByRef AtEnd As %Integer 0As %Status PlaceAfter = NewQuery1Execute ]
    {
      if qHandle>1 {
        set Row=qHandle($increment(qHandle,-1))
      else {
        set AtEnd=1
      }
    
      quit $$$OK
    }
Vitaliy Serdtsev · Apr 11, 2020 go to post
 

Example

Class dc.test Abstract ]
{

/// Encode a stream as BASE64
ClassMethod Base64EncodeStream(
  pStream As %Stream.Object,
  Output pEncoded As %Stream.ObjectAs %Status
{
  try{
     $$$ThrowOnError(pStream.Rewind())

    pEncoded=##class(%Stream.TmpCharacter).%New()
    while 'pStream.AtEnd {
      tLen=5700 
      sc=pEncoded.Write($system.Encryption.Base64Encode(pStream.Read(.tLen)))
      return:$$$ISERR(scsc
    }
        
    sc=pEncoded.Rewind()
  }catch(ex{
    sc=ex.AsStatus()
  }
  sc
}

ClassMethod AESCBCBase64EncryptStream(
  Plaintext As %Stream.Object,
  Output Base64text As %Stream.Object,
  Key As %String,
  IV As %StringAs %Status
{
  try{
    $$$ThrowOnError(Plaintext.Rewind())
    
    length=$$$MaxLocalLengthwasWide=$$$NO
    i $System.Version.IsUnicode() {
      while 'Plaintext.AtEnd && 'wasWide {
        wasWide=$ZISWIDE(Plaintext.Read(length,.sc)) return:$$$ISERR(scsc
      }
      $$$ThrowOnError(Plaintext.Rewind())
    }

    wasWide {
      tmp=##class(%IO.StringStream).%New()
      tmp.CharEncoding="UTF8" 
      tmp.CopyFrom(Plaintext,,$$$YES,.screturn:$$$ISERR(scsc
      tmp.CharEncoding="Binary"

      $$$ThrowOnError(Plaintext.Clear())
      $$$ThrowOnError(Plaintext.CopyFrom(tmp))
    }

    chiperstream=##class(%Stream.TmpCharacter).%New()
    $$$ThrowOnError($system.Encryption.AESCBCEncryptStream(Plaintext,chiperstream,Key,IV))
    sc=..Base64EncodeStream(chiperstream,.Base64text)
  }catch(ex{
    sc=ex.AsStatus()
  }
  sc
}

/// d ##class(dc.test).Test()
ClassMethod Test(plaintext = {"test"_$c(768)})
{
  key=$tr($j("",16)," ","0"),
    IV="",
    plainstream=##class(%Stream.TmpCharacter).%New()

  plainstream.Write(plaintext)
    
  ;w $system.Encryption.Base64Encode($system.Encryption.AESCBCEncrypt($zcvt(plaintext,"O","UTF8"),key,IV)),!
  d $system.OBJ.DisplayError(..AESCBCBase64EncryptStream(plainstream,.base64stream,key,IV))
  base64stream.Read(),!
}

}

Result:

USER>##class(dc.test).Test("test"_$c(68))
gTNg0UMkvQ3o+ehJkvr6lA==

USER>##class(dc.test).Test("test"_$c(768))
R8UuZkjDVZidYckYMTpnVg==

USER>##class(dc.test).Test("测试")
lsYxFAQgNtiXHyaeGTWJ0A==
Vitaliy Serdtsev · Apr 8, 2020 go to post
/// Encode a stream as BASE64
ClassMethod Base64EncodeStream(
  pStream As %Stream,
  Output pEncoded As %StreamAs %Status
{
  tSC=$$$OK
  try {
    tSC=pStream.Rewind()
    q:$$$ISERR(tSC)

    

pEncoded=##class(%Stream.TmpCharacter).%New()     while 'pStream.AtEnd {       tLen=5700        tSC=pEncoded.Write($system.Encryption.Base64Encode(pStream.Read(.tLen)))       q:$$$ISERR(tSC)     }     q:$$$ISERR(tSC)              tSC=pEncoded.Rewind()   catch (e{     tSC=e.AsStatus()   }   tSC }
Vitaliy Serdtsev · Apr 2, 2020 go to post

Or something like that:

Class dc.test Extends %Persistent
{

Property Name As %String;

Property Age As %Numeric;

Property City As %String;

Property Phone As %String;

/// d ##class(dc.test).Test()
ClassMethod Test()
{
    do ..%KillExtent()
    
    // This is your Data-Object...

    ;set data="",data=[],data=..%New()

    set data=[
      {"Name":"Joe""Age":44"City":"Boston""Phone":"1-234-4567"},
      {"Name":"Ron""Age":48"City":"Dallas""Phone":"1-234-5678"},
      {"Name":"Eve""Age":40"City":"Miami",  "Phone":"1-234-4567"},
      ($listbuild("Tommy", 50, "New York""1-345-6789")),
      ($listbuild("Alexa", 35, "Portland""1-567-8901"))
    ]

    // Now insert all the above data into your table...
    &sql(
      INSERT INTO dc.test(Name,Age,City,Phone)
      SELECT $list(row,1),$list(row,2),$list(row,3),$list(row,4) 
      FROM dc.DataProvider(:data)
    )
    
    zwrite ^dc.testD

    // Now update your table...
    set data=[($listbuild("Vitaliy", 35, "Moscow""1-999-1234"))]

    &sql(
      UPDATE dc.test t
      SET t.Name=obj.Name
      FROM (SELECT $list(row,1) Name,$list(row,2) Age  FROM dc.DataProvider(:data)) obj
      WHERE t.Age=obj.Age
    )
    
    zwrite ^dc.testD
}

Query NewQuery1(dataAs %Query(ROWSPEC "row:%List") [ SqlName DataProviderSqlProc ]
{
}

ClassMethod NewQuery1Execute(
  ByRef qHandle As %Binary,
  dataAs %Status
{
  if $IsObject(data),data.%IsA("%Library.DynamicArray"),data.%Size() {

    set qHandle=data.%Size()+1

    for i=1:1:data.%Size() {
      set r=data.%Get(i-1)

      if $IsObject(r{
        set it=r.%GetIterator(),row=""
        while it.%GetNext(,.valset row=row_$listbuild(val} 

      } else {
        set row=r
      }
      set qHandle(qHandle-i)=$listbuild(row)
    }

  }

  quit $$$OK
}

ClassMethod NewQuery1Fetch(
  ByRef qHandle As %Binary,
  ByRef Row As %List,
  ByRef AtEnd As %Integer 0As %Status PlaceAfter = NewQuery1Execute ]
{
  if qHandle {
    set Row=qHandle($increment(qHandle,-1))
  else {
    set AtEnd=1
  }

  quit $$$OK
}

ClassMethod NewQuery1Close(ByRef qHandle As %BinaryAs %Status PlaceAfter = NewQuery1Execute ]
{
  quit $$$OK
}

Storage Default
{
<Data name="testDefaultData">
<Value name="1">
<Value>%%CLASSNAME</Value>
</Value>
<Value name="2">
<Value>Name</Value>
</Value>
<Value name="3">
<Value>Age</Value>
</Value>
<Value name="4">
<Value>City</Value>
</Value>
<Value name="5">
<Value>Phone</Value>
</Value>
</Data>
<DataLocation>^dc.testD</DataLocation>
<DefaultData>testDefaultData</DefaultData>
<IdLocation>^dc.testD</IdLocation>
<IndexLocation>^dc.testI</IndexLocation>
<StreamLocation>^dc.testS</StreamLocation>
<Type>%Storage.Persistent</Type>
}

}

Result:

USER>##class(dc.test).Test()
^dc.testD=5
^dc.testD(1)=$lb("","Joe",44,"Boston","1-234-4567")
^dc.testD(2)=$lb("","Ron",48,"Dallas","1-234-5678")
^dc.testD(3)=$lb("","Eve",40,"Miami","1-234-4567")
^dc.testD(4)=$lb("","Tommy",50,"New York","1-345-6789")
^dc.testD(5)=$lb("","Alexa",35,"Portland","1-567-8901")
^dc.testD=5
^dc.testD(1)=$lb("","Joe",44,"Boston","1-234-4567")
^dc.testD(2)=$lb("","Ron",48,"Dallas","1-234-5678")
^dc.testD(3)=$lb("","Eve",40,"Miami","1-234-4567")
^dc.testD(4)=$lb("","Tommy",50,"New York","1-345-6789")
^dc.testD(5)=$lb("","Vitaliy",35,"Portland","1-567-8901")
Vitaliy Serdtsev · Mar 27, 2020 go to post

Using my simple example, when the method receive a name param or a age param to build the where clause. I really don't know how to solve it using query class.

Query FilterBy(
  Name As %String "",
  Age As %Integer ""As %SQLQuery(CONTAINID 1SELECTMODE "RUNTIME") [ SqlName SP_Sample_Filter_BySqlProc ]
{
SELECT TOP IDNameAgeSSN FROM Sample.Person
WHERE 
(nvl(:Name,'')='' or Name %STARTSWITH :Name)
AND
(nvl(:Age,'')='' or Age >= :Age)
}

Run Examples:
select * from Sample.SP_Sample_Filter_By(,47)
select * from Sample.SP_Sample_Filter_By('',47)

select * from Sample.SP_Sample_Filter_By('s',47)

select * from Sample.SP_Sample_Filter_By('s')
select * from Sample.SP_Sample_Filter_By('s','')
Vitaliy Serdtsev · Mar 19, 2020 go to post
SELECT 
NVL(order_description,'') ||
CASE WHEN NVL(dosage,'')='' OR NVL(dosage_unit,'')='' THEN ''
     ELSE ', ' || dosage || ' ' || dosage_unit || CASE WHEN NVL(dosage_form,'')='' THEN ''
                                                       ELSE ' ' || dosage_form
                                                  END || ''
END ||
CASE WHEN NVL(administration_route,'')='' THEN ''
     ELSE ', ' || administration_route
END ||
CASE WHEN NVL(frequency_description,'')='' THEN ''
     ELSE ', ' || CASE WHEN NVL(quantity,'')='' OR NVL(quantity_unit,'')='' THEN ''
                       ELSE quantity || ' ' || quantity_unit || ' '
                  END || frequency_description
END ||
IFNULL(start_date,'',', ' || 'Start Date: ' || CONVERT(SQL_DATE,start_date,101)) As MedicationOrder
FROM CWSOrderEntry.history_client_order
WHERE PATID='100068'
--AND FACILITY=?FACILITY
AND order_status_code='A'
AND order_type_category_code='P'
Vitaliy Serdtsev · Mar 19, 2020 go to post

Symbols Used in Caché SQL

SELECT 
(CASE WHEN (order_description IS NULL OR (order_description='')) THEN '' ELSE (order_descriptionEND) ||
(CASE WHEN (dosage IS NULL OR dosage_unit IS NULL OR (dosage='') OR (dosage_unit='')) THEN ''
ELSE (', ' || dosage || ' ' || dosage_unit || (CASE WHEN (dosage_form IS NULL OR (dosage_form='')) THEN '' ELSE (' ' || dosage_formEND) || ''END) ||
(CASE WHEN (administration_route IS NULL OR (administration_route='')) THEN '' ELSE (', ' || administration_routeEND) ||
(CASE WHEN (frequency_description IS NULL OR (frequency_description='')) THEN '' ELSE (', ' || (CASE WHEN (quantity IS NULL OR quantity_unit IS NULL OR (quantity='') OR (quantity_unit='')) THEN '' ELSE (quantity || ' ' || quantity_unit || ' 'END) || frequency_descriptionEND) ||
(CASE WHEN start_date IS NULL THEN '' ELSE (', ' || 'Start Date: ' || CONVERT(SQL_DATE,start_date,101)) ENDAs MedicationOrder
FROM CWSOrderEntry.history_client_order
WHERE PATID='100068'
--AND FACILITY=?FACILITY
AND order_status_code='A'
AND order_type_category_code='P'

Try changing the font, it's possible that you just have "||" displayed as "!!".

Vitaliy Serdtsev · Mar 18, 2020 go to post

Using the JSON Adaptor.

Simple example:

Class dc.test Extends (%RegisteredObject%JSON.Adaptor)
{

Property list As list Of %String;

/// d ##class(dc.test).test()
ClassMethod test()
{
  json={"list":["green","yellow,red","blue"]}
  
  t=..%New()
  t.%JSONImport(json)
  t.list.Count(),!,t.list.GetAt(2)
}

}

USER>##class(dc.test).test()
3
yellow,red

Also look at Using Document Database (DocDB).

Another option without %ZEN.Auxiliary:

Class dc.mylist Extends %ListOfDataTypes
{

Method SizeSet(newvalue As %IntegerAs %Status
{
  i%Size=newvalue
  q $$$OK
}

}


Class dc.test Abstract ]
{

/// d ##class(dc.test).test()
ClassMethod test()
{
  json=["green","yellow,red","blue"]
  t=##class(%Document.Object).CSON(json.%ToJSON())
  
  l=##class(dc.mylist).%New()
  
  l.Data=t."_data"
  zk l.Data
  s l.Size=t.Count()
  
  l.Count(),!,l.GetAt(2)
}

}
Vitaliy Serdtsev · Mar 16, 2020 go to post

You can do this very simply ($get(array.Data("key"),"default")), for example:

set array=##class(%ArrayOfDataTypes).%New()

write $get(array.Data("oops"),"what?!"),!

do array.SetAt("blabla","oops")
   
write $get(array.Data("oops"),"what?!"),!