Cannot remove record from a hash object which has been locked by an iterator. Have you ever seen that error message? If you have and want to understand it, then read this post. I will demonstrate when the error happens. When it does not happen. And how to prevent the error.
When is a Hash Object Locked by an Iterator?
First, let us see an example of an iterator object that locks a hash object. Consider the example below. Here, I create a simple hash object and an iterator assigned to it. Then, I add the key values fro mone to three. Then, I call the First() Method. Now, the iterator points to an item inside the hash object. Finally, I call the Clear() Method. This attempts to empty the hash object, ie. remove all items. The method call fails and gives an error in the log.
The Clear Method fails because the iterator locks the object. However, if you attempt to use the Delete() Method, there is no problem. Because the Delete() Method deletes the entire structure. Not just the items within it.
data _null_; dcl hash h(); h.definekey('k'); h.definedone(); dcl hiter hi('h'); do k = 1 to 3; h.add(); end; hi.first(); put k=; h.clear(); /* h.delete */ run;
SAS Iterator Locks Item-Groups Only
Above, I (and the log) write that a hash object is locked by an iterator. This is not entirely accurate. Rather, we should say that the item-group that the iterator currently dwells on is locked by an iterator. Consider the code below. The object and items are (almost) identical to the example above. However, consider the two Remove() Method calls. Due do the fact h is ordered by k, the iterator dwells on the k=1 item group after the First() Method call. Consequently, only the k=2 item group is locked. Therefore, SAS allows us to remove the k=1 item group with no error. However, if we attempt to remove the k=2 item group, we do get an error and the data step terminates.
data _null_; dcl hash h(ordered : "Y"); h.definekey('k'); h.definedone(); dcl hiter hi('h'); do k = 1 to 3; h.add(); end; hi.first(); put k=; h.remove(key : 2); /* No Error */ h.remove(key : 1); /* Error */ run;
Replacement is Allowed
While SAS does not allow us to remove items from an item-group that is locked by an iterator, we can replace and update them. Consider the snippet below. Here, I add a data portion to h. Again, I let hi point to the k=1 item-group. The Replace() Method call executes successfully and updates the appropriate data values.
data _null_; dcl hash h(ordered : "Y"); h.definekey('k'); h.definedata('k', 'd'); h.definedone(); dcl hiter hi('h'); do k = 1 to 3; d = k * 2; h.add(); end; hi.first(); put (k d)(=); h.replace(key : 1, data : 10, data : 10); hi.first(); put (k d)(=); run;
Removedup and Replacedup Method
Finally, let us consider the Removedup and Replacedup Method. From the lessons above, it is natural to expect, that the Removedup Method fails when we call it on an item within an item-group that is locked by an iterator. And the Replacedup Method does not. Luckily, this is indeed the case. Consider the code below and verify for yourself.
data have; input k d; datalines; 1 10 1 20 1 30 2 5 2 10 2 15 3 20 3 30 3 40 ; data _null_; declare hash h(dataset : "have", multidata : "Y", ordered : "A"); h.definekey("k"); h.definedata("k", "d"); h.definedone(); declare hiter hi("h"); k = .; d = .; h.find(key : 1); h.find_next(); hi.first(); put (k d)(=); h.replacedup(data : k, data : 100); /* No Error */ h.removedup(); /* Error */ run;
Unlocking the Hash Object
Finally, let us see how to unlock the item-group and hash object again. We can unlock an item-group by moving the iterator to another item-group. This is rarely desirable though. Also, no matter where the iterator resides inside the hash object, we can not clear the object with success. Therefore, we need a convenient way to remove the iterator from the hash object, regardless of the current position. The Usage Note 42525 suggests iterating the object until there are no more items to enumerate. However, there is a much better and more efficient way. Calling the Last() and Next() method after each other does exactly that. Equivalently, call the First() and Prev method on the iterator object. Be aware that the Next() Method call, which unsets the hash table and makes the iterator step out of the table returns a non-zero return code. Therefore, make this call assigned to prevent SAS from writing an error to the log.
data _null_; dcl hash h(); h.definekey('k'); h.definedone(); dcl hiter hi('h'); do k = 1 to 3; h.add(); end; hi.first(); hi.last(); rc = hi.next(); /* Equivalent hi.first(); hi.prev(); */ h.clear(); run;
In this post, we investigate when the hash iterator object locks a hash object. We learn that the iterator actually locks only the item-group that it dwells on. Not the rest. Also, we learn that updating and replacing data values is not an issue. Finally, we see how to unlock the hash object and avoid the error message in the log.
If you want to learn more about hash objects in SAS, there is no better reference than the book Data Management Solutions Using SAS Hash Table Operations by Don Henderson and Paul Dorfman.
You can download the entire code from this post here.