If you "donate" your XML-File a version info and an extra root-node:

<?xml version="1.0" encoding="UTF-8" ?>
<Data>
  <Details>
   ...
  </Details>
</Data>

and use, for example, the %XML.Textreader class (see belov).

Then with few lines of code the job is done:

XML ; XML to CSV
#define ROOT "Data"
#define DEL    ";"        ##; your CSV-delimiter, $c(9) or ";" or ...
#;
   set inpFile="c:\temp\example.xml"
   set outFile="c:\temp\example.csv"
   if ##class(%XML.TextReader).ParseFile(inpFile, .rdr) {
      if rdr.Read(), rdr.NodeType="element", rdr.Name=$$$ROOT {
         open outFile:"nw":1
         if $t {
            use outFile
            while rdr.Read() {
               if rdr.NodeType="element",rdr.Name="Details" {
                  set line=""
               } elseif rdr.NodeType="chars" {
                 set line=line_$lb(rdr.Value)
               } elseif rdr.NodeType="endelement",rdr.Name="Details" {
                 w $lts(line,$$$DEL),!
               } elseif rdr.NodeType="endelement",rdr.Name=$$$ROOT {
                  close outFile
                  quit
               }
            }
         } else { w "file open problem",! }
      } else { w "XML root-element problem",! }
   } else { w "XML structure problem",! }

Do you really think it makes a difference if my routine contains "set xx=xx+1" instead of "set xx=1+xx"?

If yes, try the following:

Times2 ; execution time measurement

  s num=0,t=$zh f i=1:1:1E6 { s num=num+1 } w $j($zh-t,8,6),!
  s num=0,t=$zh f i=1:1:1E6 { s num=num+1 } w $j($zh-t,8,6),!
  q

my output values are

USER>d ^Times2
0.047048
0.038218

USER>d ^Times2
0.034727
0.035160

USER>d ^Times2
0.044252
0.036175

USER>d ^Times2
0.045639
0.035366

Both loops are exactly the same! And now, please explain why the times are partly more than 20% different?

With time measurements keep in mind:

- usually, you are not alone on a Cache server
  There are many other processes, some of them belongs to Cache other to the OS
  
- the time resolution (whatever you use: $now(), $zh) is also limited

- it depends also on the time, how long your mesurement runs (you are not alone!)
 

This is my short testroutine:

Times(iter=1E3,count=4) ; show times

    w ?3,"count   num+1   1+num   =$i()    $i()",!
    w ?15,"times in microseconds",!
    w $tr($j("",40)," ",-1),!
    
    f i=1:1:count d time(iter) s iter=iter*10
    q
    
time(iter)
{
    s f=1E6/iter // factor for "one operation in microseconds"
    
    w $j(iter,8)
    s num=0,t=$zh f i=1:1:iter { s num=num+1 } d t($zh-t*f)
    s num=0,t=$zh f i=1:1:iter { s num=1+num } d t($zh-t*f)
    
    s num=0,t=$zh f i=1:1:iter { s num=$i(num) } d t($zh-t*f)
    s num=0,t=$zh f i=1:1:iter { i $i(num) } d t($zh-t*f)
    w !
}

t(t)
{
    w $j(t,8,3)
}


and this is the output


USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   2.000   1.000   2.000   1.000
      10   0.100   0.100   0.100   0.200
     100   0.030   0.030   0.080   0.080
    1000   0.044   0.042   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.064   0.050
 1000000   0.018   0.014   0.031   0.032
10000000   0.011   0.011   0.031   0.032

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   4.000   0.000   2.000   1.000
      10   0.100   0.100   0.100   0.100
     100   0.040   0.030   0.080   0.580
    1000   0.044   0.041   0.088   0.088
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.076
 1000000   0.027   0.021   0.032   0.032
10000000   0.011   0.011   0.031   0.032

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   3.000   1.000   2.000   1.000
      10   0.100   0.000   0.100   0.100
     100   0.040   0.030   0.080   0.590
    1000   0.045   0.041   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.075
 1000000   0.015   0.012   0.031   0.032
10000000   0.011   0.011   0.031   0.032

USER>

USER>

USER>d ^Times(1,8)
   count   num+1   1+num   =$i()    $i()
               times in microseconds
