I did a little bit more research.

  • Maybe %STARTSWITH 'abc' was at one time faster than the equivalent predicate LIKE 'abc%'.
  • The quote comes from the FOR SOME %ELEMENT predicate documentation. This predicate can be used with Collections and an old feature called Free Text Search. The quote was actually only meant to apply to the Free Text Search usage.
  • I've tested %STARTSWITH 'abc' and LIKE 'abc%' today using FOR SOME %ELEMENT with Collections and Free Text Search. The code is identical.

Conclusion: the quote will be removed from the documentation since it's no longer true.

Thanks, @Vitaliy.Serdtsev, for making me realize that I should have been testing with placeholders rather than fixed values to the right of %STARTSWITH or LIKE. I was testing with Embedded SQL; with fixed values, my earlier statements are true. But if the query itself uses placeholders (? or host variables), or the WHERE clause is parameterized automatically (thanks, @Eduard Lebedyuk, for mentioning that) then the generated code differs, and LIKE sometimes does do an extra (slightly slower) comparison, because at runtime, LIKE could get a simple pattern ("abc%") or a complex one ("a_b%g_i") and the code has to cope with those possibilities.

New conclusion: the quote will be clarified so that it mentions placeholders/paramaterization and moved to the %STARTSWITH and LIKE documentation, instead of being buried in FOR SOME %ELEMENT.

And thanks to @Hao Ma for bringing this up!

%STARTSWITH is not faster or slower when comparing apples to apples.

LIKE can find a substring wherever it occurs, and has multi-character and single-character wildcards. %STARTSWITH is looking only at the beginning of the string, so it's equivalent to LIKE 'ABC%'.

Updating to match another updated post lower on this page. If the comparison string is parameterized, LIKE sometimes does an extra check, so %STARTSWITH will be slightly faster.

When the comparison string ('ABC%' and 'ABC') is fixed. The code that checks LIKE 'ABC%' is exactly the same as the code that checks %STARTSWITH 'ABC'

Then besides this, in the documentation for %STARTSWITH need to add the note DEPRECATED and the recommendation "use LIKE 'XXX%'"

