I just tested a variable parameter list in SQL. It works: (simplified example)

Class User.SQLvar
{
ClassMethod MyLIST(var...) As %String [ SqlName = MyLIST, SqlProc ]
{ set result="^"
 for i=1:1:var set result=result_var(i)_"^"
  quit result }
}

with SQL:

 CACHE>do $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
[SQL]CACHE>>select MyList(1,2,3)
3.      select MyList(1,2,3)
Expression_1
^1^2^3^
 
1 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0538s/33292/149491/0ms
          execute time(s)/globals/cmds/disk: 0.0002s/0/428/0ms
                          cached query class: %sqlcq.CACHE.cls1
---------------------------------------------------------------------------
[SQL]CACHE>>select MyList(1,2,3,4,5,6)
Expression_1
^1^2^3^4^5^6^
 
1 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0545s/33591/158533/4ms
          execute time(s)/globals/cmds/disk: 0.0002s/0/440/0ms
                          cached query class: %sqlcq.CACHE.cls2
---------------------------------------------------------------------------
[SQL]CACHE>>

so you can generate your JSON  straght as ClassMethd

With 2013.* it is not built-in,  but you can mimic it:

every Classmethod can be projected as SQLprocedure.  

ClassMethod MyJSON(par1 As %String, par2 As %String As %String(MAXLEN==")  SqlName My_JSONSqlProc ]
{  

    set result = ""   /* now you assemble your JSON  in this string */
   
quit result 
}

I'm not sure if you can pass a variable list of arguments from SQL.
But this is the basic mechanic behind it

Based on the changed question you may consider this solution with just 1 Common Class:

If you look to the  Storage definition you see something similar:

<SQLMap name="Map1">
<Data name="....
</Data>
<Global>^tabA</Global>

Now you change the <Global> node to

<Global>@($s($d(%GLOBAL):%GLOBAL,1:"^tabA"))@</Global>

this means if there is no variable %GLOBAL you see ^tabA  but if you set %GLOBAL you see ^tabB 

to set the variable also directly from SQL  you may add this ClassMethod as SQLprocedure

ClassMethod SetGlobal(global As %String = "tabA") As %Integer [ SqlName = SetGlobal, SqlProc ]
{  set %GLOBAL=global  quit $$$OK }

now see the effect. first the test data:

 CACHEUSER>zw ^tabA
^tabA(1)="A=Brendan Bannon^55192^SQL Manager"
^tabA(2)="A=Nicole Aaron^63375^Support Specialist"
^tabA(3)="A=Kyle Baxter^61155^Senior Support Specialist"
^tabA(4)="A=Prasad Kari^58471^Support Specialist"
^tabA(5)="A=Clive Morgan^57982^Senior Support Specialist"
 
CACHEUSER>zw ^tabB
^tabB(1)="B=Brendan Bannon^55192^SQL Manager"
^tabB(2)="B=Nicole Aaron^63375^Support Specialist"
^tabB(3)="B=Kyle Baxter^61155^Senior Support Specialist"
^tabB(4)="B=Prasad Kari^58471^Support Specialist"
^tabB(5)="B=Clive Morgan^57982^Senior Support Specialist"

and now the SQL query

 CACHEUSER>zw   ; no variables

CACHEUSER>do $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------
[SQL]CACHEUSER>>select * from Tbl  -- tableA
 
ID      HireDate        Name    Sub2    Title
1       55192   A=Brendan Bannon        1       SQL Manager
2       63375   A=Nicole Aaron  2       Support Specialist
3       61155   A=Kyle Baxter   3       Senior Support Specialist
4       58471   A=Prasad Kari   4       Support Specialist
5       57982   A=Clive Morgan  5       Senior Support Specialist
 
5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0007s/20/955/0ms
          execute time(s)/globals/cmds/disk: 0.0004s/6/1324/0ms
                          cached query class: %sqlcq.CACHEUSER.cls6
---------------------------------------------------------------------------
[SQL]CACHEUSER>>COS set %GLOBAL="^tabB"  ; going for Global ^tabB

[SQL]CACHEUSER>>select * from Tbl -- tableB

 
ID      HireDate        Name    Sub2    Title
1       55192   B=Brendan Bannon        1       SQL Manager
2       63375   B=Nicole Aaron  2       Support Specialist
3       61155   B=Kyle Baxter   3       Senior Support Specialist
4       58471   B=Prasad Kari   4       Support Specialist
5       57982   B=Clive Morgan  5       Senior Support Specialist
 
5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0005s/20/955/0ms
          execute time(s)/globals/cmds/disk: 0.0003s/6/1324/0ms
                          cached query class: %sqlcq.CACHEUSER.cls6
---------------------------------------------------------------------------

 next with pure SQL

 CACHEUSER>zw   ; no variables
CACHEUSER>do $system.SQL.Shell()
SQL Command Line Shell
----------------------------------------------------

[SQL]CACHEUSER>>select * from Tbl where SetGlobal('^tabB')=1
 
ID      HireDate        Name    Sub2    Title
1       55192   B=Brendan Bannon        1       SQL Manager
2       63375   B=Nicole Aaron  2       Support Specialist
3       61155   B=Kyle Baxter   3       Senior Support Specialist
4       58471   B=Prasad Kari   4       Support Specialist
5       57982   B=Clive Morgan  5       Senior Support Specialist
 
5 Rows(s) Affected
statement prepare time(s)/globals/cmds/disk: 0.0008s/27/3394/0ms
          execute time(s)/globals/cmds/disk: 0.0003s/6/1350/0ms
                          cached query class: %sqlcq.CACHEUSER.cls10
---------------------------------------------------------------------------
[SQL]CACHEUSER>>

The SQL procedure SetGlobal is independent of the rows:
It will be processed before the row selecting.  So we set our local variable %GLOBAL there.
As it is part of the WHERE clause we have to check the return value.
So it works with any SLQ Query tool. eg. Mgmt Portal.

Setting the variable works of course also for object access.  

If I understand you correct you expect something  like

INSERT INTO new.MESSAGE (SELECT * from  EnsLib.HL7.Message  where id = ? )

this may work for rather modest designed relational tables but I doubt it will work for some complex structure as EnsLib.HL7.Message .

Subclassing is not the way to solve your problem for 2 reasons:

  • A subclass has always to match the .IsA() class type of its parent    (e.g. Employee as subclass of Person. Employee is also always a Person)
  • SubClasses share the storage with its parent. There are dirty tricks to avoid this but I assume you do not intend to change  EnsLib.HL7.Message.

There are ways to do something like this if you fiddle around in the storage structures. 
BUT: for reasons of transparency, documentation, and maintainability
I'd strongly recommend to use DTL. 


Fiddling around with COS in Ensemble is a rather tricky exercise.
Based on practical experience I can confirm that your successors will never forget you.

Hi Jenna,

I took some time to verify my suspicion.  (Caché 18.1)

You depend on the type of stream that is used in your object

#A)  %Library.GlobalBinaryStream or %Library.GlobalCharacterStream

  • if you have no stream yet you run do obj.MyStream.Write("whatever") then your stream will land in ^SSA.DocumentCacheS as expected
  • but if you get an already existing external stream and set obj.MyStream = myStreamOref then just the oref / OID of the stream is set including ^CacheStream.

Which seems to be your case   sadcrying​​​​​​​  angry
it's not a big surprise as ENSEMBLE may still use the old style. 

#B) using %Stream.GlobalBinary, ...

  • Both cases ended as expected with the stream in ^SSA.DocumentCacheS It seems to me that a CopyFromStream happens during the assignment. I'd name it expected behavior.   smiley

PARAMETER and DISPLAYLIST are both compiler directives and you can't mix them.

But you may workaround it by writing your own pair of DisplayToLogical /  LogicalToDisplay ClassMethods for this property´.


Parameter Active = 1;
Parameter Inactive =2;
Parameter Production = 3;
Parameter Upkeep 4;

/// DISPLAYLIST = ", Active, Inactive, Production, Upkeep",
Property Status As %String(VALUELIST = ",1,2,3,4") [ Required ];

ClassMethod StatusDisplayToLogical(%val) As %String
{ Quit $case(%val,..#Active:1,..#Inactive:2,..#Production:3,..#Upkeep:4,:""}

ClassMethod StatusLogicalToDisplay(%val) As %String
Quit $case(%val,1:..#Active,2:..#Inactive,3:..#Production,4:..#Upkeep,:""}