----------------------------------------
       1   3.000   0.000   3.000   1.000
      10   0.100   0.000   0.100   0.100
     100   0.030   0.030   0.080   0.630
    1000   0.046   0.042   0.088   0.090
   10000   0.028   0.028   0.075   0.077
  100000   0.027   0.027   0.073   0.075
 1000000   0.014   0.012   0.032   0.032
10000000   0.011   0.011   0.031   0.032

USER>

I consider time measurements only as a rough approximations

If you want to detect a journal change, it means, you want to do something, when such a change took place.

I think, you could do something like this:

a) Start a process at system start (for example, from %ZSTART), which will handle journal changes
b) In that process, get the current journal file
c) try to open this file
d) if (c) fails, wait a few seconds and then goto c)
e) if (c) succeds,  handle journal file change, then goto b)
   
A simplified routine, but untested (as a suggestion):

journalchanges ; Handle journal file changes

#define SWITCHES $system.Util.GetSwitch()
#define SHUTDOWN ##Expression(2**16)
#define BITAND(%a,%b) $zboolean(%a,%b,1)
   
   set jouFile=$$getCurrentFile()  // get the current journal file
   if jouFile="" Quit              // should not happen, but who knows...
   
   while '$$$BITAND($$$SWITCHES, $$$SHUTDOWN) { // stop, if shutdown is in progress
      open jouFile:"WL":1 // try to get ownership
      continue:'$test     // locked (by the journaling system), we try once again
      close jouFile       // because we were interested in status only
   
      // Now you can use <jouFile> to handle the file change
      // By now, he journaling system uses a new journal file
      
      set jouFile=$$getCurrentFile() // get the new journal file
      if jouFile = "" quit           // just to be on the safe side
   }
   
   quit  // we are done
   
    
getCurrentFile()
{
   try { return ##class(%SYS.Journal.System).GetCurrentFile().Name } catch { return "" }
}

Yes, you have right, thank you for the hint.  One never should add an alternate function without testing it!

The correct form is:

ClassMethod CountQ(node) As %Integer
{
end=node
  if $data(@node)#10 set sum=1 else set sum=0 }
  while 1 set node=$query(@node) quit:node=""||($name(@node,$qlength(end))'=end) if $increment(sum) }
  quit sum
}

Class community.counter Extends %RegisteredObject
{
/// Example:
/// set ^x(1)=111
/// set ^x(3,5)=222
/// set ^x(3,7)=333
/// 
/// The above global has 5 nodes:
/// ^x without a value
/// ^x(1) with value
/// ^x(3) without a value
/// ^x(3,5) with value
/// ^x(3,7) with value
/// 
/// write ##class(community.counter).CountQ($name(^x)) --> 3
/// write ##class(community.counter).CountR($name(^x)) --> 3
/// 
/// Using your example:
/// write ##class(community.counter).CountQ($name(^Locations)) --> 5
/// write ##class(community.counter).CountQ($name(^Locations("USA")) --> 3
/// 
/// 
/// N.B.
/// Recursion is a tricky thing!
/// It helps one to get a clearly laid out solution
/// but you should take care about runtimes.
/// 
/// CountQ(...) is about 4-5 times faster then CountR(...)
/// 
/// --------------------------------------------------------
/// 
/// Return the count of nodes of a global- or a local variable
/// which have a value, using $QUERY() function
/// 
/// node:
/// a local or global variable, example: $na(^myGloabl), $na(abc)
/// or a local or global reference example: $na(^myGlobal(1,2))
/// 
ClassMethod CountQ(node) As %Integer
{
 if $data(@node)#10 set sum=1 else set sum=0 }
 while 1 set node=$query(@node) quit:node=""  if $increment(sum) }
 quit sum
}

/// Return the count of nodes of a global- or a local variable
/// which have a value, using recursion, using recursion
/// 
/// node:
/// a local or global variable, example: $na(^myGlobal), $na(abc)
/// or a local or global reference example: $na(^myGlobal(1,2))
///       
ClassMethod CountR(node) As %Integer
{
 set sum=0
 do ..nodeCnt($name(@node), .sum)
 quit sum
}

ClassMethod nodeCnt(ref, ByRef sum) As %Integer [ Internal, Private ]
{
 if $data(@ref)#10, $increment(sum)
 set i=""
 while 1 set i=$order(@ref@(i)) quit:i=""  do ..nodeCnt($na(@ref@(i)),.sum) }
}

}

