Check this answer if you need a CSV file.

If you need some custom format:

// prepare statement
set st =  ##class(%SQL.Statement).%New()
set st.%SelectMode = 1 // ODBC
set sc = st.%Prepare(query)
quit:$$$ISERR(sc) sc

// execute statement
#dim result As %SQL.StatementResult
set result = st.%Execute()
quit:result.%SQLCODE'=0 $$$ERROR($$$SQLError, result.%SQLCODE, result.%Message)

// iterate metadata (for example if you need a header)
#dim metadata As SQL.StatementMetadata
set metadata = result.%GetMetadata()
set columnCount = metadata.columns.Count()

for i=1:1:columnCount {
	#dim column As %SQL.StatementColumn
	set column = metadata.columns.GetAt(i)
}

// iterate results
while result.%Next() {
	for i=1:1:columnCount {
		set value = result.%GetData(i)
	}
}

can't figure out how to check if the child process (in $zchild) is still running.  

Append 2 commands to your main command.

First one, execute before your main command to create a file with a name equal to process id.

Second one, execute after your main command. It deletes the file.

In your IRIS process check if the file exists.

Ugly but it works.

1. If it's a one-off thing redefine your property setter:

Class Test.RO Extends %Persistent
{

Property RecordCreatedTime As %TimeStamp [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];

Method RecordCreatedTimeSet(value As %TimeStamp) As %Status
{
	if i%RecordCreatedTime="" {
		set i%RecordCreatedTime=value
	}
	quit $$$OK
}

/// do ##class(Test.RO).test()
ClassMethod test()
{
	set obj = ..%New()
	write obj.RecordCreatedTime,!
	
	set obj.RecordCreatedTime = "2000-01-01 00:00:01"
	write obj.RecordCreatedTime,!
}
}

It's also automatically set during object creation courtesy of InitialExpression, you can remove it if you want to set the value yourself.

2. If you have the same immutable property or a set of immutable properties you can write an abstract class:

Class Test.Base [Abstract]
{

Property RecordCreatedTime As %TimeStamp [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];

Method RecordCreatedTimeSet(value As %TimeStamp) As %Status
{
	if i%RecordCreatedTime="" {
		set i%RecordCreatedTime=value
	}
	quit $$$OK
}
}

And add it to inheritance whenever you need:

Class Test.RO Extends (%Persistent, Test.Base)
{
}

3. Finally if you have a lot of immutable properties and they are all different you'll need a custom datatype. Custom datatype defines method generators for getters, setters and all other property methods. Let's inherit from %String so we only need to redefine a setter:

Class Test.ROString Extends %String
{

/// Generate Setter
Method Set(%val) [ CodeMode = objectgenerator, NoContext ]
{
	quit:%mode'="propertymethod" $$$OK
	do %code.WriteLine($c(9) _ "if i%" _ $g(%member) _ "="""" {")
	do %code.WriteLine($c(9,9) _ "set i%" _ $g(%member) _ "=%val")
	do %code.WriteLine($c(9) _ "}")
	do %code.WriteLine($c(9) _ "quit $$$OK")
	quit $$$OK
}

}

Now we create a property of Test.ROString type:

Class Test.RO Extends %Persistent
{
Property RecordCreatedTime As Test.ROString [ InitialExpression = {$ZDATETIME($ZTIMESTAMP, 3, 1, 3)} ];
}

And it would be immutable. In fact if we check a generated setter:

zRecordCreatedTimeSet(%val) public {
    if i%RecordCreatedTime="" {
        set i%RecordCreatedTime=%val
    }
    quit 1 }

It would look quite similar to what I have wrote in (1), only automatically generated.