select count(*from del.where like 'test7%'
Row count: 1 Performance: 0.291 seconds 333340 global references 2000537 lines executed

select count(*from del.where %startswith 'test7'
Row count: 1 Performance: 0.215 seconds 333340 global references 1889349 lines executed

I'm not sure what you mean here. %STARTSWITH executed fewer lines so why would we recommend LIKE instead?

I have never heard of anyone issuing the blanket statement that InterSystems predicates are faster or slower than the ANSI standard ones. I don't think there are that many predicates that have similar functionality. As I said in a different comment, %STARTWITH 'abc' is 100% equivalent to LIKE 'abc%'. InterSystems also provides %MATCHES and %PATTERN, but they are different.

We may need more clarity in order to answer this question. This is what I think you mean. You want to take an ObjectScript array of arbitrary structure, like this:

a(1)="a"
a(3,4)="b"
a(3,6)="c"
a(5,6,7)="d"

...and turn it into JSON. But what should the target JSON for this example look like? Something like this?

{"1":"a", "3":{"4":"b","6":"c"}, "5":{"6":{"7":"d"}}}

...or something different?

In any case, to loop through an ObjectScript array of arbitrary structure, you need to use $Query.

Even though Tim's article talks about this, I'll mention it briefly here. Since ObjectScript Try/Catch construct doesn't have a "Finally" block like some other languages, the code following the Try/Catch is often used for "Finally" code. Since Return inside Try/Catch exits the Try or Catch and terminates the method, this would bypass any "Finally" code at the end. Therefore, I'd recommend avoiding using Return inside Try/Catch.

So if $$$ISERR(tSc), convert tSc into an exception and Throw it, and then handle the error in the Catch. After that, any "Finally" code will run.

I've changed my recommendation on this a little. Return the result of %ValidateObject() as the final argument, but don't return that status as the return value of the method. That way, if there are any required properties, and the call to %New() doesn't supply them, %OnNew() still works. Here's the updated example:

/// constructor
Method %OnNew(name As %String = "", phone As %String = "", dob as %Date, Output valid As %Status) As %Status [Private]
{
    set valid = $$$OK
    set ..Name = name
    set ..Phone = phone
    set ..DOB = dob
    set valid = ..%ValidateObject() // validate the new object
    return $$$OK
}

This is a very good article. I have 2 comments:

  1. Why do you need the while loop and tInitLevel at all? I think that code that uses transactions must balance each tstart with either a tcommit or trollback 1. So the difference between $tlevel and tInitTLevel should always be 1. And no code should ever use trollback without the 1. Your code should roll back your transaction and no one else's.
  2. When tstart/tcommit was first added to the language, best practice was placing the tstart and tcommitat the beginning of the code that is about to change globals, keeping it isolated as much as possible from anything else. I’d suggest moving the tstarts in your examples lower in the code block, right before the “do the important stuff” code.

Not sure exactly what you're asking for...

You can add additional properties to your custom unit test class (inheriting from %UnitTest.TestCase) and use them to share data between the methods in the class. But this doesn't show up automatically in the results.

You can use the $$$LogMessage macro to display whatever you want in the results, but this is just text, not a new property.

do $$$LogMessage(key _ ":" _ value)

I'm sorry to mislead; ReturnResultSets doesn't make a difference after all. I played around a little more, calling a simple stored procedure from a JDBC client (DBeaver).

Select Sample.Employee_StoredProcTest('jj') returns the result, and ignores anything in %sqlcontext unless you throw an exception, and the %sqlcontext properties are nested.

Call Sample.Employee_StoredProcTest('jj', '') shows the un-nested %SQLCode and %Message properties of %sqlcontext, but doesn't return the result.

So maybe the solution is to return tMsg.Size by reference and use Call?

%OnNew() must return a %Status to %New(). But the code that calls %New() also needs the %Status. I think a "best practice" is simply to add an Output  %Status argument to %OnNew() that will therefore be returned to %New(). So the %Status is being returned using return (for %New()) and by using a pass-by-reference argument (for the code that calls %New()).

/// constructor
Method %OnNew(name As %String = "", phone As %String = "", dob as %Date, Output st As %Status) As %Status [Private]
{
    set st = $$$OK
    set ..Name = name
    set ..Phone = phone
    set ..DOB = dob
    set st = ..%ValidateObject() // validate the new object
    return st
}

I started as an M guy, abbreviating everything, for many years. Once extra spaces were allowed in ObjectScript, I re-trained myself to use extra spaces and fully spelled out commands and $functions. When I'm writing code for myself in the Terminal, I don't always spell everything out. But any code I write that others will see, I spell everything out.

Some folks in this thread talk about abbreviating some commands but not others, or some $functions and not others. Imagine code at one company written by 10 programmers, all of whom choose their own way of abbreviating or not abbreviating. A new programmer joining the company will see a mess. Then imagine trying to come up with an agreed upon set of standards at the company ("you can abbreviate these commands but not those, and you can abbreviate these functions but not those") so that the code appears (at least) consistent. A new programmer joining the company will still see a messy standards document that they'll have to keep checking until they have it memorized.

Compare that to: "spell everything out, spaces around all operators, a space after every comma" (what I do) or something similar. I think the end result justifies the extra typing.

If you want to allow subclasses of class A with a property that references Abstract %Persistent Class B, and allow A objects to use that property to reference any of the subclasses of B, class B must have its own extent (all subclass data stored in the same global). Otherwise, since the A objects only store the ID of the referenced B objects, how would the system determine which subclass of B ID #52 refers to? When all the subclass data is stored in the same global, IDs are unique across all subclasses, and ID #52 refers to one object only.

Adding a BitMap extent index to class B helps compensate for the fact that all B objects are stored together, and improves the performance of queries on the subclasses.