Some hints/questions:

  • did you installed the correct driver? I mean, do have Cache/Iris and the
    driver the same architecture (32 vs. 64 bit)?

  • I'm not an MySQL expert (I do not even have an MySQL DB), so I ask, is
    there any problem with the character size (1 byte vs. 2 bytes/unicode), if
    this applies?

  • with a DB-Tool, like WinSQL, try to make an SELECT (as suggested by Eduard Lebedyuk) statement. What is the error message?

HELP: what are the markdown characters?

In the above answer, I put some words between angle braces, now all they are lost!
OK, I try once again with an apostrophe.

1) set stat=conn.Connect("odbcname", "username" ,"password")
"username" is a MySQL user.

5) If the content of the variable "variableX" is an alphanumeric value then put a single quote char (') around the value. Beforehand remove (or replace) all single quotes (with something else) from "variableX".

Some thoughts/hints to your problem:

1) set stat=conn.Connect(, ,
)
is a MySQL user.
Does this user have the rights for UPDATE and INSERT?

2) What are the status codes after
- set sc=conn.Prepare(...) and
- set sc=conn.Execute(...)?

3) Your query-string should have blanks after the table name and also before and after the VALUES keyword.

4) If the variables value1, value2, ... valueN CAN CONATIN a backslash character then you should either duplicate them
set valueX = $replace(valueX, "\", "\") or switch the escaping off at the start your query-string :
set pQuery="SET sql_mode='NO_BACKSLASH_ESCAPES'; INSERT INTO ..."
see also: https://dev.mysql.com/doc/refman/5.7/en/sql-mode.html

5) If the content of the variable is an alphanumeric value then put a single quote char (') around the value. Beforehand remove (or replace) all single quotes (with something else) from .

To make a connection to a MySQL Database via ODBC is easy.
In the next steps I assume you are on a Windows system (Linux/Unix is similar):

1) download (https://dev.mysql.com/downloads/connector/odbc/) and install the proper ODBC driver
2) go to ControlPanel-->AdministrativeTools-->DataSources(ODBC)
3) select the SystemDNS-Tab, click Add
- give a unique name to this datasource (and remember it for step 4)
- fill in the necessary fields as desired
4) use the %SQLGatewayConnection class to get/put your data from/into MySQL,
something like:

 set gtw=##class(%SQLGatewayConnection).%New()
 if gtw.Connect(datasourceName, user, pass) {
    // do your work
 } else {
    // can't connect
 }

If you work in Cache-Terminal, go to:
ManagementPortal-->SystemAdmin-->Configurations-->DeviceSettings-->DeviceSubtypes

Select: C-Cache-Terminal, click on Edit
Go to bottom line (Caption: ZUFormFeed) and change the value from $C(27,91,72,27,91,74) to your needs.
For example, if you want to retain the first and the second lines, to: $C(27,91,51,59,48,72,27,91,74).

In case, your device type is not the Cache-Terminal, search for your device in
ManagementPortal-->SystemAdmin-->Configurations-->DeviceSettings-->Devices

Select your device, for example TERM, |TNT| or whatever device type you have.
Click on edit and notice the Sub-Type.
Now, as above, go to the sub-types, and change the ZUFormFeed field.

In case, you wont find your device and/or device sub-type, there is a direct way too,
to set the desired FormFeed (i.e. write #) and the Backspace behavior:

do ##class(%Device).SetFFBS($C(27,91,51,59,48,72,27,91,74),$c(8,32,8))
The first sequence, as the method name implies, is the FormFeed sequence and
the second is the Backspace sequence

If you have your binary or character data in (sequential) global nodes, then:
- create your own stream clas
-in the %OnNew() method fetch the data from your global

and voila, you are ready to use your data with stream methods.

set ^mydata(1)="abcd"
set ^mydata(2)="efgh"

set ^yourdata("id",1)="1234"
set ^yourdata("id",2)="5678"

Class Test.Stream Extends %Stream.TmpBinary
{
Method %OnNew(Global, initval As %String = "") As %Status [ Private ]
{
Set i%%Location=$select(initval="":$$$streamGlobal,1:initval)
do ..Clear()
for set Global=$query(@Global,1,data) quit:Global="" do ..Write(data)
do ..Rewind()
Quit $$$OK
}
}

set stream=##class(Test.Stream).%New($na(^mydata))
while 'stream.AtEnd { write stream.Read(5) }
abcdefgh

set stream=##class(Test.Stream).%New($na(^yourdata("id")))
while 'stream.AtEnd { write stream.Read(5) }
12345678