assuming  Document.StreamGC is of type %Stream.Object you may
use method FindAt which combines Read() and Contains()
see: method FindAt() 

method FindAt(position As %Integer, target As %RawString,
   ByRef tmpstr As %RawString = "", caseinsensitive As %Boolean = 0) as %Integer

Find the first occurrence of target in the stream, starting the search at position.
The method returns the position of this match, counting from the beginning of 
the stream, and leaves the stream positioned at an indeterminate location. 
If it does not find the target string, it returns -1.

If position=-1 then it starts searching from the location found in the 
previous search and returns the offset from the last search. 
This is useful for searching through the entire file. 
If you are doing this, you should pass in tmpstr by reference in every call. 
This is used to store the last buffer read, so the next call will start 
where the last one left off. 
If caseinsensitive=1 then the search will be case insensitive, 
rather than the default case-sensitive search.

A very dirty hack:  possible not really a recommendation

IF Source table is not mapped to ONS

  1. copy class definition including <Storage> from ENS to ONS
  2. in <STORAGE> you see
  3. <DataLocation>^Sample.EmployeeD</DataLocation>
    <IdLocation>^Sample.EmployeeD</IdLocation>
    <IndexLocation>^Sample.EmployeeI</IndexLocation>
    <StreamLocation>^Sample.EmployeeS</StreamLocation> 
  4. Now replace all  ^  by ^|"ENS"|  looks similar 
    For my example, it's ^|"SAMPLES"|
  5. <DataLocation>^|"SAMPLES"|Sample.EmployeeD</DataLocation>
    <IdLocation>^"|SAMPLES"|Sample.EmployeeD</IdLocation>
    <IndexLocation>^|"SAMPLES"|Sample.EmployeeI</IndexLocation>
    <StreamLocation>^|"SAMPLES"|Sample.EmployeeS</StreamLocation> 
  6. Extended Global references allow access to the other namespace. Nothing new, but risky

YES Sir!

  • Process private globals live in IRISTEMP and are strictly bound to the process and its lifetime.
  • The package with %ZZ.* makes sure it is visible from ALL namespaces
  • At process start it is always empty
    • you fill it implicitely by CREATE at definiftion time
    • or if reused by 
      INSERT INTO %ZZ.resultset SELECT .......
    • this SELECT must be the same as in CREATE ...
      to  fit  column names
  • Either CREATE once + INSERT later works
  • Or DROP TABLE %ZZ..... and fresh CREATE ...
  • It's a matter of taste, I tried both

Warning: 
Test from MGMT Portal (SQL) doesn't work as the process 
(and its ID) in background changes. The process stops and the content is gone.

IF your table must survive several processes, you need some 
other tricky solution based on the same basic idea of %ZZ*
combined with a manually tuned Class definition.

Hi @Evgeny Shvarov 
I created a mix from #1 and #2 without global mapping.
Temporary tables are excellent for this challenge 
as they drop at the end of the process. No clean-up needed !
It's kind of a Snapshot
 

A totally different approach, excluding JSON, pure SQL

  1. create temporary table
  2. Insert your result
  3. consume it within the same process
  4. eventually drop the table definition for a different query

I use the same query for my small code example

SELECT TOP 5 ssn, home_state,name from Sample.employee

USER>
USER>ZN "SAMPLES"

SAMPLES>:sql
SQL Command Line Shell
-----------------------------
[SQL]SAMPLES>>  << entering multiline statement
       1>>CREATE GLOBAL TEMPORARY TABLE %ZZ.resultset as
       2>>SELECT TOP 5 ssn, home_state,name from Sample.employee
       3>>go
       
17.    CREATE GLOBAL TEMPORARY TABLE %ZZ.resultset as
       SELECT TOP 5 ssn, home_state,name from Sample.employee
5 Rows Affected
[SQL]SAMPLES>>quit

SAMPLES>zn "USER"
USER>:sql
SQL Command Line Shell
-----------------------------
[SQL]USER>>SELECT * from %ZZ.resultset
18.     SELECT * from %ZZ.resultset

| SSN | Home_State | Name |
| -- | -- | -- |
| 217-28-9642 | NC | Emerson,Xavier O. |
| 226-71-5540 | CO | Malkovich,Bob V. |
| 396-77-7486 | UT | Joyce,Juanita V. |
| 558-18-5848 | IA | Ingleman,Josephine Q. |
| 943-90-5082 | TN | Page,Valery R. |

5 Rows(s) Affected

Now it's all in your hands
Eventually run DROP TABLE %ZZ.resultset
before next use with different columns.
Or just use different temp table for repeated use with other SELECT

Once upon a time, there was no JSON support!
But your challenge could be resolved by these steps:

  1. ZN form original namespace (ONS) to execution namespace (ENS)
  2. Execute query in ENS and get ResultSet 
  3. While in ENS, let %Display print the Resultset into SPOOL
  4. ZN to ONS.
  5. Loop over the spooled ResultSet to get your data line by line

A simplified example:

USER>ZN "SAMPLES"
SAMPLES>set sql="SELECT top 5 name, ssn from sample.employee"
SAMPLES>set sta=##class(%SQL.Statement).%New()
SAMPLES>write sta.%Prepare(.sql)
1
SAMPLES>set res=sta.%Execute()
SAMPLES>kill ^SPOOL($j)
SAMPLES>open 2:$JOB
SAMPLES>use 2 do res.%Display()
SAMPLES>close 2
SAMPLES>ZN "USER"

USER>Zwrite ^|"SAMPLES"|SPOOL($JOB)
^|"SAMPLES"|SPOOL(13488,1)="Name"_$c(9)_"SSN"_$c(13,10)
^|"SAMPLES"|SPOOL(13488,2)="Emerson,Xavier O."_$c(9)_"217-28-9642"_$c(13,10)
^|"SAMPLES"|SPOOL(13488,3)="Malkovich,Bob V."_$c(9)_"226-71-5540"_$c(13,10)
^|"SAMPLES"|SPOOL(13488,4)="Joyce,Juanita V."_$c(9)_"396-77-7486"_$c(13,10)
^|"SAMPLES"|SPOOL(13488,5)="Ingleman,Josephine Q."_$c(9)_"558-18-5848"_$c(13,10)
^|"SAMPLES"|SPOOL(13488,6)="Page,Valery R."_$c(9)_"943-90-5082"_$c(13,10)
^|"SAMPLES"|SPOOL(13488,7)=$c(13,10)
^|"SAMPLES"|SPOOL(13488,8)="5 Rows(s) Affected"
^|"SAMPLES"|SPOOL(13488,2147483647)="{67640,77930{9{"
 
USER> 

The content here is:

Name    SSN
Emerson,Xavier O.       217-28-9642
Malkovich,Bob V.        226-71-5540
Joyce,Juanita V.        396-77-7486
Ingleman,Josephine Q.   558-18-5848
Page,Valery R.  943-90-5082
5 Rows(s) Affected
Now you can loop over your result as on any other Global
as you need

same situation for Caché and IRIS

  • in the package you install class zrcc.EX.ISOS.cls 
  • class method ##class(zrcc.EX.ISOS).Gset(....) shows up
  • as SQL procedure zrcc_EX.Gset(....)

inside the classmethod it is all standard COS/ISOS
similar;

  • ##class(zrcc.EX.ISOS).Xcmd(....)  >>> zrcc_EX.Xcmd( ...) 

Of course: Working with Globals requires experience in COS/ISOS

ClassMethods can be projected to SQL as Procedures.
That's what I  used in the only class (ISOS.cls) of this package
The package was written for IRIS but worked in Caché as well for ages..

READY 2026 is the place to find your ultimate answers on IRIS.

in variation of Frank Sinatra:

  •   If you don't get it there
  •   You will NOT get it anywhere ! 
  •   It's up to 👉 YOU 👉 just YOU 👉 just YOU  ! 

so this acts in some way similar to #(variable + formula)# syntax
that we know from &html<..> or &js< ..> blocks for CSP

??

first you may try to run your query in your code for a few records 
to see by what format EFFDT is returned internally

a quick check from the hip.
....  within the Management Portal I get results  .....

>> SMP knows 3 modes: Display (3/2/2026)  - Logical (+$h  67631) - ODBC (2026-03-02)
>> Which one is working for you ?
I'd guess WHERE EFFDT > :pDate is the critical point
If the formatting doesn't match, it may cause strange results.

??

set daysBack=-1*daysBack
&sql(SELECT DATEADD('day', :daysBack, CURRENT_DATE) INTO :tDate)

or  (didn't try)

&sql(SELECT DATEADD('day', (-1)*:daysBack, CURRENT_DATE) INTO :tDate)

As my 5* review in OEX is still blocked after fixing 
a minor typo in text I'lll publish it here.
-----------------------------------------------------------------------

method jsonstring() is missing a return value
suggestion
ClassMethod jsonstring(id, map = "") As %String [ SqlProc ]

The rest is working perfectly
The use of customized mapping was new to me 
Excellent improvement and very useful
Expect my review on OEX

The idea of 

set ^A($char(65535)) = ""

Looks good at first sight, but this might be the next follower

set ^A($char(65535),0) = ""

Just great !
and an incredible consumption of resources on disk and cpu
with my Windows Docker Desktop 
😎
  

list display failed at that point also text was scrambled
now it's working normal again

So it's clear.
Old_Class loads old defined properties in a local Object structure 
and writes only this structure back.

It has no idea of New properties and doesn't write it.
Just a guess:
$LB(...) in ^GlobalD of Old_class is shorter than for New_class
 So %Save() of Old_class is a data killer.
 

OK,
but prerequisit is, that both definitions are identical.
Especially in the Storage definition, where properties are mapped to globals

ATTENTION ! This made me nervous.

In essence, the two classes are sharing the same data / Index / stream globals.

This means there is also
Parameter MANAGEDEXTENT As INTEGER = 0;
Otherwise, it wouldn't compile

Re-reading your question, I understand that properties in 
New_class are not identical and synchronized with Old_class ?
So even Storage Definition might be different ? 
IF YES, I'd say it's highly risky!